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