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