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