6b9a79766e97280f0725dfb7ee1b9a8c211932c9
[supertux.git] / src / game_session.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 <fstream>
22 #include <sstream>
23 #include <cassert>
24 #include <cstdio>
25 #include <cstdlib>
26 #include <cmath>
27 #include <cstring>
28 #include <cerrno>
29 #include <unistd.h>
30 #include <ctime>
31 #include <stdexcept>
32
33 #include <SDL.h>
34
35 #include "game_session.hpp"
36 #include "log.hpp"
37 #include "worldmap.hpp"
38 #include "mainloop.hpp"
39 #include "video/screen.hpp"
40 #include "audio/sound_manager.hpp"
41 #include "gui/menu.hpp"
42 #include "sector.hpp"
43 #include "level.hpp"
44 #include "tile.hpp"
45 #include "player_status.hpp"
46 #include "object/particlesystem.hpp"
47 #include "object/background.hpp"
48 #include "object/gradient.hpp"
49 #include "object/tilemap.hpp"
50 #include "object/camera.hpp"
51 #include "object/player.hpp"
52 #include "object/level_time.hpp"
53 #include "lisp/lisp.hpp"
54 #include "lisp/parser.hpp"
55 #include "resources.hpp"
56 #include "misc.hpp"
57 #include "statistics.hpp"
58 #include "timer.hpp"
59 #include "object/fireworks.hpp"
60 #include "textscroller.hpp"
61 #include "control/codecontroller.hpp"
62 #include "control/joystickkeyboardcontroller.hpp"
63 #include "main.hpp"
64 #include "file_system.hpp"
65 #include "gameconfig.hpp"
66 #include "gettext.hpp"
67 #include "console.hpp"
68 #include "flip_level_transformer.hpp"
69
70 // the engine will be run with a logical framerate of 64fps.
71 // We chose 64fps here because it is a power of 2, so 1/64 gives an "even"
72 // binary fraction...
73 static const float LOGICAL_FPS = 64.0;
74
75 namespace {
76   const char* consoleCommands[] = {
77           "foo",
78           "whereami",
79           "camera",
80           "grease",
81           "invincible",
82           "mortal",
83           "shrink", 
84           "kill",
85           "gotoend",
86           "flip",
87           "finish",
88           "restart",
89           "quit"
90   };
91 }
92
93 using namespace WorldMapNS;
94
95 GameSession* GameSession::current_ = 0;
96
97 GameSession::GameSession(const std::string& levelfile_, GameSessionMode mode,
98     Statistics* statistics)
99   : level(0), currentsector(0), mode(mode),
100     end_sequence(NO_ENDSEQUENCE), end_sequence_controller(0),
101     levelfile(levelfile_), best_level_statistics(statistics),
102     capture_demo_stream(0), playback_demo_stream(0), demo_controller(0)
103 {
104   current_ = this;
105   currentsector = 0;
106   
107   game_pause = false;
108   fps_fps = 0;
109
110   for (uint16_t i=0; i < sizeof(::consoleCommands)/sizeof(typeof(consoleCommands[0])); i++) {
111     Console::instance->registerCommand(consoleCommands[i], this);
112   }
113
114   statistics_backdrop.reset(new Surface("images/engine/menu/score-backdrop.png"));
115
116   restart_level(true);
117 }
118
119 void
120 GameSession::restart_level(bool fromBeginning)
121 {
122   game_pause   = false;
123   end_sequence = NO_ENDSEQUENCE;
124
125   main_controller->reset();
126
127   currentsector = 0;
128
129   level.reset(new Level);
130   level->load(levelfile);
131
132   global_stats.reset();
133   global_stats.set_total_points(COINS_COLLECTED_STAT, level->get_total_coins());
134   global_stats.set_total_points(BADGUYS_KILLED_STAT, level->get_total_badguys());
135   
136   // get time
137   int time = 0;
138   for(std::vector<Sector*>::iterator i = level->sectors.begin(); i != level->sectors.end(); ++i)
139   {
140     Sector* sec = *i;
141
142     for(std::vector<GameObject*>::iterator j = sec->gameobjects.begin();
143         j != sec->gameobjects.end(); ++j)
144     {
145       GameObject* obj = *j;
146       
147       LevelTime* lt = dynamic_cast<LevelTime*> (obj);
148       if(lt)
149         time += int(lt->get_level_time());
150     }
151   }
152   global_stats.set_total_points(TIME_NEEDED_STAT, (time == 0) ? -1 : time);
153
154   if (fromBeginning) reset_sector="";
155   if(reset_sector != "") {
156     currentsector = level->get_sector(reset_sector);
157     if(!currentsector) {
158       std::stringstream msg;
159       msg << "Couldn't find sector '" << reset_sector << "' for resetting tux.";
160       throw std::runtime_error(msg.str());
161     }
162     currentsector->activate(reset_pos);
163   } else {
164     currentsector = level->get_sector("main");
165     if(!currentsector)
166       throw std::runtime_error("Couldn't find main sector");
167     currentsector->activate("main");
168   }
169   
170   if(mode == ST_GL_PLAY || mode == ST_GL_LOAD_LEVEL_FILE)
171     levelintro();
172
173   currentsector->play_music(LEVEL_MUSIC);
174
175   if(capture_file != "")
176     record_demo(capture_file);
177 }
178
179 GameSession::~GameSession()
180 {
181   delete capture_demo_stream;
182   delete playback_demo_stream;
183   delete demo_controller;
184
185   delete end_sequence_controller;
186
187   current_ = NULL;
188 }
189
190 void
191 GameSession::record_demo(const std::string& filename)
192 {
193   delete capture_demo_stream;
194   
195   capture_demo_stream = new std::ofstream(filename.c_str()); 
196   if(!capture_demo_stream->good()) {
197     std::stringstream msg;
198     msg << "Couldn't open demo file '" << filename << "' for writing.";
199     throw std::runtime_error(msg.str());
200   }
201   capture_file = filename;
202 }
203
204 void
205 GameSession::play_demo(const std::string& filename)
206 {
207   delete playback_demo_stream;
208   delete demo_controller;
209   
210   playback_demo_stream = new std::ifstream(filename.c_str());
211   if(!playback_demo_stream->good()) {
212     std::stringstream msg;
213     msg << "Couldn't open demo file '" << filename << "' for reading.";
214     throw std::runtime_error(msg.str());
215   }
216
217   Player& tux = *currentsector->player;
218   demo_controller = new CodeController();
219   tux.set_controller(demo_controller);
220 }
221
222 void
223 GameSession::levelintro()
224 {
225   char str[60];
226
227   sound_manager->stop_music();
228
229   DrawingContext context;
230   for(Sector::GameObjects::iterator i = currentsector->gameobjects.begin();
231       i != currentsector->gameobjects.end(); ++i) {
232     Background* background = dynamic_cast<Background*> (*i);
233     if(background) {
234       background->draw(context);
235     }
236     Gradient* gradient = dynamic_cast<Gradient*> (*i);
237     if(gradient) {
238       gradient->draw(context);
239     }
240   }
241
242 //  context.draw_text(gold_text, level->get_name(), Vector(SCREEN_WIDTH/2, 160),
243 //      CENTER_ALLIGN, LAYER_FOREGROUND1);
244   context.draw_center_text(gold_text, level->get_name(), Vector(0, 160),
245       LAYER_FOREGROUND1);
246
247   sprintf(str, "Coins: %d", player_status->coins);
248   context.draw_text(white_text, str, Vector(SCREEN_WIDTH/2, 210),
249       CENTER_ALLIGN, LAYER_FOREGROUND1);
250
251   if((level->get_author().size()) && (level->get_author() != "SuperTux Team"))
252     //TODO make author check case/blank-insensitive
253     context.draw_text(white_small_text,
254       std::string(_("contributed by ")) + level->get_author(), 
255       Vector(SCREEN_WIDTH/2, 350), CENTER_ALLIGN, LAYER_FOREGROUND1);
256
257
258   if(best_level_statistics != NULL)
259     best_level_statistics->draw_message_info(context, _("Best Level Statistics"));
260
261   wait_for_event(1.0, 3.0);
262 }
263
264 void
265 GameSession::on_escape_press()
266 {
267   if(currentsector->player->is_dying() || end_sequence != NO_ENDSEQUENCE)
268     return;   // don't let the player open the menu, when he is dying
269   
270   if(mode == ST_GL_TEST) {
271     main_loop->exit_screen();
272   } else if (!Menu::current()) {
273     Menu::set_current(game_menu);
274     game_menu->set_active_item(MNID_CONTINUE);
275     game_pause = true;
276   } else {
277     game_pause = false;
278   }
279 }
280
281 void
282 GameSession::process_events()
283 {
284   Player& tux = *currentsector->player;
285
286   // end of pause mode?
287   if(!Menu::current() && game_pause) {
288     game_pause = false;
289   }
290
291   if (end_sequence != NO_ENDSEQUENCE) {
292     if(end_sequence_controller == 0) {
293       end_sequence_controller = new CodeController();
294       tux.set_controller(end_sequence_controller);
295     }
296
297     end_sequence_controller->press(Controller::RIGHT);
298     
299     if (int(last_x_pos) == int(tux.get_pos().x))
300       end_sequence_controller->press(Controller::JUMP);    
301     last_x_pos = tux.get_pos().x;
302   }
303
304   // playback a demo?
305   if(playback_demo_stream != 0) {
306     demo_controller->update();
307     char left = false;
308     char right = false;
309     char up = false;
310     char down = false;
311     char jump = false;
312     char action = false;
313     playback_demo_stream->get(left);
314     playback_demo_stream->get(right);
315     playback_demo_stream->get(up);
316     playback_demo_stream->get(down);
317     playback_demo_stream->get(jump);
318     playback_demo_stream->get(action);
319     demo_controller->press(Controller::LEFT, left);
320     demo_controller->press(Controller::RIGHT, right);
321     demo_controller->press(Controller::UP, up);
322     demo_controller->press(Controller::DOWN, down);
323     demo_controller->press(Controller::JUMP, jump);
324     demo_controller->press(Controller::ACTION, action);
325   }
326
327   // save input for demo?
328   if(capture_demo_stream != 0) {
329     capture_demo_stream ->put(main_controller->hold(Controller::LEFT));
330     capture_demo_stream ->put(main_controller->hold(Controller::RIGHT));
331     capture_demo_stream ->put(main_controller->hold(Controller::UP));
332     capture_demo_stream ->put(main_controller->hold(Controller::DOWN));
333     capture_demo_stream ->put(main_controller->hold(Controller::JUMP));   
334     capture_demo_stream ->put(main_controller->hold(Controller::ACTION));
335   }
336 }
337
338 bool
339 GameSession::consoleCommand(std::string command, std::vector<std::string>)
340 {
341   if (command == "foo") {
342     log_info << "bar" << std::endl;
343     return true;
344   }
345
346   if (currentsector == 0) return false;
347   Player& tux = *currentsector->player;
348   
349   // Cheating words (the goal of this is really for debugging,
350   // but could be used for some cheating, nothing wrong with that)
351   if (command == "grease") {
352     tux.physic.set_velocity_x(tux.physic.get_velocity_x()*3);
353     return true;
354   }
355   if (command == "invincible") {
356     // be invincle for the rest of the level
357     tux.invincible_timer.start(10000);
358     return true;
359   }
360   if (command == "mortal") {
361     // give up invincibility
362     tux.invincible_timer.stop();
363     return true;
364   }
365   if (command == "shrink") {
366     // remove powerups
367     tux.kill(tux.SHRINK);
368     return true;
369   }
370   if (command == "kill") {
371     tux.kill(tux.KILL);
372     return true;
373   }
374   if (command == "restart") {
375     restart_level(true);
376     return true;
377   }
378   if (command == "whereami") {
379     log_info << "You are at x " << tux.get_pos().x << ", y " << tux.get_pos().y << std::endl;
380     return true;
381   }
382   if (command == "gotoend") {
383     // goes to the end of the level
384     tux.move(Vector(
385           (currentsector->solids->get_width()*32) - (SCREEN_WIDTH*2), 0));
386     currentsector->camera->reset(
387         Vector(tux.get_pos().x, tux.get_pos().y));
388     return true;
389   }
390   if (command == "flip") {
391     FlipLevelTransformer flip_transformer;
392     flip_transformer.transform(GameSession::current()->get_current_level());
393     return true;
394   }
395   if (command == "finish") {
396     finish(true);
397     return true;
398   }
399   if (command == "camera") {
400     log_info << "Camera is at " << Sector::current()->camera->get_translation().x << "," << Sector::current()->camera->get_translation().y << std::endl;
401     return true;
402   }
403   if (command == "quit") {
404     main_loop->quit();
405     return true;
406   }
407
408   return false;
409 }
410
411 void
412 GameSession::check_end_conditions()
413 {
414   Player* tux = currentsector->player;
415
416   /* End of level? */
417   if(end_sequence && endsequence_timer.check()) {
418     finish(true);
419     return;
420   } else if (!end_sequence && tux->is_dead()) {
421     if (player_status->coins < 0) { 
422       // No more coins: restart level from beginning
423       player_status->coins = 0;
424       restart_level(true);
425     } else { 
426       // Still has coins: restart level from last reset point
427       restart_level(false);
428     }
429     
430     return;
431   }
432 }
433
434 void 
435 GameSession::draw(DrawingContext& context)
436 {
437   currentsector->draw(context);
438   drawstatus(context);
439
440   if(game_pause)
441     draw_pause(context);
442 }
443
444 void
445 GameSession::draw_pause(DrawingContext& context)
446 {
447   context.draw_filled_rect(
448       Vector(0,0), Vector(SCREEN_WIDTH, SCREEN_HEIGHT),
449       Color(.2, .2, .2, .5), LAYER_FOREGROUND1);
450 }
451   
452 void
453 GameSession::process_menu()
454 {
455   Menu* menu = Menu::current();
456   if(menu) {
457     menu->update();
458
459     if(menu == game_menu) {
460       switch (game_menu->check()) {
461         case MNID_CONTINUE:
462           Menu::set_current(0);
463           break;
464         case MNID_ABORTLEVEL:
465           Menu::set_current(0);
466           main_loop->exit_screen();
467           break;
468       }
469     }
470   }
471 }
472
473 void
474 GameSession::setup()
475 {
476   Menu::set_current(NULL);
477   current_ = this;
478
479   // Eat unneeded events
480   SDL_Event event;
481   while(SDL_PollEvent(&event))
482   {}
483 }
484
485 void
486 GameSession::update(float elapsed_time)
487 {
488   process_events();
489   process_menu();
490
491   check_end_conditions();
492
493   // handle controller
494   if(main_controller->pressed(Controller::PAUSE_MENU))
495     on_escape_press();
496   
497   // respawning in new sector?
498   if(newsector != "" && newspawnpoint != "") {
499     Sector* sector = level->get_sector(newsector);
500     if(sector == 0) {
501       log_warning << "Sector '" << newsector << "' not found" << std::endl;
502     }
503     sector->activate(newspawnpoint);
504     sector->play_music(LEVEL_MUSIC);
505     currentsector = sector;
506     newsector = "";
507     newspawnpoint = "";
508   }
509
510   // Update the world state and all objects in the world
511   if(!game_pause) {
512     // Update the world
513     if (end_sequence == ENDSEQUENCE_RUNNING) {
514       currentsector->update(elapsed_time/2);
515     } else if(end_sequence == NO_ENDSEQUENCE) {
516       if(!currentsector->player->growing_timer.started())
517         currentsector->update(elapsed_time);
518     } 
519   }
520
521   // update sounds
522   sound_manager->set_listener_position(currentsector->player->get_pos());
523
524   /* Handle music: */
525   if (end_sequence)
526     return;
527   
528   if(currentsector->player->invincible_timer.started()) {
529     if(currentsector->player->invincible_timer.get_timeleft() <=
530        TUX_INVINCIBLE_TIME_WARNING) {
531       currentsector->play_music(HERRING_WARNING_MUSIC);
532     } else {
533       currentsector->play_music(HERRING_MUSIC);
534     }
535   } else if(currentsector->get_music_type() != LEVEL_MUSIC) {
536     currentsector->play_music(LEVEL_MUSIC);
537   }
538 }
539
540 #if 0
541 GameSession::ExitStatus
542 GameSession::run()
543 {
544   Menu::set_current(0);
545   current_ = this;
546   
547   int fps_cnt = 0;
548
549   // Eat unneeded events
550   SDL_Event event;
551   while(SDL_PollEvent(&event))
552   {}
553
554   draw();
555
556   Uint32 fps_ticks = SDL_GetTicks();
557   Uint32 fps_nextframe_ticks = SDL_GetTicks();
558   Uint32 ticks;
559   bool skipdraw = false;
560
561   while (exit_status == ES_NONE) {
562     // we run in a logical framerate so elapsed time is a constant
563     // This will make the game run determistic and not different on different
564     // machines
565     static const float elapsed_time = 1.0 / LOGICAL_FPS;
566     // old code... float elapsed_time = float(ticks - lastticks) / 1000.;
567     if(!game_pause)
568       game_time += elapsed_time;
569
570     // regulate fps
571     ticks = SDL_GetTicks();
572     if(ticks > fps_nextframe_ticks) {
573       if(skipdraw == true) {
574         // already skipped last frame? we have to slow down the game then...
575         skipdraw = false;
576         fps_nextframe_ticks -= (Uint32) (1000.0 / LOGICAL_FPS);
577       } else {
578         // don't draw all frames when we're getting too slow
579         skipdraw = true;
580       }
581     } else {
582       skipdraw = false;
583       while(fps_nextframe_ticks > ticks) {
584         /* just wait */
585         // If we really have to wait long, then do an imprecise SDL_Delay()
586         Uint32 diff = fps_nextframe_ticks - ticks;
587         if(diff > 15) {
588           SDL_Delay(diff - 10);
589         } 
590         ticks = SDL_GetTicks();
591       }
592     }
593     fps_nextframe_ticks = ticks + (Uint32) (1000.0 / LOGICAL_FPS);
594
595     process_events();
596     process_menu();
597
598     // Update the world state and all objects in the world
599     if(!game_pause)
600     {
601       // Update the world
602       check_end_conditions();
603       if (end_sequence == ENDSEQUENCE_RUNNING)
604         update(elapsed_time/2);
605       else if(end_sequence == NO_ENDSEQUENCE)
606         update(elapsed_time);
607     }
608
609     if(!skipdraw)
610       draw();
611
612     // update sounds
613     sound_manager->update();
614
615     /* Time stops in pause mode */
616     if(game_pause || Menu::current())
617     {
618       continue;
619     }
620
621     /* Handle music: */
622     if (currentsector->player->invincible_timer.started() && 
623             currentsector->player->invincible_timer.get_timeleft() 
624             > TUX_INVINCIBLE_TIME_WARNING && !end_sequence)
625     {
626       currentsector->play_music(HERRING_MUSIC);
627     }
628     /* or just normal music? */
629     else if(currentsector->get_music_type() != LEVEL_MUSIC && !end_sequence)
630     {
631       currentsector->play_music(LEVEL_MUSIC);
632     }
633
634     /* Calculate frames per second */
635     if(config->show_fps)
636     {
637       ++fps_cnt;
638       
639       if(SDL_GetTicks() - fps_ticks >= 500)
640       {
641         fps_fps = (float) fps_cnt / .5;
642         fps_cnt = 0;
643         fps_ticks = SDL_GetTicks();
644       }
645     }
646   }
647  
648   // just in case
649   currentsector = 0;
650   main_controller->reset();
651   return exit_status;
652 }
653 #endif
654
655 void
656 GameSession::finish(bool win)
657 {
658   if(win) {
659     if(WorldMap::current())
660       WorldMap::current()->finished_level(levelfile);
661   }
662   
663   main_loop->exit_screen();
664 }
665
666 void
667 GameSession::respawn(const std::string& sector, const std::string& spawnpoint)
668 {
669   newsector = sector;
670   newspawnpoint = spawnpoint;
671 }
672
673 void
674 GameSession::set_reset_point(const std::string& sector, const Vector& pos)
675 {
676   reset_sector = sector;
677   reset_pos = pos;
678 }
679
680 std::string
681 GameSession::get_working_directory()
682 {
683   return FileSystem::dirname(levelfile);
684 }
685
686 void
687 GameSession::display_info_box(const std::string& text)
688 {
689   InfoBox* box = new InfoBox(text);
690
691   bool running = true;
692   DrawingContext context;
693   
694   while(running)  {
695
696     // TODO make a screen out of this, another mainloop is ugly
697     main_controller->update();
698     SDL_Event event;
699     while (SDL_PollEvent(&event)) {
700       main_controller->process_event(event);
701       if(event.type == SDL_QUIT)
702         main_loop->quit();
703     }
704
705     if(main_controller->pressed(Controller::JUMP)
706         || main_controller->pressed(Controller::ACTION)
707         || main_controller->pressed(Controller::PAUSE_MENU)
708         || main_controller->pressed(Controller::MENU_SELECT))
709       running = false;
710     else if(main_controller->pressed(Controller::DOWN))
711       box->scrolldown();
712     else if(main_controller->pressed(Controller::UP))
713       box->scrollup();
714     box->draw(context);
715     draw(context);
716     context.do_drawing();
717     sound_manager->update();
718   }
719
720   delete box;
721 }
722
723 void
724 GameSession::start_sequence(const std::string& sequencename)
725 {
726   if(sequencename == "endsequence" || sequencename == "fireworks") {
727     if(end_sequence)
728       return;
729
730     end_sequence = ENDSEQUENCE_RUNNING;
731     endsequence_timer.start(7.3);
732     last_x_pos = -1;
733     sound_manager->play_music("music/leveldone.ogg", false);
734     currentsector->player->invincible_timer.start(7.3);
735
736     // Stop all clocks.
737     for(std::vector<GameObject*>::iterator i = currentsector->gameobjects.begin();
738         i != currentsector->gameobjects.end(); ++i)
739     {
740       GameObject* obj = *i;
741
742       LevelTime* lt = dynamic_cast<LevelTime*> (obj);
743       if(lt)
744         lt->stop();
745     }
746
747     // add time spent to statistics
748     int tottime = 0, remtime = 0;
749     for(std::vector<Sector*>::iterator i = level->sectors.begin(); i != level->sectors.end(); ++i)
750     {
751       Sector* sec = *i;
752
753       for(std::vector<GameObject*>::iterator j = sec->gameobjects.begin();
754           j != sec->gameobjects.end(); ++j)
755       {
756         GameObject* obj = *j;
757
758         LevelTime* lt = dynamic_cast<LevelTime*> (obj);
759         if(lt)
760         {
761           tottime += int(lt->get_level_time());
762           remtime += int(lt->get_remaining_time());
763         }
764       }
765     }
766     global_stats.set_points(TIME_NEEDED_STAT, (tottime == 0 ? -1 : (tottime-remtime)));
767
768     if(sequencename == "fireworks") {
769       currentsector->add_object(new Fireworks());
770     }
771   } else if(sequencename == "stoptux") {
772     if(!end_sequence) {
773       log_warning << "Final target reached without an active end sequence" << std::endl;
774       this->start_sequence("endsequence");
775     }
776     end_sequence =  ENDSEQUENCE_WAITING;
777   } else {
778     log_warning << "Unknown sequence '" << sequencename << "'" << std::endl;
779   }
780 }
781
782 /* (Status): */
783 void
784 GameSession::drawstatus(DrawingContext& context)
785 {
786   player_status->draw(context);
787
788   if(config->show_fps) {
789     char str[60];
790     snprintf(str, sizeof(str), "%3.1f", fps_fps);
791     const char* fpstext = "FPS";
792     context.draw_text(white_text, fpstext, Vector(SCREEN_WIDTH - white_text->get_text_width(fpstext) - gold_text->get_text_width(" 99999") - BORDER_X, BORDER_Y + 20), LEFT_ALLIGN, LAYER_FOREGROUND1);
793     context.draw_text(gold_text, str, Vector(SCREEN_WIDTH - BORDER_X, BORDER_Y + 20), RIGHT_ALLIGN, LAYER_FOREGROUND1);
794   }
795
796   // draw level stats while end_sequence is running
797   if (end_sequence) {
798     global_stats.draw_endseq_panel(context, best_level_statistics, statistics_backdrop.get());
799   }
800 }
801