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