1 //
2 //  SuperTuxKart - a fun racing game with go-kart
3 //  Copyright (C) 2010-2015 Joerg Henrichs
4 //
5 //  This program is free software; you can redistribute it and/or
6 //  modify it under the terms of the GNU General Public License
7 //  as published by the Free Software Foundation; either version 3
8 //  of the License, or (at your option) any later version.
9 //
10 //  This program is distributed in the hope that it will be useful,
11 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //  GNU General Public License for more details.
14 //
15 //  You should have received a copy of the GNU General Public License
16 //  along with this program; if not, write to the Free Software
17 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 
19 #include "states_screens/race_result_gui.hpp"
20 
21 #include "audio/music_manager.hpp"
22 #include "audio/sfx_manager.hpp"
23 #include "audio/sfx_base.hpp"
24 #include "challenges/story_mode_timer.hpp"
25 #include "challenges/unlock_manager.hpp"
26 #include "config/player_manager.hpp"
27 #include "config/user_config.hpp"
28 #include "graphics/2dutils.hpp"
29 #include "graphics/material.hpp"
30 #include "guiengine/engine.hpp"
31 #include "guiengine/message_queue.hpp"
32 #include "guiengine/modaldialog.hpp"
33 #include "guiengine/scalable_font.hpp"
34 #include "guiengine/screen_keyboard.hpp"
35 #include "guiengine/widget.hpp"
36 #include "guiengine/widgets/icon_button_widget.hpp"
37 #include "guiengine/widgets/label_widget.hpp"
38 #include "guiengine/widgets/ribbon_widget.hpp"
39 #include "io/file_manager.hpp"
40 #include "karts/abstract_kart.hpp"
41 #include "karts/controller/controller.hpp"
42 #include "karts/controller/end_controller.hpp"
43 #include "karts/controller/local_player_controller.hpp"
44 #include "karts/kart_properties.hpp"
45 #include "karts/kart_properties_manager.hpp"
46 #include "modes/cutscene_world.hpp"
47 #include "modes/demo_world.hpp"
48 #include "modes/capture_the_flag.hpp"
49 #include "modes/overworld.hpp"
50 #include "modes/soccer_world.hpp"
51 #include "network/network_config.hpp"
52 #include "network/stk_host.hpp"
53 #include "network/protocols/client_lobby.hpp"
54 #include "race/highscores.hpp"
55 #include "replay/replay_play.hpp"
56 #include "replay/replay_recorder.hpp"
57 #include "scriptengine/property_animator.hpp"
58 #include "states_screens/cutscene_general.hpp"
59 #include "states_screens/feature_unlocked.hpp"
60 #include "states_screens/main_menu_screen.hpp"
61 #include "states_screens/online/networking_lobby.hpp"
62 #include "states_screens/race_setup_screen.hpp"
63 #include "tips/tips_manager.hpp"
64 #include "tracks/track.hpp"
65 #include "tracks/track_manager.hpp"
66 #include "utils/string_utils.hpp"
67 #include "utils/translation.hpp"
68 
69 #include <algorithm>
70 
71 /** Constructor, initialises internal data structures.
72  */
RaceResultGUI()73 RaceResultGUI::RaceResultGUI() : Screen("race_result.stkgui",
74     /*pause race*/ false)
75 {
76 }   // RaceResultGUI
77 
78 //-----------------------------------------------------------------------------
79 /** Besides calling init in the base class this makes all buttons of this
80  *  screen invisible. The buttons will be displayed only once the animation is
81  *  over.
82  */
init()83 void RaceResultGUI::init()
84 {
85     Screen::init();
86     determineTableLayout();
87     m_animation_state = RR_INIT;
88 
89     m_timer = 0;
90 
91     getWidget("left")->setVisible(false);
92     getWidget("middle")->setVisible(false);
93     getWidget("right")->setVisible(false);
94 
95     music_manager->stopMusic();
96 
97     bool human_win = true;
98     unsigned int num_karts = RaceManager::get()->getNumberOfKarts();
99     for (unsigned int kart_id = 0; kart_id < num_karts; kart_id++)
100     {
101         const AbstractKart *kart = World::getWorld()->getKart(kart_id);
102         if (kart->getController()->isLocalPlayerController())
103             human_win = human_win && kart->getRaceResult();
104     }
105 
106     m_finish_sound = SFXManager::get()->quickSound(
107         human_win ? "race_finish_victory" : "race_finish");
108 
109     //std::string path = (human_win ? Different result music too later
110     //    file_manager->getAsset(FileManager::MUSIC, "race_summary.music") :
111     //    file_manager->getAsset(FileManager::MUSIC, "race_summary.music"));
112     std::string path = file_manager->getAsset(FileManager::MUSIC, "race_summary.music");
113     m_race_over_music = music_manager->getMusicInformation(path);
114 
115     if (!m_finish_sound)
116     {
117         // If there is no finish sound (because sfx are disabled), start
118         // the race over music here (since the race over music is only started
119         // when the finish sound has been played).
120         music_manager->startMusic(m_race_over_music);
121     }
122 
123     // Calculate how many track screenshots can fit into the "result-table" widget
124     GUIEngine::Widget* result_table = getWidget("result-table");
125     assert(result_table != NULL);
126     m_sshot_height = (int)(UserConfigParams::m_height*0.1275);
127     m_max_tracks = std::max(1, ((result_table->m_h - getFontHeight() * 5) /
128         (m_sshot_height + SSHOT_SEPARATION))); //Show at least one
129 
130     // Calculate screenshot scrolling parameters
131     const std::vector<std::string> tracks =
132         RaceManager::get()->getGrandPrix().getTrackNames();
133     int n_tracks = (int)tracks.size();
134     int currentTrack = RaceManager::get()->getTrackNumber();
135     m_start_track = currentTrack;
136     if (n_tracks > m_max_tracks)
137     {
138         m_start_track = std::min(currentTrack, n_tracks - m_max_tracks);
139         m_end_track = std::min(currentTrack + m_max_tracks, n_tracks);
140     }
141     else
142     {
143         m_start_track = 0;
144         m_end_track = (int)tracks.size();
145     }
146 
147 #ifndef SERVER_ONLY
148     if (!human_win && !NetworkConfig::get()->isNetworking())
149     {
150         std::string tipset = "race";
151         if (RaceManager::get()->isSoccerMode())
152             tipset = "soccer";
153         core::stringw tip = TipsManager::get()->getTip(tipset);
154         core::stringw tips_string = _("Tip: %s", tip);
155         MessageQueue::add(MessageQueue::MT_GENERIC, tips_string);
156     }
157 #endif
158 }   // init
159 
160 //-----------------------------------------------------------------------------
tearDown()161 void RaceResultGUI::tearDown()
162 {
163     Screen::tearDown();
164     //m_font->setMonospaceDigits(m_was_monospace);
165 
166     if (m_finish_sound != NULL &&
167         m_finish_sound->getStatus() == SFXBase::SFX_PLAYING)
168     {
169         m_finish_sound->stop();
170     }
171 }   // tearDown
172 
173 //-----------------------------------------------------------------------------
174 /** Makes the correct buttons visible again, and gives them the right label.
175  *  1) If something was unlocked, only a 'next' button is displayed.
176  */
enableAllButtons()177 void RaceResultGUI::enableAllButtons()
178 {
179     GUIEngine::IconButtonWidget *left = getWidget<GUIEngine::IconButtonWidget>("left");
180     GUIEngine::IconButtonWidget *middle = getWidget<GUIEngine::IconButtonWidget>("middle");
181     GUIEngine::IconButtonWidget *right = getWidget<GUIEngine::IconButtonWidget>("right");
182     GUIEngine::RibbonWidget *operations = getWidget<GUIEngine::RibbonWidget>("operations");
183     operations->setFocusForPlayer(PLAYER_ID_GAME_MASTER);
184 
185     if (RaceManager::get()->getMajorMode() == RaceManager::MAJOR_MODE_GRAND_PRIX)
186     {
187         enableGPProgress();
188     }
189 
190     // If we're in a network world, change the buttons text
191     if (World::getWorld()->isNetworkWorld())
192     {
193         left->setLabel(_("Continue"));
194         left->setImage("gui/icons/green_check.png");
195         left->setVisible(true);
196         operations->select("left", PLAYER_ID_GAME_MASTER);
197         middle->setVisible(false);
198         right->setLabel(_("Quit the server"));
199         right->setImage("gui/icons/main_quit.png");
200         right->setVisible(true);
201         return;
202     }
203 
204     // If something was unlocked
205     // -------------------------
206     int n = (int)PlayerManager::getCurrentPlayer()
207         ->getRecentlyCompletedChallenges().size();
208     if (n > 0 &&
209          (RaceManager::get()->getMajorMode() != RaceManager::MAJOR_MODE_GRAND_PRIX ||
210           RaceManager::get()->getTrackNumber() + 1 == RaceManager::get()->getNumOfTracks() ) )
211     {
212         middle->setLabel(n == 1 ? _("You completed a challenge!")
213             : _("You completed challenges!"));
214         middle->setImage("gui/icons/cup_gold.png");
215         middle->setVisible(true);
216         operations->select("middle", PLAYER_ID_GAME_MASTER);
217     }
218     else if (RaceManager::get()->getMajorMode() == RaceManager::MAJOR_MODE_GRAND_PRIX)
219     {
220         // In case of a GP:
221         // ----------------
222         middle->setLabel(_("Continue"));
223         middle->setImage("gui/icons/green_check.png");
224         middle->setVisible(false);
225         middle->setFocusable(false);
226         right->setVisible(false);
227         right->setFocusable(false);
228 
229         // Two continue buttons to make sure the buttons in the bar is balanced
230         left->setLabel(_("Continue"));
231         left->setImage("gui/icons/green_check.png");
232         left->setVisible(true);
233 
234         if (RaceManager::get()->getTrackNumber() + 1 < RaceManager::get()->getNumOfTracks())
235         {
236             right->setLabel(_("Abort Grand Prix"));
237             right->setImage("gui/icons/race_giveup.png");
238             right->setVisible(true);
239             right->setFocusable(true);
240             operations->select("left", PLAYER_ID_GAME_MASTER);
241         }
242         else
243         {
244             left->setVisible(false);
245             left->setFocusable(false);
246             middle->setVisible(true);
247             operations->select("middle", PLAYER_ID_GAME_MASTER);
248         }
249 
250     }
251     else
252     {
253         // Normal race
254         // -----------
255 
256         left->setLabel(_("Restart"));
257         left->setImage("gui/icons/restart.png");
258         left->setVisible(true);
259         operations->select("left", PLAYER_ID_GAME_MASTER);
260         if (RaceManager::get()->raceWasStartedFromOverworld())
261         {
262             middle->setVisible(false);
263             right->setLabel(_("Back to challenge selection"));
264             right->setImage("gui/icons/back.png");
265         }
266         else
267         {
268             middle->setImage("gui/icons/main_race.png");
269             if (RaceManager::get()->isRecordingRace())
270             {
271                 middle->setLabel(_("Race against the new ghost replay"));
272                 middle->setVisible(!World::getWorld()->hasRaceEndedEarly());
273             }
274             else
275             {
276                 middle->setLabel(_("Setup New Race"));
277                 middle->setVisible(true);
278             }
279             right->setLabel(_("Back to the menu"));
280             right->setImage("gui/icons/back.png");
281         }
282         right->setVisible(true);
283     }
284 }   // enableAllButtons
285 
286 //-----------------------------------------------------------------------------
eventCallback(GUIEngine::Widget * widget,const std::string & name,const int playerID)287 void RaceResultGUI::eventCallback(GUIEngine::Widget* widget,
288     const std::string& name, const int playerID)
289 {
290     int n_tracks = RaceManager::get()->getGrandPrix().getNumberOfTracks();
291     if (name == "up_button" && n_tracks > m_max_tracks && m_start_track > 0)
292     {
293         m_start_track--;
294         m_end_track--;
295         displayScreenShots();
296     }
297     else if (name == "down_button" && n_tracks > m_max_tracks &&
298         m_start_track < (n_tracks - m_max_tracks))
299     {
300         m_start_track++;
301         m_end_track++;
302         displayScreenShots();
303     }
304 
305     if(name == "operations")
306     {
307         const std::string& action =
308             getWidget<GUIEngine::RibbonWidget>("operations")->getSelectionIDString(PLAYER_ID_GAME_MASTER);
309         // If we're playing online :
310         if (World::getWorld()->isNetworkWorld())
311         {
312             if (action == "left") // Continue button (return to server lobby)
313             {
314                 // Signal to the server that this client is back in the lobby now.
315                 auto cl = LobbyProtocol::get<ClientLobby>();
316                 if (cl)
317                     cl->doneWithResults();
318                 getWidget<GUIEngine::IconButtonWidget>("left")->setLabel(_("Waiting for others"));
319             }
320             if (action == "right") // Quit server (return to online lan / wan menu)
321             {
322                 RaceManager::get()->clearNetworkGrandPrixResult();
323                 if (STKHost::existHost())
324                 {
325                     STKHost::get()->shutdown();
326                 }
327                 RaceManager::get()->exitRace();
328                 RaceManager::get()->setAIKartOverride("");
329                 StateManager::get()->resetAndSetStack(
330                     NetworkConfig::get()->getResetScreens().data());
331                 NetworkConfig::get()->unsetNetworking();
332             }
333             return;
334         }
335 
336         // If something was unlocked, the 'continue' button was
337         // actually used to display "Show unlocked feature(s)" text.
338         // ---------------------------------------------------------
339         PlayerProfile *player = PlayerManager::getCurrentPlayer();
340 
341         int n = (int)player->getRecentlyCompletedChallenges().size();
342 
343         if (n > 0 &&
344              (RaceManager::get()->getMajorMode() != RaceManager::MAJOR_MODE_GRAND_PRIX ||
345               RaceManager::get()->getTrackNumber() + 1 == RaceManager::get()->getNumOfTracks() ) )
346 
347         {
348             if (action == "middle")
349             {
350                 if (RaceManager::get()->getMajorMode() == RaceManager::MAJOR_MODE_GRAND_PRIX)
351                 {
352                     cleanupGPProgress();
353                 }
354 
355                 std::vector<const ChallengeData*> unlocked = player->getRecentlyCompletedChallenges();
356 
357                 bool gameCompleted = false;
358                 for (unsigned int n = 0; n < unlocked.size(); n++)
359                 {
360                     if (unlocked[n]->getChallengeId() == "fortmagma")
361                     {
362                         gameCompleted = true;
363                         story_mode_timer->stopTimer();
364                         player->setFinished();
365                         player->setStoryModeTimer(story_mode_timer->getStoryModeTime());
366                         if (story_mode_timer->speedrunIsFinished())
367                         {
368                             player->setSpeedrunTimer(story_mode_timer->getSpeedrunTime());
369                             player->setSpeedrunFinished();
370                         }
371                         break;
372                     }
373                 }
374 
375                 if (gameCompleted)
376                 {
377                     // clear the race
378 
379                     // kart will no longer be available during cutscene, drop reference
380                     StateManager::get()->getActivePlayer(playerID)->setKart(NULL);
381                     PropertyAnimator::get()->clear();
382                     World::deleteWorld();
383 
384                     CutsceneWorld::setUseDuration(true);
385                     StateManager::get()->enterGameState();
386                     RaceManager::get()->setMinorMode(RaceManager::MINOR_MODE_CUTSCENE);
387                     RaceManager::get()->setNumKarts(0);
388                     RaceManager::get()->setNumPlayers(0);
389                     RaceManager::get()->startSingleRace("endcutscene", 999, false);
390 
391                     std::vector<std::string> parts;
392                     parts.push_back("endcutscene");
393                     ((CutsceneWorld*)World::getWorld())->setParts(parts);
394 
395                     CutSceneGeneral* scene = CutSceneGeneral::getInstance();
396                     scene->push();
397                 }
398                 else
399                 {
400                     StateManager::get()->popMenu();
401                     PropertyAnimator::get()->clear();
402                     World::deleteWorld();
403 
404                     CutsceneWorld::setUseDuration(false);
405                     StateManager::get()->enterGameState();
406                     RaceManager::get()->setMinorMode(RaceManager::MINOR_MODE_CUTSCENE);
407                     RaceManager::get()->setNumKarts(0);
408                     RaceManager::get()->setNumPlayers(0);
409                     RaceManager::get()->startSingleRace("featunlocked", 999, RaceManager::get()->raceWasStartedFromOverworld());
410 
411                     FeatureUnlockedCutScene* scene =
412                         FeatureUnlockedCutScene::getInstance();
413 
414                     scene->addTrophy(RaceManager::get()->getDifficulty(),false);
415                     scene->findWhatWasUnlocked(RaceManager::get()->getDifficulty(),unlocked);
416                     scene->push();
417                     RaceManager::get()->setAIKartOverride("");
418 
419                     std::vector<std::string> parts;
420                     parts.push_back("featunlocked");
421                     ((CutsceneWorld*)World::getWorld())->setParts(parts);
422                 }
423 
424                 PlayerManager::getCurrentPlayer()->clearUnlocked();
425 
426                 return;
427             }
428             Log::warn("RaceResultGUI", "Incorrect event '%s' when things are unlocked.",
429                 action.c_str());
430         }
431 
432         // Next check for GP
433         // -----------------
434         if (RaceManager::get()->getMajorMode() == RaceManager::MAJOR_MODE_GRAND_PRIX)
435         {
436             if (action == "left" || action == "middle")        // Next GP
437             {
438                 cleanupGPProgress();
439                 StateManager::get()->popMenu();
440                 RaceManager::get()->next();
441             }
442             else if (action == "right")        // Abort
443             {
444                 new MessageDialog(_("Do you really want to abort the Grand Prix?"),
445                     MessageDialog::MESSAGE_DIALOG_CONFIRM, this, false);
446             }
447             else if (!getWidget(action.c_str())->isVisible())
448             {
449                 Log::warn("RaceResultGUI", "Incorrect event '%s' when things are unlocked.",
450                     action.c_str());
451             }
452             return;
453         }
454 
455         StateManager::get()->popMenu();
456         if (action == "left")        // Restart
457         {
458             RaceManager::get()->rerunRace();
459         }
460         else if (action == "middle")                 // Setup new race
461         {
462             // Save current race data for race against new ghost
463             std::string track_name = RaceManager::get()->getTrackName();
464             int laps = RaceManager::get()->getNumLaps();
465             bool reverse = RaceManager::get()->getReverseTrack();
466             bool new_ghost_race = RaceManager::get()->isRecordingRace();
467 
468             RaceManager::get()->exitRace();
469             RaceManager::get()->setAIKartOverride("");
470 
471             //If pressing continue quickly in a losing challenge
472             if (RaceManager::get()->raceWasStartedFromOverworld())
473             {
474                 StateManager::get()->resetAndGoToScreen(MainMenuScreen::getInstance());
475                 OverWorld::enterOverWorld();
476             }
477             // Special case : race against a newly saved ghost
478             else if (new_ghost_race)
479             {
480                 ReplayPlay::get()->loadAllReplayFile();
481                 unsigned long long int last_uid = ReplayRecorder::get()->getLastUID();
482                 ReplayPlay::get()->setReplayFileByUID(last_uid);
483 
484                 RaceManager::get()->setRecordRace(true);
485                 RaceManager::get()->setRaceGhostKarts(true);
486 
487                 RaceManager::get()->setNumKarts(RaceManager::get()->getNumLocalPlayers());
488 
489                 // Disable accidentally unlocking of a challenge
490                 PlayerManager::getCurrentPlayer()->setCurrentChallenge("");
491 
492                 RaceManager::get()->setReverseTrack(reverse);
493                 RaceManager::get()->startSingleRace(track_name, laps, false);
494             }
495             else
496             {
497                 Screen* newStack[] = { MainMenuScreen::getInstance(),
498                                       RaceSetupScreen::getInstance(),
499                                       NULL };
500                 StateManager::get()->resetAndSetStack(newStack);
501             }
502         }
503         else if (action == "right")        // Back to main
504         {
505             RaceManager::get()->exitRace();
506             RaceManager::get()->setAIKartOverride("");
507             StateManager::get()->resetAndGoToScreen(MainMenuScreen::getInstance());
508 
509             if (RaceManager::get()->raceWasStartedFromOverworld())
510             {
511                 OverWorld::enterOverWorld();
512             }
513         }
514     }
515     else
516         Log::warn("RaceResultGUI", "Incorrect event '%s' for normal race.",
517             name.c_str());
518     return;
519 }   // eventCallback
520 
521 //-----------------------------------------------------------------------------
displayCTFResults()522 void RaceResultGUI::displayCTFResults()
523 {
524 #ifndef SERVER_ONLY
525     //Draw win text
526     core::stringw result_text;
527     video::SColor color = video::SColor(255, 255, 255, 255);
528     video::SColor red_color = video::SColor(255, 255, 0, 0);
529     gui::IGUIFont* font = GUIEngine::getTitleFont();
530     int current_x = UserConfigParams::m_width / 2;
531     RowInfo *ri = &(m_all_row_infos[0]);
532     int current_y = (int)ri->m_y_pos;
533     CaptureTheFlag* ctf = dynamic_cast<CaptureTheFlag*>(World::getWorld());
534     const int red_score = ctf->getRedScore();
535     const int blue_score = ctf->getBlueScore();
536 
537     GUIEngine::Widget *table_area = getWidget("result-table");
538     int height = table_area->m_h + table_area->m_y;
539 
540     if (red_score > blue_score)
541         result_text = _("Red Team Wins");
542     else if (blue_score > red_score)
543         result_text = _("Blue Team Wins");
544     else
545         result_text = _("It's a draw");
546 
547     core::rect<s32> pos(current_x, current_y, current_x, current_y);
548     font->draw(result_text.c_str(), pos, color, true, true);
549 
550     core::dimension2du rect = font->getDimension(result_text.c_str());
551 
552     //Draw team scores:
553     current_y += rect.Height;
554     current_x /= 2;
555     irr::video::ITexture* red_icon = irr_driver->getTexture(FileManager::GUI_ICON,
556         "red_flag.png");
557     irr::video::ITexture* blue_icon = irr_driver->getTexture(FileManager::GUI_ICON,
558         "blue_flag.png");
559 
560     core::recti source_rect(core::vector2di(0, 0), red_icon->getSize());
561     core::recti dest_rect(current_x, current_y,
562         current_x + red_icon->getSize().Width / 2,
563         current_y + red_icon->getSize().Height / 2);
564     draw2DImage(red_icon, dest_rect, source_rect,
565         NULL, NULL, true);
566     current_x += UserConfigParams::m_width / 2 - red_icon->getSize().Width / 2;
567     dest_rect = core::recti(current_x, current_y,
568         current_x + red_icon->getSize().Width / 2,
569         current_y + red_icon->getSize().Height / 2);
570     draw2DImage(blue_icon, dest_rect, source_rect,
571         NULL, NULL, true);
572 
573     result_text = StringUtils::toWString(blue_score);
574     rect = font->getDimension(result_text.c_str());
575     current_x += red_icon->getSize().Width / 4;
576     current_y += red_icon->getSize().Height / 2 + rect.Height / 4;
577     pos = core::rect<s32>(current_x, current_y, current_x, current_y);
578     font->draw(result_text.c_str(), pos, color, true, false);
579 
580     current_x -= UserConfigParams::m_width / 2 - red_icon->getSize().Width / 2;
581     result_text = StringUtils::toWString(red_score);
582     pos = core::rect<s32>(current_x, current_y, current_x, current_y);
583     font->draw(result_text.c_str(), pos, color, true, false);
584 
585     int center_x = UserConfigParams::m_width / 2;
586     pos = core::rect<s32>(center_x, current_y, center_x, current_y);
587     font->draw("-", pos, color, true, false);
588 
589     // The red team player scores:
590     current_y += rect.Height / 2 + rect.Height / 4;
591     font = GUIEngine::getSmallFont();
592     irr::video::ITexture* kart_icon;
593 
594     int prev_y = current_y;
595     const unsigned num_karts = ctf->getNumKarts();
596     for (unsigned int i = 0; i < num_karts; i++)
597     {
598         AbstractKart* kart = ctf->getKartAtPosition(i + 1);
599         unsigned kart_id = kart->getWorldKartId();
600         if (ctf->getKartTeam(kart_id) != KART_TEAM_RED)
601             continue;
602         result_text = kart->getController()->getName();
603         if (RaceManager::get()->getKartGlobalPlayerId(kart_id) > -1)
604         {
605             const core::stringw& flag = StringUtils::getCountryFlag(
606                 RaceManager::get()->getKartInfo(kart_id).getCountryCode());
607             if (!flag.empty())
608             {
609                 result_text += L" ";
610                 result_text += flag;
611             }
612         }
613         result_text.append("  ");
614         if (kart->isEliminated())
615         {
616             continue;
617         }
618         else
619         {
620             result_text.append(
621                 StringUtils::toWString(ctf->getKartScore(kart_id)));
622         }
623         rect = font->getDimension(result_text.c_str());
624         current_y += rect.Height;
625 
626         if (current_y > height) break;
627 
628         pos = core::rect<s32>(current_x, current_y, current_x, current_y);
629         font->draw(result_text, pos,
630             kart->getController()->isLocalPlayerController() ?
631             red_color : color, true, false);
632         kart_icon = kart->getKartProperties()->getIconMaterial()->getTexture();
633         source_rect = core::recti(core::vector2di(0, 0), kart_icon->getSize());
634         irr::u32 offset_x =
635             (irr::u32)(font->getDimension(result_text.c_str()).Width / 1.5f);
636         dest_rect = core::recti(current_x - offset_x - 30, current_y,
637             current_x - offset_x, current_y + 30);
638         draw2DImage(kart_icon, dest_rect, source_rect, NULL, NULL, true);
639     }
640 
641     // The blue team player scores:
642     current_y = prev_y;
643     current_x += UserConfigParams::m_width / 2 - red_icon->getSize().Width / 2;
644     for (unsigned int i = 0; i < num_karts; i++)
645     {
646         AbstractKart* kart = ctf->getKartAtPosition(i + 1);
647         unsigned kart_id = kart->getWorldKartId();
648         if (ctf->getKartTeam(kart_id) != KART_TEAM_BLUE)
649             continue;
650         result_text = kart->getController()->getName();
651         if (RaceManager::get()->getKartGlobalPlayerId(kart_id) > -1)
652         {
653             const core::stringw& flag = StringUtils::getCountryFlag(
654                 RaceManager::get()->getKartInfo(kart_id).getCountryCode());
655             if (!flag.empty())
656             {
657                 result_text += L" ";
658                 result_text += flag;
659             }
660         }
661         result_text.append("  ");
662         if (kart->isEliminated())
663         {
664             continue;
665         }
666         else
667         {
668             result_text.append(
669                 StringUtils::toWString(ctf->getKartScore(kart_id)));
670         }
671         rect = font->getDimension(result_text.c_str());
672         current_y += rect.Height;
673 
674         if (current_y > height) break;
675 
676         pos = core::rect<s32>(current_x, current_y, current_x, current_y);
677         font->draw(result_text, pos,
678             kart->getController()->isLocalPlayerController() ?
679             red_color : color, true, false);
680         kart_icon = kart->getKartProperties()->getIconMaterial()->getTexture();
681         source_rect = core::recti(core::vector2di(0, 0), kart_icon->getSize());
682         irr::u32 offset_x = (irr::u32)
683             (font->getDimension(result_text.c_str()).Width / 1.5f);
684         dest_rect = core::recti(current_x - offset_x - 30, current_y,
685             current_x - offset_x, current_y + 30);
686         draw2DImage(kart_icon, dest_rect, source_rect, NULL, NULL, true);
687     }
688 #endif
689 }
690 
691 //-----------------------------------------------------------------------------
onConfirm()692     void RaceResultGUI::onConfirm()
693     {
694         //RaceManager::get()->saveGP(); // Save the aborted GP
695         GUIEngine::ModalDialog::dismiss();
696         cleanupGPProgress();
697         StateManager::get()->popMenu();
698         RaceManager::get()->exitRace();
699         RaceManager::get()->setAIKartOverride("");
700         StateManager::get()->resetAndGoToScreen(
701             MainMenuScreen::getInstance());
702 
703         if (RaceManager::get()->raceWasStartedFromOverworld())
704         {
705             OverWorld::enterOverWorld();
706         }
707     }
708 
709     //-----------------------------------------------------------------------------
710     /** This determines the layout, i.e. the size of all columns, font size etc.
711      */
determineTableLayout()712     void RaceResultGUI::determineTableLayout()
713     {
714         GUIEngine::Widget *table_area = getWidget("result-table");
715 
716         m_font = GUIEngine::getFont();
717         assert(m_font);
718         //m_was_monospace = m_font->getMonospaceDigits();
719         //m_font->setMonospaceDigits(true);
720         WorldWithRank *rank_world = (WorldWithRank*)World::getWorld();
721 
722         unsigned int first_position = 1;
723         unsigned int sta = RaceManager::get()->getNumSpareTireKarts();
724         if (RaceManager::get()->isFollowMode())
725             first_position = 2;
726 
727         // Use only the karts that are supposed to be displayed (and
728         // ignore e.g. the leader in a FTL race).
729         unsigned int num_karts = RaceManager::get()->getNumberOfKarts() - first_position + 1 - sta;
730 
731         // Remove previous entries to avoid reserved kart in network being displayed
732         m_all_row_infos.clear();
733         // In FTL races the leader kart is not displayed
734         m_all_row_infos.resize(num_karts);
735 
736         // Determine the kart to display in the right order,
737         // and the maximum width for the kart name column
738         // -------------------------------------------------
739         m_width_kart_name = 0;
740         float max_finish_time = 0;
741 
742         FreeForAll* ffa = dynamic_cast<FreeForAll*>(World::getWorld());
743 
744         int time_precision = RaceManager::get()->currentModeTimePrecision();
745         bool active_gp = (RaceManager::get()->getMajorMode() == RaceManager::MAJOR_MODE_GRAND_PRIX);
746 
747         auto cl = LobbyProtocol::get<ClientLobby>();
748         for (unsigned int position = first_position;
749         position <= RaceManager::get()->getNumberOfKarts() - sta; position++)
750         {
751             const AbstractKart *kart = rank_world->getKartAtPosition(position);
752 
753             if (ffa && kart->isEliminated())
754                 continue;
755             // Save a pointer to the current row_info entry
756             RowInfo *ri = &(m_all_row_infos[position - first_position]);
757             ri->m_is_player_kart = kart->getController()->isLocalPlayerController();
758             ri->m_kart_name = kart->getController()->getName();
759             if (RaceManager::get()->getKartGlobalPlayerId(kart->getWorldKartId()) > -1)
760             {
761                 const core::stringw& flag = StringUtils::getCountryFlag(
762                     RaceManager::get()->getKartInfo(kart->getWorldKartId()).getCountryCode());
763                 if (!flag.empty())
764                 {
765                     ri->m_kart_name += L" ";
766                     ri->m_kart_name += flag;
767                 }
768             }
769             video::ITexture *icon =
770                 kart->getKartProperties()->getIconMaterial()->getTexture();
771             ri->m_kart_icon = icon;
772 
773             // FTL karts will get a time assigned, they are not shown as eliminated
774             if (kart->isEliminated() && !(RaceManager::get()->isFollowMode()))
775             {
776                 ri->m_finish_time_string = core::stringw(_("Eliminated"));
777             }
778             else if (   RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_FREE_FOR_ALL
779                      || RaceManager::get()->isCTFMode())
780             {
781                 assert(ffa);
782                 ri->m_finish_time_string =
783                     StringUtils::toWString(ffa->getKartScore(kart->getWorldKartId()));
784             }
785             else
786             {
787                 const float time = kart->getFinishTime();
788                 if (time > max_finish_time) max_finish_time = time;
789                 std::string time_string = StringUtils::timeToString(time, time_precision);
790                 ri->m_finish_time_string = time_string.c_str();
791             }
792             if (cl && !cl->getRankingChanges().empty())
793             {
794                 unsigned kart_id = kart->getWorldKartId();
795                 if (kart_id < cl->getRankingChanges().size())
796                 {
797                     ri->m_finish_time_string += L" ";
798                     float ranking_change = cl->getRankingChanges()[kart_id];
799                     if (ranking_change > 0)
800                     {
801                         ri->m_finish_time_string += L"+";
802                         ri->m_finish_time_string += StringUtils::toWString(ranking_change);
803                     }
804                     else
805                         ri->m_finish_time_string += StringUtils::toWString(ranking_change);
806                 }
807             }
808 
809             core::dimension2du rect =
810                 m_font->getDimension(ri->m_kart_name.c_str());
811             if (rect.Width > m_width_kart_name)
812                 m_width_kart_name = rect.Width;
813         }   // for position
814 
815         std::string max_time = StringUtils::timeToString(max_finish_time, time_precision, true, /*display hours*/ active_gp);
816         core::stringw string_max_time(max_time.c_str());
817         core::dimension2du r = m_font->getDimension(string_max_time.c_str());
818         m_width_finish_time = r.Width;
819 
820         // Top pixel where to display text
821         m_top = table_area->m_y;
822 
823         // Height of the result display
824         unsigned int height = table_area->m_h;
825 
826         // Setup different timing information for the different phases
827         // -----------------------------------------------------------
828         // How much time between consecutive rows
829         m_time_between_rows = 0.1f;
830 
831         // How long it takes for one line to scroll from right to left
832         m_time_single_scroll = 0.2f;
833 
834         // Time to rotate the entries to the proper GP position.
835         m_time_rotation = 1.0f;
836 
837         // The time the first phase is being displayed: add the start time
838         // of the last kart to the duration of the scroll plus some time
839         // of rest before the next phase starts
840         m_time_overall_scroll = (num_karts - 1)*m_time_between_rows
841             + m_time_single_scroll + 2.0f;
842 
843         // The time to increase the number of points.
844         m_time_for_points = 1.0f;
845 
846         // Determine text height
847         r = m_font->getDimension(L"Y");
848         m_distance_between_rows = (int)(1.5f*r.Height);
849         m_distance_between_meta_rows = m_distance_between_rows;
850 
851         // If there are too many highscores, reduce size between rows
852         Highscores* scores = World::getWorld()->getHighscores();
853         if (scores != NULL &&
854             scores->getNumberEntries() * m_distance_between_meta_rows > height * 0.5f)
855             m_distance_between_meta_rows *= 0.8f;
856 
857         // If there are too many karts, reduce size between rows
858         if (m_distance_between_rows * num_karts > height)
859             m_distance_between_rows = height / num_karts;
860 
861         m_width_icon = std::min((int)(table_area->m_h / num_karts),
862                                    GUIEngine::getFontHeight());
863 
864         m_width_column_space = 10;
865 
866         // Determine width of new points column
867 
868         //m_font->setMonospaceDigits(true);
869         core::dimension2du r_new_p = m_font->getDimension(L"+99");
870 
871         m_width_new_points = r_new_p.Width;
872 
873         // Determine width of overall points column
874         core::dimension2du r_all_p = m_font->getDimension(L"999");
875         //m_font->setMonospaceDigits(false);
876 
877         m_width_all_points = r_all_p.Width;
878 
879         m_table_width = m_width_icon + m_width_column_space
880             + m_width_kart_name;
881 
882         if (!RaceManager::get()->isFollowMode())
883             m_table_width += m_width_finish_time + m_width_column_space;
884 
885         // Only in GP mode are the points displayed.
886         if (active_gp)
887             m_table_width += m_width_new_points + m_width_all_points
888             + 2 * m_width_column_space;
889 
890         m_leftmost_column = table_area->m_x;
891     }   // determineTableLayout
892 
893     //-----------------------------------------------------------------------------
894     /** This function is called when one of the player presses 'fire'. The next
895      *  phase of the animation will be displayed. E.g.
896      *  in a GP: pressing fire while/after showing the latest race result will
897      *           start the animation for the current GP result
898      *  in a normal race: when pressing fire while an animation is played,
899      *           start the menu showing 'rerun, new race, back to main' etc.
900      */
nextPhase()901     void RaceResultGUI::nextPhase()
902     {
903         // This will trigger the next phase in the next render call.
904         m_timer = 9999;
905     }   // nextPhase
906 
907     //-----------------------------------------------------------------------------
908     /** If escape is pressed, don't do the default option (close the screen), but
909      *  advance to the next animation phase.
910      */
onEscapePressed()911     bool RaceResultGUI::onEscapePressed()
912     {
913         nextPhase();
914         return false;   // indicates 'do not close'
915     }   // onEscapePressed
916 
917     //-----------------------------------------------------------------------------
918     /** This is called before an event is sent to a widget. Since in this case
919      *  no widget is active, the event would be lost, so we act on fire events
920      *  here and trigger the next phase.
921      */
filterActions(PlayerAction action,int deviceID,const unsigned int value,Input::InputType type,int playerId)922     GUIEngine::EventPropagation RaceResultGUI::filterActions(PlayerAction action,
923         int deviceID,
924         const unsigned int value,
925         Input::InputType type,
926         int playerId)
927     {
928         if (action != PA_FIRE) return GUIEngine::EVENT_LET;
929 
930         // If the buttons are already visible, let the event go through since
931         // it will be triggering eventCallback where this is handles.
932 
933         if (m_animation_state == RR_WAIT_TILL_END) return GUIEngine::EVENT_LET;
934 
935         nextPhase();
936         return GUIEngine::EVENT_BLOCK;
937     }   // filterActions
938 
939     //-----------------------------------------------------------------------------
940     /** Called once a frame */
onUpdate(float dt)941     void RaceResultGUI::onUpdate(float dt)
942     {
943         // When the finish sound has been played, start the race over music.
944         if (m_finish_sound && m_finish_sound->getStatus() != SFXBase::SFX_PLAYING)
945         {
946             try
947             {
948                 // This call is done once each frame, but startMusic() is cheap
949                 // if the music is already playing.
950                 music_manager->startMusic(m_race_over_music);
951             }
952             catch (std::exception& e)
953             {
954                 Log::error("RaceResultGUI", "Exception caught when "
955                     "trying to load music: %s", e.what());
956             }
957         }
958     }   // onUpdate
959 
960     //-----------------------------------------------------------------------------
961     /** Called once a frame, this now triggers the rendering of the actual
962      *  race result gui.
963      */
onDraw(float dt)964     void RaceResultGUI::onDraw(float dt)
965     {
966         renderGlobal(dt);
967     }   // onDraw
968 
969 
970     //-----------------------------------------------------------------------------
971     /** Render all global parts of the race gui, i.e. things that are only
972      *  displayed once even in splitscreen.
973      *  \param dt Timestep sized.
974      */
renderGlobal(float dt)975     void RaceResultGUI::renderGlobal(float dt)
976     {
977 #ifndef SERVER_ONLY
978         m_timer += dt;
979         assert(World::getWorld()->getPhase() == WorldStatus::RESULT_DISPLAY_PHASE);
980         unsigned int num_karts = (unsigned int)m_all_row_infos.size();
981 
982         // First: Update the finite state machine
983         // ======================================
984         switch (m_animation_state)
985         {
986         case RR_INIT:
987             for (unsigned int i = 0; i < num_karts; i++)
988             {
989                 RowInfo *ri = &(m_all_row_infos[i]);
990                 ri->m_start_at = m_time_between_rows * i;
991                 ri->m_x_pos = (float)UserConfigParams::m_width;
992                 ri->m_y_pos = (float)(m_top + i*m_distance_between_rows);
993             }
994             m_animation_state = RR_RACE_RESULT;
995             break;
996         case RR_RACE_RESULT:
997             if (m_timer > m_time_overall_scroll)
998             {
999                 // Make sure that all lines are aligned to the left
1000                 // (in case that the animation was skipped).
1001                 for (unsigned int i = 0; i < num_karts; i++)
1002                 {
1003                     RowInfo *ri = &(m_all_row_infos[i]);
1004                     ri->m_x_pos = (float)m_leftmost_column;
1005                 }
1006                 if (RaceManager::get()->getMajorMode() !=
1007                     RaceManager::MAJOR_MODE_GRAND_PRIX)
1008                 {
1009                     m_animation_state = RR_WAIT_TILL_END;
1010                     enableAllButtons();
1011                     break;
1012                 }
1013 
1014                 determineGPLayout();
1015                 m_animation_state = RR_OLD_GP_RESULTS;
1016                 m_timer = 0;
1017             }
1018             break;
1019         case RR_OLD_GP_RESULTS:
1020             if (m_timer > m_time_overall_scroll)
1021             {
1022                 m_animation_state = RR_INCREASE_POINTS;
1023                 m_timer = 0;
1024                 for (unsigned int i = 0; i < num_karts; i++)
1025                 {
1026                     RowInfo *ri = &(m_all_row_infos[i]);
1027                     ri->m_x_pos = (float)m_leftmost_column;
1028                 }
1029             }
1030             break;
1031         case RR_INCREASE_POINTS:
1032             // Have one second delay before the resorting starts.
1033             if (m_timer > 1 + m_time_for_points)
1034             {
1035                 m_animation_state = RR_RESORT_TABLE;
1036                 if (m_gp_position_was_changed)
1037                     m_timer = 0;
1038                 else
1039                     // This causes the phase to go to RESORT_TABLE once, and then
1040                     // immediately wait till end. This has the advantage that any
1041                     // phase change settings will be processed properly.
1042                     m_timer = m_time_rotation + 1;
1043                 // Make the new row permanent; necessary in case
1044                 // that the animation is skipped.
1045                 for (unsigned int i = 0; i < num_karts; i++)
1046                 {
1047                     RowInfo *ri = &(m_all_row_infos[i]);
1048                     ri->m_new_points = 0;
1049                     ri->m_current_displayed_points =
1050                         (float)ri->m_new_overall_points;
1051                 }
1052 
1053             }
1054             break;
1055         case RR_RESORT_TABLE:
1056             if (m_timer > m_time_rotation)
1057             {
1058                 m_animation_state = RR_WAIT_TILL_END;
1059                 // Make the new row permanent.
1060                 for (unsigned int i = 0; i < num_karts; i++)
1061                 {
1062                     RowInfo *ri = &(m_all_row_infos[i]);
1063                     ri->m_y_pos = ri->m_centre_point - ri->m_radius;
1064                 }
1065                 enableAllButtons();
1066             }
1067             break;
1068         case RR_WAIT_TILL_END:
1069             if (RaceManager::get()->getMajorMode() == RaceManager::MAJOR_MODE_GRAND_PRIX)
1070                 displayGPProgress();
1071             if (m_timer - m_time_rotation > 1.0f &&
1072                 dynamic_cast<DemoWorld*>(World::getWorld()))
1073             {
1074                 RaceManager::get()->exitRace();
1075                 StateManager::get()->resetAndGoToScreen(MainMenuScreen::getInstance());
1076             }
1077             break;
1078         }   // switch
1079 
1080         // Second phase: update X and Y positions for the various animations
1081         // =================================================================
1082         float v = 0.9f*UserConfigParams::m_width / m_time_single_scroll;
1083         if (RaceManager::get()->isSoccerMode())
1084         {
1085             displaySoccerResults();
1086         }
1087         else if (RaceManager::get()->isCTFMode())
1088         {
1089             displayCTFResults();
1090         }
1091         else
1092         {
1093             for (unsigned int i = 0; i < m_all_row_infos.size(); i++)
1094             {
1095                 RowInfo *ri = &(m_all_row_infos[i]);
1096                 float x = ri->m_x_pos;
1097                 float y = ri->m_y_pos;
1098                 switch (m_animation_state)
1099                 {
1100                     // Both states use the same scrolling:
1101                 case RR_INIT: break;   // Remove compiler warning
1102                 case RR_RACE_RESULT:
1103                 case RR_OLD_GP_RESULTS:
1104                     if (m_timer > ri->m_start_at)
1105                     {   // if active
1106                         ri->m_x_pos -= dt*v;
1107                         if (ri->m_x_pos < m_leftmost_column)
1108                             ri->m_x_pos = (float)m_leftmost_column;
1109                         x = ri->m_x_pos;
1110                     }
1111                     break;
1112                 case RR_INCREASE_POINTS:
1113                 {
1114                     WorldWithRank *wwr = dynamic_cast<WorldWithRank*>(World::getWorld());
1115                     assert(wwr);
1116                     int most_points;
1117                     if (RaceManager::get()->isFollowMode())
1118                         most_points = wwr->getScoreForPosition(2);
1119                     else
1120                         most_points = wwr->getScoreForPosition(1);
1121                     ri->m_current_displayed_points +=
1122                         dt*most_points / m_time_for_points;
1123                     if (ri->m_current_displayed_points > ri->m_new_overall_points)
1124                     {
1125                         ri->m_current_displayed_points =
1126                             (float)ri->m_new_overall_points;
1127                     }
1128                     ri->m_new_points -=
1129                         dt*most_points / m_time_for_points;
1130                     if (ri->m_new_points < 0)
1131                         ri->m_new_points = 0;
1132                     break;
1133                 }
1134                 case RR_RESORT_TABLE:
1135                     x = ri->m_x_pos
1136                         - ri->m_radius*sinf(m_timer / m_time_rotation*M_PI);
1137                     y = ri->m_centre_point
1138                         + ri->m_radius*cosf(m_timer / m_time_rotation*M_PI);
1139                     break;
1140                 case RR_WAIT_TILL_END:
1141                     break;
1142                 }   // switch
1143                 displayOneEntry((unsigned int)x, (unsigned int)y, i, true);
1144             }   // for i
1145         }
1146 
1147         // Display highscores
1148         if (RaceManager::get()->getMajorMode() != RaceManager::MAJOR_MODE_GRAND_PRIX ||
1149             m_animation_state == RR_RACE_RESULT)
1150         {
1151             displayPostRaceInfo();
1152         }
1153 #endif
1154     }   // renderGlobal
1155 
1156     //-----------------------------------------------------------------------------
1157     /** Determine the layout and fields for the GP table based on the previous
1158      *  GP results.
1159      */
determineGPLayout()1160     void RaceResultGUI::determineGPLayout()
1161     {
1162 #ifndef SERVER_ONLY
1163         unsigned int num_karts = RaceManager::get()->getNumberOfKarts();
1164         std::vector<int> old_rank(num_karts, 0);
1165         // Update the kart GP ranks
1166         // This is useful, e.g., when continuing a saved GP.
1167         RaceManager::get()->computeGPRanks();
1168 
1169         int time_precision = RaceManager::get()->currentModeTimePrecision();
1170 
1171         float max_time = 0;
1172         /* Compute highest overall time to know if hours should be displayed */
1173         for (unsigned int kart_id = 0; kart_id < num_karts; kart_id++)
1174         {
1175             max_time = std::max(RaceManager::get()->getOverallTime(kart_id), max_time);
1176         }
1177 
1178         for (unsigned int kart_id = 0; kart_id < num_karts; kart_id++)
1179         {
1180             int rank = RaceManager::get()->getKartGPRank(kart_id);
1181             // In case of FTL mode: ignore the leader
1182             if (rank < 0) continue;
1183             old_rank[kart_id] = rank;
1184             const AbstractKart *kart = World::getWorld()->getKart(kart_id);
1185             RowInfo *ri = &(m_all_row_infos[rank]);
1186             ri->m_kart_icon =
1187                 kart->getKartProperties()->getIconMaterial()->getTexture();
1188             ri->m_is_player_kart = kart->getController()->isLocalPlayerController();
1189             ri->m_kart_name = kart->getController()->getName();
1190             if (RaceManager::get()->getKartGlobalPlayerId(kart->getWorldKartId()) > -1)
1191             {
1192                 const core::stringw& flag = StringUtils::getCountryFlag(
1193                     RaceManager::get()->getKartInfo(kart->getWorldKartId()).getCountryCode());
1194                 if (!flag.empty())
1195                 {
1196                     ri->m_kart_name += L" ";
1197                     ri->m_kart_name += flag;
1198                 }
1199             }
1200             // In FTL karts do have a time, which is shown even when the kart
1201             // is eliminated
1202             if (kart->isEliminated() && !(RaceManager::get()->isFollowMode()))
1203             {
1204                 ri->m_finish_time_string = core::stringw(_("Eliminated"));
1205             }
1206             else
1207             {
1208                 float time = RaceManager::get()->getOverallTime(kart_id);
1209                 ri->m_finish_time_string
1210                     = StringUtils::timeToString(time, time_precision, true, /*display hours*/ (max_time > 3599.99f)).c_str();
1211             }
1212             ri->m_start_at = m_time_between_rows * rank;
1213             ri->m_x_pos = (float)UserConfigParams::m_width;
1214             ri->m_y_pos = (float)(m_top + rank*m_distance_between_rows);
1215             int p = RaceManager::get()->getKartPrevScore(kart_id);
1216             ri->m_current_displayed_points = (float)p;
1217             if (kart->isEliminated() && !(RaceManager::get()->isFollowMode()))
1218             {
1219                 ri->m_new_points = 0;
1220             }
1221             else
1222             {
1223                 WorldWithRank *wwr = dynamic_cast<WorldWithRank*>(World::getWorld());
1224                 assert(wwr);
1225                 ri->m_new_points =
1226                     (float)wwr->getScoreForPosition(kart->getPosition());
1227             }
1228         }
1229 
1230         // Now update the GP ranks, and determine the new position
1231         // -------------------------------------------------------
1232         RaceManager::get()->computeGPRanks();
1233         m_gp_position_was_changed = false;
1234         for (unsigned int i = 0; i < num_karts; i++)
1235         {
1236             int j = old_rank[i];
1237             int gp_position = RaceManager::get()->getKartGPRank(i);
1238             m_gp_position_was_changed |= j != gp_position;
1239             RowInfo *ri = &(m_all_row_infos[j]);
1240             ri->m_radius = (j - gp_position)*(int)m_distance_between_rows*0.5f;
1241             ri->m_centre_point = m_top + (gp_position + j)*m_distance_between_rows*0.5f;
1242             int p = RaceManager::get()->getKartScore(i);
1243             ri->m_new_overall_points = p;
1244         }   // i < num_karts
1245 #endif
1246     }   // determineGPLayout
1247 
1248     //-----------------------------------------------------------------------------
1249     /** Displays the race results for a single kart.
1250      *  \param n Index of the kart to be displayed.
1251      *  \param display_points True if GP points should be displayed, too
1252      */
displayOneEntry(unsigned int x,unsigned int y,unsigned int n,bool display_points)1253     void RaceResultGUI::displayOneEntry(unsigned int x, unsigned int y,
1254         unsigned int n, bool display_points)
1255     {
1256 #ifndef SERVER_ONLY
1257         RowInfo *ri = &(m_all_row_infos[n]);
1258         video::SColor color = ri->m_is_player_kart
1259             ? video::SColor(255, 255, 0, 0)
1260             : video::SColor(255, 255, 255, 255);
1261 
1262         unsigned int current_x = x;
1263 
1264         // First draw the icon
1265         // -------------------
1266         if (ri->m_kart_icon)
1267         {
1268             core::recti source_rect(core::vector2di(0, 0),
1269                 ri->m_kart_icon->getSize());
1270             core::recti dest_rect(current_x, y,
1271                 current_x + m_width_icon, y + m_width_icon);
1272             draw2DImage(ri->m_kart_icon, dest_rect,
1273                 source_rect, NULL, NULL,
1274                 true);
1275         }
1276 
1277         current_x += m_width_icon + m_width_column_space;
1278 
1279         // Draw the name
1280         // -------------
1281 
1282         core::recti pos_name(current_x, y,
1283             current_x + m_width_kart_name, y + m_distance_between_rows);
1284         m_font->draw(ri->m_kart_name, pos_name, color, false, false, NULL,
1285             true /* ignoreRTL */);
1286         current_x += m_width_kart_name + m_width_column_space;
1287 
1288 
1289         core::recti dest_rect = core::recti(current_x, y, current_x + 100, y + 10);
1290         m_font->draw(ri->m_finish_time_string, dest_rect, color, false, false,
1291             NULL, true /* ignoreRTL */);
1292         current_x += m_width_finish_time + m_width_column_space;
1293 
1294         // Only display points in GP mode and when the GP results are displayed.
1295         // =====================================================================
1296         if (RaceManager::get()->getMajorMode() == RaceManager::MAJOR_MODE_GRAND_PRIX &&
1297             m_animation_state != RR_RACE_RESULT)
1298         {
1299             // Draw the new points
1300             // -------------------
1301             if (ri->m_new_points > 0)
1302             {
1303                 core::recti dest_rect = core::recti(current_x, y,
1304                     current_x + 100, y + 10);
1305                 core::stringw point_string = core::stringw("+")
1306                     + core::stringw((int)ri->m_new_points);
1307                 // With mono-space digits space has the same width as each digit,
1308                 // so we can simply fill up the string with spaces to get the
1309                 // right aligned.
1310                 while (point_string.size() < 3)
1311                     point_string = core::stringw(" ") + point_string;
1312                 m_font->draw(point_string, dest_rect, color, false, false, NULL,
1313                     true /* ignoreRTL */);
1314             }
1315             current_x += m_width_new_points + m_width_column_space;
1316 
1317             // Draw the old_points plus increase value
1318             // ---------------------------------------
1319             core::recti dest_rect = core::recti(current_x, y, current_x + 100, y + 10);
1320             core::stringw point_inc_string =
1321                 core::stringw((int)(ri->m_current_displayed_points));
1322             while (point_inc_string.size() < 3)
1323                 point_inc_string = core::stringw(" ") + point_inc_string;
1324             m_font->draw(point_inc_string, dest_rect, color, false, false, NULL,
1325                 true /* ignoreRTL */);
1326         }
1327 #endif
1328     }   // displayOneEntry
1329 
1330     //-----------------------------------------------------------------------------
displaySoccerResults()1331     void RaceResultGUI::displaySoccerResults()
1332     {
1333 #ifndef SERVER_ONLY
1334         //Draw win text
1335         core::stringw result_text;
1336         static video::SColor color = video::SColor(255, 255, 255, 255);
1337         gui::IGUIFont* font = GUIEngine::getTitleFont();
1338         int current_x = UserConfigParams::m_width / 2;
1339         RowInfo *ri = &(m_all_row_infos[0]);
1340         int current_y = (int)ri->m_y_pos;
1341         SoccerWorld* sw = (SoccerWorld*)World::getWorld();
1342         const int red_score = sw->getScore(KART_TEAM_RED);
1343         const int blue_score = sw->getScore(KART_TEAM_BLUE);
1344 
1345         GUIEngine::Widget *table_area = getWidget("result-table");
1346         int height = table_area->m_h + table_area->m_y;
1347 
1348         if (red_score > blue_score)
1349         {
1350             result_text = _("Red Team Wins");
1351         }
1352         else if (blue_score > red_score)
1353         {
1354             result_text = _("Blue Team Wins");
1355         }
1356         else
1357         {
1358             //Cannot really happen now. Only in time limited matches.
1359             result_text = _("It's a draw");
1360         }
1361         core::rect<s32> pos(current_x, current_y, current_x, current_y);
1362         font->draw(result_text.c_str(), pos, color, true, true);
1363 
1364         core::dimension2du rect = font->getDimension(result_text.c_str());
1365 
1366         //Draw team scores:
1367         current_y += rect.Height;
1368         current_x /= 2;
1369         irr::video::ITexture* red_icon = irr_driver->getTexture(FileManager::GUI_ICON,
1370             "soccer_ball_red.png");
1371         irr::video::ITexture* blue_icon = irr_driver->getTexture(FileManager::GUI_ICON,
1372             "soccer_ball_blue.png");
1373 
1374         core::recti source_rect(core::vector2di(0, 0), red_icon->getSize());
1375         core::recti dest_rect(current_x, current_y, current_x + red_icon->getSize().Width / 2,
1376             current_y + red_icon->getSize().Height / 2);
1377         draw2DImage(red_icon, dest_rect, source_rect,
1378             NULL, NULL, true);
1379         current_x += UserConfigParams::m_width / 2 - red_icon->getSize().Width / 2;
1380         dest_rect = core::recti(current_x, current_y, current_x + red_icon->getSize().Width / 2,
1381             current_y + red_icon->getSize().Height / 2);
1382         draw2DImage(blue_icon, dest_rect, source_rect,
1383             NULL, NULL, true);
1384 
1385         result_text = StringUtils::toWString(blue_score);
1386         rect = font->getDimension(result_text.c_str());
1387         current_x += red_icon->getSize().Width / 4;
1388         current_y += red_icon->getSize().Height / 2 + rect.Height / 4;
1389         pos = core::rect<s32>(current_x, current_y, current_x, current_y);
1390         font->draw(result_text.c_str(), pos, color, true, false);
1391 
1392         current_x -= UserConfigParams::m_width / 2 - red_icon->getSize().Width / 2;
1393         result_text = StringUtils::toWString(red_score);
1394         pos = core::rect<s32>(current_x, current_y, current_x, current_y);
1395         font->draw(result_text.c_str(), pos, color, true, false);
1396 
1397         int center_x = UserConfigParams::m_width / 2;
1398         pos = core::rect<s32>(center_x, current_y, center_x, current_y);
1399         font->draw("-", pos, color, true, false);
1400 
1401         //Draw goal scorers:
1402         //The red scorers:
1403         current_y += rect.Height / 2 + rect.Height / 4;
1404         font = GUIEngine::getSmallFont();
1405         std::vector<SoccerWorld::ScorerData> scorers = sw->getScorers(KART_TEAM_RED);
1406 
1407         // Maximum 10 scorers displayed in result screen
1408         while (scorers.size() > 10)
1409         {
1410             scorers.erase(scorers.begin());
1411         }
1412 
1413         int prev_y = current_y;
1414 
1415         for (unsigned int i = 0; i < scorers.size(); i++)
1416         {
1417             const bool own_goal = !(scorers.at(i).m_correct_goal);
1418 
1419             result_text = scorers.at(i).m_player;
1420             if (scorers.at(i).m_handicap_level == HANDICAP_MEDIUM)
1421                 result_text = _("%s (handicapped)", result_text);
1422 
1423             if (own_goal)
1424             {
1425                 result_text.append(" ");
1426                 //I18N: indicates a player that scored in their own goal in result screen
1427                 result_text.append(_("(Own Goal)"));
1428             }
1429             if (!scorers.at(i).m_country_code.empty())
1430             {
1431                 result_text += " ";
1432                 result_text += StringUtils::getCountryFlag(scorers.at(i).m_country_code);
1433             }
1434 
1435             result_text.append("  ");
1436             result_text.append(StringUtils::timeToString(scorers.at(i).m_time).c_str());
1437             rect = font->getDimension(result_text.c_str());
1438 
1439             if (height - prev_y < ((short)scorers.size() + 1)*(short)rect.Height)
1440                 current_y += (height - prev_y) / ((short)scorers.size() + 1);
1441             else
1442                 current_y += rect.Height;
1443 
1444             if (current_y > height) break;
1445 
1446             pos = core::rect<s32>(current_x, current_y, current_x, current_y);
1447             font->draw(result_text, pos, (own_goal ?
1448                 video::SColor(255, 255, 0, 0) : color), true, false);
1449             irr::video::ITexture* scorer_icon = NULL;
1450             const KartProperties* kp = kart_properties_manager->getKart(scorers.at(i).m_kart);
1451             // For addon kart online
1452             if (!kp)
1453                 kp = kart_properties_manager->getKart("tux");
1454             if (kp)
1455                 scorer_icon = kp->getIconMaterial()->getTexture();
1456             if (scorer_icon)
1457             {
1458                 source_rect = core::recti(core::vector2di(0, 0), scorer_icon->getSize());
1459                 irr::u32 offset_x = (irr::u32)(font->getDimension(result_text.c_str()).Width / 1.5f);
1460                 core::recti r = core::recti(current_x - offset_x - 30, current_y, current_x - offset_x, current_y + 30);
1461                 draw2DImage(scorer_icon, r, source_rect,
1462                     NULL, NULL, true);
1463             }
1464         }
1465 
1466         //The blue scorers:
1467         current_y = prev_y;
1468         current_x += UserConfigParams::m_width / 2 - red_icon->getSize().Width / 2;
1469         scorers = sw->getScorers(KART_TEAM_BLUE);
1470 
1471         while (scorers.size() > 10)
1472         {
1473             scorers.erase(scorers.begin());
1474         }
1475 
1476         for (unsigned int i = 0; i < scorers.size(); i++)
1477         {
1478             const bool own_goal = !(scorers.at(i).m_correct_goal);
1479 
1480             result_text = scorers.at(i).m_player;
1481             if (scorers.at(i).m_handicap_level == HANDICAP_MEDIUM)
1482                 result_text = _("%s (handicapped)", result_text);
1483 
1484             if (own_goal)
1485             {
1486                 result_text.append(" ");
1487                 //I18N: indicates a player that scored in their own goal in result screen
1488                 result_text.append(_("(Own Goal)"));
1489             }
1490             if (!scorers.at(i).m_country_code.empty())
1491             {
1492                 result_text += " ";
1493                 result_text += StringUtils::getCountryFlag(scorers.at(i).m_country_code);
1494             }
1495 
1496             result_text.append("  ");
1497             result_text.append(StringUtils::timeToString(scorers.at(i).m_time).c_str());
1498             rect = font->getDimension(result_text.c_str());
1499 
1500             if (height - prev_y < ((short)scorers.size() + 1)*(short)rect.Height)
1501                 current_y += (height - prev_y) / ((short)scorers.size() + 1);
1502             else
1503                 current_y += rect.Height;
1504 
1505             if (current_y > height) break;
1506 
1507             pos = core::rect<s32>(current_x, current_y, current_x, current_y);
1508             font->draw(result_text, pos, (own_goal ?
1509                 video::SColor(255, 255, 0, 0) : color), true, false);
1510             irr::video::ITexture* scorer_icon = NULL;
1511             const KartProperties* kp = kart_properties_manager->getKart(scorers.at(i).m_kart);
1512             // For addon kart online
1513             if (!kp)
1514                 kp = kart_properties_manager->getKart("tux");
1515             if (kp)
1516                 scorer_icon = kp->getIconMaterial()->getTexture();
1517             if (scorer_icon)
1518             {
1519                 source_rect = core::recti(core::vector2di(0, 0), scorer_icon->getSize());
1520                 irr::u32 offset_x = (irr::u32)(font->getDimension(result_text.c_str()).Width / 1.5f);
1521                 core::recti r = core::recti(current_x - offset_x - 30, current_y, current_x - offset_x, current_y + 30);
1522                 draw2DImage(scorer_icon, r, source_rect,
1523                     NULL, NULL, true);
1524             }
1525         }
1526 #endif
1527     }
1528 
1529     //-----------------------------------------------------------------------------
1530 
clearHighscores()1531     void RaceResultGUI::clearHighscores()
1532     {
1533         m_highscore_rank = 0;
1534     }   // clearHighscores
1535 
1536     //-----------------------------------------------------------------------------
1537 
setHighscore(int rank)1538     void RaceResultGUI::setHighscore(int rank)
1539     {
1540         m_highscore_rank = rank;
1541     }   // setHighscore
1542 
1543     // ----------------------------------------------------------------------------
enableGPProgress()1544     void RaceResultGUI::enableGPProgress()
1545     {
1546         if (RaceManager::get()->getMajorMode() == RaceManager::MAJOR_MODE_GRAND_PRIX)
1547         {
1548             GUIEngine::Widget* result_table = getWidget("result-table");
1549             assert(result_table != NULL);
1550 
1551             int currentTrack = RaceManager::get()->getTrackNumber();
1552             int font_height = getFontHeight();
1553             int w = (int)(UserConfigParams::m_width*0.17);
1554             int x = (int)(result_table->m_x + result_table->m_w - w - 15);
1555             int y = (m_top + font_height + 5);
1556 
1557             //Current progress
1558             GUIEngine::LabelWidget* status_label = new GUIEngine::LabelWidget();
1559             status_label->m_properties[GUIEngine::PROP_ID] = "status_label";
1560             status_label->m_properties[GUIEngine::PROP_TEXT_ALIGN] = "center";
1561             status_label->m_x = x;
1562             status_label->m_y = y;
1563             status_label->m_w = w;
1564             status_label->m_h = font_height;
1565             status_label->add();
1566             status_label->setText(_("Track %i/%i", currentTrack + 1,
1567                 RaceManager::get()->getGrandPrix().getNumberOfTracks()), true);
1568             addGPProgressWidget(status_label);
1569             y = (status_label->m_y + status_label->m_h + 5);
1570 
1571             //Scroll up button
1572             GUIEngine::IconButtonWidget* up_button = new GUIEngine::IconButtonWidget(
1573                 GUIEngine::IconButtonWidget::SCALE_MODE_KEEP_CUSTOM_ASPECT_RATIO,
1574                 false, false, GUIEngine::IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE);
1575             up_button->m_properties[GUIEngine::PROP_ID] = "up_button";
1576             up_button->m_x = x;
1577             up_button->m_y = y;
1578             up_button->m_w = w;
1579             up_button->m_h = font_height;
1580             up_button->add();
1581             up_button->setImage(file_manager->getAsset(FileManager::GUI_ICON, "scroll_up.png"));
1582             addGPProgressWidget(up_button);
1583             y = (up_button->m_y + up_button->m_h + SSHOT_SEPARATION);
1584 
1585             //Track screenshots and labels
1586             int n_sshot = 1;
1587             for (int i = m_start_track; i < m_end_track; i++)
1588             {
1589                 //Screenshot
1590                 GUIEngine::IconButtonWidget* screenshot_widget =
1591                     new GUIEngine::IconButtonWidget(
1592                         GUIEngine::IconButtonWidget::
1593                         SCALE_MODE_KEEP_CUSTOM_ASPECT_RATIO,
1594                         false, false,
1595                         GUIEngine::IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE);
1596                 screenshot_widget->setCustomAspectRatio(4.0f / 3.0f);
1597                 screenshot_widget->m_x = x;
1598                 screenshot_widget->m_y = y;
1599                 screenshot_widget->m_w = w;
1600                 screenshot_widget->m_h = m_sshot_height;
1601                 screenshot_widget->m_properties[GUIEngine::PROP_ID] =
1602                     ("sshot_" + StringUtils::toString(n_sshot));
1603                 screenshot_widget->add();
1604                 addGPProgressWidget(screenshot_widget);
1605 
1606                 //Label
1607                 GUIEngine::LabelWidget* sshot_label = new GUIEngine::LabelWidget();
1608                 sshot_label->m_properties[GUIEngine::PROP_ID] =
1609                     ("sshot_label_" + StringUtils::toString(n_sshot));
1610                 sshot_label->m_properties[GUIEngine::PROP_TEXT_ALIGN] = "left";
1611                 sshot_label->m_x = (x + w + 5);
1612                 sshot_label->m_y = (y + (m_sshot_height / 2) - (font_height / 2));
1613                 sshot_label->m_w = (w / 2);
1614                 sshot_label->m_h = font_height;
1615                 sshot_label->add();
1616                 addGPProgressWidget(sshot_label);
1617 
1618                 y += (m_sshot_height + SSHOT_SEPARATION);
1619                 n_sshot++;
1620             }   // for
1621             displayScreenShots();
1622 
1623             //Scroll down button
1624             GUIEngine::IconButtonWidget* down_button = new GUIEngine::IconButtonWidget(
1625                 GUIEngine::IconButtonWidget::SCALE_MODE_KEEP_CUSTOM_ASPECT_RATIO,
1626                 false, false, GUIEngine::IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE);
1627             down_button->m_properties[GUIEngine::PROP_ID] = "down_button";
1628             down_button->m_x = x;
1629             down_button->m_y = y;
1630             down_button->m_w = w;
1631             down_button->m_h = font_height;
1632             down_button->add();
1633             down_button->setImage(file_manager->getAsset(FileManager::GUI_ICON, "scroll_down.png"));
1634             addGPProgressWidget(down_button);
1635 
1636         }   // if MAJOR_MODE_GRAND_PRIX)
1637 
1638     }   // enableGPProgress
1639 
1640     // ----------------------------------------------------------------------------
addGPProgressWidget(GUIEngine::Widget * widget)1641     void RaceResultGUI::addGPProgressWidget(GUIEngine::Widget* widget)
1642     {
1643         m_widgets.push_back(widget);
1644         m_gp_progress_widgets.push_back(widget);
1645     }
1646 
1647     // ----------------------------------------------------------------------------
displayGPProgress()1648     void RaceResultGUI::displayGPProgress()
1649     {
1650         core::stringw msg = _("Grand Prix progress:");
1651 
1652         GUIEngine::Widget* result_table = getWidget("result-table");
1653         assert(result_table != NULL);
1654 
1655         video::SColor color = video::SColor(255, 255, 0, 0);
1656         // 0.96 from stkgui
1657         core::recti dest_rect(
1658             result_table->m_x + result_table->m_w - m_font->getDimension(msg.c_str()).Width - 5,
1659             m_top, UserConfigParams::m_width * 0.96f,
1660             m_top + GUIEngine::getFontHeight());
1661 
1662         m_font->draw(msg, dest_rect, color, false, false, NULL, true);
1663     }   // displayGPProgress
1664 
1665     // ----------------------------------------------------------------------------
cleanupGPProgress()1666     void RaceResultGUI::cleanupGPProgress()
1667     {
1668         for (unsigned int i = 0; i < m_gp_progress_widgets.size(); i++)
1669             m_widgets.remove(m_gp_progress_widgets.get(i));
1670         m_gp_progress_widgets.clearAndDeleteAll();
1671     }   // cleanupGPProgress
1672 
1673     // ----------------------------------------------------------------------------
displayPostRaceInfo()1674     void RaceResultGUI::displayPostRaceInfo()
1675     {
1676 #ifndef SERVER_ONLY
1677         // This happens in demo world
1678         if (!World::getWorld())
1679             return;
1680 
1681         Highscores* scores = World::getWorld()->getHighscores();
1682 
1683         video::SColor white_color = video::SColor(255, 255, 255, 255);
1684 
1685         int x = (int)(UserConfigParams::m_width*0.65f);
1686         int y = m_top;
1687 
1688         int current_y = y;
1689 
1690         int time_precision = RaceManager::get()->currentModeTimePrecision();
1691 
1692         // In some case for exemple FTL they will be no highscores
1693         if (scores != NULL)
1694         {
1695             // First draw title
1696             GUIEngine::getFont()->draw(_("Highscores"),
1697                 // 0.96 from stkgui
1698                 core::recti(x, y, UserConfigParams::m_width * 0.96f, y + GUIEngine::getFontHeight()),
1699                 white_color,
1700                 false, false, NULL, true /* ignoreRTL */);
1701 
1702             std::string kart_name;
1703             irr::core::stringw player_name;
1704 
1705             // prevent excessive long name
1706             unsigned int max_characters = 15;
1707             unsigned int max_width = (UserConfigParams::m_width / 2 - 200) / 10;
1708             if (max_width < 15)
1709                 max_characters = max_width;
1710 
1711             float time;
1712             for (int i = 0; i < scores->getNumberEntries(); i++)
1713             {
1714                 scores->getEntry(i, kart_name, player_name, &time);
1715                 if (player_name.size() > max_characters)
1716                 {
1717                     int begin = (int(m_timer / 0.4f)) % (player_name.size() - max_characters);
1718                     player_name = player_name.subString(begin, max_characters, false);
1719                 }
1720 
1721                 video::SColor text_color = white_color;
1722                 if (m_highscore_rank - 1 == i)
1723                 {
1724                     text_color = video::SColor(255, 255, 0, 0);
1725                 }
1726 
1727                 int current_x = x;
1728                 current_y = y + (int)((i + 1) * m_distance_between_meta_rows);
1729 
1730                 const KartProperties* prop = kart_properties_manager->getKart(kart_name);
1731                 if (prop != NULL)
1732                 {
1733                     const std::string &icon_path = prop->getAbsoluteIconFile();
1734                     video::ITexture* kart_icon_texture = irr_driver->getTexture(icon_path);
1735 
1736                     if (kart_icon_texture != NULL)
1737                     {
1738                         core::recti source_rect(core::vector2di(0, 0),
1739                             kart_icon_texture->getSize());
1740 
1741                         core::recti dest_rect(current_x, current_y,
1742                             current_x + m_width_icon, current_y + m_width_icon);
1743 
1744                         draw2DImage(
1745                             kart_icon_texture, dest_rect,
1746                             source_rect, NULL, NULL,
1747                             true);
1748 
1749                         current_x += m_width_icon + m_width_column_space;
1750                     }
1751                 }
1752 
1753                 // draw the player name
1754                 GUIEngine::getSmallFont()->draw(player_name.c_str(),
1755                     core::recti(current_x, current_y, current_x + 150, current_y + 10),
1756                     text_color,
1757                     false, false, NULL, true /* ignoreRTL */);
1758 
1759                 current_x = (int)(UserConfigParams::m_width * 0.85f);
1760 
1761                 // Finally draw the time
1762                 std::string time_string = StringUtils::timeToString(time, time_precision);
1763                 GUIEngine::getSmallFont()->draw(time_string.c_str(),
1764                     core::recti(current_x, current_y, current_x + 100, current_y + 10),
1765                     text_color,
1766                     false, false, NULL, true /* ignoreRTL */);
1767             }
1768         }
1769 
1770         if (!RaceManager::get()->isSoccerMode())
1771         {
1772             // display lap count
1773             if (RaceManager::get()->modeHasLaps())
1774             {
1775                 core::stringw laps = _("Laps: %i", RaceManager::get()->getNumLaps());
1776                 current_y += int(m_distance_between_meta_rows * 0.8f * 2);
1777                 GUIEngine::getFont()->draw(laps,
1778                     // 0.96 from stkgui
1779                     core::recti(x, current_y, UserConfigParams::m_width * 0.96f, current_y + GUIEngine::getFontHeight()),
1780                     white_color, false, false, nullptr, true);
1781             }
1782             // display difficulty
1783             const core::stringw& difficulty_name =
1784                 RaceManager::get()->getDifficultyName(RaceManager::get()->getDifficulty());
1785             core::stringw difficulty_string = _("Difficulty: %s", difficulty_name);
1786             current_y += int(m_distance_between_meta_rows * 0.8f);
1787             GUIEngine::getFont()->draw(difficulty_string,
1788                 // 0.96 from stkgui
1789                 core::recti(x, current_y, UserConfigParams::m_width * 0.96f, current_y + GUIEngine::getFontHeight()),
1790                 white_color, false, false, nullptr, true);
1791             // show fastest lap
1792             if (RaceManager::get()->modeHasLaps())
1793             {
1794                 float best_lap_time = static_cast<LinearWorld*>(World::getWorld())->getFastestLap();
1795                 // The fastest lap ticks is set to INT_MAX, so the best_lap_time will be
1796                 // very high when none has been set yet.
1797                 if (best_lap_time <= 3600.0)
1798                 {
1799                     core::stringw best_lap_string = _("Best lap time: %s",
1800                         StringUtils::timeToString(best_lap_time, time_precision).c_str());
1801                     current_y += int(m_distance_between_meta_rows * 0.8f);
1802                     GUIEngine::getFont()->draw(best_lap_string,
1803                         // 0.96 from stkgui
1804                         core::recti(x, current_y, UserConfigParams::m_width * 0.96f, current_y + GUIEngine::getFontHeight()),
1805                         white_color, false, false,
1806                         nullptr, true);
1807 
1808                     core::stringw best_lap_by = dynamic_cast<LinearWorld*>(World::getWorld())->getFastestLapKartName();
1809 
1810                     if (best_lap_by != "")
1811                     {
1812                         //I18N: is used to indicate who has the bast laptime (best laptime "by kart_name")
1813                         core::stringw best_lap_by_string = _("by %s", best_lap_by);
1814                         // Make it closer to the above line
1815                         current_y += int(GUIEngine::getFontHeight() * 0.8f);
1816                         GUIEngine::getFont()->draw(best_lap_by_string,
1817                             // 0.96 from stkgui
1818                             core::recti(x, current_y, UserConfigParams::m_width * 0.96f, current_y + GUIEngine::getFontHeight()),
1819                             white_color, false, false,
1820                             nullptr, true);
1821                     }
1822                 }
1823             }   // if mode has laps
1824         }   // if not soccer mode
1825 #endif
1826     }
1827 
1828     // ----------------------------------------------------------------------------
displayScreenShots()1829     void RaceResultGUI::displayScreenShots()
1830     {
1831         const std::vector<std::string> tracks =
1832             RaceManager::get()->getGrandPrix().getTrackNames();
1833         int currentTrack = RaceManager::get()->getTrackNumber();
1834 
1835         int n_sshot = 1;
1836         for (int i = m_start_track; i < m_end_track; i++)
1837         {
1838             Track* track = track_manager->getTrack(tracks[i]);
1839             GUIEngine::IconButtonWidget* sshot = getWidget<GUIEngine::IconButtonWidget>(
1840                 ("sshot_" + StringUtils::toString(n_sshot)).c_str());
1841             GUIEngine::LabelWidget* label = getWidget<GUIEngine::LabelWidget>(
1842                 ("sshot_label_" + StringUtils::toString(n_sshot)).c_str());
1843             assert(sshot != NULL && label != NULL);
1844 
1845             // Network grand prix chooses each track 1 by 1
1846             if (track == NULL)
1847             {
1848                 sshot->setImage(file_manager->getAsset(FileManager::GUI_ICON,
1849                     "main_help.png"));
1850             }
1851             else
1852                 sshot->setImage(track->getScreenshotFile());
1853             if (i <= currentTrack)
1854                 sshot->setBadge(GUIEngine::OK_BADGE);
1855             else
1856                 sshot->resetAllBadges();
1857 
1858             label->setText(StringUtils::toWString(i + 1), true);
1859 
1860             n_sshot++;
1861         }
1862     }
1863 
1864     // ----------------------------------------------------------------------------
getFontHeight() const1865     int RaceResultGUI::getFontHeight() const
1866     {
1867         assert(m_font != NULL);
1868         return m_font->getDimension(L"A").Height; //Could be any capital letter
1869     }
1870