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.
35 #include <sys/types.h>
39 #include "app/defines.h"
40 #include "app/globals.h"
42 #include "video/screen.h"
43 #include "app/setup.h"
44 #include "high_scores.h"
52 #include "collision.h"
54 #include "particlesystem.h"
55 #include "resources.h"
56 #include "background.h"
58 #include "app/gettext.h"
63 GameSession* GameSession::current_ = 0;
65 GameSession::GameSession(const std::string& levelname_, int mode, bool flip_level_)
66 : level(0), currentsector(0), st_gl_mode(mode),
67 end_sequence(NO_ENDSEQUENCE), levelname(levelname_), flip_level(flip_level_)
71 global_frame_counter = 0;
76 frame_timer.init(true);
78 context = new DrawingContext();
87 GameSession::restart_level()
90 exit_status = ES_NONE;
91 end_sequence = NO_ENDSEQUENCE;
94 frame_timer.init(true);
99 { // Tux has lost a life, so we try to respawn him at the nearest reset point
100 old_x_pos = world->get_tux()->base.x;
108 level->load(levelname);
110 level->do_vertical_flip();
111 currentsector = level->get_sector("main");
113 st_abort("Level has no main sector.", "");
114 currentsector->activate("main");
117 // Set Tux to the nearest reset point
120 ResetPoint best_reset_point = { -1, -1 };
121 for(std::vector<ResetPoint>::iterator i = get_level()->reset_points.begin();
122 i != get_level()->reset_points.end(); ++i)
124 if (i->x < old_x_pos && best_reset_point.x < i->x)
125 best_reset_point = *i;
128 if (best_reset_point.x != -1)
130 world->get_tux()->base.x = best_reset_point.x;
131 world->get_tux()->base.y = best_reset_point.y;
136 if (st_gl_mode != ST_GL_DEMO_GAME)
138 if(st_gl_mode == ST_GL_PLAY || st_gl_mode == ST_GL_LOAD_LEVEL_FILE)
142 time_left.init(true);
144 currentsector->play_music(LEVEL_MUSIC);
147 GameSession::~GameSession()
154 GameSession::levelintro(void)
156 sound_manager->halt_music();
160 DrawingContext context;
161 currentsector->background->draw(context);
163 context.draw_text_center(gold_text, level->get_name(), Vector(0, 220),
166 sprintf(str, "TUX x %d", player_status.lives);
167 context.draw_text_center(white_text, str, Vector(0, 240),
170 if(level->get_author().size())
171 context.draw_text_center(white_small_text,
172 std::string(_("by ")) + level->get_author(),
173 Vector(0, 400), LAYER_FOREGROUND1);
177 context.draw_text_center(white_text,
178 _("Level Vertically Flipped!"),
179 Vector(0, 310), LAYER_FOREGROUND1);
181 context.do_drawing();
184 wait_for_event(event,1000,3000,true);
189 GameSession::start_timers()
191 time_left.start(level->time_left*1000);
192 st_pause_ticks_init();
193 update_time = st_get_ticks();
197 GameSession::on_escape_press()
199 if(currentsector->player->dying || end_sequence != NO_ENDSEQUENCE)
200 return; // don't let the player open the menu, when he is dying
205 if(st_gl_mode == ST_GL_TEST)
207 exit_status = ES_LEVEL_ABORT;
209 else if (!Menu::current())
211 /* Tell Tux that the keys are all down, otherwise
212 it could have nasty bugs, like going allways to the right
213 or whatever that key does */
214 Player& tux = *(currentsector->player);
215 tux.key_event((SDLKey)keymap.jump, UP);
216 tux.key_event((SDLKey)keymap.duck, UP);
217 tux.key_event((SDLKey)keymap.left, UP);
218 tux.key_event((SDLKey)keymap.right, UP);
219 tux.key_event((SDLKey)keymap.fire, UP);
221 Menu::set_current(game_menu);
222 st_pause_ticks_start();
227 GameSession::process_events()
229 if (end_sequence != NO_ENDSEQUENCE)
231 Player& tux = *currentsector->player;
235 tux.input.right = DOWN;
238 if (int(last_x_pos) == int(tux.base.x))
243 last_x_pos = tux.base.x;
246 while (SDL_PollEvent(&event))
248 /* Check for menu-events, if the menu is shown */
251 Menu::current()->event(event);
253 st_pause_ticks_stop();
258 case SDL_QUIT: /* Quit event - quit: */
259 st_abort("Received window close", "");
262 case SDL_KEYDOWN: /* A keypress! */
264 SDLKey key = event.key.keysym.sym;
268 case SDLK_ESCAPE: /* Escape: Open/Close the menu: */
276 case SDL_JOYBUTTONDOWN:
277 if (event.jbutton.button == joystick_keymap.start_button)
285 if(!Menu::current() && !game_pause)
286 st_pause_ticks_stop();
289 while (SDL_PollEvent(&event))
291 /* Check for menu-events, if the menu is shown */
294 Menu::current()->event(event);
296 st_pause_ticks_stop();
300 Player& tux = *currentsector->player;
304 case SDL_QUIT: /* Quit event - quit: */
305 st_abort("Received window close", "");
308 case SDL_KEYDOWN: /* A keypress! */
310 SDLKey key = event.key.keysym.sym;
312 if(tux.key_event(key,DOWN))
317 case SDLK_ESCAPE: /* Escape: Open/Close the menu: */
325 case SDL_KEYUP: /* A keyrelease! */
327 SDLKey key = event.key.keysym.sym;
329 if(tux.key_event(key, UP))
338 snprintf(buf, sizeof(buf), "P: %4.1f,%4.1f",
339 tux.base.x, tux.base.y);
340 context->draw_text(white_text, buf,
341 Vector(0, screen->h - white_text->get_height()),
343 context->do_drawing();
353 st_pause_ticks_stop();
358 st_pause_ticks_start();
370 player_status.distros += 50;
374 tux.got_power = tux.FIRE_POWER;
378 tux.got_power = tux.ICE_POWER;
382 tux.invincible_timer.start(TUX_INVINCIBLE_TIME);
386 --player_status.lives;
390 player_status.score += 1000;
403 case SDL_JOYAXISMOTION:
404 if (event.jaxis.axis == joystick_keymap.x_axis)
406 if (event.jaxis.value < -joystick_keymap.dead_zone)
408 tux.input.left = DOWN;
409 tux.input.right = UP;
411 else if (event.jaxis.value > joystick_keymap.dead_zone)
414 tux.input.right = DOWN;
418 tux.input.left = DOWN;
419 tux.input.right = DOWN;
422 else if (event.jaxis.axis == joystick_keymap.y_axis)
424 if (event.jaxis.value > joystick_keymap.dead_zone)
425 tux.input.down = DOWN;
426 else if (event.jaxis.value < -joystick_keymap.dead_zone)
433 case SDL_JOYBUTTONDOWN:
434 if (event.jbutton.button == joystick_keymap.a_button)
436 else if (event.jbutton.button == joystick_keymap.b_button)
437 tux.input.fire = DOWN;
438 else if (event.jbutton.button == joystick_keymap.start_button)
441 case SDL_JOYBUTTONUP:
442 if (event.jbutton.button == joystick_keymap.a_button)
444 else if (event.jbutton.button == joystick_keymap.b_button)
457 GameSession::check_end_conditions()
459 Player* tux = currentsector->player;
462 Tile* endtile = collision_goal(tux->base);
464 if(end_sequence && !endsequence_timer.check())
466 exit_status = ES_LEVEL_FINISHED;
469 else if(end_sequence == ENDSEQUENCE_RUNNING && endtile && endtile->data >= 1)
471 end_sequence = ENDSEQUENCE_WAITING;
473 else if(!end_sequence && endtile && endtile->data == 0)
475 end_sequence = ENDSEQUENCE_RUNNING;
477 sound_manager->play_music(level_end_song, 0);
478 endsequence_timer.start(7000); // 5 seconds until we finish the map
479 tux->invincible_timer.start(7000); //FIXME: Implement a winning timer for the end sequence (with special winning animation etc.)
481 else if (!end_sequence && tux->is_dead())
483 player_status.bonus = PlayerStatus::NO_BONUS;
485 if (player_status.lives < 0)
487 exit_status = ES_GAME_OVER;
490 { // Still has lives, so reset Tux to the levelstart
499 GameSession::action(double frame_ratio)
501 if (exit_status == ES_NONE && !currentsector->player->growing_timer.check())
503 // Update Tux and the World
504 currentsector->action(frame_ratio);
507 // respawning in new sector?
508 if(newsector != "" && newspawnpoint != "") {
509 Sector* sector = level->get_sector(newsector);
510 currentsector = sector;
511 currentsector->activate(newspawnpoint);
512 currentsector->play_music(LEVEL_MUSIC);
513 newsector = newspawnpoint = "";
520 currentsector->draw(*context);
521 drawstatus(*context);
525 int x = screen->h / 20;
526 for(int i = 0; i < x; ++i)
528 context->draw_filled_rect(
529 Vector(i % 2 ? (pause_menu_frame * i)%screen->w :
530 -((pause_menu_frame * i)%screen->w)
531 ,(i*20+pause_menu_frame)%screen->h),
532 Vector(screen->w,10),
533 Color(20,20,20, rand() % 20 + 1), LAYER_FOREGROUND1+1);
535 context->draw_filled_rect(
536 Vector(0,0), Vector(screen->w, screen->h),
537 Color(rand() % 50, rand() % 50, rand() % 50, 128), LAYER_FOREGROUND1);
538 context->draw_text_center(blue_text, _("PAUSE - Press 'P' To Play"),
539 Vector(0, 230), LAYER_FOREGROUND1+2);
543 sprintf(str1, _("Playing: "));
544 sprintf(str2, level->name.c_str());
546 context->draw_text(blue_text, str1,
547 Vector((screen->w - (blue_text->get_text_width(str1) + white_text->get_text_width(str2)))/2, 340),
548 LAYER_FOREGROUND1+2);
549 context->draw_text(white_text, str2,
550 Vector(((screen->w - (blue_text->get_text_width(str1) + white_text->get_text_width(str2)))/2)+blue_text->get_text_width(str1), 340),
551 LAYER_FOREGROUND1+2);
556 Menu::current()->draw(*context);
557 mouse_cursor->draw(*context);
560 context->do_drawing();
564 GameSession::process_menu()
566 Menu* menu = Menu::current();
571 if(menu == game_menu)
573 switch (game_menu->check())
576 st_pause_ticks_stop();
578 case MNID_ABORTLEVEL:
579 st_pause_ticks_stop();
580 exit_status = ES_LEVEL_ABORT;
584 else if(menu == options_menu)
586 process_options_menu();
588 else if(menu == load_game_menu )
590 process_load_game_menu();
595 GameSession::ExitStatus
598 Menu::set_current(0);
603 update_time = last_update_time = st_get_ticks();
605 // Eat unneeded events
607 while (SDL_PollEvent(&event)) {}
611 while (exit_status == ES_NONE)
613 /* Calculate the movement-factor */
614 double frame_ratio = ((double)(update_time-last_update_time))/((double)FRAME_RATE);
616 if(!frame_timer.check())
618 frame_timer.start(25);
619 ++global_frame_counter;
623 currentsector->player->input.old_fire
624 = currentsector->player->input.fire;
629 // Update the world state and all objects in the world
630 // Do that with a constante time-delta so that the game will run
631 // determistic and not different on different machines
632 if(!game_pause && !Menu::current())
635 check_end_conditions();
636 if (end_sequence == ENDSEQUENCE_RUNNING)
637 action(frame_ratio/2);
638 else if(end_sequence == NO_ENDSEQUENCE)
649 /* Time stops in pause mode */
650 if(game_pause || Menu::current())
655 /* Set the time of the last update and the time of the current update */
656 last_update_time = update_time;
657 update_time = st_get_ticks();
659 /* Pause till next frame, if the machine running the game is too fast: */
660 /* FIXME: Works great for in OpenGl mode, where the CPU doesn't have to do that much. But
661 the results in SDL mode aren't perfect (thought the 100 FPS are reached), even on an AMD2500+. */
662 if(last_update_time >= update_time - 12)
665 update_time = st_get_ticks();
669 if (!time_left.check() && currentsector->player->dying == DYING_NOT
671 currentsector->player->kill(Player::KILL);
674 if(currentsector->player->invincible_timer.check() && !end_sequence)
676 currentsector->play_music(HERRING_MUSIC);
678 /* are we low on time ? */
679 else if (time_left.get_left() < TIME_WARNING && !end_sequence)
681 currentsector->play_music(HURRYUP_MUSIC);
683 /* or just normal music? */
684 else if(currentsector->get_music_type() != LEVEL_MUSIC && !end_sequence)
686 currentsector->play_music(LEVEL_MUSIC);
689 /* Calculate frames per second */
693 fps_fps = (1000.0 / (float)fps_timer.get_gone()) * (float)fps_cnt;
695 if(!fps_timer.check())
697 fps_timer.start(1000);
707 GameSession::respawn(const std::string& sector, const std::string& spawnpoint)
710 newspawnpoint = spawnpoint;
713 /* Bounce a brick: */
714 void bumpbrick(float x, float y)
716 Sector::current()->add_bouncy_brick(Vector(((int)(x + 1) / 32) * 32,
717 (int)(y / 32) * 32));
719 sound_manager->play_sound(sounds[SND_BRICK], Vector(x, y), Sector::current()->player->get_pos());
724 GameSession::drawstatus(DrawingContext& context)
728 snprintf(str, 60, " %d", player_status.score);
729 context.draw_text(white_text, _("SCORE"), Vector(0, 0), LAYER_FOREGROUND1);
730 context.draw_text(gold_text, str, Vector(96, 0), LAYER_FOREGROUND1);
732 if(st_gl_mode == ST_GL_TEST)
734 context.draw_text(white_text, _("Press ESC To Return"), Vector(0,20),
738 if(!time_left.check()) {
739 context.draw_text_center(white_text, _("TIME's UP"), Vector(0, 0),
741 } else if (time_left.get_left() > TIME_WARNING || (global_frame_counter % 10) < 5) {
742 sprintf(str, " %d", time_left.get_left() / 1000 );
743 context.draw_text_center(white_text, _("TIME"),
744 Vector(0, 0), LAYER_FOREGROUND1);
745 context.draw_text_center(gold_text, str,
746 Vector(4*16, 0), LAYER_FOREGROUND1);
749 sprintf(str, " %d", player_status.distros);
750 context.draw_text(white_text, _("COINS"),
751 Vector(screen->w - white_text->get_text_width(_("COINS"))-white_text->get_text_width(" 99"), 0),
753 context.draw_text(gold_text, str,
754 Vector(screen->w - gold_text->get_text_width(" 99"), 0),LAYER_FOREGROUND1);
756 if (player_status.lives >= 5)
758 sprintf(str, "%dx", player_status.lives);
759 float x = screen->w - gold_text->get_text_width(str) - tux_life->w;
760 context.draw_text(gold_text, str, Vector(x, 20), LAYER_FOREGROUND1);
761 context.draw_surface(tux_life, Vector(screen->w - 16, 20),
766 for(int i= 0; i < player_status.lives; ++i)
767 context.draw_surface(tux_life,
768 Vector(screen->w - tux_life->w*4 +(tux_life->w*i), 20),
772 context.draw_text(white_text, _("LIVES"),
773 Vector(screen->w - white_text->get_text_width(_("LIVES")) - white_text->get_text_width(" 99"), 20),
778 sprintf(str, "%2.1f", fps_fps);
779 context.draw_text(white_text, "FPS",
780 Vector(screen->w - white_text->get_text_width("FPS "), 40),
782 context.draw_text(gold_text, str,
783 Vector(screen->w-4*16, 40), LAYER_FOREGROUND1);
788 GameSession::drawresultscreen(void)
792 DrawingContext context;
793 currentsector->background->draw(context);
795 context.draw_text_center(blue_text, _("Result:"), Vector(0, 200),
798 sprintf(str, _("SCORE: %d"), player_status.score);
799 context.draw_text_center(gold_text, str, Vector(0, 224), LAYER_FOREGROUND1);
801 sprintf(str, _("COINS: %d"), player_status.distros);
802 context.draw_text_center(gold_text, str, Vector(0, 256), LAYER_FOREGROUND1);
804 context.do_drawing();
807 wait_for_event(event,2000,5000,true);
810 std::string slotinfo(int slot)
815 sprintf(slotfile,"%s/slot%d.stsg",st_save_dir,slot);
817 lisp_object_t* savegame = lisp_read_from_file(slotfile);
820 LispReader reader(lisp_cdr(savegame));
821 reader.read_string("title", title);
825 if (access(slotfile, F_OK) == 0)
828 snprintf(tmp,1024,"Slot %d - %s",slot, title.c_str());
830 snprintf(tmp, 1024,_("Slot %d - Savegame"),slot);
833 sprintf(tmp,_("Slot %d - Free"),slot);
838 bool process_load_game_menu()
840 int slot = load_game_menu->check();
842 if(slot != -1 && load_game_menu->get_item_by_id(slot).kind == MN_ACTION)
845 snprintf(slotfile, 1024, "%s/slot%d.stsg", st_save_dir, slot);
847 if (access(slotfile, F_OK) != 0)
852 // shrink_fade(Point((screen->w/2),(screen->h/2)), 1000);
854 WorldMapNS::WorldMap worldmap;
856 // Load the game or at least set the savegame_file variable
857 worldmap.loadgame(slotfile);
861 Menu::set_current(main_menu);
863 st_pause_ticks_stop();