e613666e78d4308f68955ac970ea69287a3e7b5b
[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 "main.hpp"
33 #include "resources.hpp"
34 #include "screen.hpp"
35 #include "screen_fade.hpp"
36 #include "timer.hpp"
37 #include "player_status.hpp"
38 #include "video/renderer.hpp"
39 #include "random_generator.hpp"
40
41 /** ticks (as returned from SDL_GetTicks) per frame */
42 static const Uint32 TICKS_PER_FRAME = (Uint32) (1000.0 / LOGICAL_FPS);
43 /** don't skip more than every 2nd frame */
44 static const int MAX_FRAME_SKIP = 2;
45
46 float game_speed = 1.0f;
47
48 MainLoop* main_loop = NULL;
49
50 MainLoop::MainLoop()
51   : speed(1.0), nextpop(false), nextpush(false), fps(0), screenshot_requested(false)
52 {
53   using namespace Scripting;
54   TimeScheduler::instance = new TimeScheduler();
55 }
56
57 MainLoop::~MainLoop()
58 {
59   using namespace Scripting;
60   delete TimeScheduler::instance;
61   TimeScheduler::instance = NULL;
62
63   for(std::vector<Screen*>::iterator i = screen_stack.begin();
64       i != screen_stack.end(); ++i) {
65     delete *i;
66   }
67 }
68
69 void
70 MainLoop::push_screen(Screen* screen, ScreenFade* screen_fade)
71 {
72   this->next_screen.reset(screen);
73   this->screen_fade.reset(screen_fade);
74   nextpush = !nextpop;
75   nextpop = false;
76   speed = 1.0f;
77 }
78
79 void
80 MainLoop::exit_screen(ScreenFade* screen_fade)
81 {
82   next_screen.reset(NULL);
83   this->screen_fade.reset(screen_fade);
84   nextpop = true;
85   nextpush = false;
86 }
87
88 void
89 MainLoop::set_screen_fade(ScreenFade* screen_fade)
90 {
91   this->screen_fade.reset(screen_fade);
92 }
93
94 void
95 MainLoop::quit(ScreenFade* screen_fade)
96 {
97   for(std::vector<Screen*>::iterator i = screen_stack.begin();
98           i != screen_stack.end(); ++i)
99     delete *i;
100   screen_stack.clear();
101
102   exit_screen(screen_fade);
103 }
104
105 void
106 MainLoop::set_speed(float speed)
107 {
108   this->speed = speed;
109 }
110
111 float
112 MainLoop::get_speed() const
113 {
114   return speed;
115 }
116
117 void
118 MainLoop::draw_fps(DrawingContext& context, float fps_fps)
119 {
120   char str[60];
121   snprintf(str, sizeof(str), "%3.1f", fps_fps);
122   const char* fpstext = "FPS";
123   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);
124   context.draw_text(small_font, str, Vector(SCREEN_WIDTH - BORDER_X, BORDER_Y + 20), ALIGN_RIGHT, LAYER_HUD);
125 }
126
127 void
128 MainLoop::draw(DrawingContext& context)
129 {
130   static Uint32 fps_ticks = SDL_GetTicks();
131   static int frame_count = 0;
132
133   current_screen->draw(context);
134   if(Menu::current() != NULL)
135     Menu::current()->draw(context);
136   if(screen_fade.get() != NULL)
137     screen_fade->draw(context);
138   Console::instance->draw(context);
139
140   if(config->show_fps)
141     draw_fps(context, fps);
142
143   // if a screenshot was requested, pass request on to drawing_context
144   if (screenshot_requested) {
145     context.take_screenshot();
146     screenshot_requested = false;
147   }
148   context.do_drawing();
149
150   /* Calculate frames per second */
151   if(config->show_fps)
152   {
153     ++frame_count;
154
155     if(SDL_GetTicks() - fps_ticks >= 500)
156     {
157       fps = (float) frame_count / .5;
158       frame_count = 0;
159       fps_ticks = SDL_GetTicks();
160     }
161   }
162 }
163
164 void
165 MainLoop::update_gamelogic(float elapsed_time)
166 {
167   Scripting::update_debugger();
168   Scripting::TimeScheduler::instance->update(game_time);
169   current_screen->update(elapsed_time);
170   if (Menu::current() != NULL)
171         Menu::current()->update();
172   if(screen_fade.get() != NULL)
173     screen_fade->update(elapsed_time);
174   Console::instance->update(elapsed_time);
175 }
176
177 void
178 MainLoop::process_events()
179 {
180   main_controller->update();
181   Uint8* keystate = SDL_GetKeyState(NULL);
182   SDL_Event event;
183   while(SDL_PollEvent(&event)) 
184     {
185       main_controller->process_event(event);
186
187       if(Menu::current() != NULL)
188         Menu::current()->event(event);
189
190       switch(event.type)
191         {
192           case SDL_QUIT:
193             quit();
194             break;
195               
196           case SDL_VIDEORESIZE:
197             Renderer::instance()->resize(event.resize.w, event.resize.h);
198             Menu::recalc_pos();
199             break;
200             
201           case SDL_KEYDOWN:
202             if (event.key.keysym.sym == SDLK_F10)
203               {
204                 config->show_fps = !config->show_fps;
205               }
206             if (event.key.keysym.sym == SDLK_F11) 
207               {
208                 config->use_fullscreen = !config->use_fullscreen;
209                 init_video();
210                 Menu::recalc_pos();
211               }
212             else if (event.key.keysym.sym == SDLK_PRINT ||
213                      event.key.keysym.sym == SDLK_F12)
214               {
215                 take_screenshot();
216               }
217             else if (event.key.keysym.sym == SDLK_F1 &&
218                      (keystate[SDLK_LCTRL] || keystate[SDLK_RCTRL]) &&
219                      keystate[SDLK_c])
220               {
221                 Console::instance->toggle();
222                 config->console_enabled = true;
223                 config->save();
224               }
225             break;
226         }
227     }
228 }
229
230 void
231 MainLoop::handle_screen_switch()
232 {
233   while( (next_screen.get() != NULL || nextpop) &&
234       (screen_fade.get() == NULL || screen_fade->done())) {
235     if(current_screen.get() != NULL) {
236       current_screen->leave();
237     }
238
239     if(nextpop) {
240       if(screen_stack.empty()) {
241         running = false;
242         break;
243       }
244       next_screen.reset(screen_stack.back());
245       screen_stack.pop_back();
246     }
247     if(nextpush && current_screen.get() != NULL) {
248       screen_stack.push_back(current_screen.release());
249     }
250
251     nextpush = false;
252     nextpop = false;
253     speed = 1.0;
254     Screen* next_screen_ptr = next_screen.release();
255     next_screen.reset(0);
256     if(next_screen_ptr)
257       next_screen_ptr->setup();
258     current_screen.reset(next_screen_ptr);
259     screen_fade.reset(NULL);
260
261     waiting_threads.wakeup();
262   }
263 }
264
265 void
266 MainLoop::run(DrawingContext &context)
267 {
268   Uint32 last_ticks = 0;
269   Uint32 elapsed_ticks = 0;
270
271   running = true;
272   while(running) {
273
274     handle_screen_switch();
275     if(!running || current_screen.get() == NULL)
276       break;
277
278     Uint32 ticks = SDL_GetTicks();
279     elapsed_ticks += ticks - last_ticks;
280     last_ticks = ticks;
281
282     Uint32 ticks_per_frame = (Uint32) (TICKS_PER_FRAME * game_speed);
283
284     if (elapsed_ticks > ticks_per_frame*4) {
285       // when the game loads up or levels are switched the
286       // elapsed_ticks grows extremely large, so we just ignore those
287       // large time jumps
288       elapsed_ticks = 0;
289     }
290
291     int frames = 0;
292
293     if (elapsed_ticks > ticks_per_frame) 
294       {
295         while(elapsed_ticks > ticks_per_frame && frames < MAX_FRAME_SKIP) 
296           {
297             elapsed_ticks -= ticks_per_frame;
298             float timestep = 1.0 / LOGICAL_FPS;
299             real_time += timestep;
300             timestep *= speed;
301             game_time += timestep;
302
303             process_events();
304             update_gamelogic(timestep);
305             frames += 1;
306           }
307
308         draw(context);
309       }
310
311     sound_manager->update();
312
313     SDL_Delay(0);
314   }
315 }
316
317 void 
318 MainLoop::take_screenshot()
319 {
320   screenshot_requested = true;
321 }
322