4 // Copyright (C) 2000 Bill Kendrick <bill@newbreedsoftware.com>
5 // Copyright (C) 2004 Tobias Glaesser <tobi.web@gmx.de>
6 // Copyright (C) 2004 Ingo Ruhnke <grumbel@gmx.de>
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License
10 // as published by the Free Software Foundation; either version 2
11 // of the License, or (at your option) any later version.
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU General Public License for more details.
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
37 #include "game_session.hpp"
39 #include "video/screen.hpp"
40 #include "audio/sound_manager.hpp"
41 #include "gui/menu.hpp"
45 #include "player_status.hpp"
46 #include "object/particlesystem.hpp"
47 #include "object/background.hpp"
48 #include "object/gradient.hpp"
49 #include "object/tilemap.hpp"
50 #include "object/camera.hpp"
51 #include "object/player.hpp"
52 #include "object/level_time.hpp"
53 #include "lisp/lisp.hpp"
54 #include "lisp/parser.hpp"
55 #include "resources.hpp"
56 #include "worldmap.hpp"
58 #include "statistics.hpp"
60 #include "object/fireworks.hpp"
61 #include "textscroller.hpp"
62 #include "control/codecontroller.hpp"
63 #include "control/joystickkeyboardcontroller.hpp"
65 #include "file_system.hpp"
66 #include "gameconfig.hpp"
67 #include "gettext.hpp"
68 #include "exceptions.hpp"
69 #include "flip_level_transformer.hpp"
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"
74 static const float LOGICAL_FPS = 64.0;
76 GameSession* GameSession::current_ = 0;
78 GameSession::GameSession(const std::string& levelfile_, GameSessionMode mode,
79 Statistics* statistics)
80 : level(0), currentsector(0), mode(mode),
81 end_sequence(NO_ENDSEQUENCE), end_sequence_controller(0),
82 levelfile(levelfile_), best_level_statistics(statistics),
83 capture_demo_stream(0), playback_demo_stream(0), demo_controller(0)
91 context = new DrawingContext();
92 console = new Console(context);
93 Console::registerCommandReceiver(this);
99 GameSession::restart_level(bool fromBeginning)
102 exit_status = ES_NONE;
103 end_sequence = NO_ENDSEQUENCE;
105 main_controller->reset();
111 level->load(levelfile);
113 global_stats.reset();
114 global_stats.set_total_points(COINS_COLLECTED_STAT, level->get_total_coins());
115 global_stats.set_total_points(BADGUYS_KILLED_STAT, level->get_total_badguys());
119 for(std::vector<Sector*>::iterator i = level->sectors.begin(); i != level->sectors.end(); ++i)
123 for(std::vector<GameObject*>::iterator j = sec->gameobjects.begin();
124 j != sec->gameobjects.end(); ++j)
126 GameObject* obj = *j;
128 LevelTime* lt = dynamic_cast<LevelTime*> (obj);
130 time += int(lt->get_level_time());
133 global_stats.set_total_points(TIME_NEEDED_STAT, (time == 0) ? -1 : time);
135 if (fromBeginning) reset_sector="";
136 if(reset_sector != "") {
137 currentsector = level->get_sector(reset_sector);
139 std::stringstream msg;
140 msg << "Couldn't find sector '" << reset_sector << "' for resetting tux.";
141 throw std::runtime_error(msg.str());
143 currentsector->activate(reset_pos);
145 currentsector = level->get_sector("main");
147 throw std::runtime_error("Couldn't find main sector");
148 currentsector->activate("main");
151 if(mode == ST_GL_PLAY || mode == ST_GL_LOAD_LEVEL_FILE)
154 currentsector->play_music(LEVEL_MUSIC);
156 if(capture_file != "")
157 record_demo(capture_file);
160 GameSession::~GameSession()
162 delete capture_demo_stream;
163 delete playback_demo_stream;
164 delete demo_controller;
166 delete end_sequence_controller;
169 Console::unregisterCommandReceiver(this);
176 GameSession::record_demo(const std::string& filename)
178 delete capture_demo_stream;
180 capture_demo_stream = new std::ofstream(filename.c_str());
181 if(!capture_demo_stream->good()) {
182 std::stringstream msg;
183 msg << "Couldn't open demo file '" << filename << "' for writing.";
184 throw std::runtime_error(msg.str());
186 capture_file = filename;
190 GameSession::play_demo(const std::string& filename)
192 delete playback_demo_stream;
193 delete demo_controller;
195 playback_demo_stream = new std::ifstream(filename.c_str());
196 if(!playback_demo_stream->good()) {
197 std::stringstream msg;
198 msg << "Couldn't open demo file '" << filename << "' for reading.";
199 throw std::runtime_error(msg.str());
202 Player& tux = *currentsector->player;
203 demo_controller = new CodeController();
204 tux.set_controller(demo_controller);
208 GameSession::levelintro()
212 sound_manager->stop_music();
214 DrawingContext context;
215 for(Sector::GameObjects::iterator i = currentsector->gameobjects.begin();
216 i != currentsector->gameobjects.end(); ++i) {
217 Background* background = dynamic_cast<Background*> (*i);
219 background->draw(context);
221 Gradient* gradient = dynamic_cast<Gradient*> (*i);
223 gradient->draw(context);
227 // context.draw_text(gold_text, level->get_name(), Vector(SCREEN_WIDTH/2, 160),
228 // CENTER_ALLIGN, LAYER_FOREGROUND1);
229 context.draw_center_text(gold_text, level->get_name(), Vector(0, 160),
232 sprintf(str, "Coins: %d", player_status->coins);
233 context.draw_text(white_text, str, Vector(SCREEN_WIDTH/2, 210),
234 CENTER_ALLIGN, LAYER_FOREGROUND1);
236 if((level->get_author().size()) && (level->get_author() != "SuperTux Team"))
237 //TODO make author check case/blank-insensitive
238 context.draw_text(white_small_text,
239 std::string(_("contributed by ")) + level->get_author(),
240 Vector(SCREEN_WIDTH/2, 350), CENTER_ALLIGN, LAYER_FOREGROUND1);
243 if(best_level_statistics != NULL)
244 best_level_statistics->draw_message_info(context, _("Best Level Statistics"));
247 context.do_drawing();
249 wait_for_event(1.0, 3.0);
253 GameSession::on_escape_press()
255 if(currentsector->player->is_dying() || end_sequence != NO_ENDSEQUENCE)
256 return; // don't let the player open the menu, when he is dying
258 if(mode == ST_GL_TEST) {
259 exit_status = ES_LEVEL_ABORT;
260 } else if (!Menu::current()) {
261 Menu::set_current(game_menu);
262 game_menu->set_active_item(MNID_CONTINUE);
270 GameSession::process_events()
272 Player& tux = *currentsector->player;
273 main_controller->update();
275 // end of pause mode?
276 if(!Menu::current() && game_pause) {
280 if (end_sequence != NO_ENDSEQUENCE) {
281 if(end_sequence_controller == 0) {
282 end_sequence_controller = new CodeController();
283 tux.set_controller(end_sequence_controller);
286 end_sequence_controller->press(Controller::RIGHT);
288 if (int(last_x_pos) == int(tux.get_pos().x))
289 end_sequence_controller->press(Controller::JUMP);
290 last_x_pos = tux.get_pos().x;
293 main_controller->update();
295 while (SDL_PollEvent(&event)) {
296 /* Check for menu-events, if the menu is shown */
298 Menu::current()->event(event);
299 main_controller->process_event(event);
300 if(event.type == SDL_QUIT)
301 throw graceful_shutdown();
305 if(playback_demo_stream != 0) {
306 demo_controller->update();
313 playback_demo_stream->get(left);
314 playback_demo_stream->get(right);
315 playback_demo_stream->get(up);
316 playback_demo_stream->get(down);
317 playback_demo_stream->get(jump);
318 playback_demo_stream->get(action);
319 demo_controller->press(Controller::LEFT, left);
320 demo_controller->press(Controller::RIGHT, right);
321 demo_controller->press(Controller::UP, up);
322 demo_controller->press(Controller::DOWN, down);
323 demo_controller->press(Controller::JUMP, jump);
324 demo_controller->press(Controller::ACTION, action);
327 // save input for demo?
328 if(capture_demo_stream != 0) {
329 capture_demo_stream ->put(main_controller->hold(Controller::LEFT));
330 capture_demo_stream ->put(main_controller->hold(Controller::RIGHT));
331 capture_demo_stream ->put(main_controller->hold(Controller::UP));
332 capture_demo_stream ->put(main_controller->hold(Controller::DOWN));
333 capture_demo_stream ->put(main_controller->hold(Controller::JUMP));
334 capture_demo_stream ->put(main_controller->hold(Controller::ACTION));
339 GameSession::consoleCommand(std::string command)
341 if (command == "foo") {
346 //TODO: Build command list automatically
347 if (command == "cmdlist") {
348 msg_info("foo, cmdlist, cheats, whereami, camera");
351 //TODO: remove (or at least hide) this before release
352 if (command == "cheats") {
353 msg_info("grow, fire, ice, lifeup, numberofthebeast, lifedown, grease, invincible, mortal, shrink, kill, gotoend, flip, finish");
357 if (currentsector == 0) return false;
358 Player& tux = *currentsector->player;
360 // Cheating words (the goal of this is really for debugging,
361 // but could be used for some cheating, nothing wrong with that)
362 if (command == "grow") {
363 tux.set_bonus(GROWUP_BONUS, false);
366 if (command == "fire") {
367 tux.set_bonus(FIRE_BONUS, false);
370 if (command == "ice") {
371 tux.set_bonus(ICE_BONUS, false);
374 if (command == "lifeup") {
375 player_status->incLives();
378 if (command == "numberofthebeast") {
379 player_status->coins = 55;
382 if (command == "lifedown") {
383 player_status->coins = std::max(player_status->coins-25, 0);
386 if (command == "grease") {
387 tux.physic.set_velocity_x(tux.physic.get_velocity_x()*3);
390 if (command == "invincible") {
391 // be invincle for the rest of the level
392 tux.invincible_timer.start(10000);
395 if (command == "mortal") {
396 // give up invincibility
397 tux.invincible_timer.stop();
400 if (command == "shrink") {
402 tux.kill(tux.SHRINK);
405 if (command == "kill") {
409 if (command == "whereami") {
410 msg_info("You are at x " << tux.get_pos().x << ", y " << tux.get_pos().y);
414 if(command == "grid")) {
416 debug_grid = !debug_grid;
420 if (command == "gotoend") {
421 // goes to the end of the level
423 (currentsector->solids->get_width()*32) - (SCREEN_WIDTH*2), 0));
424 currentsector->camera->reset(
425 Vector(tux.get_pos().x, tux.get_pos().y));
428 if (command == "flip") {
429 FlipLevelTransformer flip_transformer;
430 flip_transformer.transform(GameSession::current()->get_current_level());
433 if (command == "finish") {
434 // finish current sector
435 exit_status = ES_LEVEL_FINISHED;
436 // don't add points to stats though...
439 if (command == "camera") {
440 msg_info("Camera is at "
441 << Sector::current()->camera->get_translation().x << ","
442 << Sector::current()->camera->get_translation().y);
450 GameSession::check_end_conditions()
452 Player* tux = currentsector->player;
455 if(end_sequence && endsequence_timer.check()) {
456 exit_status = ES_LEVEL_FINISHED;
458 // add time spent to statistics
459 int tottime = 0, remtime = 0;
460 for(std::vector<Sector*>::iterator i = level->sectors.begin(); i != level->sectors.end(); ++i)
464 for(std::vector<GameObject*>::iterator j = sec->gameobjects.begin();
465 j != sec->gameobjects.end(); ++j)
467 GameObject* obj = *j;
469 LevelTime* lt = dynamic_cast<LevelTime*> (obj);
472 tottime += int(lt->get_level_time());
473 remtime += int(lt->get_remaining_time());
477 global_stats.set_points(TIME_NEEDED_STAT, (tottime == 0 ? -1 : (tottime-remtime)));
480 } else if (!end_sequence && tux->is_dead()) {
481 if (player_status->coins < 0) {
482 // No more coins: restart level from beginning
483 player_status->coins = 0;
486 // Still has coins: restart level from last reset point
487 restart_level(false);
495 GameSession::update(float elapsed_time)
498 if(main_controller->pressed(Controller::PAUSE_MENU))
502 if(!currentsector->player->growing_timer.started()) {
503 // Update Tux and the World
504 currentsector->update(elapsed_time);
507 // respawning in new sector?
508 if(newsector != "" && newspawnpoint != "") {
509 Sector* sector = level->get_sector(newsector);
511 msg_warning("Sector '" << newsector << "' not found");
513 sector->activate(newspawnpoint);
514 sector->play_music(LEVEL_MUSIC);
515 currentsector = sector;
521 sound_manager->set_listener_position(currentsector->player->get_pos());
527 currentsector->draw(*context);
528 drawstatus(*context);
533 if(Menu::current()) {
534 Menu::current()->draw(*context);
538 context->do_drawing();
542 GameSession::draw_pause()
544 context->draw_filled_rect(
545 Vector(0,0), Vector(SCREEN_WIDTH, SCREEN_HEIGHT),
546 Color(.2, .2, .2, .5), LAYER_FOREGROUND1);
550 GameSession::process_menu()
552 Menu* menu = Menu::current();
556 if(menu == game_menu) {
557 switch (game_menu->check()) {
559 Menu::set_current(0);
561 case MNID_ABORTLEVEL:
562 Menu::set_current(0);
563 exit_status = ES_LEVEL_ABORT;
566 } else if(menu == options_menu) {
567 process_options_menu();
568 } else if(menu == load_game_menu ) {
569 process_load_game_menu();
575 GameSession::ExitStatus
578 Menu::set_current(0);
583 // Eat unneeded events
585 while(SDL_PollEvent(&event))
590 Uint32 fps_ticks = SDL_GetTicks();
591 Uint32 fps_nextframe_ticks = SDL_GetTicks();
593 bool skipdraw = false;
595 while (exit_status == ES_NONE) {
596 // we run in a logical framerate so elapsed time is a constant
597 static const float elapsed_time = 1.0 / LOGICAL_FPS;
598 // old code... float elapsed_time = float(ticks - lastticks) / 1000.;
600 game_time += elapsed_time;
603 ticks = SDL_GetTicks();
604 if(ticks > fps_nextframe_ticks) {
605 if(skipdraw == true) {
606 // already skipped last frame? we have to slow down the game then...
608 fps_nextframe_ticks -= (Uint32) (1000.0 / LOGICAL_FPS);
610 // don't draw all frames when we're getting too slow
615 while(fps_nextframe_ticks > ticks) {
617 // If we really have to wait long, then do an imprecise SDL_Delay()
618 Uint32 diff = fps_nextframe_ticks - ticks;
620 SDL_Delay(diff - 10);
622 ticks = SDL_GetTicks();
625 fps_nextframe_ticks = ticks + (Uint32) (1000.0 / LOGICAL_FPS);
628 float diff = SDL_GetTicks() - fps_nextframe_ticks;
630 // sets the ticks that must have elapsed
631 fps_nextframe_ticks = SDL_GetTicks() + (1000.0 / LOGICAL_FPS);
633 // sets the ticks that must have elapsed
634 // in order for the next frame to start.
635 fps_nextframe_ticks += 1000.0 / LOGICAL_FPS;
642 // Update the world state and all objects in the world
643 // Do that with a constante time-delta so that the game will run
644 // determistic and not different on different machines
645 if(!game_pause && !Menu::current())
648 check_end_conditions();
649 if (end_sequence == ENDSEQUENCE_RUNNING)
650 update(elapsed_time/2);
651 else if(end_sequence == NO_ENDSEQUENCE)
652 update(elapsed_time);
663 sound_manager->update();
665 /* Time stops in pause mode */
666 if(game_pause || Menu::current())
671 //frame_rate.update();
674 if (currentsector->player->invincible_timer.started() &&
675 currentsector->player->invincible_timer.get_timeleft()
676 > TUX_INVINCIBLE_TIME_WARNING && !end_sequence)
678 currentsector->play_music(HERRING_MUSIC);
680 /* or just normal music? */
681 else if(currentsector->get_music_type() != LEVEL_MUSIC && !end_sequence)
683 currentsector->play_music(LEVEL_MUSIC);
686 /* Calculate frames per second */
691 if(SDL_GetTicks() - fps_ticks >= 500)
693 fps_fps = (float) fps_cnt / .5;
695 fps_ticks = SDL_GetTicks();
702 main_controller->reset();
707 GameSession::finish(bool win)
710 exit_status = ES_LEVEL_FINISHED;
712 exit_status = ES_LEVEL_ABORT;
716 GameSession::respawn(const std::string& sector, const std::string& spawnpoint)
719 newspawnpoint = spawnpoint;
723 GameSession::set_reset_point(const std::string& sector, const Vector& pos)
725 reset_sector = sector;
730 GameSession::get_working_directory()
732 return FileSystem::dirname(levelfile);
736 GameSession::display_info_box(const std::string& text)
738 InfoBox* box = new InfoBox(text);
743 main_controller->update();
745 while (SDL_PollEvent(&event)) {
746 main_controller->process_event(event);
747 if(event.type == SDL_QUIT)
748 throw graceful_shutdown();
751 if(main_controller->pressed(Controller::JUMP)
752 || main_controller->pressed(Controller::ACTION)
753 || main_controller->pressed(Controller::PAUSE_MENU)
754 || main_controller->pressed(Controller::MENU_SELECT))
756 else if(main_controller->pressed(Controller::DOWN))
758 else if(main_controller->pressed(Controller::UP))
762 sound_manager->update();
769 GameSession::start_sequence(const std::string& sequencename)
771 if(sequencename == "endsequence" || sequencename == "fireworks") {
775 end_sequence = ENDSEQUENCE_RUNNING;
776 endsequence_timer.start(7.3);
778 sound_manager->play_music("music/leveldone.ogg", false);
779 currentsector->player->invincible_timer.start(7.3);
782 for(std::vector<GameObject*>::iterator i = currentsector->gameobjects.begin();
783 i != currentsector->gameobjects.end(); ++i)
785 GameObject* obj = *i;
787 LevelTime* lt = dynamic_cast<LevelTime*> (obj);
792 if(sequencename == "fireworks") {
793 currentsector->add_object(new Fireworks());
795 } else if(sequencename == "stoptux") {
797 msg_warning("Final target reached without "
798 << "an active end sequence");
799 this->start_sequence("endsequence");
801 end_sequence = ENDSEQUENCE_WAITING;
803 msg_warning("Unknown sequence '" << sequencename << "'");
809 GameSession::drawstatus(DrawingContext& context)
811 player_status->draw(context);
813 if(config->show_fps) {
815 snprintf(str, sizeof(str), "%3.1f", fps_fps);
816 const char* fpstext = "FPS";
817 context.draw_text(white_text, fpstext, Vector(SCREEN_WIDTH - white_text->get_text_width(fpstext) - gold_text->get_text_width(" 99999") - BORDER_X, BORDER_Y + 20), LEFT_ALLIGN, LAYER_FOREGROUND1);
818 context.draw_text(gold_text, str, Vector(SCREEN_WIDTH - BORDER_X, BORDER_Y + 20), RIGHT_ALLIGN, LAYER_FOREGROUND1);
823 GameSession::drawresultscreen()
827 DrawingContext context;
828 for(Sector::GameObjects::iterator i = currentsector->gameobjects.begin();
829 i != currentsector->gameobjects.end(); ++i) {
830 Background* background = dynamic_cast<Background*> (*i);
832 background->draw(context);
836 context.draw_text(blue_text, _("Result:"), Vector(SCREEN_WIDTH/2, 200),
837 CENTER_ALLIGN, LAYER_FOREGROUND1);
839 // sprintf(str, _("SCORE: %d"), global_stats.get_points(SCORE_STAT));
840 // context.draw_text(gold_text, str, Vector(SCREEN_WIDTH/2, 224), CENTER_ALLIGN, LAYER_FOREGROUND1);
842 // y == 256 before removal of score
843 sprintf(str, _("COINS: %d"), player_status->coins);
844 context.draw_text(gold_text, str, Vector(SCREEN_WIDTH/2, 224), CENTER_ALLIGN, LAYER_FOREGROUND1);
847 context.do_drawing();
849 wait_for_event(2.0, 5.0);
852 std::string slotinfo(int slot)
855 std::string slotfile;
857 std::stringstream stream;
859 slotfile = "save/slot" + stream.str() + ".stsg";
863 std::auto_ptr<lisp::Lisp> root (parser.parse(slotfile));
865 const lisp::Lisp* savegame = root->get_lisp("supertux-savegame");
867 throw std::runtime_error("file is not a supertux-savegame.");
869 savegame->get("title", title);
870 } catch(std::exception& e) {
871 return std::string(_("Slot")) + " " + stream.str() + " - " +
872 std::string(_("Free"));
875 return std::string("Slot ") + stream.str() + " - " + title;
878 bool process_load_game_menu()
880 int slot = load_game_menu->check();
885 if(load_game_menu->get_item_by_id(slot).kind != MN_ACTION)
888 std::stringstream stream;
890 std::string slotfile = "save/slot" + stream.str() + ".stsg";
892 sound_manager->stop_music();
894 DrawingContext context;
895 context.draw_text(white_text, "Loading...",
896 Vector(SCREEN_WIDTH/2, SCREEN_HEIGHT/2),
897 CENTER_ALLIGN, LAYER_FOREGROUND1);
898 context.do_drawing();
900 WorldMapNS::WorldMap worldmap;
902 worldmap.set_map_filename("/levels/world1/worldmap.stwm");
903 // Load the game or at least set the savegame_file variable
904 worldmap.loadgame(slotfile);
908 Menu::set_current(main_menu);