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