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