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