0807a5800adbcc9a9df71cfa8cd7dcd4a6d47f65
[supertux.git] / src / mainloop.cpp
1 //  $Id$
2 //
3 //  SuperTux
4 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 2
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19 #include <config.h>
20
21 #include "mainloop.hpp"
22
23 #include <stdlib.h>
24 #include <SDL.h>
25 #include "video/drawing_context.hpp"
26 #include "control/joystickkeyboardcontroller.hpp"
27 #include "gui/menu.hpp"
28 #include "audio/sound_manager.hpp"
29 #include "scripting/time_scheduler.hpp"
30 #include "scripting/squirrel_util.hpp"
31 #include "gameconfig.hpp"
32 #include "constants.hpp"
33 #include "main.hpp"
34 #include "resources.hpp"
35 #include "screen.hpp"
36 #include "screen_fade.hpp"
37 #include "timer.hpp"
38 #include "player_status.hpp"
39 #include "video/renderer.hpp"
40 #include "random_generator.hpp"
41
42 /** ticks (as returned from SDL_GetTicks) per frame */
43 static const Uint32 TICKS_PER_FRAME = (Uint32) (1000.0 / LOGICAL_FPS);
44 /** don't skip more than every 2nd frame */
45 static const int MAX_FRAME_SKIP = 2;
46
47 float game_speed = 1.0f;
48
49 MainLoop* main_loop = NULL;
50
51 MainLoop::MainLoop()
52   : speed(1.0), nextpop(false), nextpush(false), fps(0), screenshot_requested(false)
53 {
54   using namespace Scripting;
55   TimeScheduler::instance = new TimeScheduler();
56 }
57
58 MainLoop::~MainLoop()
59 {
60   using namespace Scripting;
61   delete TimeScheduler::instance;
62   TimeScheduler::instance = NULL;
63
64   for(std::vector<Screen*>::iterator i = screen_stack.begin();
65       i != screen_stack.end(); ++i) {
66     delete *i;
67   }
68 }
69
70 void
71 MainLoop::push_screen(Screen* screen, ScreenFade* screen_fade)
72 {
73   this->next_screen.reset(screen);
74   this->screen_fade.reset(screen_fade);
75   nextpush = !nextpop;
76   nextpop = false;
77   speed = 1.0f;
78 }
79
80 void
81 MainLoop::exit_screen(ScreenFade* screen_fade)
82 {
83   next_screen.reset(NULL);
84   this->screen_fade.reset(screen_fade);
85   nextpop = true;
86   nextpush = false;
87 }
88
89 void
90 MainLoop::set_screen_fade(ScreenFade* screen_fade)
91 {
92   this->screen_fade.reset(screen_fade);
93 }
94
95 void
96 MainLoop::quit(ScreenFade* screen_fade)
97 {
98   for(std::vector<Screen*>::iterator i = screen_stack.begin();
99           i != screen_stack.end(); ++i)
100     delete *i;
101   screen_stack.clear();
102
103   exit_screen(screen_fade);
104 }
105
106 void
107 MainLoop::set_speed(float speed)
108 {
109   this->speed = speed;
110 }
111
112 float
113 MainLoop::get_speed() const
114 {
115   return speed;
116 }
117
118 bool
119 MainLoop::has_no_pending_fadeout() const
120 {
121   return screen_fade.get() == NULL || screen_fade->done();
122 }
123
124 void
125 MainLoop::draw_fps(DrawingContext& context, float fps_fps)
126 {
127   char str[60];
128   snprintf(str, sizeof(str), "%3.1f", fps_fps);
129   const char* fpstext = "FPS";
130   context.draw_text(small_font, fpstext, Vector(SCREEN_WIDTH - small_font->get_text_width(fpstext) - small_font->get_text_width(" 99999") - BORDER_X, BORDER_Y + 20), ALIGN_LEFT, LAYER_HUD);
131   context.draw_text(small_font, str, Vector(SCREEN_WIDTH - BORDER_X, BORDER_Y + 20), ALIGN_RIGHT, LAYER_HUD);
132 }
133
134 void
135 MainLoop::draw(DrawingContext& context)
136 {
137   static Uint32 fps_ticks = SDL_GetTicks();
138   static int frame_count = 0;
139
140   current_screen->draw(context);
141   if(Menu::current() != NULL)
142     Menu::current()->draw(context);
143   if(screen_fade.get() != NULL)
144     screen_fade->draw(context);
145   Console::instance->draw(context);
146
147   if(config->show_fps)
148     draw_fps(context, fps);
149
150   // if a screenshot was requested, pass request on to drawing_context
151   if (screenshot_requested) {
152     context.take_screenshot();
153     screenshot_requested = false;
154   }
155   context.do_drawing();
156
157   /* Calculate frames per second */
158   if(config->show_fps)
159   {
160     ++frame_count;
161
162     if(SDL_GetTicks() - fps_ticks >= 500)
163     {
164       fps = (float) frame_count / .5;
165       frame_count = 0;
166       fps_ticks = SDL_GetTicks();
167     }
168   }
169 }
170
171 void
172 MainLoop::update_gamelogic(float elapsed_time)
173 {
174   Scripting::update_debugger();
175   Scripting::TimeScheduler::instance->update(game_time);
176   current_screen->update(elapsed_time);
177   if (Menu::current() != NULL)
178         Menu::current()->update();
179   if(screen_fade.get() != NULL)
180     screen_fade->update(elapsed_time);
181   Console::instance->update(elapsed_time);
182 }
183
184 void
185 MainLoop::process_events()
186 {
187   main_controller->update();
188   Uint8* keystate = SDL_GetKeyState(NULL);
189   SDL_Event event;
190   while(SDL_PollEvent(&event)) 
191     {
192       main_controller->process_event(event);
193
194       if(Menu::current() != NULL)
195         Menu::current()->event(event);
196
197       switch(event.type)
198         {
199           case SDL_QUIT:
200             quit();
201             break;
202               
203           case SDL_VIDEORESIZE:
204             Renderer::instance()->resize(event.resize.w, event.resize.h);
205             Menu::recalc_pos();
206             break;
207             
208           case SDL_KEYDOWN:
209             if (event.key.keysym.sym == SDLK_F10)
210               {
211                 config->show_fps = !config->show_fps;
212               }
213             if (event.key.keysym.sym == SDLK_F11) 
214               {
215                 config->use_fullscreen = !config->use_fullscreen;
216                 init_video();
217                 Menu::recalc_pos();
218               }
219             else if (event.key.keysym.sym == SDLK_PRINT ||
220                      event.key.keysym.sym == SDLK_F12)
221               {
222                 take_screenshot();
223               }
224             else if (event.key.keysym.sym == SDLK_F1 &&
225                      (keystate[SDLK_LCTRL] || keystate[SDLK_RCTRL]) &&
226                      keystate[SDLK_c])
227               {
228                 Console::instance->toggle();
229                 config->console_enabled = true;
230                 config->save();
231               }
232             break;
233         }
234     }
235 }
236
237 void
238 MainLoop::handle_screen_switch()
239 {
240   while( (next_screen.get() != NULL || nextpop) &&
241       has_no_pending_fadeout()) {
242     if(current_screen.get() != NULL) {
243       current_screen->leave();
244     }
245
246     if(nextpop) {
247       if(screen_stack.empty()) {
248         running = false;
249         break;
250       }
251       next_screen.reset(screen_stack.back());
252       screen_stack.pop_back();
253     }
254     if(nextpush && current_screen.get() != NULL) {
255       screen_stack.push_back(current_screen.release());
256     }
257
258     nextpush = false;
259     nextpop = false;
260     speed = 1.0;
261     Screen* next_screen_ptr = next_screen.release();
262     next_screen.reset(0);
263     if(next_screen_ptr)
264       next_screen_ptr->setup();
265     current_screen.reset(next_screen_ptr);
266     screen_fade.reset(NULL);
267
268     waiting_threads.wakeup();
269   }
270 }
271
272 void
273 MainLoop::run(DrawingContext &context)
274 {
275   Uint32 last_ticks = 0;
276   Uint32 elapsed_ticks = 0;
277
278   running = true;
279   while(running) {
280
281     handle_screen_switch();
282     if(!running || current_screen.get() == NULL)
283       break;
284
285     Uint32 ticks = SDL_GetTicks();
286     elapsed_ticks += ticks - last_ticks;
287     last_ticks = ticks;
288
289     Uint32 ticks_per_frame = (Uint32) (TICKS_PER_FRAME * game_speed);
290
291     if (elapsed_ticks > ticks_per_frame*4) {
292       // when the game loads up or levels are switched the
293       // elapsed_ticks grows extremely large, so we just ignore those
294       // large time jumps
295       elapsed_ticks = 0;
296     }
297
298     int frames = 0;
299
300     if (elapsed_ticks > ticks_per_frame) 
301       {
302         while(elapsed_ticks > ticks_per_frame && frames < MAX_FRAME_SKIP) 
303           {
304             elapsed_ticks -= ticks_per_frame;
305             float timestep = 1.0 / LOGICAL_FPS;
306             real_time += timestep;
307             timestep *= speed;
308             game_time += timestep;
309
310             process_events();
311             update_gamelogic(timestep);
312             frames += 1;
313           }
314
315         draw(context);
316       }
317
318     sound_manager->update();
319
320     SDL_Delay(0);
321   }
322 }
323
324 void 
325 MainLoop::take_screenshot()
326 {
327   screenshot_requested = true;
328 }
329