- added way to interupt exit sequence
[supertux.git] / src / gameloop.cpp
1 //  $Id$
2 // 
3 //  SuperTux
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>
7 //
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.
12 //
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.
17 // 
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.
21
22 #include <iostream>
23 #include <assert.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <math.h>
27 #include <string.h>
28 #include <errno.h>
29 #include <unistd.h>
30 #include <math.h>
31 #include <time.h>
32 #include <SDL.h>
33
34 #ifndef WIN32
35 #include <sys/types.h>
36 #include <ctype.h>
37 #endif
38
39 #include "defines.h"
40 #include "globals.h"
41 #include "gameloop.h"
42 #include "screen.h"
43 #include "setup.h"
44 #include "high_scores.h"
45 #include "menu.h"
46 #include "badguy.h"
47 #include "world.h"
48 #include "special.h"
49 #include "player.h"
50 #include "level.h"
51 #include "scene.h"
52 #include "collision.h"
53 #include "tile.h"
54 #include "particlesystem.h"
55 #include "resources.h"
56 #include "music_manager.h"
57
58 GameSession* GameSession::current_ = 0;
59
60 GameSession::GameSession(const std::string& subset_, int levelnb_, int mode)
61   : world(0), st_gl_mode(mode), levelnb(levelnb_), end_sequence(false),
62     subset(subset_)
63 {
64   current_ = this;
65   
66   global_frame_counter = 0;
67   game_pause = false;
68
69   fps_timer.init(true);            
70   frame_timer.init(true);
71
72   restart_level();
73 }
74
75 void
76 GameSession::restart_level()
77 {
78   game_pause   = false;
79   exit_status  = NONE;
80   end_sequence = false;
81
82   fps_timer.init(true);
83   frame_timer.init(true);
84
85   float old_x_pos = -1;
86
87   if (world)
88     { // Tux has lost a life, so we try to respawn him at the nearest reset point
89       old_x_pos = world->get_tux()->base.x;
90     }
91   
92   delete world;
93
94   if (st_gl_mode == ST_GL_LOAD_LEVEL_FILE)
95     {
96       world = new World(subset);
97     }
98   else if (st_gl_mode == ST_GL_DEMO_GAME)
99     {
100       world = new World(subset);
101     }
102   else
103     {
104       world = new World(subset, levelnb);
105     }
106
107   // Set Tux to the nearest reset point
108   if (old_x_pos != -1)
109     {
110       ResetPoint best_reset_point = { -1, -1 };
111       for(std::vector<ResetPoint>::iterator i = get_level()->reset_points.begin();
112           i != get_level()->reset_points.end(); ++i)
113         {
114           if (i->x < old_x_pos && best_reset_point.x < i->x)
115             best_reset_point = *i;
116         }
117       
118       if (best_reset_point.x != -1)
119         {
120           world->get_tux()->base.x = best_reset_point.x;
121           world->get_tux()->base.y = best_reset_point.y;
122         }
123     }
124
125     
126   if (st_gl_mode != ST_GL_DEMO_GAME)
127     {
128       if(st_gl_mode == ST_GL_PLAY || st_gl_mode == ST_GL_LOAD_LEVEL_FILE)
129         levelintro();
130     }
131
132   time_left.init(true);
133   start_timers();
134   world->play_music(LEVEL_MUSIC);
135 }
136
137 GameSession::~GameSession()
138 {
139   delete world;
140 }
141
142 void
143 GameSession::levelintro(void)
144 {
145   music_manager->halt_music();
146   
147   char str[60];
148  
149   if (get_level()->img_bkgd)
150     get_level()->img_bkgd->draw(0, 0);
151   else
152     drawgradient(get_level()->bkgd_top, get_level()->bkgd_bottom);
153
154   sprintf(str, "%s", world->get_level()->name.c_str());
155   gold_text->drawf(str, 0, 200, A_HMIDDLE, A_TOP, 1);
156
157   sprintf(str, "TUX x %d", player_status.lives);
158   white_text->drawf(str, 0, 224, A_HMIDDLE, A_TOP, 1);
159   
160   sprintf(str, "by %s", world->get_level()->author.c_str());
161   white_small_text->drawf(str, 0, 400, A_HMIDDLE, A_TOP, 1);
162   
163
164   flipscreen();
165
166   SDL_Event event;
167   wait_for_event(event,1000,3000,true);
168 }
169
170 /* Reset Timers */
171 void
172 GameSession::start_timers()
173 {
174   time_left.start(world->get_level()->time_left*1000);
175   st_pause_ticks_init();
176   update_time = st_get_ticks();
177 }
178
179 void
180 GameSession::on_escape_press()
181 {
182   if(game_pause)
183     return;
184
185   if(st_gl_mode == ST_GL_TEST)
186     {
187       exit_status = LEVEL_ABORT;
188     }
189   else if (!Menu::current())
190     {
191       Menu::set_current(game_menu);
192     }
193 }
194
195 void
196 GameSession::process_events()
197 {
198   if (end_sequence)
199     {
200       Player& tux = *world->get_tux();
201           
202       tux.input.left  = UP;
203       tux.input.right = DOWN; 
204       tux.input.down  = UP; 
205
206       if (int(last_x_pos) == int(tux.base.x))
207         tux.input.up    = DOWN; 
208       else
209         tux.input.up    = UP; 
210
211       last_x_pos = tux.base.x;
212
213       SDL_Event event;
214       while (SDL_PollEvent(&event))
215         {
216           /* Check for menu-events, if the menu is shown */
217           if (Menu::current())
218             {
219               Menu::current()->event(event);
220               st_pause_ticks_start();
221             }
222
223           switch(event.type)
224             {
225             case SDL_QUIT:        /* Quit event - quit: */
226               st_abort("Received window close", "");
227               break;
228               
229             case SDL_KEYDOWN:     /* A keypress! */
230               {
231                 SDLKey key = event.key.keysym.sym;
232            
233                 switch(key)
234                   {
235                   case SDLK_ESCAPE:    /* Escape: Open/Close the menu: */
236                     on_escape_press();
237                     break;
238                   default:
239                     break;
240                   }
241               }
242           
243             case SDL_JOYBUTTONDOWN:
244               if (event.jbutton.button == joystick_keymap.start_button)
245                 on_escape_press();
246               break;
247             }
248         }
249     }
250   else
251     {
252       if(!Menu::current() && !game_pause)
253         st_pause_ticks_stop();
254
255       SDL_Event event;
256       while (SDL_PollEvent(&event))
257         {
258           /* Check for menu-events, if the menu is shown */
259           if (Menu::current())
260             {
261               Menu::current()->event(event);
262               st_pause_ticks_start();
263             }
264           else
265             {
266               Player& tux = *world->get_tux();
267   
268               switch(event.type)
269                 {
270                 case SDL_QUIT:        /* Quit event - quit: */
271                   st_abort("Received window close", "");
272                   break;
273
274                 case SDL_KEYDOWN:     /* A keypress! */
275                   {
276                     SDLKey key = event.key.keysym.sym;
277             
278                     if(tux.key_event(key,DOWN))
279                       break;
280
281                     switch(key)
282                       {
283                       case SDLK_ESCAPE:    /* Escape: Open/Close the menu: */
284                         on_escape_press();
285                         break;
286                       default:
287                         break;
288                       }
289                   }
290                   break;
291                 case SDL_KEYUP:      /* A keyrelease! */
292                   {
293                     SDLKey key = event.key.keysym.sym;
294
295                     if(tux.key_event(key, UP))
296                       break;
297
298                     switch(key)
299                       {
300                       case SDLK_p:
301                         if(!Menu::current())
302                           {
303                             if(game_pause)
304                               {
305                                 game_pause = false;
306                                 st_pause_ticks_stop();
307                               }
308                             else
309                               {
310                                 game_pause = true;
311                                 st_pause_ticks_start();
312                               }
313                           }
314                         break;
315                       case SDLK_TAB:
316                         if(debug_mode)
317                           {
318                             tux.size = !tux.size;
319                             if(tux.size == BIG)
320                               {
321                                 tux.base.height = 64;
322                               }
323                             else
324                               tux.base.height = 32;
325                           }
326                         break;
327                       case SDLK_END:
328                         if(debug_mode)
329                           player_status.distros += 50;
330                         break;
331                       case SDLK_DELETE:
332                         if(debug_mode)
333                           tux.got_coffee = 1;
334                         break;
335                       case SDLK_INSERT:
336                         if(debug_mode)
337                           tux.invincible_timer.start(TUX_INVINCIBLE_TIME);
338                         break;
339                       case SDLK_l:
340                         if(debug_mode)
341                           --player_status.lives;
342                         break;
343                       case SDLK_s:
344                         if(debug_mode)
345                           player_status.score += 1000;
346                       case SDLK_f:
347                         if(debug_fps)
348                           debug_fps = false;
349                         else
350                           debug_fps = true;
351                         break;
352                       default:
353                         break;
354                       }
355                   }
356                   break;
357
358                 case SDL_JOYAXISMOTION:
359                   if (event.jaxis.axis == joystick_keymap.x_axis)
360                     {
361                       if (event.jaxis.value < -joystick_keymap.dead_zone)
362                         {
363                           tux.input.left  = DOWN;
364                           tux.input.right = UP;
365                         }
366                       else if (event.jaxis.value > joystick_keymap.dead_zone)
367                         {
368                           tux.input.left  = UP;
369                           tux.input.right = DOWN;
370                         }
371                       else
372                         {
373                           tux.input.left  = DOWN;
374                           tux.input.right = DOWN;
375                         }
376                     }
377                   else if (event.jaxis.axis == joystick_keymap.y_axis)
378                     {
379                       if (event.jaxis.value > joystick_keymap.dead_zone)
380                         tux.input.down = DOWN;
381                       else if (event.jaxis.value < -joystick_keymap.dead_zone)
382                         tux.input.down = UP;
383                       else
384                         tux.input.down = UP;
385                     }
386                   break;
387             
388                 case SDL_JOYBUTTONDOWN:
389                   if (event.jbutton.button == joystick_keymap.a_button)
390                     tux.input.up = DOWN;
391                   else if (event.jbutton.button == joystick_keymap.b_button)
392                     tux.input.fire = DOWN;
393                   else if (event.jbutton.button == joystick_keymap.start_button)
394                     on_escape_press();
395                   break;
396                 case SDL_JOYBUTTONUP:
397                   if (event.jbutton.button == joystick_keymap.a_button)
398                     tux.input.up = UP;
399                   else if (event.jbutton.button == joystick_keymap.b_button)
400                     tux.input.fire = UP;
401                   break;
402
403                 default:
404                   break;
405                 }  /* switch */
406             }
407         } /* while */
408     }
409 }
410
411
412 void
413 GameSession::check_end_conditions()
414 {
415   Player* tux = world->get_tux();
416
417   /* End of level? */
418   if (tux->base.x >= World::current()->get_level()->endpos + 32 * (get_level()->use_endsequence ? 22 : 10))
419     {
420       exit_status = LEVEL_FINISHED;
421     }
422   else if (tux->base.x >= World::current()->get_level()->endpos && !end_sequence)
423     {
424       end_sequence = true;
425       last_x_pos = -1;
426       music_manager->halt_music();
427     }
428   else
429     {
430       // Check End conditions
431       if (tux->is_dead())
432         {
433           player_status.lives -= 1;             
434     
435           if (player_status.lives < 0)
436             { // No more lives!?
437               if(st_gl_mode != ST_GL_TEST)
438                 drawendscreen();
439               
440               exit_status = GAME_OVER;
441             }
442           else
443             { // Still has lives, so reset Tux to the levelstart
444               restart_level();
445             }
446         }
447     } 
448 }
449
450 void
451 GameSession::action(double frame_ratio)
452 {
453   check_end_conditions();
454   
455   if (exit_status == NONE)
456     {
457       // Update Tux and the World
458       world->action(frame_ratio);
459     }
460 }
461
462 void 
463 GameSession::draw()
464 {
465   world->draw();
466   drawstatus();
467
468   if(game_pause)
469     {
470       int x = screen->h / 20;
471       for(int i = 0; i < x; ++i)
472         {
473           fillrect(i % 2 ? (pause_menu_frame * i)%screen->w : -((pause_menu_frame * i)%screen->w) ,(i*20+pause_menu_frame)%screen->h,screen->w,10,20,20,20, rand() % 20 + 1);
474         }
475       fillrect(0,0,screen->w,screen->h,rand() % 50, rand() % 50, rand() % 50, 128);
476       blue_text->drawf("PAUSE - Press 'P' To Play", 0, 230, A_HMIDDLE, A_TOP, 1);
477     }
478
479   if(Menu::current())
480     {
481       Menu::current()->draw();
482       mouse_cursor->draw();
483     }
484
485   updatescreen();
486 }
487
488 void
489 GameSession::process_menu()
490 {
491   Menu* menu = Menu::current();
492   if(menu)
493     {
494       menu->action();
495
496       if(menu == game_menu)
497         {
498           switch (game_menu->check())
499             {
500             case MNID_CONTINUE:
501               st_pause_ticks_stop();
502               break;
503             case MNID_ABORTLEVEL:
504               st_pause_ticks_stop();
505               exit_status = LEVEL_ABORT;
506               break;
507             }
508         }
509       else if(menu == options_menu)
510         {
511           process_options_menu();
512         }
513       else if(menu == load_game_menu )
514         {
515           process_load_game_menu();
516         }
517     }
518 }
519
520 GameSession::ExitStatus
521 GameSession::run()
522 {
523   Menu::set_current(0);
524   current_ = this;
525   
526   int fps_cnt = 0;
527
528   update_time = last_update_time = st_get_ticks();
529
530   /* Clear screen: */
531   clearscreen(0, 0, 0);
532   updatescreen();
533
534   // Eat unneeded events
535   SDL_Event event;
536   while (SDL_PollEvent(&event)) {}
537
538   draw();
539
540   float overlap = 0.0f;
541   while (exit_status == NONE)
542     {
543       /* Calculate the movement-factor */
544       double frame_ratio = ((double)(update_time-last_update_time))/((double)FRAME_RATE);
545
546       if(!frame_timer.check())
547         {
548           frame_timer.start(25);
549           ++global_frame_counter;
550         }
551
552       /* Handle events: */
553       world->get_tux()->input.old_fire = world->get_tux()->input.fire;
554
555       process_events();
556       process_menu();
557
558       // Update the world state and all objects in the world
559       // Do that with a constante time-delta so that the game will run
560       // determistic and not different on different machines
561       if(!game_pause && !Menu::current())
562         {
563           frame_ratio *= game_speed;
564           frame_ratio += overlap;
565           while (frame_ratio > 0)
566             {
567               // Update the world
568               if (end_sequence)
569                 action(.5f);
570               else
571                 action(1.0f);
572               frame_ratio -= 1.0f;
573             }
574           overlap = frame_ratio;
575         }
576       else
577         {
578           ++pause_menu_frame;
579           SDL_Delay(50);
580         }
581
582       draw();
583
584       /* Time stops in pause mode */
585       if(game_pause || Menu::current())
586         {
587           continue;
588         }
589
590       /* Set the time of the last update and the time of the current update */
591       last_update_time = update_time;
592       update_time      = st_get_ticks();
593
594       /* Pause till next frame, if the machine running the game is too fast: */
595       /* FIXME: Works great for in OpenGl mode, where the CPU doesn't have to do that much. But
596          the results in SDL mode aren't perfect (thought the 100 FPS are reached), even on an AMD2500+. */
597       if(last_update_time >= update_time - 12) 
598         {
599           SDL_Delay(10);
600           update_time = st_get_ticks();
601         }
602
603       /* Handle time: */
604       if (!time_left.check() && world->get_tux()->dying == DYING_NOT)
605         world->get_tux()->kill(Player::KILL);
606
607       /* Handle music: */
608       if(world->get_tux()->invincible_timer.check())
609         {
610           if(world->get_music_type() != HERRING_MUSIC)
611             world->play_music(HERRING_MUSIC);
612         }
613       /* are we low on time ? */
614       else if (time_left.get_left() < TIME_WARNING
615          && (world->get_music_type() == LEVEL_MUSIC))
616         {
617           world->play_music(HURRYUP_MUSIC);
618         }
619       /* or just normal music? */
620       else if(world->get_music_type() != LEVEL_MUSIC)
621         {
622           world->play_music(LEVEL_MUSIC);
623         }
624
625       /* Calculate frames per second */
626       if(show_fps)
627         {
628           ++fps_cnt;
629           fps_fps = (1000.0 / (float)fps_timer.get_gone()) * (float)fps_cnt;
630
631           if(!fps_timer.check())
632             {
633               fps_timer.start(1000);
634               fps_cnt = 0;
635             }
636         }
637     }
638   
639   return exit_status;
640 }
641
642 /* Bounce a brick: */
643 void bumpbrick(float x, float y)
644 {
645   World::current()->add_bouncy_brick(((int)(x + 1) / 32) * 32,
646                          (int)(y / 32) * 32);
647
648   play_sound(sounds[SND_BRICK], SOUND_CENTER_SPEAKER);
649 }
650
651 /* (Status): */
652 void
653 GameSession::drawstatus()
654 {
655   char str[60];
656
657   sprintf(str, "%d", player_status.score);
658   white_text->draw("SCORE", 0, 0, 1);
659   gold_text->draw(str, 96, 0, 1);
660
661   if(st_gl_mode == ST_GL_TEST)
662     {
663       white_text->draw("Press ESC To Return",0,20,1);
664     }
665
666   if (time_left.get_left() > TIME_WARNING || (global_frame_counter % 10) < 5)
667     {
668       sprintf(str, "%d", time_left.get_left() / 1000 );
669       white_text->draw("TIME", 224, 0, 1);
670       gold_text->draw(str, 304, 0, 1);
671     }
672
673   sprintf(str, "%d", player_status.distros);
674   white_text->draw("COINS", screen->h, 0, 1);
675   gold_text->draw(str, 608, 0, 1);
676
677   white_text->draw("LIVES", screen->h, 20, 1);
678
679   if(show_fps)
680     {
681       sprintf(str, "%2.1f", fps_fps);
682       white_text->draw("FPS", screen->h, 40, 1);
683       gold_text->draw(str, screen->h + 60, 40, 1);
684     }
685
686   for(int i= 0; i < player_status.lives; ++i)
687     {
688       tux_life->draw(565+(18*i),20);
689     }
690 }
691
692 void
693 GameSession::drawendscreen()
694 {
695   char str[80];
696
697   if (get_level()->img_bkgd)
698     get_level()->img_bkgd->draw(0, 0);
699   else
700     drawgradient(get_level()->bkgd_top, get_level()->bkgd_bottom);
701
702   blue_text->drawf("GAMEOVER", 0, 200, A_HMIDDLE, A_TOP, 1);
703
704   sprintf(str, "SCORE: %d", player_status.score);
705   gold_text->drawf(str, 0, 224, A_HMIDDLE, A_TOP, 1);
706
707   sprintf(str, "COINS: %d", player_status.distros);
708   gold_text->drawf(str, 0, 256, A_HMIDDLE, A_TOP, 1);
709
710   flipscreen();
711   
712   SDL_Event event;
713   wait_for_event(event,2000,5000,true);
714 }
715
716 void
717 GameSession::drawresultscreen(void)
718 {
719   char str[80];
720
721   if (get_level()->img_bkgd)
722     get_level()->img_bkgd->draw(0, 0);
723   else
724     drawgradient(get_level()->bkgd_top, get_level()->bkgd_bottom);
725
726   blue_text->drawf("Result:", 0, 200, A_HMIDDLE, A_TOP, 1);
727
728   sprintf(str, "SCORE: %d", player_status.score);
729   gold_text->drawf(str, 0, 224, A_HMIDDLE, A_TOP, 1);
730
731   sprintf(str, "COINS: %d", player_status.distros);
732   gold_text->drawf(str, 0, 256, A_HMIDDLE, A_TOP, 1);
733
734   flipscreen();
735   
736   SDL_Event event;
737   wait_for_event(event,2000,5000,true);
738 }
739
740 std::string slotinfo(int slot)
741 {
742   char tmp[1024];
743   char slotfile[1024];
744   sprintf(slotfile,"%s/slot%d.stsg",st_save_dir,slot);
745
746   if (access(slotfile, F_OK) == 0)
747     sprintf(tmp,"Slot %d - Savegame",slot);
748   else
749     sprintf(tmp,"Slot %d - Free",slot);
750
751   return tmp;
752 }
753
754