Changed up and down to previous and next. And added up and down icons.
[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   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 = ES_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_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           if(st_gl_mode != ST_GL_TEST)
470             drawendscreen();
471           
472           exit_status = ES_GAME_OVER;
473         }
474       else
475         { // Still has lives, so reset Tux to the levelstart
476           restart_level();
477         }
478
479       return;
480     }
481 }
482
483 void
484 GameSession::action(double frame_ratio)
485 {
486   if (exit_status == ES_NONE)
487     {
488       // Update Tux and the World
489       world->action(frame_ratio);
490     }
491 }
492
493 void 
494 GameSession::draw()
495 {
496   world->draw();
497   drawstatus();
498
499   if(game_pause)
500     {
501       int x = screen->h / 20;
502       for(int i = 0; i < x; ++i)
503         {
504           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);
505         }
506       fillrect(0,0,screen->w,screen->h,rand() % 50, rand() % 50, rand() % 50, 128);
507       blue_text->drawf("PAUSE - Press 'P' To Play", 0, 230, A_HMIDDLE, A_TOP, 1);
508     }
509
510   if(Menu::current())
511     {
512       Menu::current()->draw();
513       mouse_cursor->draw();
514     }
515
516   updatescreen();
517 }
518
519 void
520 GameSession::process_menu()
521 {
522   Menu* menu = Menu::current();
523   if(menu)
524     {
525       menu->action();
526
527       if(menu == game_menu)
528         {
529           switch (game_menu->check())
530             {
531             case MNID_CONTINUE:
532               st_pause_ticks_stop();
533               break;
534             case MNID_ABORTLEVEL:
535               st_pause_ticks_stop();
536               exit_status = ES_LEVEL_ABORT;
537               break;
538             }
539         }
540       else if(menu == options_menu)
541         {
542           process_options_menu();
543         }
544       else if(menu == load_game_menu )
545         {
546           process_load_game_menu();
547         }
548     }
549 }
550
551 GameSession::ExitStatus
552 GameSession::run()
553 {
554   Menu::set_current(0);
555   current_ = this;
556   
557   int fps_cnt = 0;
558
559   update_time = last_update_time = st_get_ticks();
560
561   // Eat unneeded events
562   SDL_Event event;
563   while (SDL_PollEvent(&event)) {}
564
565   draw();
566
567   while (exit_status == ES_NONE)
568     {
569       /* Calculate the movement-factor */
570       double frame_ratio = ((double)(update_time-last_update_time))/((double)FRAME_RATE);
571
572       if(!frame_timer.check())
573         {
574           frame_timer.start(25);
575           ++global_frame_counter;
576         }
577
578       /* Handle events: */
579       world->get_tux()->input.old_fire = world->get_tux()->input.fire;
580
581       process_events();
582       process_menu();
583
584       // Update the world state and all objects in the world
585       // Do that with a constante time-delta so that the game will run
586       // determistic and not different on different machines
587       if(!game_pause && !Menu::current())
588         {
589           // Update the world
590           check_end_conditions();
591           if (end_sequence == ENDSEQUENCE_RUNNING)
592              action(frame_ratio/2);
593           else if(end_sequence == NO_ENDSEQUENCE)
594              action(frame_ratio);
595         }
596       else
597         {
598           ++pause_menu_frame;
599           SDL_Delay(50);
600         }
601
602       draw();
603
604       /* Time stops in pause mode */
605       if(game_pause || Menu::current())
606         {
607           continue;
608         }
609
610       /* Set the time of the last update and the time of the current update */
611       last_update_time = update_time;
612       update_time      = st_get_ticks();
613
614       /* Pause till next frame, if the machine running the game is too fast: */
615       /* FIXME: Works great for in OpenGl mode, where the CPU doesn't have to do that much. But
616          the results in SDL mode aren't perfect (thought the 100 FPS are reached), even on an AMD2500+. */
617       if(last_update_time >= update_time - 12) 
618         {
619           SDL_Delay(10);
620           update_time = st_get_ticks();
621         }
622
623       /* Handle time: */
624       if (!time_left.check() && world->get_tux()->dying == DYING_NOT
625               && !end_sequence)
626         world->get_tux()->kill(Player::KILL);
627
628       /* Handle music: */
629       if(world->get_tux()->invincible_timer.check() && !end_sequence)
630         {
631           world->play_music(HERRING_MUSIC);
632         }
633       /* are we low on time ? */
634       else if (time_left.get_left() < TIME_WARNING && !end_sequence)
635         {
636           world->play_music(HURRYUP_MUSIC);
637         }
638       /* or just normal music? */
639       else if(world->get_music_type() != LEVEL_MUSIC && !end_sequence)
640         {
641           world->play_music(LEVEL_MUSIC);
642         }
643
644       /* Calculate frames per second */
645       if(show_fps)
646         {
647           ++fps_cnt;
648           fps_fps = (1000.0 / (float)fps_timer.get_gone()) * (float)fps_cnt;
649
650           if(!fps_timer.check())
651             {
652               fps_timer.start(1000);
653               fps_cnt = 0;
654             }
655         }
656     }
657   
658   return exit_status;
659 }
660
661 /* Bounce a brick: */
662 void bumpbrick(float x, float y)
663 {
664   World::current()->add_bouncy_brick(((int)(x + 1) / 32) * 32,
665                          (int)(y / 32) * 32);
666
667   play_sound(sounds[SND_BRICK], SOUND_CENTER_SPEAKER);
668 }
669
670 /* (Status): */
671 void
672 GameSession::drawstatus()
673 {
674   char str[60];
675
676   sprintf(str, "%d", player_status.score);
677   white_text->draw("SCORE", 0, 0, 1);
678   gold_text->draw(str, 96, 0, 1);
679
680   if(st_gl_mode == ST_GL_TEST)
681     {
682       white_text->draw("Press ESC To Return",0,20,1);
683     }
684
685   if(!time_left.check()) {
686     white_text->draw("TIME'S UP", 224, 0, 1);
687   } else if (time_left.get_left() > TIME_WARNING || (global_frame_counter % 10) < 5) {
688     sprintf(str, "%d", time_left.get_left() / 1000 );
689     white_text->draw("TIME", 224, 0, 1);
690     gold_text->draw(str, 304, 0, 1);
691   }
692
693   sprintf(str, "%d", player_status.distros);
694   white_text->draw("COINS", screen->h, 0, 1);
695   gold_text->draw(str, 608, 0, 1);
696
697   white_text->draw("LIVES", 480, 20);
698   if (player_status.lives >= 5)
699     {
700       sprintf(str, "%dx", player_status.lives);
701       gold_text->draw_align(str, 617, 20, A_RIGHT, A_TOP);
702       tux_life->draw(565+(18*3), 20);
703     }
704   else
705     {
706       for(int i= 0; i < player_status.lives; ++i)
707         tux_life->draw(565+(18*i),20);
708     }
709
710   if(show_fps)
711     {
712       sprintf(str, "%2.1f", fps_fps);
713       white_text->draw("FPS", screen->h, 40, 1);
714       gold_text->draw(str, screen->h + 60, 40, 1);
715     }
716 }
717
718 void
719 GameSession::drawendscreen()
720 {
721   char str[80];
722
723   if (get_level()->img_bkgd)
724     get_level()->img_bkgd->draw(0, 0);
725   else
726     drawgradient(get_level()->bkgd_top, get_level()->bkgd_bottom);
727
728   blue_text->drawf("GAMEOVER", 0, 200, A_HMIDDLE, A_TOP, 1);
729
730   sprintf(str, "SCORE: %d", player_status.score);
731   gold_text->drawf(str, 0, 224, A_HMIDDLE, A_TOP, 1);
732
733   sprintf(str, "COINS: %d", player_status.distros);
734   gold_text->drawf(str, 0, 256, A_HMIDDLE, A_TOP, 1);
735
736   flipscreen();
737   
738   SDL_Event event;
739   wait_for_event(event,2000,5000,true);
740 }
741
742 void
743 GameSession::drawresultscreen(void)
744 {
745   char str[80];
746
747   if (get_level()->img_bkgd)
748     get_level()->img_bkgd->draw(0, 0);
749   else
750     drawgradient(get_level()->bkgd_top, get_level()->bkgd_bottom);
751
752   blue_text->drawf("Result:", 0, 200, A_HMIDDLE, A_TOP, 1);
753
754   sprintf(str, "SCORE: %d", player_status.score);
755   gold_text->drawf(str, 0, 224, A_HMIDDLE, A_TOP, 1);
756
757   sprintf(str, "COINS: %d", player_status.distros);
758   gold_text->drawf(str, 0, 256, A_HMIDDLE, A_TOP, 1);
759
760   flipscreen();
761   
762   SDL_Event event;
763   wait_for_event(event,2000,5000,true);
764 }
765
766 std::string slotinfo(int slot)
767 {
768   char tmp[1024];
769   char slotfile[1024];
770   std::string title;
771   sprintf(slotfile,"%s/slot%d.stsg",st_save_dir,slot);
772
773   lisp_object_t* savegame = lisp_read_from_file(slotfile);
774   if (savegame)
775     {
776       LispReader reader(lisp_cdr(savegame));
777       reader.read_string("title", &title);
778       lisp_free(savegame);
779     }
780
781   if (access(slotfile, F_OK) == 0)
782     {
783       if (!title.empty())
784         snprintf(tmp,1024,"Slot %d - %s",slot, title.c_str());
785       else
786         snprintf(tmp, 1024,"Slot %d - Savegame",slot);
787     }
788   else
789     sprintf(tmp,"Slot %d - Free",slot);
790
791   return tmp;
792 }
793
794