- memleak fix and menu fix from MatzeB
[supertux.git] / src / gameloop.cpp
1 //  $Id$
2 // 
3 //  SuperTux
4 //  Copyright (C) 2000 Bill Kendrick <bill@newbreedsoftware.com>
5 //  Copyright (C) 2004 Tobias Glaesser <tobi.web@gmx.de>
6 //  Copyright (C) 2004 Ingo Ruhnke <grumbel@gmx.de>
7 //
8 //  This program is free software; you can redistribute it and/or
9 //  modify it under the terms of the GNU General Public License
10 //  as published by the Free Software Foundation; either version 2
11 //  of the License, or (at your option) any later version.
12 //
13 //  This program is distributed in the hope that it will be useful,
14 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 //  GNU General Public License for more details.
17 // 
18 //  You should have received a copy of the GNU General Public License
19 //  along with this program; if not, write to the Free Software
20 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
21
22 #include <iostream>
23 #include <assert.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <math.h>
27 #include <string.h>
28 #include <errno.h>
29 #include <unistd.h>
30 #include <math.h>
31 #include <time.h>
32 #include <SDL.h>
33
34 #ifndef WIN32
35 #include <sys/types.h>
36 #include <ctype.h>
37 #endif
38
39 #include "defines.h"
40 #include "globals.h"
41 #include "gameloop.h"
42 #include "screen.h"
43 #include "setup.h"
44 #include "high_scores.h"
45 #include "menu.h"
46 #include "badguy.h"
47 #include "world.h"
48 #include "special.h"
49 #include "player.h"
50 #include "level.h"
51 #include "scene.h"
52 #include "collision.h"
53 #include "tile.h"
54 #include "particlesystem.h"
55 #include "resources.h"
56 #include "music_manager.h"
57
58 GameSession* GameSession::current_ = 0;
59
60 GameSession::GameSession(const std::string& subset_, int levelnb_, int mode)
61   : world(0), st_gl_mode(mode), levelnb(levelnb_), end_sequenze(false),
62     subset(subset_)
63 {
64   current_ = this;
65   
66   global_frame_counter = 0;
67   game_pause = false;
68
69   fps_timer.init(true);            
70   frame_timer.init(true);
71
72   restart_level();
73 }
74
75 void
76 GameSession::restart_level()
77 {
78   game_pause   = false;
79   exit_status  = NONE;
80   end_sequenze = false;
81
82   fps_timer.init(true);
83   frame_timer.init(true);
84
85   float old_x_pos = -1;
86
87   if (world)
88     { // Tux has lost a life, so we try to respawn him at the nearest reset point
89       old_x_pos = world->get_tux()->base.x;
90     }
91   
92   delete world;
93
94   if (st_gl_mode == ST_GL_LOAD_LEVEL_FILE)
95     {
96       world = new World(subset);
97     }
98   else if (st_gl_mode == ST_GL_DEMO_GAME)
99     {
100       world = new World(subset);
101     }
102   else
103     {
104       world = new World(subset, levelnb);
105     }
106
107   // Set Tux to the nearest reset point
108   if (old_x_pos != -1)
109     {
110       ResetPoint best_reset_point = { -1, -1 };
111       for(std::vector<ResetPoint>::iterator i = get_level()->reset_points.begin();
112           i != get_level()->reset_points.end(); ++i)
113         {
114           if (i->x < old_x_pos && best_reset_point.x < i->x)
115             best_reset_point = *i;
116         }
117       
118       if (best_reset_point.x != -1)
119         {
120           world->get_tux()->base.x = best_reset_point.x;
121           world->get_tux()->base.y = best_reset_point.y;
122         }
123     }
124
125     
126   if (st_gl_mode != ST_GL_DEMO_GAME)
127     {
128       if(st_gl_mode == ST_GL_PLAY || st_gl_mode == ST_GL_LOAD_LEVEL_FILE)
129         levelintro();
130     }
131
132   time_left.init(true);
133   start_timers();
134   world->play_music(LEVEL_MUSIC);
135 }
136
137 GameSession::~GameSession()
138 {
139   delete world;
140 }
141
142 void
143 GameSession::levelintro(void)
144 {
145   music_manager->halt_music();
146   
147   char str[60];
148  
149   if (get_level()->img_bkgd)
150     get_level()->img_bkgd->draw(0, 0);
151   else
152     drawgradient(get_level()->bkgd_top, get_level()->bkgd_bottom);
153
154   sprintf(str, "%s", world->get_level()->name.c_str());
155   gold_text->drawf(str, 0, 200, A_HMIDDLE, A_TOP, 1);
156
157   sprintf(str, "TUX x %d", player_status.lives);
158   white_text->drawf(str, 0, 224, A_HMIDDLE, A_TOP, 1);
159   
160   sprintf(str, "by %s", world->get_level()->author.c_str());
161   white_small_text->drawf(str, 0, 400, A_HMIDDLE, A_TOP, 1);
162   
163
164   flipscreen();
165
166   SDL_Event event;
167   wait_for_event(event,1000,3000,true);
168 }
169
170 /* Reset Timers */
171 void
172 GameSession::start_timers()
173 {
174   time_left.start(world->get_level()->time_left*1000);
175   st_pause_ticks_init();
176   update_time = st_get_ticks();
177 }
178
179 void
180 GameSession::on_escape_press()
181 {
182   if(game_pause)
183     return;
184
185   if(st_gl_mode == ST_GL_TEST)
186     {
187       exit_status = LEVEL_ABORT;
188     }
189   else if (!Menu::current())
190     {
191       Menu::set_current(game_menu);
192     }
193 }
194
195 void
196 GameSession::process_events()
197 {
198   if (end_sequenze)
199     {
200       Player& tux = *world->get_tux();
201           
202       tux.input.left  = UP;
203       tux.input.right = DOWN; 
204       tux.input.down  = UP; 
205
206       if (int(last_x_pos) == int(tux.base.x))
207         tux.input.up    = DOWN; 
208       else
209         tux.input.up    = UP; 
210
211       last_x_pos = tux.base.x;
212     }
213   else
214     {
215       if(!Menu::current() && !game_pause)
216         st_pause_ticks_stop();
217
218       SDL_Event event;
219       while (SDL_PollEvent(&event))
220         {
221           /* Check for menu-events, if the menu is shown */
222           if (Menu::current())
223             {
224               Menu::current()->event(event);
225               st_pause_ticks_start();
226             }
227           else
228             {
229               Player& tux = *world->get_tux();
230   
231               switch(event.type)
232                 {
233                 case SDL_QUIT:        /* Quit event - quit: */
234                   st_abort("Received window close", "");
235                   break;
236
237                 case SDL_KEYDOWN:     /* A keypress! */
238                   {
239                     SDLKey key = event.key.keysym.sym;
240             
241                     if(tux.key_event(key,DOWN))
242                       break;
243
244                     switch(key)
245                       {
246                       case SDLK_ESCAPE:    /* Escape: Open/Close the menu: */
247                         on_escape_press();
248                         break;
249                       default:
250                         break;
251                       }
252                   }
253                   break;
254                 case SDL_KEYUP:      /* A keyrelease! */
255                   {
256                     SDLKey key = event.key.keysym.sym;
257
258                     if(tux.key_event(key, UP))
259                       break;
260
261                     switch(key)
262                       {
263                       case SDLK_p:
264                         if(!Menu::current())
265                           {
266                             if(game_pause)
267                               {
268                                 game_pause = false;
269                                 st_pause_ticks_stop();
270                               }
271                             else
272                               {
273                                 game_pause = true;
274                                 st_pause_ticks_start();
275                               }
276                           }
277                         break;
278                       case SDLK_TAB:
279                         if(debug_mode)
280                           {
281                             tux.size = !tux.size;
282                             if(tux.size == BIG)
283                               {
284                                 tux.base.height = 64;
285                               }
286                             else
287                               tux.base.height = 32;
288                           }
289                         break;
290                       case SDLK_END:
291                         if(debug_mode)
292                           player_status.distros += 50;
293                         break;
294                       case SDLK_DELETE:
295                         if(debug_mode)
296                           tux.got_coffee = 1;
297                         break;
298                       case SDLK_INSERT:
299                         if(debug_mode)
300                           tux.invincible_timer.start(TUX_INVINCIBLE_TIME);
301                         break;
302                       case SDLK_l:
303                         if(debug_mode)
304                           --player_status.lives;
305                         break;
306                       case SDLK_s:
307                         if(debug_mode)
308                           player_status.score += 1000;
309                       case SDLK_f:
310                         if(debug_fps)
311                           debug_fps = false;
312                         else
313                           debug_fps = true;
314                         break;
315                       default:
316                         break;
317                       }
318                   }
319                   break;
320
321                 case SDL_JOYAXISMOTION:
322                   if (event.jaxis.axis == joystick_keymap.x_axis)
323                     {
324                       if (event.jaxis.value < -joystick_keymap.dead_zone)
325                         {
326                           tux.input.left  = DOWN;
327                           tux.input.right = UP;
328                         }
329                       else if (event.jaxis.value > joystick_keymap.dead_zone)
330                         {
331                           tux.input.left  = UP;
332                           tux.input.right = DOWN;
333                         }
334                       else
335                         {
336                           tux.input.left  = DOWN;
337                           tux.input.right = DOWN;
338                         }
339                     }
340                   else if (event.jaxis.axis == joystick_keymap.y_axis)
341                     {
342                       if (event.jaxis.value > joystick_keymap.dead_zone)
343                         tux.input.down = DOWN;
344                       else if (event.jaxis.value < -joystick_keymap.dead_zone)
345                         tux.input.down = UP;
346                       else
347                         tux.input.down = UP;
348                     }
349                   break;
350             
351                 case SDL_JOYBUTTONDOWN:
352                   if (event.jbutton.button == joystick_keymap.a_button)
353                     tux.input.up = DOWN;
354                   else if (event.jbutton.button == joystick_keymap.b_button)
355                     tux.input.fire = DOWN;
356                   else if (event.jbutton.button == joystick_keymap.start_button)
357                     on_escape_press();
358                   break;
359                 case SDL_JOYBUTTONUP:
360                   if (event.jbutton.button == joystick_keymap.a_button)
361                     tux.input.up = UP;
362                   else if (event.jbutton.button == joystick_keymap.b_button)
363                     tux.input.fire = UP;
364                   break;
365
366                 default:
367                   break;
368                 }  /* switch */
369             }
370         } /* while */
371     }
372 }
373
374
375 void
376 GameSession::check_end_conditions()
377 {
378   Player* tux = world->get_tux();
379
380   /* End of level? */
381   if (tux->base.x >= World::current()->get_level()->endpos + 320)
382     {
383       exit_status = LEVEL_FINISHED;
384     }
385   else if (tux->base.x >= World::current()->get_level()->endpos && !end_sequenze)
386     {
387       end_sequenze = true;
388       last_x_pos = -1;
389       music_manager->halt_music();
390     }
391   else
392     {
393       // Check End conditions
394       if (tux->is_dead())
395         {
396           player_status.lives -= 1;             
397     
398           if (player_status.lives < 0)
399             { // No more lives!?
400               if(st_gl_mode != ST_GL_TEST)
401                 drawendscreen();
402               
403               exit_status = GAME_OVER;
404             }
405           else
406             { // Still has lives, so reset Tux to the levelstart
407               restart_level();
408             }
409         }
410     } 
411 }
412
413 void
414 GameSession::action(double frame_ratio)
415 {
416   check_end_conditions();
417   
418   if (exit_status == NONE)
419     {
420       // Update Tux and the World
421       world->action(frame_ratio);
422     }
423 }
424
425 void 
426 GameSession::draw()
427 {
428   world->draw();
429   drawstatus();
430
431   if(game_pause)
432     {
433       int x = screen->h / 20;
434       for(int i = 0; i < x; ++i)
435         {
436           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);
437         }
438       fillrect(0,0,screen->w,screen->h,rand() % 50, rand() % 50, rand() % 50, 128);
439       blue_text->drawf("PAUSE - Press 'P' To Play", 0, 230, A_HMIDDLE, A_TOP, 1);
440     }
441
442   if(Menu::current())
443     {
444       Menu::current()->draw();
445       mouse_cursor->draw();
446     }
447
448   updatescreen();
449 }
450
451 void
452 GameSession::process_menu()
453 {
454   Menu* menu = Menu::current();
455   if(menu)
456     {
457       menu->action();
458
459       if(menu == game_menu)
460         {
461           switch (game_menu->check())
462             {
463             case MNID_CONTINUE:
464               st_pause_ticks_stop();
465               break;
466             case MNID_ABORTLEVEL:
467               st_pause_ticks_stop();
468               exit_status = LEVEL_ABORT;
469               break;
470             }
471         }
472       else if(menu == options_menu)
473         {
474           process_options_menu();
475         }
476       else if(menu == load_game_menu )
477         {
478           process_load_game_menu();
479         }
480     }
481 }
482
483 GameSession::ExitStatus
484 GameSession::run()
485 {
486   Menu::set_current(0);
487   current_ = this;
488   
489   int fps_cnt = 0;
490
491   update_time = last_update_time = st_get_ticks();
492
493   /* Clear screen: */
494   clearscreen(0, 0, 0);
495   updatescreen();
496
497   // Eat unneeded events
498   SDL_Event event;
499   while (SDL_PollEvent(&event)) {}
500
501   draw();
502
503   float overlap = 0.0f;
504   while (exit_status == NONE)
505     {
506       /* Calculate the movement-factor */
507       double frame_ratio = ((double)(update_time-last_update_time))/((double)FRAME_RATE);
508
509       if(!frame_timer.check())
510         {
511           frame_timer.start(25);
512           ++global_frame_counter;
513         }
514
515       /* Handle events: */
516       world->get_tux()->input.old_fire = world->get_tux()->input.fire;
517
518       process_events();
519       process_menu();
520
521       // Update the world state and all objects in the world
522       // Do that with a constante time-delta so that the game will run
523       // determistic and not different on different machines
524       if(!game_pause && !Menu::current())
525         {
526           frame_ratio *= game_speed;
527           frame_ratio += overlap;
528           while (frame_ratio > 0)
529             {
530               // Update the world
531               if (end_sequenze)
532                 action(.5f);
533               else
534                 action(1.0f);
535               frame_ratio -= 1.0f;
536             }
537           overlap = frame_ratio;
538         }
539       else
540         {
541           ++pause_menu_frame;
542           SDL_Delay(50);
543         }
544
545       draw();
546
547       /* Time stops in pause mode */
548       if(game_pause || Menu::current())
549         {
550           continue;
551         }
552
553       /* Set the time of the last update and the time of the current update */
554       last_update_time = update_time;
555       update_time      = st_get_ticks();
556
557       /* Pause till next frame, if the machine running the game is too fast: */
558       /* FIXME: Works great for in OpenGl mode, where the CPU doesn't have to do that much. But
559          the results in SDL mode aren't perfect (thought the 100 FPS are reached), even on an AMD2500+. */
560       if(last_update_time >= update_time - 12) 
561         {
562           SDL_Delay(10);
563           update_time = st_get_ticks();
564         }
565
566       /* Handle time: */
567       if (!time_left.check() && world->get_tux()->dying == DYING_NOT)
568         world->get_tux()->kill(Player::KILL);
569
570       /* Handle music: */
571       if(world->get_tux()->invincible_timer.check())
572         {
573           if(world->get_music_type() != HERRING_MUSIC)
574             world->play_music(HERRING_MUSIC);
575         }
576       /* are we low on time ? */
577       else if (time_left.get_left() < TIME_WARNING
578          && (world->get_music_type() == LEVEL_MUSIC))
579         {
580           world->play_music(HURRYUP_MUSIC);
581         }
582       /* or just normal music? */
583       else if(world->get_music_type() != LEVEL_MUSIC)
584         {
585           world->play_music(LEVEL_MUSIC);
586         }
587
588       /* Calculate frames per second */
589       if(show_fps)
590         {
591           ++fps_cnt;
592           fps_fps = (1000.0 / (float)fps_timer.get_gone()) * (float)fps_cnt;
593
594           if(!fps_timer.check())
595             {
596               fps_timer.start(1000);
597               fps_cnt = 0;
598             }
599         }
600     }
601   
602   delete world;
603   world = 0;
604
605   return exit_status;
606 }
607
608 /* Bounce a brick: */
609 void bumpbrick(float x, float y)
610 {
611   World::current()->add_bouncy_brick(((int)(x + 1) / 32) * 32,
612                          (int)(y / 32) * 32);
613
614   play_sound(sounds[SND_BRICK], SOUND_CENTER_SPEAKER);
615 }
616
617 /* (Status): */
618 void
619 GameSession::drawstatus()
620 {
621   char str[60];
622
623   sprintf(str, "%d", player_status.score);
624   white_text->draw("SCORE", 0, 0, 1);
625   gold_text->draw(str, 96, 0, 1);
626
627   if(st_gl_mode == ST_GL_TEST)
628     {
629       white_text->draw("Press ESC To Return",0,20,1);
630     }
631
632   if (time_left.get_left() > TIME_WARNING || (global_frame_counter % 10) < 5)
633     {
634       sprintf(str, "%d", time_left.get_left() / 1000 );
635       white_text->draw("TIME", 224, 0, 1);
636       gold_text->draw(str, 304, 0, 1);
637     }
638
639   sprintf(str, "%d", player_status.distros);
640   white_text->draw("COINS", screen->h, 0, 1);
641   gold_text->draw(str, 608, 0, 1);
642
643   white_text->draw("LIVES", screen->h, 20, 1);
644
645   if(show_fps)
646     {
647       sprintf(str, "%2.1f", fps_fps);
648       white_text->draw("FPS", screen->h, 40, 1);
649       gold_text->draw(str, screen->h + 60, 40, 1);
650     }
651
652   for(int i= 0; i < player_status.lives; ++i)
653     {
654       tux_life->draw(565+(18*i),20);
655     }
656 }
657
658 void
659 GameSession::drawendscreen()
660 {
661   char str[80];
662
663   if (get_level()->img_bkgd)
664     get_level()->img_bkgd->draw(0, 0);
665   else
666     drawgradient(get_level()->bkgd_top, get_level()->bkgd_bottom);
667
668   blue_text->drawf("GAMEOVER", 0, 200, A_HMIDDLE, A_TOP, 1);
669
670   sprintf(str, "SCORE: %d", player_status.score);
671   gold_text->drawf(str, 0, 224, A_HMIDDLE, A_TOP, 1);
672
673   sprintf(str, "COINS: %d", player_status.distros);
674   gold_text->drawf(str, 0, 256, A_HMIDDLE, A_TOP, 1);
675
676   flipscreen();
677   
678   SDL_Event event;
679   wait_for_event(event,2000,5000,true);
680 }
681
682 void
683 GameSession::drawresultscreen(void)
684 {
685   char str[80];
686
687   if (get_level()->img_bkgd)
688     get_level()->img_bkgd->draw(0, 0);
689   else
690     drawgradient(get_level()->bkgd_top, get_level()->bkgd_bottom);
691
692   blue_text->drawf("Result:", 0, 200, A_HMIDDLE, A_TOP, 1);
693
694   sprintf(str, "SCORE: %d", player_status.score);
695   gold_text->drawf(str, 0, 224, A_HMIDDLE, A_TOP, 1);
696
697   sprintf(str, "COINS: %d", player_status.distros);
698   gold_text->drawf(str, 0, 256, A_HMIDDLE, A_TOP, 1);
699
700   flipscreen();
701   
702   SDL_Event event;
703   wait_for_event(event,2000,5000,true);
704 }
705
706 std::string slotinfo(int slot)
707 {
708   char tmp[1024];
709   char slotfile[1024];
710   sprintf(slotfile,"%s/slot%d.stsg",st_save_dir,slot);
711
712   if (access(slotfile, F_OK) == 0)
713     sprintf(tmp,"Slot %d - Savegame",slot);
714   else
715     sprintf(tmp,"Slot %d - Free",slot);
716
717   return tmp;
718 }
719
720