0243bfc35ec96f9dd4fc0860adc8ba719e5b0dce
[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_sequence(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_sequence = 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_sequence)
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 + 32 * (get_level()->use_endsequence ? 22 : 10))
382     {
383       exit_status = LEVEL_FINISHED;
384     }
385   else if (tux->base.x >= World::current()->get_level()->endpos && !end_sequence)
386     {
387       end_sequence = 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_sequence)
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   return exit_status;
603 }
604
605 /* Bounce a brick: */
606 void bumpbrick(float x, float y)
607 {
608   World::current()->add_bouncy_brick(((int)(x + 1) / 32) * 32,
609                          (int)(y / 32) * 32);
610
611   play_sound(sounds[SND_BRICK], SOUND_CENTER_SPEAKER);
612 }
613
614 /* (Status): */
615 void
616 GameSession::drawstatus()
617 {
618   char str[60];
619
620   sprintf(str, "%d", player_status.score);
621   white_text->draw("SCORE", 0, 0, 1);
622   gold_text->draw(str, 96, 0, 1);
623
624   if(st_gl_mode == ST_GL_TEST)
625     {
626       white_text->draw("Press ESC To Return",0,20,1);
627     }
628
629   if (time_left.get_left() > TIME_WARNING || (global_frame_counter % 10) < 5)
630     {
631       sprintf(str, "%d", time_left.get_left() / 1000 );
632       white_text->draw("TIME", 224, 0, 1);
633       gold_text->draw(str, 304, 0, 1);
634     }
635
636   sprintf(str, "%d", player_status.distros);
637   white_text->draw("COINS", screen->h, 0, 1);
638   gold_text->draw(str, 608, 0, 1);
639
640   white_text->draw("LIVES", screen->h, 20, 1);
641
642   if(show_fps)
643     {
644       sprintf(str, "%2.1f", fps_fps);
645       white_text->draw("FPS", screen->h, 40, 1);
646       gold_text->draw(str, screen->h + 60, 40, 1);
647     }
648
649   for(int i= 0; i < player_status.lives; ++i)
650     {
651       tux_life->draw(565+(18*i),20);
652     }
653 }
654
655 void
656 GameSession::drawendscreen()
657 {
658   char str[80];
659
660   if (get_level()->img_bkgd)
661     get_level()->img_bkgd->draw(0, 0);
662   else
663     drawgradient(get_level()->bkgd_top, get_level()->bkgd_bottom);
664
665   blue_text->drawf("GAMEOVER", 0, 200, A_HMIDDLE, A_TOP, 1);
666
667   sprintf(str, "SCORE: %d", player_status.score);
668   gold_text->drawf(str, 0, 224, A_HMIDDLE, A_TOP, 1);
669
670   sprintf(str, "COINS: %d", player_status.distros);
671   gold_text->drawf(str, 0, 256, A_HMIDDLE, A_TOP, 1);
672
673   flipscreen();
674   
675   SDL_Event event;
676   wait_for_event(event,2000,5000,true);
677 }
678
679 void
680 GameSession::drawresultscreen(void)
681 {
682   char str[80];
683
684   if (get_level()->img_bkgd)
685     get_level()->img_bkgd->draw(0, 0);
686   else
687     drawgradient(get_level()->bkgd_top, get_level()->bkgd_bottom);
688
689   blue_text->drawf("Result:", 0, 200, A_HMIDDLE, A_TOP, 1);
690
691   sprintf(str, "SCORE: %d", player_status.score);
692   gold_text->drawf(str, 0, 224, A_HMIDDLE, A_TOP, 1);
693
694   sprintf(str, "COINS: %d", player_status.distros);
695   gold_text->drawf(str, 0, 256, A_HMIDDLE, A_TOP, 1);
696
697   flipscreen();
698   
699   SDL_Event event;
700   wait_for_event(event,2000,5000,true);
701 }
702
703 std::string slotinfo(int slot)
704 {
705   char tmp[1024];
706   char slotfile[1024];
707   sprintf(slotfile,"%s/slot%d.stsg",st_save_dir,slot);
708
709   if (access(slotfile, F_OK) == 0)
710     sprintf(tmp,"Slot %d - Savegame",slot);
711   else
712     sprintf(tmp,"Slot %d - Free",slot);
713
714   return tmp;
715 }
716
717