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