1 //
2 //  Copyright (C) 2009-2010  Nick Gasson
3 //
4 //  This program is free software: you can redistribute it and/or modify
5 //  it under the terms of the GNU General Public License as published by
6 //  the Free Software Foundation, either version 3 of the License, or
7 //  (at your option) any later version.
8 //
9 //  This program is distributed in the hope that it will be useful,
10 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //  GNU General Public License for more details.
13 //
14 //  You should have received a copy of the GNU General Public License
15 //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 //
17 
18 #include "IScreen.hpp"
19 #include "IGraphics.hpp"
20 #include "IMap.hpp"
21 #include "IRollingStock.hpp"
22 #include "ITrain.hpp"
23 #include "ILogger.hpp"
24 #include "ILight.hpp"
25 #include "GameScreens.hpp"
26 #include "IBillboard.hpp"
27 #include "IterateTrack.hpp"
28 #include "IConfig.hpp"
29 #include "IMessageArea.hpp"
30 #include "IRenderStats.hpp"
31 
32 #include "gui/ILayout.hpp"
33 #include "gui/Label.hpp"
34 #include "gui/ThrottleMeter.hpp"
35 #include "gui/IFont.hpp"
36 
37 // Implementation of the main play screen
38 class Game : public IScreen {
39 public:
40    Game(IMapPtr a_map);
41    ~Game();
42 
43    void display(IGraphicsPtr a_context) const;
44    void overlay() const;
45    void update(IPickBufferPtr a_pick_buffer, int a_delta);
46    void on_key_down(SDLKey a_key);
47    void on_key_up(SDLKey a_key);
48    void on_mouse_move(IPickBufferPtr a_pick_buffer, int x, int y,
49                       int xrel, int yrel);
50    void on_mouse_click(IPickBufferPtr a_pick_buffer, int x, int y,
51                        MouseButton a_button);
52    void on_mouse_release(IPickBufferPtr a_pick_buffer, int x, int y,
53                          MouseButton a_button);
54 private:
55    void look_ahead();
56    void near_station(IStationPtr s);
57    void left_station();
58    Vector<float> camera_position(float a_radius) const;
59    void switch_to_bird_camera();
60    void stopped_at_station();
61 
62    enum TrackStateReq { NEXT, PREV };
63    void alter_track_state(TrackStateReq req);
64 
65    IMapPtr map;
66    ITrainPtr train;
67    ILightPtr sun;
68 
69    // Station the train is either approaching or stopped at
70    IStationPtr active_station;
71 
72    // Camera position
73    float horiz_angle, vert_angle, view_radius;
74 
75    // Camera adjustment
76    float cameraHTarget, cameraVTarget;
77    float camera_speed;
78    bool panning;
79 
80    enum CameraMode { CAMERA_FLOATING, CAMERA_BIRD };
81    CameraMode camera_mode;
82 
83    gui::ILayoutPtr layout;
84    IMessageAreaPtr message_area;
85    IRenderStatsPtr render_stats;
86 };
87 
Game(IMapPtr a_map)88 Game::Game(IMapPtr a_map)
89    : map(a_map),
90      horiz_angle(M_PI/4.0f),
91      vert_angle(M_PI/4.0f),
92      view_radius(20.0f),
93      panning(false)
94 {
95    train = make_train(map);
96    sun = make_sun_light();
97 
98    map->set_grid(false);
99 
100    // Build the GUI
101    layout = gui::make_layout("layouts/game.xml");
102    message_area = make_message_area();
103    render_stats = make_render_stats(layout, "/fps/fps_label");
104 
105    // Intial camera state is floating but in the bird position
106    switch_to_bird_camera();
107    camera_mode = CAMERA_FLOATING;
108 }
109 
~Game()110 Game::~Game()
111 {
112 
113 }
114 
camera_position(float a_radius) const115 Vector<float> Game::camera_position(float a_radius) const
116 {
117    // Two angles give unique position on surface of a sphere
118    // Look up ``spherical coordinates''
119    const float y_centre = 0.9f;
120    Vector<float> position = train->front();
121    position.x += a_radius * cosf(horiz_angle) * sinf(vert_angle);
122    position.z += a_radius * sinf(horiz_angle) * sinf(vert_angle);
123    position.y = a_radius * cosf(vert_angle) + y_centre;
124 
125    return position;
126 }
127 
switch_to_bird_camera()128 void Game::switch_to_bird_camera()
129 {
130    camera_mode = CAMERA_BIRD;
131 
132    cameraHTarget = M_PI/4.0f;
133    cameraVTarget = M_PI/4.0f;
134 
135    camera_speed = 100.0f;
136 }
137 
display(IGraphicsPtr a_context) const138 void Game::display(IGraphicsPtr a_context) const
139 {
140    Vector<float> train_pos = train->front();
141 
142    Vector<float> position = camera_position(view_radius);
143 
144    a_context->look_at(position, train_pos);
145    set_billboard_cameraOrigin(position);
146 
147    sun->apply();
148 
149    map->render(a_context);
150    train->render();
151 
152    render_billboards();
153 }
154 
overlay() const155 void Game::overlay() const
156 {
157    layout->render();
158    message_area->render();
159 }
160 
stopped_at_station()161 void Game::stopped_at_station()
162 {
163    //layout->get("/station").visible(true);
164 }
165 
update(IPickBufferPtr a_pick_buffer,int a_delta)166 void Game::update(IPickBufferPtr a_pick_buffer, int a_delta)
167 {
168    message_area->update(a_delta);
169    render_stats->update(a_delta);
170 
171    train->update(a_delta);
172 
173    // Update the GUI elements
174    layout->cast<gui::ThrottleMeter>("/throttle_meter").value(
175       train->controller()->throttle());
176 
177    const double ms_toMPH = 2.237;
178    layout->cast<gui::Label>("/speed_label").format(
179       "Speed: %.1lfmph", abs(train->speed()) * ms_toMPH);
180 
181    IControllerPtr ctrl = train->controller();
182    layout->get("/brake_label").visible(ctrl->brake_on());
183    layout->get("/reverse_label").visible(ctrl->reverse_on());
184 
185    look_ahead();
186 
187    // Move the camera vertically if it's currently underground
188 #if 0
189    // Calculate the location of the near clip plane
190    const float near_clip = get_config()->get<float>("NearClip");
191    Vector<float> clip_position = camera_position(view_radius - near_clip);
192 
193    // A hack because we don't calculate the height properly
194    const float MIN_HEIGHT = 0.25f;
195    float h = map->height_at(clip_position.x, clip_position.z);
196 
197    if (h + MIN_HEIGHT > clip_position.y) {
198       cameraVTarget -= 0.001f * static_cast<float>(a_delta);
199       camera_speed = 200.0f;
200    }
201 #endif
202 
203    // Bounce the camera if we need to
204    vert_angle -= (vert_angle - cameraVTarget) / camera_speed;
205    horiz_angle -= (horiz_angle - cameraHTarget) / camera_speed;
206 }
207 
208 // Signal that we are approaching a station
near_station(IStationPtr s)209 void Game::near_station(IStationPtr s)
210 {
211    left_station();  // Clear any previous station
212 
213    if (s != active_station) {
214       active_station = s;
215       s->set_highlight_visible(true);
216 
217       //gui::Widget& station_wnd = layout->get("/station");
218 
219       layout->cast<gui::Label>("/station/name").text(s->name());
220    }
221 }
222 
223 // Signal that we are no longer at or approaching a station
left_station()224 void Game::left_station()
225 {
226    if (active_station) {
227       active_station->set_highlight_visible(false);
228       active_station.reset();
229 
230       //layout->get("/station").visible(false);
231    }
232 }
233 
234 // Look along the track and notify the player of any stations, points, etc.
235 // that they are approaching
look_ahead()236 void Game::look_ahead()
237 {
238    TrackIterator it = iterate_track(map, train->tile(),
239       train->direction());
240 
241    // Are we sitting on a station?
242    if (it.status == TRACK_STATION) {
243       near_station(it.station);
244 
245       if (train->controller()->stopped())
246          stopped_at_station();
247       else
248          message_area->post("Stop here for station " + it.station->name());
249 
250       return;
251    }
252 
253    const int max_look = 10;
254    for (int i = 0; i < max_look; i++) {
255       it = it.next();
256 
257       if (it.status != TRACK_OK) {
258          bool clear_station = true;
259 
260          switch (it.status) {
261          case TRACK_STATION:
262             message_area->post("Approaching station " + it.station->name());
263             near_station(it.station);
264             clear_station = false;
265             return;
266          case TRACK_NO_MORE:
267             message_area->post("Oh no! You're going to crash!");
268             break;
269          case TRACK_CHOICE:
270             message_area->post("Oh no! You have to make a decision!");
271             it.track->set_state_render_hint();
272             break;
273          default:
274             break;
275          }
276 
277          if (!clear_station)
278             left_station();
279          return;
280       }
281    }
282 
283    // We're not approaching any station
284    left_station();
285 }
286 
alter_track_state(TrackStateReq req)287 void Game::alter_track_state(TrackStateReq req)
288 {
289    // Change the state of the nearest points, etc.
290    TrackIterator it = iterate_track(map, train->tile(),
291       train->direction());
292 
293    const int max_alter_look = 10;
294 
295    for (int i = 0; i < max_alter_look; i++) {
296 
297       // Skip over the first section of track which may be some
298       // points - we don't want to alter the track we're on!
299       it = it.next();
300 
301       if (it.status == TRACK_CHOICE) {
302          switch (req) {
303          case NEXT:
304             it.track->next_state();
305             break;
306          case PREV:
307             it.track->prev_state();
308             break;
309          }
310 
311          return;
312       }
313    }
314 
315    warn() << "No nearby track state to change";
316 }
317 
on_key_down(SDLKey a_key)318 void Game::on_key_down(SDLKey a_key)
319 {
320    switch (a_key) {
321    case SDLK_PAGEUP:
322       view_radius = max(view_radius - 0.2f, 0.1f);
323       break;
324    case SDLK_PAGEDOWN:
325       view_radius += 0.2f;
326       break;
327    case SDLK_b:
328       train->controller()->act_on(BRAKE_TOGGLE);
329       break;
330    case SDLK_r:
331       if (train->controller()->throttle() == 0)
332          train->controller()->act_on(TOGGLE_REVERSE);
333       else
334          message_area->post("Reduce power first!", 51, 1000);
335       break;
336    case SDLK_LCTRL:
337       train->controller()->act_on(SHOVEL_COAL);
338       break;
339    case SDLK_PRINT:
340       get_game_window()->take_screen_shot();
341       break;
342    case SDLK_LEFT:
343       alter_track_state(PREV);
344       break;
345    case SDLK_RIGHT:
346       alter_track_state(NEXT);
347       break;
348    case SDLK_UP:
349       train->controller()->act_on(THROTTLE_UP);
350       break;
351    case SDLK_DOWN:
352       train->controller()->act_on(THROTTLE_DOWN);
353       break;
354    case SDLK_TAB:
355       if (camera_mode == CAMERA_FLOATING)
356          switch_to_bird_camera();
357       else
358          camera_mode = CAMERA_FLOATING;
359       break;
360    default:
361       break;
362    }
363 }
364 
on_key_up(SDLKey a_key)365 void Game::on_key_up(SDLKey a_key)
366 {
367 
368 }
369 
on_mouse_click(IPickBufferPtr a_pick_buffer,int x,int y,MouseButton a_button)370 void Game::on_mouse_click(IPickBufferPtr a_pick_buffer, int x, int y,
371                           MouseButton a_button)
372 {
373    switch (a_button) {
374    case MOUSE_RIGHT:
375       panning = true;
376       break;
377    case MOUSE_WHEEL_UP:
378       view_radius = max(view_radius - 1.0f, 0.1f);
379       break;
380    case MOUSE_WHEEL_DOWN:
381       view_radius += 1.0f;
382       break;
383    default:
384       break;
385    }
386 }
387 
on_mouse_release(IPickBufferPtr pick_buffer,int x,int y,MouseButton a_button)388 void Game::on_mouse_release(IPickBufferPtr pick_buffer, int x, int y,
389                             MouseButton a_button)
390 {
391    switch (a_button) {
392    case MOUSE_RIGHT:
393       panning = false;
394       break;
395    default:
396       break;
397    }
398 }
399 
on_mouse_move(IPickBufferPtr a_pick_buffer,int x,int y,int xrel,int yrel)400 void Game::on_mouse_move(IPickBufferPtr a_pick_buffer, int x, int y,
401    int xrel, int yrel)
402 {
403    if (camera_mode == CAMERA_FLOATING && panning) {
404       cameraHTarget -= xrel / 100.0f;
405       cameraVTarget += yrel / 100.0f;
406 
407       // Don't allow the camera to go under the ground
408       const float ground = (M_PI / 2.0f) - 0.01f;
409       if (cameraVTarget > ground)
410          cameraVTarget = ground;
411 
412       // Don't let the camera flip over the top
413       const float top = 0.01f;
414       if (cameraVTarget < top)
415          cameraVTarget = top;
416 
417       camera_speed = 2.0f;
418    }
419 }
420 
421 // Create an instance of the play screen with the given map
make_game_screen(IMapPtr a_map)422 IScreenPtr make_game_screen(IMapPtr a_map)
423 {
424    return IScreenPtr(new Game(a_map));
425 }
426