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