Merged changes from branches/supertux-milestone2-grumbel/ to trunk/supertux/
[supertux.git] / src / supertux / game_session.cpp
1 //  SuperTux
2 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
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 #include "supertux/game_session.hpp"
18
19 #include <float.h>
20 #include <fstream>
21
22 #include "audio/sound_manager.hpp"
23 #include "control/joystickkeyboardcontroller.hpp"
24 #include "gui/menu.hpp"
25 #include "math/random_generator.hpp"
26 #include "object/camera.hpp"
27 #include "object/endsequence_fireworks.hpp"
28 #include "object/endsequence_walkleft.hpp"
29 #include "object/endsequence_walkright.hpp"
30 #include "object/level_time.hpp"
31 #include "object/player.hpp"
32 #include "scripting/squirrel_util.hpp"
33 #include "supertux/gameconfig.hpp"
34 #include "supertux/levelintro.hpp"
35 #include "supertux/main.hpp"
36 #include "supertux/mainloop.hpp"
37 #include "supertux/options_menu.hpp"
38 #include "supertux/sector.hpp"
39 #include "util/file_system.hpp"
40 #include "util/gettext.hpp"
41 #include "worldmap/worldmap.hpp"
42
43 enum GameMenuIDs {
44   MNID_CONTINUE,
45   MNID_ABORTLEVEL
46 };
47
48 GameSession::GameSession(const std::string& levelfile_, Statistics* statistics) :
49   level(0), 
50   currentsector(0),
51   end_sequence(0),
52   levelfile(levelfile_), 
53   best_level_statistics(statistics),
54   capture_demo_stream(0), 
55   playback_demo_stream(0), 
56   demo_controller(0),
57   play_time(0), 
58   edit_mode(false), 
59   levelintro_shown(false)
60 {
61   currentsector = NULL;
62
63   game_pause = false;
64   speed_before_pause = g_main_loop->get_speed();
65
66   statistics_backdrop.reset(new Surface("images/engine/menu/score-backdrop.png"));
67
68   restart_level();
69
70   game_menu.reset(new Menu());
71   game_menu->add_label(level->name);
72   game_menu->add_hl();
73   game_menu->add_entry(MNID_CONTINUE, _("Continue"));
74   game_menu->add_submenu(_("Options"), get_options_menu());
75   game_menu->add_hl();
76   game_menu->add_entry(MNID_ABORTLEVEL, _("Abort Level"));
77 }
78
79 void
80 GameSession::restart_level()
81 {
82
83   if (edit_mode) {
84     force_ghost_mode();
85     return;
86   }
87
88   game_pause   = false;
89   end_sequence = 0;
90
91   g_main_controller->reset();
92
93   currentsector = 0;
94
95   level.reset(new Level);
96   try {
97     level->load(levelfile);
98     level->stats.total_coins = level->get_total_coins();
99     level->stats.total_badguys = level->get_total_badguys();
100     level->stats.total_secrets = level->get_total_secrets();
101     level->stats.reset();
102
103     if(reset_sector != "") {
104       currentsector = level->get_sector(reset_sector);
105       if(!currentsector) {
106         std::stringstream msg;
107         msg << "Couldn't find sector '" << reset_sector << "' for resetting tux.";
108         throw std::runtime_error(msg.str());
109       }
110       level->stats.declare_invalid();
111       currentsector->activate(reset_pos);
112     } else {
113       currentsector = level->get_sector("main");
114       if(!currentsector)
115         throw std::runtime_error("Couldn't find main sector");
116       play_time = 0;
117       currentsector->activate("main");
118     }
119   } catch(std::exception& e) {
120     log_fatal << "Couldn't start level: " << e.what() << std::endl;
121     g_main_loop->exit_screen();
122   }
123
124   sound_manager->stop_music();
125   currentsector->play_music(LEVEL_MUSIC);
126
127   if(capture_file != "") {
128     int newSeed=0;               // next run uses a new seed
129     while (newSeed == 0)            // which is the next non-zero random num.
130       newSeed = systemRandom.rand();
131     g_config->random_seed = systemRandom.srand(newSeed);
132     log_info << "Next run uses random seed " << g_config->random_seed <<std::endl;
133     record_demo(capture_file);
134   }
135 }
136
137 GameSession::~GameSession()
138 {
139   delete capture_demo_stream;
140   delete playback_demo_stream;
141   delete demo_controller;
142   free_options_menu();
143 }
144
145 void
146 GameSession::record_demo(const std::string& filename)
147 {
148   delete capture_demo_stream;
149
150   capture_demo_stream = new std::ofstream(filename.c_str());
151   if(!capture_demo_stream->good()) {
152     std::stringstream msg;
153     msg << "Couldn't open demo file '" << filename << "' for writing.";
154     throw std::runtime_error(msg.str());
155   }
156   capture_file = filename;
157
158   char buf[30];                            // save the seed in the demo file
159   snprintf(buf, sizeof(buf), "random_seed=%10d", g_config->random_seed);
160   for (int i=0; i==0 || buf[i-1]; i++)
161     capture_demo_stream->put(buf[i]);
162 }
163
164 int
165 GameSession::get_demo_random_seed(const std::string& filename)
166 {
167   std::istream* test_stream = new std::ifstream(filename.c_str());
168   if(test_stream->good()) {
169     char buf[30];                     // recall the seed from the demo file
170     int seed;
171     for (int i=0; i<30 && (i==0 || buf[i-1]); i++)
172       test_stream->get(buf[i]);
173     if (sscanf(buf, "random_seed=%10d", &seed) == 1) {
174       log_info << "Random seed " << seed << " from demo file" << std::endl;
175       return seed;
176     }
177     else
178       log_info << "Demo file contains no random number" << std::endl;
179   }
180   return 0;
181 }
182
183 void
184 GameSession::play_demo(const std::string& filename)
185 {
186   delete playback_demo_stream;
187   delete demo_controller;
188
189   playback_demo_stream = new std::ifstream(filename.c_str());
190   if(!playback_demo_stream->good()) {
191     std::stringstream msg;
192     msg << "Couldn't open demo file '" << filename << "' for reading.";
193     throw std::runtime_error(msg.str());
194   }
195
196   Player& tux = *currentsector->player;
197   demo_controller = new CodeController();
198   tux.set_controller(demo_controller);
199
200   // skip over random seed, if it exists in the file
201   char buf[30];                            // ascii decimal seed
202   int seed;
203   for (int i=0; i<30 && (i==0 || buf[i-1]); i++)
204     playback_demo_stream->get(buf[i]);
205   if (sscanf(buf, "random_seed=%010d", &seed) != 1)
206     playback_demo_stream->seekg(0);     // old style w/o seed, restart at beg
207 }
208
209 void
210 GameSession::on_escape_press()
211 {
212   if(currentsector->player->is_dying() || end_sequence)
213   {
214     // Let the timers run out, we fast-forward them to force past a sequence
215     if (end_sequence)
216       end_sequence->stop();
217
218     currentsector->player->dying_timer.start(FLT_EPSILON);
219     return;   // don't let the player open the menu, when he is dying
220   }
221
222   if(level->on_menukey_script != "") {
223     std::istringstream in(level->on_menukey_script);
224     run_script(in, "OnMenuKeyScript");
225   } else {
226     toggle_pause();
227   }
228 }
229
230 void
231 GameSession::toggle_pause()
232 {
233   // pause
234   if(!game_pause) {
235     speed_before_pause = g_main_loop->get_speed();
236     g_main_loop->set_speed(0);
237     Menu::set_current(game_menu.get());
238     game_menu->set_active_item(MNID_CONTINUE);
239     game_pause = true;
240   }
241
242   // unpause is done in update() after the menu is processed
243 }
244
245 void
246 GameSession::set_editmode(bool edit_mode)
247 {
248   if (this->edit_mode == edit_mode) return;
249   this->edit_mode = edit_mode;
250
251   currentsector->get_players()[0]->set_edit_mode(edit_mode);
252
253   if (edit_mode) {
254
255     // entering edit mode
256
257   } else {
258
259     // leaving edit mode
260     restart_level();
261
262   }
263 }
264
265 void
266 GameSession::force_ghost_mode()
267 {
268   currentsector->get_players()[0]->set_ghost_mode(true);
269 }
270
271 HSQUIRRELVM
272 GameSession::run_script(std::istream& in, const std::string& sourcename)
273 {
274   using namespace Scripting;
275
276   // garbage collect thread list
277   for(ScriptList::iterator i = scripts.begin();
278       i != scripts.end(); ) {
279     HSQOBJECT& object = *i;
280     HSQUIRRELVM vm = object_to_vm(object);
281
282     if(sq_getvmstate(vm) != SQ_VMSTATE_SUSPENDED) {
283       sq_release(global_vm, &object);
284       i = scripts.erase(i);
285       continue;
286     }
287
288     ++i;
289   }
290
291   HSQOBJECT object = create_thread(global_vm);
292   scripts.push_back(object);
293
294   HSQUIRRELVM vm = object_to_vm(object);
295
296   compile_and_run(vm, in, sourcename);
297
298   return vm;
299 }
300
301 void
302 GameSession::process_events()
303 {
304   // end of pause mode?
305   // XXX this looks like a fail-safe to unpause the game if there's no menu
306   // XXX having it enabled causes some unexpected problems
307   // XXX hopefully disabling it won't...
308   /*
309     if(!Menu::current() && game_pause) {
310     game_pause = false;
311     }
312   */
313
314   // playback a demo?
315   if(playback_demo_stream != 0) {
316     demo_controller->update();
317     char left = false;
318     char right = false;
319     char up = false;
320     char down = false;
321     char jump = false;
322     char action = false;
323     playback_demo_stream->get(left);
324     playback_demo_stream->get(right);
325     playback_demo_stream->get(up);
326     playback_demo_stream->get(down);
327     playback_demo_stream->get(jump);
328     playback_demo_stream->get(action);
329     demo_controller->press(Controller::LEFT, left);
330     demo_controller->press(Controller::RIGHT, right);
331     demo_controller->press(Controller::UP, up);
332     demo_controller->press(Controller::DOWN, down);
333     demo_controller->press(Controller::JUMP, jump);
334     demo_controller->press(Controller::ACTION, action);
335   }
336
337   // save input for demo?
338   if(capture_demo_stream != 0) {
339     capture_demo_stream ->put(g_main_controller->hold(Controller::LEFT));
340     capture_demo_stream ->put(g_main_controller->hold(Controller::RIGHT));
341     capture_demo_stream ->put(g_main_controller->hold(Controller::UP));
342     capture_demo_stream ->put(g_main_controller->hold(Controller::DOWN));
343     capture_demo_stream ->put(g_main_controller->hold(Controller::JUMP));
344     capture_demo_stream ->put(g_main_controller->hold(Controller::ACTION));
345   }
346 }
347
348 void
349 GameSession::check_end_conditions()
350 {
351   Player* tux = currentsector->player;
352
353   /* End of level? */
354   if(end_sequence && end_sequence->is_done()) {
355     finish(true);
356   } else if (!end_sequence && tux->is_dead()) {
357     restart_level();
358   }
359 }
360
361 void
362 GameSession::draw(DrawingContext& context)
363 {
364   currentsector->draw(context);
365   drawstatus(context);
366
367   if(game_pause)
368     draw_pause(context);
369 }
370
371 void
372 GameSession::draw_pause(DrawingContext& context)
373 {
374   context.draw_filled_rect(
375     Vector(0,0), Vector(SCREEN_WIDTH, SCREEN_HEIGHT),
376     Color(0.0f, 0.0f, 0.0f, .25f), LAYER_FOREGROUND1);
377 }
378
379 void
380 GameSession::process_menu()
381 {
382   Menu* menu = Menu::current();
383   if(menu) {
384     if(menu == game_menu.get()) {
385       switch (game_menu->check()) {
386         case MNID_CONTINUE:
387           Menu::set_current(0);
388           toggle_pause();
389           break;
390         case MNID_ABORTLEVEL:
391           Menu::set_current(0);
392           g_main_loop->exit_screen();
393           break;
394       }
395     }
396   }
397 }
398
399 void
400 GameSession::setup()
401 {
402   if(currentsector != Sector::current()) {
403     currentsector->activate(currentsector->player->get_pos());
404   }
405   currentsector->play_music(LEVEL_MUSIC);
406
407   // Eat unneeded events
408   SDL_Event event;
409   while(SDL_PollEvent(&event))
410   {}
411
412   if (!levelintro_shown) {
413     levelintro_shown = true;
414     g_main_loop->push_screen(new LevelIntro(level.get(), best_level_statistics));
415   }
416 }
417
418 void
419 GameSession::update(float elapsed_time)
420 {
421   // handle controller
422   if(g_main_controller->pressed(Controller::PAUSE_MENU))
423     on_escape_press();
424
425   process_events();
426   process_menu();
427
428   // Unpause the game if the menu has been closed
429   if (game_pause && !Menu::current()) {
430     g_main_loop->set_speed(speed_before_pause);
431     game_pause = false;
432   }
433
434   check_end_conditions();
435
436   // respawning in new sector?
437   if(newsector != "" && newspawnpoint != "") {
438     Sector* sector = level->get_sector(newsector);
439     if(sector == 0) {
440       log_warning << "Sector '" << newsector << "' not found" << std::endl;
441       sector = level->get_sector("main");
442     }
443     sector->activate(newspawnpoint);
444     sector->play_music(LEVEL_MUSIC);
445     currentsector = sector;
446     //Keep persistent across sectors
447     if(edit_mode)
448       currentsector->get_players()[0]->set_edit_mode(edit_mode);
449     newsector = "";
450     newspawnpoint = "";
451   }
452
453   // Update the world state and all objects in the world
454   if(!game_pause) {
455     // Update the world
456     if (!end_sequence) {
457       play_time += elapsed_time; //TODO: make sure we don't count cutscene time
458       level->stats.time = play_time;
459       currentsector->update(elapsed_time);
460     } else {
461       if (!end_sequence->is_tux_stopped()) {
462         currentsector->update(elapsed_time);
463       } else {
464         end_sequence->update(elapsed_time);
465       }
466     }
467   }
468
469   // update sounds
470   if (currentsector && currentsector->camera) sound_manager->set_listener_position(currentsector->camera->get_center());
471
472   /* Handle music: */
473   if (end_sequence)
474     return;
475
476   if(currentsector->player->invincible_timer.started()) {
477     if(currentsector->player->invincible_timer.get_timeleft() <=
478        TUX_INVINCIBLE_TIME_WARNING) {
479       currentsector->play_music(HERRING_WARNING_MUSIC);
480     } else {
481       currentsector->play_music(HERRING_MUSIC);
482     }
483   } else if(currentsector->get_music_type() != LEVEL_MUSIC) {
484     currentsector->play_music(LEVEL_MUSIC);
485   }
486 }
487
488 void
489 GameSession::finish(bool win)
490 {
491   using namespace WorldMapNS;
492
493   if (edit_mode) {
494     force_ghost_mode();
495     return;
496   }
497
498   if(win) {
499     if(WorldMap::current())
500       WorldMap::current()->finished_level(level.get());
501   }
502
503   g_main_loop->exit_screen();
504 }
505
506 void
507 GameSession::respawn(const std::string& sector, const std::string& spawnpoint)
508 {
509   newsector = sector;
510   newspawnpoint = spawnpoint;
511 }
512
513 void
514 GameSession::set_reset_point(const std::string& sector, const Vector& pos)
515 {
516   reset_sector = sector;
517   reset_pos = pos;
518 }
519
520 std::string
521 GameSession::get_working_directory()
522 {
523   return FileSystem::dirname(levelfile);
524 }
525
526 void
527 GameSession::start_sequence(const std::string& sequencename)
528 {
529   // do not play sequences when in edit mode
530   if (edit_mode) {
531     force_ghost_mode();
532     return;
533   }
534
535   // handle special "stoptux" sequence
536   if (sequencename == "stoptux") {
537     if (!end_sequence) {
538       log_warning << "Final target reached without an active end sequence" << std::endl;
539       this->start_sequence("endsequence");
540     }
541     if (end_sequence) end_sequence->stop_tux();
542     return;
543   }
544
545   // abort if a sequence is already playing
546   if (end_sequence)
547     return;
548
549   if (sequencename == "endsequence") {
550     if (currentsector->get_players()[0]->get_physic().get_velocity_x() < 0) {
551       end_sequence = new EndSequenceWalkLeft();
552     } else {
553       end_sequence = new EndSequenceWalkRight();
554     }
555   } else if (sequencename == "fireworks") {
556     end_sequence = new EndSequenceFireworks();
557   } else {
558     log_warning << "Unknown sequence '" << sequencename << "'. Ignoring." << std::endl;
559     return;
560   }
561
562   /* slow down the game for end-sequence */
563   g_main_loop->set_speed(0.5f);
564
565   currentsector->add_object(end_sequence);
566   end_sequence->start();
567
568   sound_manager->play_music("music/leveldone.music", false);
569   currentsector->player->invincible_timer.start(10000.0f);
570
571   // Stop all clocks.
572   for(std::vector<GameObject*>::iterator i = currentsector->gameobjects.begin();
573       i != currentsector->gameobjects.end(); ++i)
574   {
575     GameObject* obj = *i;
576
577     LevelTime* lt = dynamic_cast<LevelTime*> (obj);
578     if(lt)
579       lt->stop();
580   }
581 }
582
583 /* (Status): */
584 void
585 GameSession::drawstatus(DrawingContext& context)
586 {
587   player_status->draw(context);
588
589   // draw level stats while end_sequence is running
590   if (end_sequence) {
591     level->stats.draw_endseq_panel(context, best_level_statistics, statistics_backdrop.get());
592   }
593 }
594
595 /* EOF */