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