2dec06bd5af6bf062631c2f969a837e6c644cfc5
[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::process_events()
133 {
134   Player& tux = *world->get_tux();
135
136   SDL_Event event;
137   while (SDL_PollEvent(&event))
138     {
139       /* Check for menu-events, if the menu is shown */
140       current_menu->event(event);
141
142       switch(event.type)
143         {
144         case SDL_QUIT:        /* Quit event - quit: */
145           st_abort("Received window close", "");
146           break;
147
148         case SDL_KEYDOWN:     /* A keypress! */
149           {
150             SDLKey key = event.key.keysym.sym;
151             
152             if(tux.key_event(key,DOWN))
153               break;
154
155             switch(key)
156               {
157               case SDLK_ESCAPE:    /* Escape: Open/Close the menu: */
158                 if(!game_pause)
159                   {
160                     if(st_gl_mode == ST_GL_TEST)
161                       {
162                         exit_status = LEVEL_ABORT;
163                       }
164                     else if(!show_menu)
165                       {
166                         Menu::set_current(game_menu);
167                         show_menu = 0;
168                         st_pause_ticks_stop();
169                       }
170                     else
171                       {
172                         Menu::set_current(game_menu);
173                         show_menu = 1;
174                         st_pause_ticks_start();
175                       }
176                   }
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(!show_menu)
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           break;
289         case SDL_JOYBUTTONUP:
290           if (event.jbutton.button == JOY_A)
291             tux.input.up = UP;
292           else if (event.jbutton.button == JOY_B)
293             tux.input.fire = UP;
294             
295           break;
296
297         default:
298           break;
299
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           
323           if (player_status.lives < 0)
324             { // No more lives!?
325               if(st_gl_mode != ST_GL_TEST)
326                 drawendscreen();
327           
328               if(st_gl_mode != ST_GL_TEST)
329                 {
330                   if (player_status.score > hs_score)
331                     save_hs(player_status.score);
332                 }
333               
334               exit_status = GAME_OVER;
335             }
336           else
337             { // Still has lives, so reset Tux to the levelstart
338               restart_level();
339             }
340         }
341     } 
342 }
343
344 void
345 GameSession::action(double frame_ratio)
346 {
347   check_end_conditions();
348   
349   if (exit_status == NONE)
350     {
351       // Update Tux and the World
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   float overlap = 0.0f;
414   while (exit_status == NONE)
415     {
416       /* Calculate the movement-factor */
417       double frame_ratio = ((double)(update_time-last_update_time))/((double)FRAME_RATE);
418
419       if(!frame_timer.check())
420         {
421           frame_timer.start(25);
422           ++global_frame_counter;
423         }
424
425       /* Handle events: */
426       tux->input.old_fire = tux->input.fire;
427
428       process_events();
429
430       if(show_menu)
431         {
432           if(current_menu == game_menu)
433             {
434               switch (game_menu->check())
435                 {
436                 case 2:
437                   st_pause_ticks_stop();
438                   break;
439                 case 3:
440                   // FIXME:
441                   //update_load_save_game_menu(save_game_menu);
442                   break;
443                 case 4:
444                   update_load_save_game_menu(load_game_menu);
445                   break;
446                 case 7:
447                   st_pause_ticks_stop();
448                   exit_status = LEVEL_ABORT;
449                   break;
450                 }
451             }
452           else if(current_menu == options_menu)
453             {
454               process_options_menu();
455             }
456           else if(current_menu == load_game_menu )
457             {
458               process_load_game_menu();
459             }
460         }
461       
462       // Handle actions:
463       if(!game_pause && !show_menu)
464         {
465           frame_ratio *= game_speed;
466           frame_ratio += overlap;
467           while (frame_ratio > 0)
468             {
469               action(1.0f);
470               frame_ratio -= 1.0f;
471             }
472           overlap = frame_ratio;
473
474           if (exit_status != NONE)
475             return exit_status;
476         }
477       else
478         {
479           ++pause_menu_frame;
480           SDL_Delay(50);
481         }
482
483       if(debug_mode && debug_fps)
484         SDL_Delay(60);
485
486       /*Draw the current scene to the screen */
487       /*If the machine running the game is too slow
488         skip the drawing of the frame (so the calculations are more precise and
489         the FPS aren't affected).*/
490       /*if( ! fps_fps < 50.0 )
491         game_draw();
492         else
493         jump = true;*/ /*FIXME: Implement this tweak right.*/
494       draw();
495
496       /* Time stops in pause mode */
497       if(game_pause || show_menu )
498         {
499           continue;
500         }
501
502       /* Set the time of the last update and the time of the current update */
503       last_update_time = update_time;
504       update_time = st_get_ticks();
505
506       /* Pause till next frame, if the machine running the game is too fast: */
507       /* FIXME: Works great for in OpenGl mode, where the CPU doesn't have to do that much. But
508          the results in SDL mode aren't perfect (thought the 100 FPS are reached), even on an AMD2500+. */
509       if(last_update_time >= update_time - 12) {
510         SDL_Delay(10);
511         update_time = st_get_ticks();
512       }
513       /*if((update_time - last_update_time) < 10)
514         SDL_Delay((11 - (update_time - last_update_time))/2);*/
515
516       /* Handle time: */
517       if (time_left.check())
518         {
519           /* are we low on time ? */
520           if (time_left.get_left() < TIME_WARNING
521               && (get_current_music() != HURRYUP_MUSIC)) /* play the fast music */
522             {
523               set_current_music(HURRYUP_MUSIC);
524               play_current_music();
525             }
526         }
527       else if(tux->dying == DYING_NOT)
528         tux->kill(KILL);
529
530       /* Calculate frames per second */
531       if(show_fps)
532         {
533           ++fps_cnt;
534           fps_fps = (1000.0 / (float)fps_timer.get_gone()) * (float)fps_cnt;
535
536           if(!fps_timer.check())
537             {
538               fps_timer.start(1000);
539               fps_cnt = 0;
540             }
541         }
542     }
543
544   halt_music();
545
546   world->get_level()->free_gfx();
547   world->get_level()->cleanup();
548   world->get_level()->free_song();
549
550   return exit_status;
551 }
552
553 /* Bounce a brick: */
554 void bumpbrick(float x, float y)
555 {
556   World::current()->add_bouncy_brick(((int)(x + 1) / 32) * 32,
557                          (int)(y / 32) * 32);
558
559   play_sound(sounds[SND_BRICK], SOUND_CENTER_SPEAKER);
560 }
561
562 /* (Status): */
563 void
564 GameSession::drawstatus()
565 {
566   char str[60];
567
568   sprintf(str, "%d", player_status.score);
569   white_text->draw("SCORE", 0, 0, 1);
570   gold_text->draw(str, 96, 0, 1);
571
572   if(st_gl_mode != ST_GL_TEST)
573     {
574       sprintf(str, "%d", hs_score);
575       white_text->draw("HIGH", 0, 20, 1);
576       gold_text->draw(str, 96, 20, 1);
577     }
578   else
579     {
580       white_text->draw("Press ESC To Return",0,20,1);
581     }
582
583   if (time_left.get_left() > TIME_WARNING || (global_frame_counter % 10) < 5)
584     {
585       sprintf(str, "%d", time_left.get_left() / 1000 );
586       white_text->draw("TIME", 224, 0, 1);
587       gold_text->draw(str, 304, 0, 1);
588     }
589
590   sprintf(str, "%d", player_status.distros);
591   white_text->draw("DISTROS", screen->h, 0, 1);
592   gold_text->draw(str, 608, 0, 1);
593
594   white_text->draw("LIVES", screen->h, 20, 1);
595
596   if(show_fps)
597     {
598       sprintf(str, "%2.1f", fps_fps);
599       white_text->draw("FPS", screen->h, 40, 1);
600       gold_text->draw(str, screen->h + 60, 40, 1);
601     }
602
603   for(int i= 0; i < player_status.lives; ++i)
604     {
605       tux_life->draw(565+(18*i),20);
606     }
607 }
608
609 void
610 GameSession::drawendscreen()
611 {
612   char str[80];
613
614   clearscreen(0, 0, 0);
615
616   blue_text->drawf("GAMEOVER", 0, 200, A_HMIDDLE, A_TOP, 1);
617
618   sprintf(str, "SCORE: %d", player_status.score);
619   gold_text->drawf(str, 0, 224, A_HMIDDLE, A_TOP, 1);
620
621   sprintf(str, "COINS: %d", player_status.distros);
622   gold_text->drawf(str, 0, 256, A_HMIDDLE, A_TOP, 1);
623
624   flipscreen();
625   
626   SDL_Event event;
627   wait_for_event(event,2000,5000,true);
628 }
629
630 void
631 GameSession::drawresultscreen(void)
632 {
633   char str[80];
634
635   clearscreen(0, 0, 0);
636
637   blue_text->drawf("Result:", 0, 200, A_HMIDDLE, A_TOP, 1);
638
639   sprintf(str, "SCORE: %d", player_status.score);
640   gold_text->drawf(str, 0, 224, A_HMIDDLE, A_TOP, 1);
641
642   sprintf(str, "DISTROS: %d", player_status.distros);
643   gold_text->drawf(str, 0, 256, A_HMIDDLE, A_TOP, 1);
644
645   flipscreen();
646   
647   SDL_Event event;
648   wait_for_event(event,2000,5000,true);
649 }
650
651 std::string slotinfo(int slot)
652 {
653   char tmp[1024];
654   char slotfile[1024];
655   sprintf(slotfile,"%s/slot%d.stsg",st_save_dir,slot);
656
657   if (access(slotfile, F_OK) == 0)
658     sprintf(tmp,"Slot %d - Savegame",slot);
659   else
660     sprintf(tmp,"Slot %d - Free",slot);
661
662   return tmp;
663 }
664
665