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