- removed a few menu variables
[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::on_escape_press()
133 {
134   if(!game_pause)
135     {
136       if(st_gl_mode == ST_GL_TEST)
137         {
138           exit_status = LEVEL_ABORT;
139         }
140       else if (!Menu::current())
141         {
142           Menu::set_current(game_menu);
143           st_pause_ticks_stop();
144         }
145       else
146         {
147           Menu::set_current(NULL);
148           st_pause_ticks_start();
149         }
150     }
151 }
152
153 void
154 GameSession::process_events()
155 {
156   Player& tux = *world->get_tux();
157
158   SDL_Event event;
159   while (SDL_PollEvent(&event))
160     {
161       /* Check for menu-events, if the menu is shown */
162       if (Menu::current())
163         Menu::current()->event(event);
164
165       switch(event.type)
166         {
167         case SDL_QUIT:        /* Quit event - quit: */
168           st_abort("Received window close", "");
169           break;
170
171         case SDL_KEYDOWN:     /* A keypress! */
172           {
173             SDLKey key = event.key.keysym.sym;
174             
175             if(tux.key_event(key,DOWN))
176               break;
177
178             switch(key)
179               {
180               case SDLK_ESCAPE:    /* Escape: Open/Close the menu: */
181                 on_escape_press();
182                 break;
183               default:
184                 break;
185               }
186           }
187           break;
188         case SDL_KEYUP:      /* A keyrelease! */
189           {
190             SDLKey key = event.key.keysym.sym;
191
192             if(tux.key_event(key, UP))
193               break;
194
195             switch(key)
196               {
197               case SDLK_p:
198                 if(!Menu::current())
199                   {
200                     if(game_pause)
201                       {
202                         game_pause = false;
203                         st_pause_ticks_stop();
204                       }
205                     else
206                       {
207                         game_pause = true;
208                         st_pause_ticks_start();
209                       }
210                   }
211                 break;
212               case SDLK_TAB:
213                 if(debug_mode)
214                   {
215                     tux.size = !tux.size;
216                     if(tux.size == BIG)
217                       {
218                         tux.base.height = 64;
219                       }
220                     else
221                       tux.base.height = 32;
222                   }
223                 break;
224               case SDLK_END:
225                 if(debug_mode)
226                   player_status.distros += 50;
227                 break;
228               case SDLK_DELETE:
229                 if(debug_mode)
230                   tux.got_coffee = 1;
231                 break;
232               case SDLK_INSERT:
233                 if(debug_mode)
234                   tux.invincible_timer.start(TUX_INVINCIBLE_TIME);
235                 break;
236               case SDLK_l:
237                 if(debug_mode)
238                   --player_status.lives;
239                 break;
240               case SDLK_s:
241                 if(debug_mode)
242                   player_status.score += 1000;
243               case SDLK_f:
244                 if(debug_fps)
245                   debug_fps = false;
246                 else
247                   debug_fps = true;
248                 break;
249               default:
250                 break;
251               }
252           }
253           break;
254
255         case SDL_JOYAXISMOTION:
256           switch(event.jaxis.axis)
257             {
258             case JOY_X:
259               if (event.jaxis.value < -JOYSTICK_DEAD_ZONE)
260                 {
261                   tux.input.left  = DOWN;
262                   tux.input.right = UP;
263                 }
264               else if (event.jaxis.value > JOYSTICK_DEAD_ZONE)
265                 {
266                   tux.input.left  = UP;
267                   tux.input.right = DOWN;
268                 }
269               else
270                 {
271                   tux.input.left  = DOWN;
272                   tux.input.right = DOWN;
273                 }
274               break;
275             case JOY_Y:
276               if (event.jaxis.value > JOYSTICK_DEAD_ZONE)
277                 tux.input.down = DOWN;
278               else if (event.jaxis.value < -JOYSTICK_DEAD_ZONE)
279                 tux.input.down = UP;
280               else
281                 tux.input.down = UP;
282               
283               break;
284             default:
285               break;
286             }
287           break;
288         case SDL_JOYBUTTONDOWN:
289           if (event.jbutton.button == JOY_A)
290             tux.input.up = DOWN;
291           else if (event.jbutton.button == JOY_B)
292             tux.input.fire = DOWN;
293           else if (event.jbutton.button == JOY_START)
294             on_escape_press();
295           break;
296         case SDL_JOYBUTTONUP:
297           if (event.jbutton.button == JOY_A)
298             tux.input.up = UP;
299           else if (event.jbutton.button == JOY_B)
300             tux.input.fire = UP;
301           break;
302
303         default:
304           break;
305
306         }  /* switch */
307
308     } /* while */
309 }
310
311
312 void
313 GameSession::check_end_conditions()
314 {
315   Player* tux = world->get_tux();
316
317   /* End of level? */
318   if (tux->base.x >= World::current()->get_level()->endpos
319       && World::current()->get_level()->endpos != 0)
320     {
321       exit_status = LEVEL_FINISHED;
322     }
323   else
324     {
325       // Check End conditions
326       if (tux->is_dead())
327         {
328           player_status.lives -= 1;             
329     
330           if (player_status.lives < 0)
331             { // No more lives!?
332               if(st_gl_mode != ST_GL_TEST)
333                 drawendscreen();
334           
335               if(st_gl_mode != ST_GL_TEST)
336                 {
337                   // FIXME: highscore soving doesn't make sense in its
338                   // current form
339                   //if (player_status.score > hs_score)
340                   //save_hs(player_status.score);
341                 }
342               
343               exit_status = GAME_OVER;
344             }
345           else
346             { // Still has lives, so reset Tux to the levelstart
347               restart_level();
348             }
349         }
350     } 
351 }
352
353 void
354 GameSession::action(double frame_ratio)
355 {
356   check_end_conditions();
357   
358   if (exit_status == NONE)
359     {
360       // Update Tux and the World
361       world->action(frame_ratio);
362     }
363 }
364
365 void 
366 GameSession::draw()
367 {
368   world->draw();
369   drawstatus();
370
371   if(game_pause)
372     {
373       int x = screen->h / 20;
374       for(int i = 0; i < x; ++i)
375         {
376           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);
377         }
378       fillrect(0,0,screen->w,screen->h,rand() % 50, rand() % 50, rand() % 50, 128);
379       blue_text->drawf("PAUSE - Press 'P' To Play", 0, 230, A_HMIDDLE, A_TOP, 1);
380     }
381
382   if(Menu::current())
383     {
384       menu_process_current();
385       mouse_cursor->draw();
386     }
387
388   updatescreen();
389 }
390
391
392 GameSession::ExitStatus
393 GameSession::run()
394 {
395   Player* tux = world->get_tux();
396   current_ = this;
397   
398   int  fps_cnt;
399
400   global_frame_counter = 0;
401   game_pause = false;
402
403   fps_timer.init(true);
404   frame_timer.init(true);
405
406   last_update_time = st_get_ticks();
407   fps_cnt = 0;
408
409   /* Clear screen: */
410   clearscreen(0, 0, 0);
411   updatescreen();
412
413   /* Play music: */
414   play_current_music();
415
416   // Eat unneeded events
417   SDL_Event event;
418   while (SDL_PollEvent(&event)) {}
419
420   draw();
421
422   float overlap = 0.0f;
423   while (exit_status == NONE)
424     {
425       /* Calculate the movement-factor */
426       double frame_ratio = ((double)(update_time-last_update_time))/((double)FRAME_RATE);
427
428       if(!frame_timer.check())
429         {
430           frame_timer.start(25);
431           ++global_frame_counter;
432         }
433
434       /* Handle events: */
435       tux->input.old_fire = tux->input.fire;
436
437       process_events();
438
439       if(Menu::current())
440         {
441           if(Menu::current() == game_menu)
442             {
443               switch (game_menu->check())
444                 {
445                 case 2:
446                   st_pause_ticks_stop();
447                   break;
448                 case 5:
449                   st_pause_ticks_stop();
450                   exit_status = LEVEL_ABORT;
451                   break;
452                 }
453             }
454           else if(Menu::current() == options_menu)
455             {
456               process_options_menu();
457             }
458           else if(Menu::current() == load_game_menu )
459             {
460               process_load_game_menu();
461             }
462         }
463       
464       // Handle actions:
465       if(!game_pause && !Menu::current())
466         {
467           frame_ratio *= game_speed;
468           frame_ratio += overlap;
469           while (frame_ratio > 0)
470             {
471               action(1.0f);
472               frame_ratio -= 1.0f;
473             }
474           overlap = frame_ratio;
475
476           if (exit_status != NONE)
477             return exit_status;
478         }
479       else
480         {
481           ++pause_menu_frame;
482           SDL_Delay(50);
483         }
484
485       if(debug_mode && debug_fps)
486         SDL_Delay(60);
487
488       /*Draw the current scene to the screen */
489       /*If the machine running the game is too slow
490         skip the drawing of the frame (so the calculations are more precise and
491         the FPS aren't affected).*/
492       /*if( ! fps_fps < 50.0 )
493         game_draw();
494         else
495         jump = true;*/ /*FIXME: Implement this tweak right.*/
496       draw();
497
498       /* Time stops in pause mode */
499       if(game_pause || Menu::current())
500         {
501           continue;
502         }
503
504       /* Set the time of the last update and the time of the current update */
505       last_update_time = update_time;
506       update_time = st_get_ticks();
507
508       /* Pause till next frame, if the machine running the game is too fast: */
509       /* FIXME: Works great for in OpenGl mode, where the CPU doesn't have to do that much. But
510          the results in SDL mode aren't perfect (thought the 100 FPS are reached), even on an AMD2500+. */
511       if(last_update_time >= update_time - 12) {
512         SDL_Delay(10);
513         update_time = st_get_ticks();
514       }
515       /*if((update_time - last_update_time) < 10)
516         SDL_Delay((11 - (update_time - last_update_time))/2);*/
517
518       /* Handle time: */
519       if (time_left.check())
520         {
521           /* are we low on time ? */
522           if (time_left.get_left() < TIME_WARNING
523               && (get_current_music() != HURRYUP_MUSIC)) /* play the fast music */
524             {
525               set_current_music(HURRYUP_MUSIC);
526               play_current_music();
527             }
528         }
529       else if(tux->dying == DYING_NOT)
530         tux->kill(KILL);
531
532       /* Calculate frames per second */
533       if(show_fps)
534         {
535           ++fps_cnt;
536           fps_fps = (1000.0 / (float)fps_timer.get_gone()) * (float)fps_cnt;
537
538           if(!fps_timer.check())
539             {
540               fps_timer.start(1000);
541               fps_cnt = 0;
542             }
543         }
544     }
545
546   halt_music();
547
548   world->get_level()->free_gfx();
549   world->get_level()->cleanup();
550   world->get_level()->free_song();
551
552   return exit_status;
553 }
554
555 /* Bounce a brick: */
556 void bumpbrick(float x, float y)
557 {
558   World::current()->add_bouncy_brick(((int)(x + 1) / 32) * 32,
559                          (int)(y / 32) * 32);
560
561   play_sound(sounds[SND_BRICK], SOUND_CENTER_SPEAKER);
562 }
563
564 /* (Status): */
565 void
566 GameSession::drawstatus()
567 {
568   char str[60];
569
570   sprintf(str, "%d", player_status.score);
571   white_text->draw("SCORE", 0, 0, 1);
572   gold_text->draw(str, 96, 0, 1);
573
574   if(st_gl_mode != ST_GL_TEST)
575     {
576       sprintf(str, "%d", hs_score);
577       white_text->draw("HIGH", 0, 20, 1);
578       gold_text->draw(str, 96, 20, 1);
579     }
580   else
581     {
582       white_text->draw("Press ESC To Return",0,20,1);
583     }
584
585   if (time_left.get_left() > TIME_WARNING || (global_frame_counter % 10) < 5)
586     {
587       sprintf(str, "%d", time_left.get_left() / 1000 );
588       white_text->draw("TIME", 224, 0, 1);
589       gold_text->draw(str, 304, 0, 1);
590     }
591
592   sprintf(str, "%d", player_status.distros);
593   white_text->draw("DISTROS", screen->h, 0, 1);
594   gold_text->draw(str, 608, 0, 1);
595
596   white_text->draw("LIVES", screen->h, 20, 1);
597
598   if(show_fps)
599     {
600       sprintf(str, "%2.1f", fps_fps);
601       white_text->draw("FPS", screen->h, 40, 1);
602       gold_text->draw(str, screen->h + 60, 40, 1);
603     }
604
605   for(int i= 0; i < player_status.lives; ++i)
606     {
607       tux_life->draw(565+(18*i),20);
608     }
609 }
610
611 void
612 GameSession::drawendscreen()
613 {
614   char str[80];
615
616   clearscreen(0, 0, 0);
617
618   blue_text->drawf("GAMEOVER", 0, 200, A_HMIDDLE, A_TOP, 1);
619
620   sprintf(str, "SCORE: %d", player_status.score);
621   gold_text->drawf(str, 0, 224, A_HMIDDLE, A_TOP, 1);
622
623   sprintf(str, "COINS: %d", player_status.distros);
624   gold_text->drawf(str, 0, 256, A_HMIDDLE, A_TOP, 1);
625
626   flipscreen();
627   
628   SDL_Event event;
629   wait_for_event(event,2000,5000,true);
630 }
631
632 void
633 GameSession::drawresultscreen(void)
634 {
635   char str[80];
636
637   clearscreen(0, 0, 0);
638
639   blue_text->drawf("Result:", 0, 200, A_HMIDDLE, A_TOP, 1);
640
641   sprintf(str, "SCORE: %d", player_status.score);
642   gold_text->drawf(str, 0, 224, A_HMIDDLE, A_TOP, 1);
643
644   sprintf(str, "DISTROS: %d", player_status.distros);
645   gold_text->drawf(str, 0, 256, A_HMIDDLE, A_TOP, 1);
646
647   flipscreen();
648   
649   SDL_Event event;
650   wait_for_event(event,2000,5000,true);
651 }
652
653 std::string slotinfo(int slot)
654 {
655   char tmp[1024];
656   char slotfile[1024];
657   sprintf(slotfile,"%s/slot%d.stsg",st_save_dir,slot);
658
659   if (access(slotfile, F_OK) == 0)
660     sprintf(tmp,"Slot %d - Savegame",slot);
661   else
662     sprintf(tmp,"Slot %d - Free",slot);
663
664   return tmp;
665 }
666
667