d843f5ef383d4d92d6d9555dd1ab73bf4df0ea10
[supertux.git] / src / game_session.cpp
1 //  $Id$
2 //
3 //  SuperTux
4 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 2
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19 #include <config.h>
20
21 #include <fstream>
22 #include <sstream>
23 #include <cassert>
24 #include <cstdio>
25 #include <cstdlib>
26 #include <cmath>
27 #include <cstring>
28 #include <cerrno>
29 #include <unistd.h>
30 #include <ctime>
31 #include <stdexcept>
32
33 #include <SDL.h>
34
35 #include "game_session.hpp"
36 #include "log.hpp"
37 #include "worldmap/worldmap.hpp"
38 #include "mainloop.hpp"
39 #include "audio/sound_manager.hpp"
40 #include "gui/menu.hpp"
41 #include "sector.hpp"
42 #include "level.hpp"
43 #include "tile.hpp"
44 #include "player_status.hpp"
45 #include "object/particlesystem.hpp"
46 #include "object/background.hpp"
47 #include "object/gradient.hpp"
48 #include "object/tilemap.hpp"
49 #include "object/camera.hpp"
50 #include "object/player.hpp"
51 #include "object/level_time.hpp"
52 #include "lisp/lisp.hpp"
53 #include "lisp/parser.hpp"
54 #include "resources.hpp"
55 #include "statistics.hpp"
56 #include "timer.hpp"
57 #include "options_menu.hpp"
58 #include "object/fireworks.hpp"
59 #include "textscroller.hpp"
60 #include "control/codecontroller.hpp"
61 #include "control/joystickkeyboardcontroller.hpp"
62 #include "main.hpp"
63 #include "file_system.hpp"
64 #include "gameconfig.hpp"
65 #include "gettext.hpp"
66 #include "console.hpp"
67 #include "flip_level_transformer.hpp"
68 #include "trigger/secretarea_trigger.hpp"
69 #include "random_generator.hpp"
70
71 // the engine will be run with a logical framerate of 64fps.
72 // We chose 64fps here because it is a power of 2, so 1/64 gives an "even"
73 // binary fraction...
74 static const float LOGICAL_FPS = 64.0;
75
76 enum GameMenuIDs {
77   MNID_CONTINUE,
78   MNID_ABORTLEVEL
79 };
80
81 GameSession* GameSession::current_ = NULL;
82
83 GameSession::GameSession(const std::string& levelfile_, Statistics* statistics)
84   : level(0), currentsector(0),
85     end_sequence(NO_ENDSEQUENCE), end_sequence_controller(0),
86     levelfile(levelfile_), best_level_statistics(statistics),
87     capture_demo_stream(0), playback_demo_stream(0), demo_controller(0),
88     play_time(0)
89 {
90   current_ = this;
91   currentsector = NULL;
92   
93   game_pause = false;
94
95   statistics_backdrop.reset(new Surface("images/engine/menu/score-backdrop.png"));
96
97   restart_level(true);
98
99   game_menu.reset(new Menu());
100   game_menu->add_label(_("Pause"));
101   game_menu->add_hl();
102   game_menu->add_entry(MNID_CONTINUE, _("Continue"));
103   game_menu->add_submenu(_("Options"), get_options_menu());
104   game_menu->add_hl();
105   game_menu->add_entry(MNID_ABORTLEVEL, _("Abort Level"));
106 }
107
108 void
109 GameSession::restart_level(bool fromBeginning)
110 {
111   game_pause   = false;
112   end_sequence = NO_ENDSEQUENCE;
113
114   main_controller->reset();
115
116   currentsector = 0;
117
118   level.reset(new Level);
119   level->load(levelfile);
120   level->stats.total_coins = level->get_total_coins();
121   level->stats.total_badguys = level->get_total_badguys();
122   level->stats.total_secrets = level->get_total_count<SecretAreaTrigger>();
123   level->stats.reset();
124
125   if (fromBeginning) reset_sector="";
126   if(reset_sector != "") {
127     currentsector = level->get_sector(reset_sector);
128     if(!currentsector) {
129       std::stringstream msg;
130       msg << "Couldn't find sector '" << reset_sector << "' for resetting tux.";
131       throw std::runtime_error(msg.str());
132     }
133     currentsector->activate(reset_pos);
134   } else {
135     currentsector = level->get_sector("main");
136     if(!currentsector)
137       throw std::runtime_error("Couldn't find main sector");
138     currentsector->activate("main");
139   }
140   
141   //levelintro();
142
143   currentsector->play_music(LEVEL_MUSIC);
144
145   if(capture_file != "") {
146     int newSeed=0;               // next run uses a new seed
147     while (newSeed == 0)            // which is the next non-zero random num.
148         newSeed = systemRandom.rand();
149     config->random_seed = systemRandom.srand(newSeed);
150     log_info << "Next run uses random seed " <<config->random_seed <<std::endl;
151     record_demo(capture_file);
152   }
153 }
154
155 GameSession::~GameSession()
156 {
157   delete capture_demo_stream;
158   delete playback_demo_stream;
159   delete demo_controller;
160
161   delete end_sequence_controller;
162
163   current_ = NULL;
164 }
165
166 void
167 GameSession::record_demo(const std::string& filename)
168 {
169   delete capture_demo_stream;
170   
171   capture_demo_stream = new std::ofstream(filename.c_str()); 
172   if(!capture_demo_stream->good()) {
173     std::stringstream msg;
174     msg << "Couldn't open demo file '" << filename << "' for writing.";
175     throw std::runtime_error(msg.str());
176   }
177   capture_file = filename;
178
179   char buf[30];                            // save the seed in the demo file
180   sprintf(buf, "random_seed=%010d", config->random_seed);
181   for (int i=0; i==0 || buf[i-1]; i++)
182     capture_demo_stream->put(buf[i]);
183 }
184
185 void
186 GameSession::play_demo(const std::string& filename)
187 {
188   delete playback_demo_stream;
189   delete demo_controller;
190   
191   playback_demo_stream = new std::ifstream(filename.c_str());
192   if(!playback_demo_stream->good()) {
193     std::stringstream msg;
194     msg << "Couldn't open demo file '" << filename << "' for reading.";
195     throw std::runtime_error(msg.str());
196   }
197
198   Player& tux = *currentsector->player;
199   demo_controller = new CodeController();
200   tux.set_controller(demo_controller);
201
202   char buf[30];                            // recall the seed from the demo file
203   int seed;
204   for (int i=0; i<30 && (i==0 || buf[i-1]); i++)
205     playback_demo_stream->get(buf[i]);
206   if (sscanf(buf, "random_seed=%010d", &seed) == 1) {
207     config->random_seed = seed;            // save where it will be used
208     log_info << "Taking random seed " << seed << " from demo file" <<std::endl;
209   }
210   else {
211     playback_demo_stream->seekg(0);     // old style w/o seed, restart at beg
212     log_info << "Demo file contains no random number" << std::endl;
213   }
214 }
215
216 void
217 GameSession::levelintro()
218 {
219   char str[60];
220
221   sound_manager->stop_music();
222
223   DrawingContext context;
224   for(Sector::GameObjects::iterator i = currentsector->gameobjects.begin();
225       i != currentsector->gameobjects.end(); ++i) {
226     Background* background = dynamic_cast<Background*> (*i);
227     if(background) {
228       background->draw(context);
229     }
230     Gradient* gradient = dynamic_cast<Gradient*> (*i);
231     if(gradient) {
232       gradient->draw(context);
233     }
234   }
235
236 //  context.draw_text(gold_text, level->get_name(), Vector(SCREEN_WIDTH/2, 160),
237 //      CENTER_ALLIGN, LAYER_FOREGROUND1);
238   context.draw_center_text(gold_text, level->get_name(), Vector(0, 160),
239       LAYER_FOREGROUND1);
240
241   sprintf(str, "Coins: %d", player_status->coins);
242   context.draw_text(white_text, str, Vector(SCREEN_WIDTH/2, 210),
243       CENTER_ALLIGN, LAYER_FOREGROUND1);
244
245   if((level->get_author().size()) && (level->get_author() != "SuperTux Team"))
246     context.draw_text(white_small_text,
247       std::string(_("contributed by ")) + level->get_author(), 
248       Vector(SCREEN_WIDTH/2, 350), CENTER_ALLIGN, LAYER_FOREGROUND1);
249
250   if(best_level_statistics != NULL)
251     best_level_statistics->draw_message_info(context, _("Best Level Statistics"));
252
253   wait_for_event(1.0, 3.0);
254 }
255
256 void
257 GameSession::on_escape_press()
258 {
259   if(currentsector->player->is_dying() || end_sequence != NO_ENDSEQUENCE)
260     return;   // don't let the player open the menu, when he is dying
261   
262   if (!Menu::current()) {
263     Menu::set_current(game_menu.get());
264     game_menu->set_active_item(MNID_CONTINUE);
265     game_pause = true;
266   } else {
267     Menu::set_current(NULL);
268     game_pause = false;
269   }
270 }
271
272 void
273 GameSession::process_events()
274 {
275   Player& tux = *currentsector->player;
276
277   // end of pause mode?
278   if(!Menu::current() && game_pause) {
279     game_pause = false;
280   }
281
282   if (end_sequence != NO_ENDSEQUENCE) {
283     if(end_sequence_controller == 0) {
284       end_sequence_controller = new CodeController();
285       tux.set_controller(end_sequence_controller);
286     }
287
288     end_sequence_controller->press(Controller::RIGHT);
289     
290     if (int(last_x_pos) == int(tux.get_pos().x))
291       end_sequence_controller->press(Controller::JUMP);    
292     last_x_pos = tux.get_pos().x;
293   }
294
295   // playback a demo?
296   if(playback_demo_stream != 0) {
297     demo_controller->update();
298     char left = false;
299     char right = false;
300     char up = false;
301     char down = false;
302     char jump = false;
303     char action = false;
304     playback_demo_stream->get(left);
305     playback_demo_stream->get(right);
306     playback_demo_stream->get(up);
307     playback_demo_stream->get(down);
308     playback_demo_stream->get(jump);
309     playback_demo_stream->get(action);
310     demo_controller->press(Controller::LEFT, left);
311     demo_controller->press(Controller::RIGHT, right);
312     demo_controller->press(Controller::UP, up);
313     demo_controller->press(Controller::DOWN, down);
314     demo_controller->press(Controller::JUMP, jump);
315     demo_controller->press(Controller::ACTION, action);
316   }
317
318   // save input for demo?
319   if(capture_demo_stream != 0) {
320     capture_demo_stream ->put(main_controller->hold(Controller::LEFT));
321     capture_demo_stream ->put(main_controller->hold(Controller::RIGHT));
322     capture_demo_stream ->put(main_controller->hold(Controller::UP));
323     capture_demo_stream ->put(main_controller->hold(Controller::DOWN));
324     capture_demo_stream ->put(main_controller->hold(Controller::JUMP));   
325     capture_demo_stream ->put(main_controller->hold(Controller::ACTION));
326   }
327 }
328
329 void
330 GameSession::check_end_conditions()
331 {
332   Player* tux = currentsector->player;
333
334   /* End of level? */
335   if(end_sequence && endsequence_timer.check()) {
336     finish(true);
337     return;
338   } else if (!end_sequence && tux->is_dead()) {
339     if (player_status->coins < 0) { 
340       // No more coins: restart level from beginning
341       player_status->coins = 0;
342       restart_level(true);
343     } else { 
344       // Still has coins: restart level from last reset point
345       restart_level(false);
346     }
347     
348     return;
349   }
350 }
351
352 void 
353 GameSession::draw(DrawingContext& context)
354 {
355   currentsector->draw(context);
356   drawstatus(context);
357
358   if(game_pause)
359     draw_pause(context);
360 }
361
362 void
363 GameSession::draw_pause(DrawingContext& context)
364 {
365   context.draw_filled_rect(
366       Vector(0,0), Vector(SCREEN_WIDTH, SCREEN_HEIGHT),
367       Color(.2, .2, .2, .5), LAYER_FOREGROUND1);
368 }
369   
370 void
371 GameSession::process_menu()
372 {
373   Menu* menu = Menu::current();
374   if(menu) {
375     menu->update();
376
377     if(menu == game_menu.get()) {
378       switch (game_menu->check()) {
379         case MNID_CONTINUE:
380           Menu::set_current(0);
381           break;
382         case MNID_ABORTLEVEL:
383           Menu::set_current(0);
384           main_loop->exit_screen();
385           break;
386       }
387     }
388   }
389 }
390
391 void
392 GameSession::setup()
393 {
394   Menu::set_current(NULL);
395   current_ = this;
396
397   // Eat unneeded events
398   SDL_Event event;
399   while(SDL_PollEvent(&event))
400   {}
401 }
402
403 void
404 GameSession::update(float elapsed_time)
405 {
406   process_events();
407   process_menu();
408
409   check_end_conditions();
410
411   // handle controller
412   if(main_controller->pressed(Controller::PAUSE_MENU))
413     on_escape_press();
414   
415   // respawning in new sector?
416   if(newsector != "" && newspawnpoint != "") {
417     Sector* sector = level->get_sector(newsector);
418     if(sector == 0) {
419       log_warning << "Sector '" << newsector << "' not found" << std::endl;
420     }
421     sector->activate(newspawnpoint);
422     sector->play_music(LEVEL_MUSIC);
423     currentsector = sector;
424     newsector = "";
425     newspawnpoint = "";
426   }
427
428   // Update the world state and all objects in the world
429   if(!game_pause) {
430     // Update the world
431     if (end_sequence == ENDSEQUENCE_RUNNING) {
432       currentsector->update(elapsed_time/2);
433     } else if(end_sequence == NO_ENDSEQUENCE) {
434       if(!currentsector->player->growing_timer.started()) {
435         play_time += elapsed_time; //TODO: make sure we don't count cutscene time
436         level->stats.time = play_time;
437         currentsector->update(elapsed_time);
438       }
439     } 
440   }
441
442   // update sounds
443   sound_manager->set_listener_position(currentsector->player->get_pos());
444
445   /* Handle music: */
446   if (end_sequence)
447     return;
448   
449   if(currentsector->player->invincible_timer.started()) {
450     if(currentsector->player->invincible_timer.get_timeleft() <=
451        TUX_INVINCIBLE_TIME_WARNING) {
452       currentsector->play_music(HERRING_WARNING_MUSIC);
453     } else {
454       currentsector->play_music(HERRING_MUSIC);
455     }
456   } else if(currentsector->get_music_type() != LEVEL_MUSIC) {
457     currentsector->play_music(LEVEL_MUSIC);
458   }
459 }
460
461 void
462 GameSession::finish(bool win)
463 {
464   using namespace WorldMapNS;
465
466   if(win) {
467     if(WorldMap::current())
468       WorldMap::current()->finished_level(level.get());
469   }
470   
471   main_loop->exit_screen();
472 }
473
474 void
475 GameSession::respawn(const std::string& sector, const std::string& spawnpoint)
476 {
477   newsector = sector;
478   newspawnpoint = spawnpoint;
479 }
480
481 void
482 GameSession::set_reset_point(const std::string& sector, const Vector& pos)
483 {
484   reset_sector = sector;
485   reset_pos = pos;
486 }
487
488 std::string
489 GameSession::get_working_directory()
490 {
491   return FileSystem::dirname(levelfile);
492 }
493
494 void
495 GameSession::display_info_box(const std::string& text)
496 {
497   InfoBox* box = new InfoBox(text);
498
499   bool running = true;
500   DrawingContext context;
501   
502   while(running)  {
503
504     // TODO make a screen out of this, another mainloop is ugly
505     main_controller->update();
506     SDL_Event event;
507     while (SDL_PollEvent(&event)) {
508       main_controller->process_event(event);
509       if(event.type == SDL_QUIT)
510         main_loop->quit();
511     }
512
513     if(main_controller->pressed(Controller::JUMP)
514         || main_controller->pressed(Controller::ACTION)
515         || main_controller->pressed(Controller::PAUSE_MENU)
516         || main_controller->pressed(Controller::MENU_SELECT))
517       running = false;
518     else if(main_controller->pressed(Controller::DOWN))
519       box->scrolldown();
520     else if(main_controller->pressed(Controller::UP))
521       box->scrollup();
522     box->draw(context);
523     draw(context);
524     context.do_drawing();
525     sound_manager->update();
526   }
527
528   delete box;
529 }
530
531 void
532 GameSession::start_sequence(const std::string& sequencename)
533 {
534   if(sequencename == "endsequence" || sequencename == "fireworks") {
535     if(end_sequence)
536       return;
537
538     end_sequence = ENDSEQUENCE_RUNNING;
539     endsequence_timer.start(7.3);
540     last_x_pos = -1;
541     sound_manager->play_music("music/leveldone.ogg", false);
542     currentsector->player->invincible_timer.start(7.3);
543
544     // Stop all clocks.
545     for(std::vector<GameObject*>::iterator i = currentsector->gameobjects.begin();
546         i != currentsector->gameobjects.end(); ++i)
547     {
548       GameObject* obj = *i;
549
550       LevelTime* lt = dynamic_cast<LevelTime*> (obj);
551       if(lt)
552         lt->stop();
553     }
554
555     if(sequencename == "fireworks") {
556       currentsector->add_object(new Fireworks());
557     }
558   } else if(sequencename == "stoptux") {
559     if(!end_sequence) {
560       log_warning << "Final target reached without an active end sequence" << std::endl;
561       this->start_sequence("endsequence");
562     }
563     end_sequence =  ENDSEQUENCE_WAITING;
564   } else {
565     log_warning << "Unknown sequence '" << sequencename << "'" << std::endl;
566   }
567 }
568
569 /* (Status): */
570 void
571 GameSession::drawstatus(DrawingContext& context)
572 {
573   player_status->draw(context);
574
575   // draw level stats while end_sequence is running
576   if (end_sequence) {
577     level->stats.draw_endseq_panel(context, best_level_statistics, statistics_backdrop.get());
578   }
579 }
580