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