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