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