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