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