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