-Added new object system and converted some GameObjects to it.
[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  = ES_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   get_level()->draw_bg();
149
150   sprintf(str, "%s", world->get_level()->name.c_str());
151   gold_text->drawf(str, 0, 220, A_HMIDDLE, A_TOP, 1);
152
153   sprintf(str, "TUX x %d", player_status.lives);
154   white_text->drawf(str, 0, 240, A_HMIDDLE, A_TOP, 1);
155   
156   sprintf(str, "by %s", world->get_level()->author.c_str());
157   white_small_text->drawf(str, 0, 400, A_HMIDDLE, A_TOP, 1);
158   
159
160   flipscreen();
161
162   SDL_Event event;
163   wait_for_event(event,1000,3000,true);
164 }
165
166 /* Reset Timers */
167 void
168 GameSession::start_timers()
169 {
170   time_left.start(world->get_level()->time_left*1000);
171   st_pause_ticks_init();
172   update_time = st_get_ticks();
173 }
174
175 void
176 GameSession::on_escape_press()
177 {
178   if(world->get_tux()->dying || end_sequence != NO_ENDSEQUENCE)
179     return;   // don't let the player open the menu, when he is dying
180   if(game_pause)
181     return;
182
183   if(st_gl_mode == ST_GL_TEST)
184     {
185       exit_status = ES_LEVEL_ABORT;
186     }
187   else if (!Menu::current())
188     {
189       /* Tell Tux that the keys are all down, otherwise
190         it could have nasty bugs, like going allways to the right
191         or whatever that key does */
192       Player& tux = *world->get_tux();
193       tux.key_event((SDLKey)keymap.jump, UP);
194       tux.key_event((SDLKey)keymap.duck, UP);
195       tux.key_event((SDLKey)keymap.left, UP);
196       tux.key_event((SDLKey)keymap.right, UP);
197       tux.key_event((SDLKey)keymap.fire, UP);
198
199       Menu::set_current(game_menu);
200       st_pause_ticks_start();
201     }
202 }
203
204 void
205 GameSession::process_events()
206 {
207   if (end_sequence != NO_ENDSEQUENCE)
208     {
209       Player& tux = *world->get_tux();
210          
211       tux.input.fire  = UP;
212       tux.input.left  = UP;
213       tux.input.right = DOWN;
214       tux.input.down  = UP; 
215
216       if (int(last_x_pos) == int(tux.base.x))
217         tux.input.up    = DOWN; 
218       else
219         tux.input.up    = UP; 
220
221       last_x_pos = tux.base.x;
222
223       SDL_Event event;
224       while (SDL_PollEvent(&event))
225         {
226           /* Check for menu-events, if the menu is shown */
227           if (Menu::current())
228             {
229               Menu::current()->event(event);
230               if(!Menu::current())
231               st_pause_ticks_stop();
232             }
233
234           switch(event.type)
235             {
236             case SDL_QUIT:        /* Quit event - quit: */
237               st_abort("Received window close", "");
238               break;
239               
240             case SDL_KEYDOWN:     /* A keypress! */
241               {
242                 SDLKey key = event.key.keysym.sym;
243            
244                 switch(key)
245                   {
246                   case SDLK_ESCAPE:    /* Escape: Open/Close the menu: */
247                     on_escape_press();
248                     break;
249                   default:
250                     break;
251                   }
252               }
253           
254             case SDL_JOYBUTTONDOWN:
255               if (event.jbutton.button == joystick_keymap.start_button)
256                 on_escape_press();
257               break;
258             }
259         }
260     }
261   else // normal mode
262     {
263       if(!Menu::current() && !game_pause)
264         st_pause_ticks_stop();
265
266       SDL_Event event;
267       while (SDL_PollEvent(&event))
268         {
269           /* Check for menu-events, if the menu is shown */
270           if (Menu::current())
271             {
272               Menu::current()->event(event);
273               if(!Menu::current())
274                 st_pause_ticks_stop();
275             }
276           else
277             {
278               Player& tux = *world->get_tux();
279   
280               switch(event.type)
281                 {
282                 case SDL_QUIT:        /* Quit event - quit: */
283                   st_abort("Received window close", "");
284                   break;
285
286                 case SDL_KEYDOWN:     /* A keypress! */
287                   {
288                     SDLKey key = event.key.keysym.sym;
289             
290                     if(tux.key_event(key,DOWN))
291                       break;
292
293                     switch(key)
294                       {
295                       case SDLK_ESCAPE:    /* Escape: Open/Close the menu: */
296                         on_escape_press();
297                         break;
298                       default:
299                         break;
300                       }
301                   }
302                   break;
303                 case SDL_KEYUP:      /* A keyrelease! */
304                   {
305                     SDLKey key = event.key.keysym.sym;
306
307                     if(tux.key_event(key, UP))
308                       break;
309
310                     switch(key)
311                       {
312                       case SDLK_p:
313                         if(!Menu::current())
314                           {
315                             if(game_pause)
316                               {
317                                 game_pause = false;
318                                 st_pause_ticks_stop();
319                               }
320                             else
321                               {
322                                 game_pause = true;
323                                 st_pause_ticks_start();
324                               }
325                           }
326                         break;
327                       case SDLK_TAB:
328                         if(debug_mode)
329                           {
330                             tux.size = !tux.size;
331                             if(tux.size == BIG)
332                               {
333                                 tux.base.height = 64;
334                               }
335                             else
336                               tux.base.height = 32;
337                           }
338                         break;
339                       case SDLK_END:
340                         if(debug_mode)
341                           player_status.distros += 50;
342                         break;
343                       case SDLK_DELETE:
344                         if(debug_mode)
345                           tux.got_power = tux.FIRE_POWER;
346                         break;
347                       case SDLK_HOME:
348                         if(debug_mode)
349                           tux.got_power = tux.ICE_POWER;
350                         break;
351                       case SDLK_INSERT:
352                         if(debug_mode)
353                           tux.invincible_timer.start(TUX_INVINCIBLE_TIME);
354                         break;
355                       case SDLK_l:
356                         if(debug_mode)
357                           --player_status.lives;
358                         break;
359                       case SDLK_s:
360                         if(debug_mode)
361                           player_status.score += 1000;
362                       case SDLK_f:
363                         if(debug_fps)
364                           debug_fps = false;
365                         else
366                           debug_fps = true;
367                         break;
368                       default:
369                         break;
370                       }
371                   }
372                   break;
373
374                 case SDL_JOYAXISMOTION:
375                   if (event.jaxis.axis == joystick_keymap.x_axis)
376                     {
377                       if (event.jaxis.value < -joystick_keymap.dead_zone)
378                         {
379                           tux.input.left  = DOWN;
380                           tux.input.right = UP;
381                         }
382                       else if (event.jaxis.value > joystick_keymap.dead_zone)
383                         {
384                           tux.input.left  = UP;
385                           tux.input.right = DOWN;
386                         }
387                       else
388                         {
389                           tux.input.left  = DOWN;
390                           tux.input.right = DOWN;
391                         }
392                     }
393                   else if (event.jaxis.axis == joystick_keymap.y_axis)
394                     {
395                       if (event.jaxis.value > joystick_keymap.dead_zone)
396                         tux.input.down = DOWN;
397                       else if (event.jaxis.value < -joystick_keymap.dead_zone)
398                         tux.input.down = UP;
399                       else
400                         tux.input.down = UP;
401                     }
402                   break;
403             
404                 case SDL_JOYBUTTONDOWN:
405                   if (event.jbutton.button == joystick_keymap.a_button)
406                     tux.input.up = DOWN;
407                   else if (event.jbutton.button == joystick_keymap.b_button)
408                     tux.input.fire = DOWN;
409                   else if (event.jbutton.button == joystick_keymap.start_button)
410                     on_escape_press();
411                   break;
412                 case SDL_JOYBUTTONUP:
413                   if (event.jbutton.button == joystick_keymap.a_button)
414                     tux.input.up = UP;
415                   else if (event.jbutton.button == joystick_keymap.b_button)
416                     tux.input.fire = UP;
417                   break;
418
419                 default:
420                   break;
421                 }  /* switch */
422             }
423         } /* while */
424     }
425 }
426
427 void
428 GameSession::check_end_conditions()
429 {
430   Player* tux = world->get_tux();
431
432   /* End of level? */
433   int endpos = (World::current()->get_level()->width-5) * 32;
434   Tile* endtile = collision_goal(tux->base);
435
436   // fallback in case the other endpositions don't trigger
437   if (!end_sequence && tux->base.x >= endpos)
438     {
439       end_sequence = ENDSEQUENCE_WAITING;
440       last_x_pos = -1;
441       music_manager->play_music(level_end_song, 0);
442       endsequence_timer.start(7000);
443       tux->invincible_timer.start(7000); //FIXME: Implement a winning timer for the end sequence (with special winning animation etc.)
444     }
445   else if(end_sequence && !endsequence_timer.check())
446     {
447       exit_status = ES_LEVEL_FINISHED;
448       return;
449     }
450   else if(end_sequence == ENDSEQUENCE_RUNNING && endtile && endtile->data >= 1)
451     {
452       end_sequence = ENDSEQUENCE_WAITING;
453     }
454   else if(!end_sequence && endtile && endtile->data == 0)
455     {
456       end_sequence = ENDSEQUENCE_RUNNING;
457       last_x_pos = -1;
458       music_manager->play_music(level_end_song, 0);
459       endsequence_timer.start(7000); // 5 seconds until we finish the map
460       tux->invincible_timer.start(7000); //FIXME: Implement a winning timer for the end sequence (with special winning animation etc.)
461     }
462   else if (!end_sequence && tux->is_dead())
463     {
464       player_status.bonus = PlayerStatus::NO_BONUS;
465
466       if (player_status.lives < 0)
467         { // No more lives!?
468           exit_status = ES_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 == ES_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 = ES_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 == ES_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(Vector(((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", screen->w/2 - white_text->w*8, 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", screen->w/2 - white_text->w*4, 0, 1);
686     gold_text->draw(str, screen->w/2 + gold_text->w, 0, 1);
687   }
688
689   sprintf(str, "%d", player_status.distros);
690   white_text->draw("COINS", screen->w - white_text->w*9, 0, 1);
691   gold_text->draw(str, screen->w - gold_text->w*2, 0, 1);
692
693   white_text->draw("LIVES", screen->w - white_text->w*9, 20);
694   if (player_status.lives >= 5)
695     {
696       sprintf(str, "%dx", player_status.lives);
697       gold_text->draw_align(str, screen->w - gold_text->w, 20, A_RIGHT, A_TOP);
698       tux_life->draw(screen->w - gold_text->w, 20);
699     }
700   else
701     {
702       for(int i= 0; i < player_status.lives; ++i)
703         tux_life->draw(screen->w - tux_life->w*4 +(tux_life->w*i), 20);
704     }
705
706   if(show_fps)
707     {
708       sprintf(str, "%2.1f", fps_fps);
709       white_text->draw("FPS", screen->w - white_text->w*9, 40, 1);
710       gold_text->draw_align(str, screen->w, 40, A_RIGHT, A_TOP);
711     }
712 }
713
714 void
715 GameSession::drawresultscreen(void)
716 {
717   char str[80];
718
719   get_level()->draw_bg();
720
721   blue_text->drawf("Result:", 0, 200, A_HMIDDLE, A_TOP, 1);
722
723   sprintf(str, "SCORE: %d", player_status.score);
724   gold_text->drawf(str, 0, 224, A_HMIDDLE, A_TOP, 1);
725
726   sprintf(str, "COINS: %d", player_status.distros);
727   gold_text->drawf(str, 0, 256, A_HMIDDLE, A_TOP, 1);
728
729   flipscreen();
730   
731   SDL_Event event;
732   wait_for_event(event,2000,5000,true);
733 }
734
735 std::string slotinfo(int slot)
736 {
737   char tmp[1024];
738   char slotfile[1024];
739   std::string title;
740   sprintf(slotfile,"%s/slot%d.stsg",st_save_dir,slot);
741
742   lisp_object_t* savegame = lisp_read_from_file(slotfile);
743   if (savegame)
744     {
745       LispReader reader(lisp_cdr(savegame));
746       reader.read_string("title", &title);
747       lisp_free(savegame);
748     }
749
750   if (access(slotfile, F_OK) == 0)
751     {
752       if (!title.empty())
753         snprintf(tmp,1024,"Slot %d - %s",slot, title.c_str());
754       else
755         snprintf(tmp, 1024,"Slot %d - Savegame",slot);
756     }
757   else
758     sprintf(tmp,"Slot %d - Free",slot);
759
760   return tmp;
761 }
762
763