- moved badguys to use sprite class
[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                   // FIXME:
523                   //update_load_save_game_menu(save_game_menu);
524                   break;
525                 case 4:
526                   update_load_save_game_menu(load_game_menu);
527                   break;
528                 case 7:
529                   st_pause_ticks_stop();
530                   done = true;
531                   break;
532                 }
533             }
534           else if(current_menu == options_menu)
535             {
536               process_options_menu();
537             }
538           else if(current_menu == load_game_menu )
539             {
540               process_load_game_menu();
541             }
542         }
543
544
545       /* Handle actions: */
546
547       if(!game_pause && !show_menu)
548         {
549           /*float z = frame_ratio;
550             frame_ratio = 1;
551             while(z >= 1)
552             {*/
553           if (action(frame_ratio) == 0)
554             {
555               /* == 0: no more lives */
556               /* == -1: continues */
557               return 0;
558             }
559           /*  --z;
560                      }*/
561         }
562       else
563         {
564           ++pause_menu_frame;
565           SDL_Delay(50);
566         }
567
568       if(debug_mode && debug_fps)
569         SDL_Delay(60);
570
571       /*Draw the current scene to the screen */
572       /*If the machine running the game is too slow
573         skip the drawing of the frame (so the calculations are more precise and
574         the FPS aren't affected).*/
575       /*if( ! fps_fps < 50.0 )
576         game_draw();
577         else
578         jump = true;*/ /*FIXME: Implement this tweak right.*/
579       draw();
580
581       /* Time stops in pause mode */
582       if(game_pause || show_menu )
583         {
584           continue;
585         }
586
587       /* Set the time of the last update and the time of the current update */
588       last_update_time = update_time;
589       update_time = st_get_ticks();
590
591       /* Pause till next frame, if the machine running the game is too fast: */
592       /* FIXME: Works great for in OpenGl mode, where the CPU doesn't have to do that much. But
593          the results in SDL mode aren't perfect (thought the 100 FPS are reached), even on an AMD2500+. */
594       if(last_update_time >= update_time - 12) {
595         SDL_Delay(10);
596         update_time = st_get_ticks();
597       }
598       /*if((update_time - last_update_time) < 10)
599         SDL_Delay((11 - (update_time - last_update_time))/2);*/
600
601       /* Handle time: */
602       if (time_left.check())
603         {
604           /* are we low on time ? */
605           if (time_left.get_left() < TIME_WARNING
606               && (get_current_music() != HURRYUP_MUSIC))     /* play the fast music */
607             {
608               set_current_music(HURRYUP_MUSIC);
609               play_current_music();
610             }
611
612         }
613       else if(tux.dying == DYING_NOT)
614         tux.kill(KILL);
615
616       /* Calculate frames per second */
617       if(show_fps)
618         {
619           ++fps_cnt;
620           fps_fps = (1000.0 / (float)fps_timer.get_gone()) * (float)fps_cnt;
621
622           if(!fps_timer.check())
623             {
624               fps_timer.start(1000);
625               fps_cnt = 0;
626             }
627         }
628     }
629
630   halt_music();
631
632   world->get_level()->free_gfx();
633   world->get_level()->cleanup();
634   world->get_level()->free_song();
635
636   world->arrays_free();
637
638   return quit;
639 }
640
641 /* Bounce a brick: */
642 void bumpbrick(float x, float y)
643 {
644   World::current()->add_bouncy_brick(((int)(x + 1) / 32) * 32,
645                          (int)(y / 32) * 32);
646
647   play_sound(sounds[SND_BRICK], SOUND_CENTER_SPEAKER);
648 }
649
650 /* (Status): */
651 void
652 GameSession::drawstatus()
653 {
654   char str[60];
655
656   sprintf(str, "%d", player_status.score);
657   white_text->draw("SCORE", 0, 0, 1);
658   gold_text->draw(str, 96, 0, 1);
659
660   if(st_gl_mode != ST_GL_TEST)
661     {
662       sprintf(str, "%d", hs_score);
663       white_text->draw("HIGH", 0, 20, 1);
664       gold_text->draw(str, 96, 20, 1);
665     }
666   else
667     {
668       white_text->draw("Press ESC To Return",0,20,1);
669     }
670
671   if (time_left.get_left() > TIME_WARNING || (global_frame_counter % 10) < 5)
672     {
673       sprintf(str, "%d", time_left.get_left() / 1000 );
674       white_text->draw("TIME", 224, 0, 1);
675       gold_text->draw(str, 304, 0, 1);
676     }
677
678   sprintf(str, "%d", player_status.distros);
679   white_text->draw("DISTROS", screen->h, 0, 1);
680   gold_text->draw(str, 608, 0, 1);
681
682   white_text->draw("LIVES", screen->h, 20, 1);
683
684   if(show_fps)
685     {
686       sprintf(str, "%2.1f", fps_fps);
687       white_text->draw("FPS", screen->h, 40, 1);
688       gold_text->draw(str, screen->h + 60, 40, 1);
689     }
690
691   for(int i= 0; i < player_status.lives; ++i)
692     {
693       tux_life->draw(565+(18*i),20);
694     }
695 }
696
697 void
698 GameSession::drawendscreen()
699 {
700   char str[80];
701
702   clearscreen(0, 0, 0);
703
704   blue_text->drawf("GAMEOVER", 0, 200, A_HMIDDLE, A_TOP, 1);
705
706   sprintf(str, "SCORE: %d", player_status.score);
707   gold_text->drawf(str, 0, 224, A_HMIDDLE, A_TOP, 1);
708
709   sprintf(str, "COINS: %d", player_status.distros);
710   gold_text->drawf(str, 0, 256, A_HMIDDLE, A_TOP, 1);
711
712   flipscreen();
713   
714   SDL_Event event;
715   wait_for_event(event,2000,5000,true);
716 }
717
718 void
719 GameSession::drawresultscreen(void)
720 {
721   char str[80];
722
723   clearscreen(0, 0, 0);
724
725   blue_text->drawf("Result:", 0, 200, A_HMIDDLE, A_TOP, 1);
726
727   sprintf(str, "SCORE: %d", player_status.score);
728   gold_text->drawf(str, 0, 224, A_HMIDDLE, A_TOP, 1);
729
730   sprintf(str, "DISTROS: %d", player_status.distros);
731   gold_text->drawf(str, 0, 256, A_HMIDDLE, A_TOP, 1);
732
733   flipscreen();
734   
735   SDL_Event event;
736   wait_for_event(event,2000,5000,true);
737 }
738
739 std::string slotinfo(int slot)
740 {
741   char tmp[1024];
742   char slotfile[1024];
743   sprintf(slotfile,"%s/slot%d.stsg",st_save_dir,slot);
744
745   if (access(slotfile, F_OK) == 0)
746     sprintf(tmp,"Slot %d - Savegame",slot);
747   else
748     sprintf(tmp,"Slot %d - Free",slot);
749
750   return tmp;
751 }
752
753