ac9ad58e54b8eabe5682e21be735677ad2887e8d
[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       context.push_transform();
502       context.set_translation(Vector(0, 0));
503         
504       int x = screen->h / 20;
505       for(int i = 0; i < x; ++i)
506         {
507           context.draw_filled_rect(
508               Vector(i % 2 ? (pause_menu_frame * i)%screen->w :
509                 -((pause_menu_frame * i)%screen->w)
510                 ,(i*20+pause_menu_frame)%screen->h),
511               Vector(screen->w,10),
512               Color(20,20,20, rand() % 20 + 1), LAYER_FOREGROUND1+1);
513         }
514       context.draw_filled_rect(
515           Vector(0,0), Vector(screen->w, screen->h),
516           Color(rand() % 50, rand() % 50, rand() % 50, 128), LAYER_FOREGROUND1);
517       world->context.draw_text_center(blue_text, "PAUSE - Press 'P' To Play",
518           Vector(0, 230), LAYER_FOREGROUND1+2);
519
520       context.pop_transform();
521     }
522
523   if(Menu::current())
524     {
525       Menu::current()->draw(context);
526       mouse_cursor->draw(context);
527     }
528
529   context.do_drawing();
530 }
531
532 void
533 GameSession::process_menu()
534 {
535   Menu* menu = Menu::current();
536   if(menu)
537     {
538       menu->action();
539
540       if(menu == game_menu)
541         {
542           switch (game_menu->check())
543             {
544             case MNID_CONTINUE:
545               st_pause_ticks_stop();
546               break;
547             case MNID_ABORTLEVEL:
548               st_pause_ticks_stop();
549               exit_status = ES_LEVEL_ABORT;
550               break;
551             }
552         }
553       else if(menu == options_menu)
554         {
555           process_options_menu();
556         }
557       else if(menu == load_game_menu )
558         {
559           process_load_game_menu();
560         }
561     }
562 }
563
564 GameSession::ExitStatus
565 GameSession::run()
566 {
567   Menu::set_current(0);
568   current_ = this;
569   
570   int fps_cnt = 0;
571
572   update_time = last_update_time = st_get_ticks();
573
574   // Eat unneeded events
575   SDL_Event event;
576   while (SDL_PollEvent(&event)) {}
577
578   draw();
579
580   while (exit_status == ES_NONE)
581     {
582       /* Calculate the movement-factor */
583       double frame_ratio = ((double)(update_time-last_update_time))/((double)FRAME_RATE);
584
585       if(!frame_timer.check())
586         {
587           frame_timer.start(25);
588           ++global_frame_counter;
589         }
590
591       /* Handle events: */
592       world->get_tux()->input.old_fire = world->get_tux()->input.fire;
593
594       process_events();
595       process_menu();
596
597       // Update the world state and all objects in the world
598       // Do that with a constante time-delta so that the game will run
599       // determistic and not different on different machines
600       if(!game_pause && !Menu::current())
601         {
602           // Update the world
603           check_end_conditions();
604           if (end_sequence == ENDSEQUENCE_RUNNING)
605              action(frame_ratio/2);
606           else if(end_sequence == NO_ENDSEQUENCE)
607              action(frame_ratio);
608         }
609       else
610         {
611           ++pause_menu_frame;
612           SDL_Delay(50);
613         }
614
615       draw();
616
617       /* Time stops in pause mode */
618       if(game_pause || Menu::current())
619         {
620           continue;
621         }
622
623       /* Set the time of the last update and the time of the current update */
624       last_update_time = update_time;
625       update_time      = st_get_ticks();
626
627       /* Pause till next frame, if the machine running the game is too fast: */
628       /* FIXME: Works great for in OpenGl mode, where the CPU doesn't have to do that much. But
629          the results in SDL mode aren't perfect (thought the 100 FPS are reached), even on an AMD2500+. */
630       if(last_update_time >= update_time - 12) 
631         {
632           SDL_Delay(10);
633           update_time = st_get_ticks();
634         }
635
636       /* Handle time: */
637       if (!time_left.check() && world->get_tux()->dying == DYING_NOT
638               && !end_sequence)
639         world->get_tux()->kill(Player::KILL);
640
641       /* Handle music: */
642       if(world->get_tux()->invincible_timer.check() && !end_sequence)
643         {
644           world->play_music(HERRING_MUSIC);
645         }
646       /* are we low on time ? */
647       else if (time_left.get_left() < TIME_WARNING && !end_sequence)
648         {
649           world->play_music(HURRYUP_MUSIC);
650         }
651       /* or just normal music? */
652       else if(world->get_music_type() != LEVEL_MUSIC && !end_sequence)
653         {
654           world->play_music(LEVEL_MUSIC);
655         }
656
657       /* Calculate frames per second */
658       if(show_fps)
659         {
660           ++fps_cnt;
661           fps_fps = (1000.0 / (float)fps_timer.get_gone()) * (float)fps_cnt;
662
663           if(!fps_timer.check())
664             {
665               fps_timer.start(1000);
666               fps_cnt = 0;
667             }
668         }
669     }
670   
671   return exit_status;
672 }
673
674 /* Bounce a brick: */
675 void bumpbrick(float x, float y)
676 {
677   World::current()->add_bouncy_brick(Vector(((int)(x + 1) / 32) * 32,
678                          (int)(y / 32) * 32));
679
680   play_sound(sounds[SND_BRICK], SOUND_CENTER_SPEAKER);
681 }
682
683 /* (Status): */
684 void
685 GameSession::drawstatus(DrawingContext& context)
686 {
687   context.push_transform();
688   context.set_translation(Vector(0, 0));
689   
690   char str[60];
691   
692   snprintf(str, 60, "%d", player_status.score);
693   context.draw_text(white_text, "SCORE", Vector(0, 0), LAYER_FOREGROUND1);
694   context.draw_text(gold_text, str, Vector(96, 0), LAYER_FOREGROUND1);
695
696   if(st_gl_mode == ST_GL_TEST)
697     {
698       context.draw_text(white_text, "Press ESC To Return", Vector(0,20),
699           LAYER_FOREGROUND1);
700     }
701
702   if(!time_left.check()) {
703     context.draw_text_center(white_text, "TIME's UP", Vector(0, 0),
704         LAYER_FOREGROUND1);
705   } else if (time_left.get_left() > TIME_WARNING || (global_frame_counter % 10) < 5) {
706     sprintf(str, "%d", time_left.get_left() / 1000 );
707     context.draw_text_center(white_text, "TIME",
708         Vector(0, 0), LAYER_FOREGROUND1);
709     context.draw_text_center(gold_text, str,
710         Vector(4*16, 0), LAYER_FOREGROUND1);
711   }
712
713   sprintf(str, "%d", player_status.distros);
714   context.draw_text(white_text, "COINS",
715       Vector(screen->w - white_text->w*9, 0), LAYER_FOREGROUND1);
716   context.draw_text(gold_text, str,
717       Vector(screen->w - gold_text->w*2, 0), LAYER_FOREGROUND1);
718
719   context.draw_text(white_text, "LIVES",
720       Vector(screen->w - white_text->w*9, 20), LAYER_FOREGROUND1);
721   if (player_status.lives >= 5)
722     {
723       sprintf(str, "%dx", player_status.lives);
724       float x = screen->w - gold_text->get_text_width(str) - tux_life->w;
725       context.draw_text(gold_text, str, Vector(x, 20), LAYER_FOREGROUND1);
726       context.draw_surface(tux_life, Vector(screen->w - 16, 20),
727           LAYER_FOREGROUND1);
728     }
729   else
730     {
731       for(int i= 0; i < player_status.lives; ++i)
732         context.draw_surface(tux_life, 
733             Vector(screen->w - tux_life->w*4 +(tux_life->w*i), 20),
734             LAYER_FOREGROUND1);
735     }
736
737   if(show_fps)
738     {
739       sprintf(str, "%2.1f", fps_fps);
740       context.draw_text(white_text, "FPS", 
741           Vector(screen->w - white_text->w*9, 40), LAYER_FOREGROUND1);
742       context.draw_text(gold_text, str,
743           Vector(screen->w-4*16, 40), LAYER_FOREGROUND1);
744     }
745
746   context.pop_transform();
747 }
748
749 void
750 GameSession::drawresultscreen(void)
751 {
752   char str[80];
753
754   DrawingContext context;
755   world->background->draw(context);  
756
757   context.draw_text_center(blue_text, "Result:", Vector(0, 200),
758       LAYER_FOREGROUND1);
759
760   sprintf(str, "SCORE: %d", player_status.score);
761   context.draw_text_center(gold_text, str, Vector(0, 224), LAYER_FOREGROUND1);
762
763   sprintf(str, "COINS: %d", player_status.distros);
764   context.draw_text_center(gold_text, str, Vector(0, 256), LAYER_FOREGROUND1);
765
766   context.do_drawing();
767   
768   SDL_Event event;
769   wait_for_event(event,2000,5000,true);
770 }
771
772 std::string slotinfo(int slot)
773 {
774   char tmp[1024];
775   char slotfile[1024];
776   std::string title;
777   sprintf(slotfile,"%s/slot%d.stsg",st_save_dir,slot);
778
779   lisp_object_t* savegame = lisp_read_from_file(slotfile);
780   if (savegame)
781     {
782       LispReader reader(lisp_cdr(savegame));
783       reader.read_string("title", &title);
784       lisp_free(savegame);
785     }
786
787   if (access(slotfile, F_OK) == 0)
788     {
789       if (!title.empty())
790         snprintf(tmp,1024,"Slot %d - %s",slot, title.c_str());
791       else
792         snprintf(tmp, 1024,"Slot %d - Savegame",slot);
793     }
794   else
795     sprintf(tmp,"Slot %d - Free",slot);
796
797   return tmp;
798 }
799
800