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