// // SuperTuxKart - a fun racing game with go-kart // Copyright (C) 2010-2015 Joerg Henrichs // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 3 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "states_screens/race_result_gui.hpp" #include "audio/music_manager.hpp" #include "audio/sfx_manager.hpp" #include "audio/sfx_base.hpp" #include "challenges/unlock_manager.hpp" #include "config/player_manager.hpp" #include "config/user_config.hpp" #include "graphics/2dutils.hpp" #include "graphics/material.hpp" #include "guiengine/engine.hpp" #include "guiengine/message_queue.hpp" #include "guiengine/modaldialog.hpp" #include "guiengine/scalable_font.hpp" #include "guiengine/widget.hpp" #include "guiengine/widgets/icon_button_widget.hpp" #include "guiengine/widgets/label_widget.hpp" #include "io/file_manager.hpp" #include "karts/abstract_kart.hpp" #include "karts/controller/controller.hpp" #include "karts/controller/end_controller.hpp" #include "karts/controller/local_player_controller.hpp" #include "karts/kart_properties.hpp" #include "karts/kart_properties_manager.hpp" #include "modes/cutscene_world.hpp" #include "modes/demo_world.hpp" #include "modes/capture_the_flag.hpp" #include "modes/overworld.hpp" #include "modes/soccer_world.hpp" #include "network/network_config.hpp" #include "network/stk_host.hpp" #include "network/protocols/client_lobby.hpp" #include "race/highscores.hpp" #include "replay/replay_play.hpp" #include "replay/replay_recorder.hpp" #include "scriptengine/property_animator.hpp" #include "states_screens/feature_unlocked.hpp" #include "states_screens/main_menu_screen.hpp" #include "states_screens/online/networking_lobby.hpp" #include "states_screens/race_setup_screen.hpp" #include "tracks/track.hpp" #include "tracks/track_manager.hpp" #include "utils/string_utils.hpp" #include /** Constructor, initialises internal data structures. */ RaceResultGUI::RaceResultGUI() : Screen("race_result.stkgui", /*pause race*/ false) { } // RaceResultGUI //----------------------------------------------------------------------------- /** Besides calling init in the base class this makes all buttons of this * screen invisible. The buttons will only displayed once the animation is * over. */ void RaceResultGUI::init() { Screen::init(); determineTableLayout(); m_animation_state = RR_INIT; m_timer = 0; getWidget("top")->setVisible(false); getWidget("middle")->setVisible(false); getWidget("bottom")->setVisible(false); music_manager->stopMusic(); bool human_win = true; unsigned int num_karts = race_manager->getNumberOfKarts(); for (unsigned int kart_id = 0; kart_id < num_karts; kart_id++) { const AbstractKart *kart = World::getWorld()->getKart(kart_id); if (kart->getController()->isLocalPlayerController()) human_win = human_win && kart->getRaceResult(); } m_finish_sound = SFXManager::get()->quickSound( human_win ? "race_finish_victory" : "race_finish"); //std::string path = (human_win ? Different result music too later // file_manager->getAsset(FileManager::MUSIC, "race_summary.music") : // file_manager->getAsset(FileManager::MUSIC, "race_summary.music")); std::string path = file_manager->getAsset(FileManager::MUSIC, "race_summary.music"); m_race_over_music = music_manager->getMusicInformation(path); if (!m_finish_sound) { // If there is no finish sound (because sfx are disabled), start // the race over music here (since the race over music is only started // when the finish sound has been played). music_manager->startMusic(m_race_over_music); } // Calculate how many track screenshots can fit into the "result-table" widget GUIEngine::Widget* result_table = getWidget("result-table"); assert(result_table != NULL); m_sshot_height = (int)(UserConfigParams::m_height*0.1275); m_max_tracks = std::max(1, ((result_table->m_h - getFontHeight() * 5) / (m_sshot_height + SSHOT_SEPARATION))); //Show at least one // Calculate screenshot scrolling parameters const std::vector tracks = race_manager->getGrandPrix().getTrackNames(); int n_tracks = (int)tracks.size(); int currentTrack = race_manager->getTrackNumber(); m_start_track = currentTrack; if (n_tracks > m_max_tracks) { m_start_track = std::min(currentTrack, n_tracks - m_max_tracks); m_end_track = std::min(currentTrack + m_max_tracks, n_tracks); } else { m_start_track = 0; m_end_track = (int)tracks.size(); } } // init //----------------------------------------------------------------------------- void RaceResultGUI::tearDown() { Screen::tearDown(); //m_font->setMonospaceDigits(m_was_monospace); if (m_finish_sound != NULL && m_finish_sound->getStatus() == SFXBase::SFX_PLAYING) { m_finish_sound->stop(); } } // tearDown //----------------------------------------------------------------------------- /** Makes the correct buttons visible again, and gives them the right label. * 1) If something was unlocked, only a 'next' button is displayed. */ void RaceResultGUI::enableAllButtons() { GUIEngine::Widget *top = getWidget("top"); GUIEngine::Widget *middle = getWidget("middle"); GUIEngine::Widget *bottom = getWidget("bottom"); if (race_manager->getMajorMode() == RaceManager::MAJOR_MODE_GRAND_PRIX) { enableGPProgress(); } // If we're in a network world, change the buttons text if (World::getWorld()->isNetworkWorld()) { top->setVisible(false); middle->setText(_("Continue")); middle->setVisible(true); middle->setFocusForPlayer(PLAYER_ID_GAME_MASTER); bottom->setText(_("Quit the server")); bottom->setVisible(true); return; } // If something was unlocked // ------------------------- int n = (int)PlayerManager::getCurrentPlayer()->getRecentlyCompletedChallenges().size(); if (n > 0) { top->setText(n == 1 ? _("You completed a challenge!") : _("You completed challenges!")); top->setVisible(true); top->setFocusForPlayer(PLAYER_ID_GAME_MASTER); } else if (race_manager->getMajorMode() == RaceManager::MAJOR_MODE_GRAND_PRIX) { // In case of a GP: // ---------------- top->setVisible(false); top->setFocusable(false); middle->setText(_("Continue")); middle->setVisible(true); if (race_manager->getTrackNumber() + 1 < race_manager->getNumOfTracks()) { bottom->setText(_("Abort Grand Prix")); bottom->setVisible(true); bottom->setFocusable(true); } else { bottom->setFocusable(false); } middle->setFocusForPlayer(PLAYER_ID_GAME_MASTER); } else { // Normal race // ----------- middle->setText(_("Restart")); middle->setVisible(true); middle->setFocusForPlayer(PLAYER_ID_GAME_MASTER); if (race_manager->raceWasStartedFromOverworld()) { top->setVisible(false); bottom->setText(_("Back to challenge selection")); } else { if (race_manager->isRecordingRace()) top->setText(_("Race against the new ghost replay")); else top->setText(_("Setup New Race")); top->setVisible(true); bottom->setText(_("Back to the menu")); } bottom->setVisible(true); } } // enableAllButtons //----------------------------------------------------------------------------- void RaceResultGUI::eventCallback(GUIEngine::Widget* widget, const std::string& name, const int playerID) { int n_tracks = race_manager->getGrandPrix().getNumberOfTracks(); if (name == "up_button" && n_tracks > m_max_tracks && m_start_track > 0) { m_start_track--; m_end_track--; displayScreenShots(); } else if (name == "down_button" && n_tracks > m_max_tracks && m_start_track < (n_tracks - m_max_tracks)) { m_start_track++; m_end_track++; displayScreenShots(); } // If we're playing online : if (World::getWorld()->isNetworkWorld()) { if (name == "middle") // Continue button (return to server lobby) { // Signal to the server that this client is back in the lobby now. auto cl = LobbyProtocol::get(); if (cl) cl->doneWithResults(); getWidget("middle")->setText(_("Waiting for others")); } if (name == "bottom") // Quit server (return to online lan / wan menu) { race_manager->clearNetworkGrandPrixResult(); if (STKHost::existHost()) { STKHost::get()->shutdown(); } race_manager->exitRace(); race_manager->setAIKartOverride(""); StateManager::get()->resetAndSetStack( NetworkConfig::get()->getResetScreens().data()); NetworkConfig::get()->unsetNetworking(); } return; } // If something was unlocked, the 'continue' button was // actually used to display "Show unlocked feature(s)" text. // --------------------------------------------------------- int n = (int)PlayerManager::getCurrentPlayer() ->getRecentlyCompletedChallenges().size(); if (n>0) { if (name == "top") { if (race_manager->getMajorMode() == RaceManager::MAJOR_MODE_GRAND_PRIX) { cleanupGPProgress(); } std::vector unlocked = PlayerManager::getCurrentPlayer()->getRecentlyCompletedChallenges(); bool gameCompleted = false; for (unsigned int n = 0; n < unlocked.size(); n++) { if (unlocked[n]->getChallengeId() == "fortmagma") { gameCompleted = true; break; } } if (gameCompleted) { // clear the race // kart will no longer be available during cutscene, drop reference StateManager::get()->getActivePlayer(playerID)->setKart(NULL); PropertyAnimator::get()->clear(); World::deleteWorld(); CutsceneWorld::setUseDuration(true); StateManager::get()->enterGameState(); race_manager->setMinorMode(RaceManager::MINOR_MODE_CUTSCENE); race_manager->setNumKarts(0); race_manager->setNumPlayers(0); race_manager->startSingleRace("endcutscene", 999, false); std::vector parts; parts.push_back("endcutscene"); ((CutsceneWorld*)World::getWorld())->setParts(parts); } else { StateManager::get()->popMenu(); PropertyAnimator::get()->clear(); World::deleteWorld(); CutsceneWorld::setUseDuration(false); StateManager::get()->enterGameState(); race_manager->setMinorMode(RaceManager::MINOR_MODE_CUTSCENE); race_manager->setNumKarts(0); race_manager->setNumPlayers(0); race_manager->startSingleRace("featunlocked", 999, race_manager->raceWasStartedFromOverworld()); FeatureUnlockedCutScene* scene = FeatureUnlockedCutScene::getInstance(); scene->addTrophy(race_manager->getDifficulty(),false); scene->findWhatWasUnlocked(race_manager->getDifficulty(),unlocked); scene->push(); race_manager->setAIKartOverride(""); std::vector parts; parts.push_back("featunlocked"); ((CutsceneWorld*)World::getWorld())->setParts(parts); } PlayerManager::getCurrentPlayer()->clearUnlocked(); return; } Log::warn("RaceResultGUI", "Incorrect event '%s' when things are unlocked.", name.c_str()); } // Next check for GP // ----------------- if (race_manager->getMajorMode() == RaceManager::MAJOR_MODE_GRAND_PRIX) { if (name == "middle") // Next GP { cleanupGPProgress(); StateManager::get()->popMenu(); race_manager->next(); } else if (name == "bottom") // Abort { new MessageDialog(_("Do you really want to abort the Grand Prix?"), MessageDialog::MESSAGE_DIALOG_CONFIRM, this, false); } else if (!getWidget(name.c_str())->isVisible()) { Log::warn("RaceResultGUI", "Incorrect event '%s' when things are unlocked.", name.c_str()); } return; } StateManager::get()->popMenu(); if (name == "top") // Setup new race { // Save current race data for race against new ghost std::string track_name = race_manager->getTrackName(); int laps = race_manager->getNumLaps(); bool reverse = race_manager->getReverseTrack(); bool new_ghost_race = race_manager->isRecordingRace(); race_manager->exitRace(); race_manager->setAIKartOverride(""); //If pressing continue quickly in a losing challenge if (race_manager->raceWasStartedFromOverworld()) { StateManager::get()->resetAndGoToScreen(MainMenuScreen::getInstance()); OverWorld::enterOverWorld(); } // Special case : race against a newly saved ghost else if (new_ghost_race) { ReplayPlay::get()->loadAllReplayFile(); unsigned long long int last_uid = ReplayRecorder::get()->getLastUID(); ReplayPlay::get()->setReplayFileByUID(last_uid); race_manager->setRecordRace(true); race_manager->setRaceGhostKarts(true); race_manager->setNumKarts(race_manager->getNumLocalPlayers()); // Disable accidentally unlocking of a challenge PlayerManager::getCurrentPlayer()->setCurrentChallenge(""); race_manager->setReverseTrack(reverse); race_manager->startSingleRace(track_name, laps, false); } else { Screen* newStack[] = { MainMenuScreen::getInstance(), RaceSetupScreen::getInstance(), NULL }; StateManager::get()->resetAndSetStack(newStack); } } else if (name == "middle") // Restart { race_manager->rerunRace(); } else if (name == "bottom") // Back to main { race_manager->exitRace(); race_manager->setAIKartOverride(""); StateManager::get()->resetAndGoToScreen(MainMenuScreen::getInstance()); if (race_manager->raceWasStartedFromOverworld()) { OverWorld::enterOverWorld(); } } else Log::warn("RaceResultGUI", "Incorrect event '%s' for normal race.", name.c_str()); return; } // eventCallback //----------------------------------------------------------------------------- /** Sets up the gui to go back to the lobby. Can only be called in case of a * networked game. */ void RaceResultGUI::backToLobby() { if (race_manager->getMajorMode() == RaceManager::MAJOR_MODE_GRAND_PRIX && race_manager->getTrackNumber() == race_manager->getNumOfTracks() - 1) { core::stringw msg = _("Network grand prix has been finished."); MessageQueue::add(MessageQueue::MT_ACHIEVEMENT, msg); } race_manager->clearNetworkGrandPrixResult(); race_manager->exitRace(); race_manager->setAIKartOverride(""); GUIEngine::ModalDialog::dismiss(); cleanupGPProgress(); StateManager::get()->resetAndSetStack( NetworkConfig::get()->getResetScreens(true/*lobby*/).data()); NetworkingLobby::getInstance()->addMoreServerInfo(L"--------------------"); } // backToLobby //----------------------------------------------------------------------------- void RaceResultGUI::displayCTFResults() { #ifndef SERVER_ONLY //Draw win text core::stringw result_text; video::SColor color = video::SColor(255, 255, 255, 255); video::SColor red_color = video::SColor(255, 255, 0, 0); gui::IGUIFont* font = GUIEngine::getTitleFont(); int current_x = UserConfigParams::m_width / 2; RowInfo *ri = &(m_all_row_infos[0]); int current_y = (int)ri->m_y_pos; CaptureTheFlag* ctf = dynamic_cast(World::getWorld()); const int red_score = ctf->getRedScore(); const int blue_score = ctf->getBlueScore(); GUIEngine::Widget *table_area = getWidget("result-table"); int height = table_area->m_h + table_area->m_y; if (red_score > blue_score) result_text = _("Red Team Wins"); else if (blue_score > red_score) result_text = _("Blue Team Wins"); else result_text = _("It's a draw"); core::rect pos(current_x, current_y, current_x, current_y); font->draw(result_text.c_str(), pos, color, true, true); core::dimension2du rect = font->getDimension(result_text.c_str()); //Draw team scores: current_y += rect.Height; current_x /= 2; irr::video::ITexture* red_icon = irr_driver->getTexture(FileManager::GUI_ICON, "red_flag.png"); irr::video::ITexture* blue_icon = irr_driver->getTexture(FileManager::GUI_ICON, "blue_flag.png"); core::recti source_rect(core::vector2di(0, 0), red_icon->getSize()); core::recti dest_rect(current_x, current_y, current_x + red_icon->getSize().Width / 2, current_y + red_icon->getSize().Height / 2); draw2DImage(red_icon, dest_rect, source_rect, NULL, NULL, true); current_x += UserConfigParams::m_width / 2 - red_icon->getSize().Width / 2; dest_rect = core::recti(current_x, current_y, current_x + red_icon->getSize().Width / 2, current_y + red_icon->getSize().Height / 2); draw2DImage(blue_icon, dest_rect, source_rect, NULL, NULL, true); result_text = StringUtils::toWString(blue_score); rect = font->getDimension(result_text.c_str()); current_x += red_icon->getSize().Width / 4; current_y += red_icon->getSize().Height / 2 + rect.Height / 4; pos = core::rect(current_x, current_y, current_x, current_y); font->draw(result_text.c_str(), pos, color, true, false); current_x -= UserConfigParams::m_width / 2 - red_icon->getSize().Width / 2; result_text = StringUtils::toWString(red_score); pos = core::rect(current_x, current_y, current_x, current_y); font->draw(result_text.c_str(), pos, color, true, false); int center_x = UserConfigParams::m_width / 2; pos = core::rect(center_x, current_y, center_x, current_y); font->draw("-", pos, color, true, false); // The red team player scores: current_y += rect.Height / 2 + rect.Height / 4; font = GUIEngine::getSmallFont(); irr::video::ITexture* kart_icon; int prev_y = current_y; const unsigned num_karts = ctf->getNumKarts(); for (unsigned int i = 0; i < num_karts; i++) { AbstractKart* kart = ctf->getKartAtPosition(i + 1); unsigned kart_id = kart->getWorldKartId(); if (ctf->getKartTeam(kart_id) != KART_TEAM_RED) continue; result_text = kart->getController()->getName(); result_text.append(" "); if (kart->isEliminated()) { result_text.append(_("Eliminated")); } else { result_text.append( StringUtils::toWString(ctf->getKartScore(kart_id))); } rect = font->getDimension(result_text.c_str()); current_y += rect.Height; if (current_y > height) break; pos = core::rect(current_x, current_y, current_x, current_y); font->draw(result_text, pos, kart->getController()->isLocalPlayerController() ? red_color : color, true, false); kart_icon = kart->getKartProperties()->getIconMaterial()->getTexture(); source_rect = core::recti(core::vector2di(0, 0), kart_icon->getSize()); irr::u32 offset_x = (irr::u32)(font->getDimension(result_text.c_str()).Width / 1.5f); dest_rect = core::recti(current_x - offset_x - 30, current_y, current_x - offset_x, current_y + 30); draw2DImage(kart_icon, dest_rect, source_rect, NULL, NULL, true); } // The blue team player scores: current_y = prev_y; current_x += UserConfigParams::m_width / 2 - red_icon->getSize().Width / 2; for (unsigned int i = 0; i < num_karts; i++) { AbstractKart* kart = ctf->getKartAtPosition(i + 1); unsigned kart_id = kart->getWorldKartId(); if (ctf->getKartTeam(kart_id) != KART_TEAM_BLUE) continue; result_text = kart->getController()->getName(); result_text.append(" "); if (kart->isEliminated()) { result_text.append(_("Eliminated")); } else { result_text.append( StringUtils::toWString(ctf->getKartScore(kart_id))); } rect = font->getDimension(result_text.c_str()); current_y += rect.Height; if (current_y > height) break; pos = core::rect(current_x, current_y, current_x, current_y); font->draw(result_text, pos, kart->getController()->isLocalPlayerController() ? red_color : color, true, false); kart_icon = kart->getKartProperties()->getIconMaterial()->getTexture(); source_rect = core::recti(core::vector2di(0, 0), kart_icon->getSize()); irr::u32 offset_x = (irr::u32) (font->getDimension(result_text.c_str()).Width / 1.5f); dest_rect = core::recti(current_x - offset_x - 30, current_y, current_x - offset_x, current_y + 30); draw2DImage(kart_icon, dest_rect, source_rect, NULL, NULL, true); } #endif } //----------------------------------------------------------------------------- void RaceResultGUI::onConfirm() { //race_manager->saveGP(); // Save the aborted GP GUIEngine::ModalDialog::dismiss(); cleanupGPProgress(); StateManager::get()->popMenu(); race_manager->exitRace(); race_manager->setAIKartOverride(""); StateManager::get()->resetAndGoToScreen( MainMenuScreen::getInstance()); if (race_manager->raceWasStartedFromOverworld()) { OverWorld::enterOverWorld(); } } //----------------------------------------------------------------------------- /** This determines the layout, i.e. the size of all columns, font size etc. */ void RaceResultGUI::determineTableLayout() { GUIEngine::Widget *table_area = getWidget("result-table"); m_font = GUIEngine::getFont(); assert(m_font); //m_was_monospace = m_font->getMonospaceDigits(); //m_font->setMonospaceDigits(true); WorldWithRank *rank_world = (WorldWithRank*)World::getWorld(); unsigned int first_position = 1; unsigned int sta = race_manager->getNumSpareTireKarts(); if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_FOLLOW_LEADER) first_position = 2; // Use only the karts that are supposed to be displayed (and // ignore e.g. the leader in a FTL race). unsigned int num_karts = race_manager->getNumberOfKarts() - first_position + 1 - sta; // In FTL races the leader kart is not displayed m_all_row_infos.resize(num_karts); // Determine the kart to display in the right order, // and the maximum width for the kart name column // ------------------------------------------------- m_width_kart_name = 0; float max_finish_time = 0; FreeForAll* ffa = dynamic_cast(World::getWorld()); int time_precision = race_manager->currentModeTimePrecision(); for (unsigned int position = first_position; position <= race_manager->getNumberOfKarts() - sta; position++) { const AbstractKart *kart = rank_world->getKartAtPosition(position); // Save a pointer to the current row_info entry RowInfo *ri = &(m_all_row_infos[position - first_position]); ri->m_is_player_kart = kart->getController()->isLocalPlayerController(); ri->m_kart_name = kart->getController()->getName(); video::ITexture *icon = kart->getKartProperties()->getIconMaterial()->getTexture(); ri->m_kart_icon = icon; // FTL karts will get a time assigned, they are not shown as eliminated if (kart->isEliminated() && race_manager->getMinorMode() != RaceManager::MINOR_MODE_FOLLOW_LEADER) { ri->m_finish_time_string = core::stringw(_("Eliminated")); } else if (race_manager->getMajorMode() == RaceManager::MAJOR_MODE_FREE_FOR_ALL || race_manager->getMajorMode() == RaceManager::MAJOR_MODE_CAPTURE_THE_FLAG) { assert(ffa); ri->m_finish_time_string = StringUtils::toWString(ffa->getKartScore(kart->getWorldKartId())); } else { const float time = kart->getFinishTime(); if (time > max_finish_time) max_finish_time = time; std::string time_string = StringUtils::timeToString(time, time_precision); ri->m_finish_time_string = time_string.c_str(); } core::dimension2du rect = m_font->getDimension(ri->m_kart_name.c_str()); if (rect.Width > m_width_kart_name) m_width_kart_name = rect.Width; } // for position std::string max_time = StringUtils::timeToString(max_finish_time, time_precision); core::stringw string_max_time(max_time.c_str()); core::dimension2du r = m_font->getDimension(string_max_time.c_str()); m_width_finish_time = r.Width; // Top pixel where to display text m_top = table_area->m_y; // Height of the result display unsigned int height = table_area->m_h; // Setup different timing information for the different phases // ----------------------------------------------------------- // How much time between consecutive rows m_time_between_rows = 0.1f; // How long it takes for one line to scroll from right to left m_time_single_scroll = 0.2f; // Time to rotate the entries to the proper GP position. m_time_rotation = 1.0f; // The time the first phase is being displayed: add the start time // of the last kart to the duration of the scroll plus some time // of rest before the next phase starts m_time_overall_scroll = (num_karts - 1)*m_time_between_rows + m_time_single_scroll + 2.0f; // The time to increase the number of points. m_time_for_points = 1.0f; // Determine text height r = m_font->getDimension(L"Y"); m_distance_between_rows = (int)(1.5f*r.Height); // If there are too many karts, reduce size between rows if (m_distance_between_rows * num_karts > height) m_distance_between_rows = height / num_karts; m_width_icon = table_area->m_h / 18; m_width_column_space = 10; // Determine width of new points column //m_font->setMonospaceDigits(true); core::dimension2du r_new_p = m_font->getDimension(L"+99"); m_width_new_points = r_new_p.Width; // Determine width of overall points column core::dimension2du r_all_p = m_font->getDimension(L"999"); //m_font->setMonospaceDigits(false); m_width_all_points = r_all_p.Width; m_table_width = m_width_icon + m_width_column_space + m_width_kart_name; if (race_manager->getMinorMode() != RaceManager::MINOR_MODE_FOLLOW_LEADER) m_table_width += m_width_finish_time + m_width_column_space; // Only in GP mode are the points displayed. if (race_manager->getMajorMode() == RaceManager::MAJOR_MODE_GRAND_PRIX) m_table_width += m_width_new_points + m_width_all_points + 2 * m_width_column_space; m_leftmost_column = table_area->m_x; } // determineTableLayout //----------------------------------------------------------------------------- /** This function is called when one of the player presses 'fire'. The next * phase of the animation will be displayed. E.g. * in a GP: pressing fire while/after showing the latest race result will * start the animation for the current GP result * in a normal race: when pressing fire while an animation is played, * start the menu showing 'rerun, new race, back to main' etc. */ void RaceResultGUI::nextPhase() { // This will trigger the next phase in the next render call. m_timer = 9999; } // nextPhase //----------------------------------------------------------------------------- /** If escape is pressed, don't do the default option (close the screen), but * advance to the next animation phase. */ bool RaceResultGUI::onEscapePressed() { nextPhase(); return false; // indicates 'do not close' } // onEscapePressed //----------------------------------------------------------------------------- /** This is called before an event is sent to a widget. Since in this case * no widget is active, the event would be lost, so we act on fire events * here and trigger the next phase. */ GUIEngine::EventPropagation RaceResultGUI::filterActions(PlayerAction action, int deviceID, const unsigned int value, Input::InputType type, int playerId) { if (action != PA_FIRE) return GUIEngine::EVENT_LET; // If the buttons are already visible, let the event go through since // it will be triggering eventCallback where this is handles. if (m_animation_state == RR_WAIT_TILL_END) return GUIEngine::EVENT_LET; nextPhase(); return GUIEngine::EVENT_BLOCK; } // filterActions //----------------------------------------------------------------------------- /** Called once a frame, this now triggers the rendering of the actual * race result gui. */ void RaceResultGUI::onUpdate(float dt) { renderGlobal(dt); // When the finish sound has been played, start the race over music. if (m_finish_sound && m_finish_sound->getStatus() != SFXBase::SFX_PLAYING) { try { // This call is done once each frame, but startMusic() is cheap // if the music is already playing. music_manager->startMusic(m_race_over_music); } catch (std::exception& e) { Log::error("RaceResultGUI", "Exception caught when " "trying to load music: %s", e.what()); } } } // onUpdate //----------------------------------------------------------------------------- /** Render all global parts of the race gui, i.e. things that are only * displayed once even in splitscreen. * \param dt Timestep sized. */ void RaceResultGUI::renderGlobal(float dt) { #ifndef SERVER_ONLY m_timer += dt; assert(World::getWorld()->getPhase() == WorldStatus::RESULT_DISPLAY_PHASE); unsigned int num_karts = (unsigned int)m_all_row_infos.size(); // First: Update the finite state machine // ====================================== switch (m_animation_state) { case RR_INIT: for (unsigned int i = 0; i < num_karts; i++) { RowInfo *ri = &(m_all_row_infos[i]); ri->m_start_at = m_time_between_rows * i; ri->m_x_pos = (float)UserConfigParams::m_width; ri->m_y_pos = (float)(m_top + i*m_distance_between_rows); } m_animation_state = RR_RACE_RESULT; break; case RR_RACE_RESULT: if (m_timer > m_time_overall_scroll) { // Make sure that all lines are aligned to the left // (in case that the animation was skipped). for (unsigned int i = 0; i < num_karts; i++) { RowInfo *ri = &(m_all_row_infos[i]); ri->m_x_pos = (float)m_leftmost_column; } if (race_manager->getMajorMode() != RaceManager::MAJOR_MODE_GRAND_PRIX) { m_animation_state = RR_WAIT_TILL_END; enableAllButtons(); break; } determineGPLayout(); m_animation_state = RR_OLD_GP_RESULTS; m_timer = 0; } break; case RR_OLD_GP_RESULTS: if (m_timer > m_time_overall_scroll) { m_animation_state = RR_INCREASE_POINTS; m_timer = 0; for (unsigned int i = 0; i < num_karts; i++) { RowInfo *ri = &(m_all_row_infos[i]); ri->m_x_pos = (float)m_leftmost_column; } } break; case RR_INCREASE_POINTS: // Have one second delay before the resorting starts. if (m_timer > 1 + m_time_for_points) { m_animation_state = RR_RESORT_TABLE; if (m_gp_position_was_changed) m_timer = 0; else // This causes the phase to go to RESORT_TABLE once, and then // immediately wait till end. This has the advantage that any // phase change settings will be processed properly. m_timer = m_time_rotation + 1; // Make the new row permanent; necessary in case // that the animation is skipped. for (unsigned int i = 0; i < num_karts; i++) { RowInfo *ri = &(m_all_row_infos[i]); ri->m_new_points = 0; ri->m_current_displayed_points = (float)ri->m_new_overall_points; } } break; case RR_RESORT_TABLE: if (m_timer > m_time_rotation) { m_animation_state = RR_WAIT_TILL_END; // Make the new row permanent. for (unsigned int i = 0; i < num_karts; i++) { RowInfo *ri = &(m_all_row_infos[i]); ri->m_y_pos = ri->m_centre_point - ri->m_radius; } enableAllButtons(); } break; case RR_WAIT_TILL_END: if (race_manager->getMajorMode() == RaceManager::MAJOR_MODE_GRAND_PRIX) displayGPProgress(); if (m_timer - m_time_rotation > 1.0f && dynamic_cast(World::getWorld())) { race_manager->exitRace(); StateManager::get()->resetAndGoToScreen(MainMenuScreen::getInstance()); } break; } // switch // Second phase: update X and Y positions for the various animations // ================================================================= float v = 0.9f*UserConfigParams::m_width / m_time_single_scroll; if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_SOCCER) { displaySoccerResults(); } else if (race_manager->getMajorMode() == RaceManager::MAJOR_MODE_CAPTURE_THE_FLAG) { displayCTFResults(); } else { for (unsigned int i = 0; i < m_all_row_infos.size(); i++) { RowInfo *ri = &(m_all_row_infos[i]); float x = ri->m_x_pos; float y = ri->m_y_pos; switch (m_animation_state) { // Both states use the same scrolling: case RR_INIT: break; // Remove compiler warning case RR_RACE_RESULT: case RR_OLD_GP_RESULTS: if (m_timer > ri->m_start_at) { // if active ri->m_x_pos -= dt*v; if (ri->m_x_pos < m_leftmost_column) ri->m_x_pos = (float)m_leftmost_column; x = ri->m_x_pos; } break; case RR_INCREASE_POINTS: { WorldWithRank *wwr = dynamic_cast(World::getWorld()); assert(wwr); int most_points; if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_FOLLOW_LEADER) most_points = wwr->getScoreForPosition(2); else most_points = wwr->getScoreForPosition(1); ri->m_current_displayed_points += dt*most_points / m_time_for_points; if (ri->m_current_displayed_points > ri->m_new_overall_points) { ri->m_current_displayed_points = (float)ri->m_new_overall_points; } ri->m_new_points -= dt*most_points / m_time_for_points; if (ri->m_new_points < 0) ri->m_new_points = 0; break; } case RR_RESORT_TABLE: x = ri->m_x_pos - ri->m_radius*sin(m_timer / m_time_rotation*M_PI); y = ri->m_centre_point + ri->m_radius*cos(m_timer / m_time_rotation*M_PI); break; case RR_WAIT_TILL_END: break; } // switch displayOneEntry((unsigned int)x, (unsigned int)y, i, true); } // for i } // Display highscores if (race_manager->getMajorMode() != RaceManager::MAJOR_MODE_GRAND_PRIX || m_animation_state == RR_RACE_RESULT) { displayPostRaceInfo(); } #endif } // renderGlobal //----------------------------------------------------------------------------- /** Determine the layout and fields for the GP table based on the previous * GP results. */ void RaceResultGUI::determineGPLayout() { #ifndef SERVER_ONLY unsigned int num_karts = race_manager->getNumberOfKarts(); std::vector old_rank(num_karts, 0); // Update the kart GP ranks // This is useful, e.g., when continuing a saved GP. race_manager->computeGPRanks(); int time_precision = race_manager->currentModeTimePrecision(); for (unsigned int kart_id = 0; kart_id < num_karts; kart_id++) { int rank = race_manager->getKartGPRank(kart_id); // In case of FTL mode: ignore the leader if (rank < 0) continue; old_rank[kart_id] = rank; const AbstractKart *kart = World::getWorld()->getKart(kart_id); RowInfo *ri = &(m_all_row_infos[rank]); ri->m_kart_icon = kart->getKartProperties()->getIconMaterial()->getTexture(); ri->m_is_player_kart = kart->getController()->isLocalPlayerController(); ri->m_kart_name = kart->getController()->getName(); // In FTL karts do have a time, which is shown even when the kart // is eliminated if (kart->isEliminated() && race_manager->getMinorMode() != RaceManager::MINOR_MODE_FOLLOW_LEADER) { ri->m_finish_time_string = core::stringw(_("Eliminated")); } else { float time = race_manager->getOverallTime(kart_id); ri->m_finish_time_string = StringUtils::timeToString(time, time_precision).c_str(); } ri->m_start_at = m_time_between_rows * rank; ri->m_x_pos = (float)UserConfigParams::m_width; ri->m_y_pos = (float)(m_top + rank*m_distance_between_rows); int p = race_manager->getKartPrevScore(kart_id); ri->m_current_displayed_points = (float)p; if (kart->isEliminated() && race_manager->getMinorMode() != RaceManager::MINOR_MODE_FOLLOW_LEADER) { ri->m_new_points = 0; } else { WorldWithRank *wwr = dynamic_cast(World::getWorld()); assert(wwr); ri->m_new_points = (float)wwr->getScoreForPosition(kart->getPosition()); } } // Now update the GP ranks, and determine the new position // ------------------------------------------------------- race_manager->computeGPRanks(); m_gp_position_was_changed = false; for (unsigned int i = 0; i < num_karts; i++) { int j = old_rank[i]; int gp_position = race_manager->getKartGPRank(i); m_gp_position_was_changed |= j != gp_position; RowInfo *ri = &(m_all_row_infos[j]); ri->m_radius = (j - gp_position)*(int)m_distance_between_rows*0.5f; ri->m_centre_point = m_top + (gp_position + j)*m_distance_between_rows*0.5f; int p = race_manager->getKartScore(i); ri->m_new_overall_points = p; } // i < num_karts #endif } // determineGPLayout //----------------------------------------------------------------------------- /** Displays the race results for a single kart. * \param n Index of the kart to be displayed. * \param display_points True if GP points should be displayed, too */ void RaceResultGUI::displayOneEntry(unsigned int x, unsigned int y, unsigned int n, bool display_points) { #ifndef SERVER_ONLY RowInfo *ri = &(m_all_row_infos[n]); video::SColor color = ri->m_is_player_kart ? video::SColor(255, 255, 0, 0) : video::SColor(255, 255, 255, 255); unsigned int current_x = x; // First draw the icon // ------------------- if (ri->m_kart_icon) { core::recti source_rect(core::vector2di(0, 0), ri->m_kart_icon->getSize()); core::recti dest_rect(current_x, y, current_x + m_width_icon, y + m_width_icon); draw2DImage(ri->m_kart_icon, dest_rect, source_rect, NULL, NULL, true); } current_x += m_width_icon + m_width_column_space; // Draw the name // ------------- core::recti pos_name(current_x, y, UserConfigParams::m_width, y + m_distance_between_rows); m_font->draw(ri->m_kart_name, pos_name, color, false, false, NULL, true /* ignoreRTL */); current_x += m_width_kart_name + m_width_column_space; core::recti dest_rect = core::recti(current_x, y, current_x + 100, y + 10); m_font->draw(ri->m_finish_time_string, dest_rect, color, false, false, NULL, true /* ignoreRTL */); current_x += m_width_finish_time + m_width_column_space; // Only display points in GP mode and when the GP results are displayed. // ===================================================================== if (race_manager->getMajorMode() == RaceManager::MAJOR_MODE_GRAND_PRIX && m_animation_state != RR_RACE_RESULT) { // Draw the new points // ------------------- if (ri->m_new_points > 0) { core::recti dest_rect = core::recti(current_x, y, current_x + 100, y + 10); core::stringw point_string = core::stringw("+") + core::stringw((int)ri->m_new_points); // With mono-space digits space has the same width as each digit, // so we can simply fill up the string with spaces to get the // right aligned. while (point_string.size() < 3) point_string = core::stringw(" ") + point_string; m_font->draw(point_string, dest_rect, color, false, false, NULL, true /* ignoreRTL */); } current_x += m_width_new_points + m_width_column_space; // Draw the old_points plus increase value // --------------------------------------- core::recti dest_rect = core::recti(current_x, y, current_x + 100, y + 10); core::stringw point_inc_string = core::stringw((int)(ri->m_current_displayed_points)); while (point_inc_string.size() < 3) point_inc_string = core::stringw(" ") + point_inc_string; m_font->draw(point_inc_string, dest_rect, color, false, false, NULL, true /* ignoreRTL */); } #endif } // displayOneEntry //----------------------------------------------------------------------------- void RaceResultGUI::displaySoccerResults() { #ifndef SERVER_ONLY //Draw win text core::stringw result_text; static video::SColor color = video::SColor(255, 255, 255, 255); gui::IGUIFont* font = GUIEngine::getTitleFont(); int current_x = UserConfigParams::m_width / 2; RowInfo *ri = &(m_all_row_infos[0]); int current_y = (int)ri->m_y_pos; SoccerWorld* sw = (SoccerWorld*)World::getWorld(); const int red_score = sw->getScore(KART_TEAM_RED); const int blue_score = sw->getScore(KART_TEAM_BLUE); GUIEngine::Widget *table_area = getWidget("result-table"); int height = table_area->m_h + table_area->m_y; if (red_score > blue_score) { result_text = _("Red Team Wins"); } else if (blue_score > red_score) { result_text = _("Blue Team Wins"); } else { //Cannot really happen now. Only in time limited matches. result_text = _("It's a draw"); } core::rect pos(current_x, current_y, current_x, current_y); font->draw(result_text.c_str(), pos, color, true, true); core::dimension2du rect = font->getDimension(result_text.c_str()); //Draw team scores: current_y += rect.Height; current_x /= 2; irr::video::ITexture* red_icon = irr_driver->getTexture(FileManager::GUI_ICON, "soccer_ball_red.png"); irr::video::ITexture* blue_icon = irr_driver->getTexture(FileManager::GUI_ICON, "soccer_ball_blue.png"); core::recti source_rect(core::vector2di(0, 0), red_icon->getSize()); core::recti dest_rect(current_x, current_y, current_x + red_icon->getSize().Width / 2, current_y + red_icon->getSize().Height / 2); draw2DImage(red_icon, dest_rect, source_rect, NULL, NULL, true); current_x += UserConfigParams::m_width / 2 - red_icon->getSize().Width / 2; dest_rect = core::recti(current_x, current_y, current_x + red_icon->getSize().Width / 2, current_y + red_icon->getSize().Height / 2); draw2DImage(blue_icon, dest_rect, source_rect, NULL, NULL, true); result_text = StringUtils::toWString(blue_score); rect = font->getDimension(result_text.c_str()); current_x += red_icon->getSize().Width / 4; current_y += red_icon->getSize().Height / 2 + rect.Height / 4; pos = core::rect(current_x, current_y, current_x, current_y); font->draw(result_text.c_str(), pos, color, true, false); current_x -= UserConfigParams::m_width / 2 - red_icon->getSize().Width / 2; result_text = StringUtils::toWString(red_score); pos = core::rect(current_x, current_y, current_x, current_y); font->draw(result_text.c_str(), pos, color, true, false); int center_x = UserConfigParams::m_width / 2; pos = core::rect(center_x, current_y, center_x, current_y); font->draw("-", pos, color, true, false); //Draw goal scorers: //The red scorers: current_y += rect.Height / 2 + rect.Height / 4; font = GUIEngine::getSmallFont(); std::vector scorers = sw->getScorers(KART_TEAM_RED); while (scorers.size() > 10) { scorers.erase(scorers.begin()); } std::vector score_times = sw->getScoreTimes(KART_TEAM_RED); while (score_times.size() > 10) { score_times.erase(score_times.begin()); } irr::video::ITexture* scorer_icon; int prev_y = current_y; for (unsigned int i = 0; i < scorers.size(); i++) { const bool own_goal = !(scorers.at(i).m_correct_goal); const int kart_id = scorers.at(i).m_id; result_text = sw->getKart(kart_id)->getController()->getName(); if (own_goal) { result_text.append(" "); //I18N: indicates a player that scored in their own goal in result screen result_text.append(_("(Own Goal)")); } result_text.append(" "); result_text.append(StringUtils::timeToString(score_times.at(i)).c_str()); rect = font->getDimension(result_text.c_str()); if (height - prev_y < ((short)scorers.size() + 1)*(short)rect.Height) current_y += (height - prev_y) / ((short)scorers.size() + 1); else current_y += rect.Height; if (current_y > height) break; pos = core::rect(current_x, current_y, current_x, current_y); font->draw(result_text, pos, (own_goal ? video::SColor(255, 255, 0, 0) : color), true, false); scorer_icon = sw->getKart(scorers.at(i).m_id) ->getKartProperties()->getIconMaterial()->getTexture(); source_rect = core::recti(core::vector2di(0, 0), scorer_icon->getSize()); irr::u32 offset_x = (irr::u32)(font->getDimension(result_text.c_str()).Width / 1.5f); dest_rect = core::recti(current_x - offset_x - 30, current_y, current_x - offset_x, current_y + 30); draw2DImage(scorer_icon, dest_rect, source_rect, NULL, NULL, true); } //The blue scorers: current_y = prev_y; current_x += UserConfigParams::m_width / 2 - red_icon->getSize().Width / 2; scorers = sw->getScorers(KART_TEAM_BLUE); while (scorers.size() > 10) { scorers.erase(scorers.begin()); } score_times = sw->getScoreTimes(KART_TEAM_BLUE); while (score_times.size() > 10) { score_times.erase(score_times.begin()); } for (unsigned int i = 0; i < scorers.size(); i++) { const bool own_goal = !(scorers.at(i).m_correct_goal); const int kart_id = scorers.at(i).m_id; result_text = sw->getKart(kart_id)->getController()->getName(); if (own_goal) { result_text.append(" "); result_text.append(_("(Own Goal)")); } result_text.append(" "); result_text.append(StringUtils::timeToString(score_times.at(i)).c_str()); rect = font->getDimension(result_text.c_str()); if (height - prev_y < ((short)scorers.size() + 1)*(short)rect.Height) current_y += (height - prev_y) / ((short)scorers.size() + 1); else current_y += rect.Height; if (current_y > height) break; pos = core::rect(current_x, current_y, current_x, current_y); font->draw(result_text, pos, (own_goal ? video::SColor(255, 255, 0, 0) : color), true, false); scorer_icon = sw->getKart(scorers.at(i).m_id)-> getKartProperties()->getIconMaterial()->getTexture(); source_rect = core::recti(core::vector2di(0, 0), scorer_icon->getSize()); irr::u32 offset_x = (irr::u32)(font->getDimension(result_text.c_str()).Width / 1.5f); dest_rect = core::recti(current_x - offset_x - 30, current_y, current_x - offset_x, current_y + 30); draw2DImage(scorer_icon, dest_rect, source_rect, NULL, NULL, true); } #endif } //----------------------------------------------------------------------------- void RaceResultGUI::clearHighscores() { m_highscore_rank = 0; } // clearHighscores //----------------------------------------------------------------------------- void RaceResultGUI::setHighscore(int rank) { m_highscore_rank = rank; } // setHighscore // ---------------------------------------------------------------------------- void RaceResultGUI::enableGPProgress() { if (race_manager->getMajorMode() == RaceManager::MAJOR_MODE_GRAND_PRIX) { GUIEngine::Widget* result_table = getWidget("result-table"); assert(result_table != NULL); int currentTrack = race_manager->getTrackNumber(); int font_height = getFontHeight(); int w = (int)(UserConfigParams::m_width*0.17); int x = (int)(result_table->m_x + result_table->m_w - w - 15); int y = (m_top + font_height + 5); //Current progress GUIEngine::LabelWidget* status_label = new GUIEngine::LabelWidget(); status_label->m_properties[GUIEngine::PROP_ID] = "status_label"; status_label->m_properties[GUIEngine::PROP_TEXT_ALIGN] = "center"; status_label->m_x = x; status_label->m_y = y; status_label->m_w = w; status_label->m_h = font_height; status_label->add(); status_label->setText(_("Track %i/%i", currentTrack + 1, race_manager->getGrandPrix().getNumberOfTracks()), true); addGPProgressWidget(status_label); y = (status_label->m_y + status_label->m_h + 5); //Scroll up button GUIEngine::IconButtonWidget* up_button = new GUIEngine::IconButtonWidget( GUIEngine::IconButtonWidget::SCALE_MODE_KEEP_CUSTOM_ASPECT_RATIO, false, false, GUIEngine::IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE); up_button->m_properties[GUIEngine::PROP_ID] = "up_button"; up_button->m_x = x; up_button->m_y = y; up_button->m_w = w; up_button->m_h = font_height; up_button->add(); up_button->setImage(file_manager->getAsset(FileManager::GUI_ICON, "scroll_up.png")); addGPProgressWidget(up_button); y = (up_button->m_y + up_button->m_h + SSHOT_SEPARATION); //Track screenshots and labels int n_sshot = 1; for (int i = m_start_track; i < m_end_track; i++) { //Screenshot GUIEngine::IconButtonWidget* screenshot_widget = new GUIEngine::IconButtonWidget( GUIEngine::IconButtonWidget:: SCALE_MODE_KEEP_CUSTOM_ASPECT_RATIO, false, false, GUIEngine::IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE); screenshot_widget->setCustomAspectRatio(4.0f / 3.0f); screenshot_widget->m_x = x; screenshot_widget->m_y = y; screenshot_widget->m_w = w; screenshot_widget->m_h = m_sshot_height; screenshot_widget->m_properties[GUIEngine::PROP_ID] = ("sshot_" + StringUtils::toString(n_sshot)); screenshot_widget->add(); addGPProgressWidget(screenshot_widget); //Label GUIEngine::LabelWidget* sshot_label = new GUIEngine::LabelWidget(); sshot_label->m_properties[GUIEngine::PROP_ID] = ("sshot_label_" + StringUtils::toString(n_sshot)); sshot_label->m_properties[GUIEngine::PROP_TEXT_ALIGN] = "left"; sshot_label->m_x = (x + w + 5); sshot_label->m_y = (y + (m_sshot_height / 2) - (font_height / 2)); sshot_label->m_w = (w / 2); sshot_label->m_h = font_height; sshot_label->add(); addGPProgressWidget(sshot_label); y += (m_sshot_height + SSHOT_SEPARATION); n_sshot++; } // for displayScreenShots(); //Scroll down button GUIEngine::IconButtonWidget* down_button = new GUIEngine::IconButtonWidget( GUIEngine::IconButtonWidget::SCALE_MODE_KEEP_CUSTOM_ASPECT_RATIO, false, false, GUIEngine::IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE); down_button->m_properties[GUIEngine::PROP_ID] = "down_button"; down_button->m_x = x; down_button->m_y = y; down_button->m_w = w; down_button->m_h = font_height; down_button->add(); down_button->setImage(file_manager->getAsset(FileManager::GUI_ICON, "scroll_down.png")); addGPProgressWidget(down_button); } // if MAJOR_MODE_GRAND_PRIX) } // enableGPProgress // ---------------------------------------------------------------------------- void RaceResultGUI::addGPProgressWidget(GUIEngine::Widget* widget) { m_widgets.push_back(widget); m_gp_progress_widgets.push_back(widget); } // ---------------------------------------------------------------------------- void RaceResultGUI::displayGPProgress() { core::stringw msg = _("Grand Prix progress:"); GUIEngine::Widget* result_table = getWidget("result-table"); assert(result_table != NULL); video::SColor color = video::SColor(255, 255, 0, 0); core::recti dest_rect( result_table->m_x + result_table->m_w - m_font->getDimension(msg.c_str()).Width - 5, m_top, 0, 0); m_font->draw(msg, dest_rect, color, false, false, NULL, true); } // displayGPProgress // ---------------------------------------------------------------------------- void RaceResultGUI::cleanupGPProgress() { for (unsigned int i = 0; i < m_gp_progress_widgets.size(); i++) m_widgets.remove(m_gp_progress_widgets.get(i)); m_gp_progress_widgets.clearAndDeleteAll(); } // cleanupGPProgress // ---------------------------------------------------------------------------- void RaceResultGUI::displayPostRaceInfo() { #ifndef SERVER_ONLY // This happens in demo world if (!World::getWorld()) return; Highscores* scores = World::getWorld()->getHighscores(); video::SColor white_color = video::SColor(255, 255, 255, 255); int x = (int)(UserConfigParams::m_width*0.65f); int y = m_top; int current_y = y; int time_precision = race_manager->currentModeTimePrecision(); // In some case for exemple FTL they will be no highscores if (scores != NULL) { // First draw title GUIEngine::getFont()->draw(_("Highscores"), core::recti(x, y, 0, 0), white_color, false, false, NULL, true /* ignoreRTL */); std::string kart_name; irr::core::stringw player_name; // prevent excessive long name unsigned int max_characters = 15; unsigned int max_width = (UserConfigParams::m_width / 2 - 200) / 10; if (max_width < 15) max_characters = max_width; float time; for (int i = 0; i < scores->getNumberEntries(); i++) { scores->getEntry(i, kart_name, player_name, &time); if (player_name.size() > max_characters) { int begin = (int(m_timer / 0.4f)) % (player_name.size() - max_characters); player_name = player_name.subString(begin, max_characters, false); } video::SColor text_color = white_color; if (m_highscore_rank - 1 == i) { text_color = video::SColor(255, 255, 0, 0); } int current_x = x; current_y = y + (int)((i + 1) * m_distance_between_rows); const KartProperties* prop = kart_properties_manager->getKart(kart_name); if (prop != NULL) { const std::string &icon_path = prop->getAbsoluteIconFile(); video::ITexture* kart_icon_texture = irr_driver->getTexture(icon_path); if (kart_icon_texture != NULL) { core::recti source_rect(core::vector2di(0, 0), kart_icon_texture->getSize()); core::recti dest_rect(current_x, current_y, current_x + m_width_icon, current_y + m_width_icon); draw2DImage( kart_icon_texture, dest_rect, source_rect, NULL, NULL, true); current_x += m_width_icon + m_width_column_space; } } // draw the player name GUIEngine::getSmallFont()->draw(player_name.c_str(), core::recti(current_x, current_y, current_x + 150, current_y + 10), text_color, false, false, NULL, true /* ignoreRTL */); current_x = (int)(UserConfigParams::m_width * 0.85f); // Finally draw the time std::string time_string = StringUtils::timeToString(time, time_precision); GUIEngine::getSmallFont()->draw(time_string.c_str(), core::recti(current_x, current_y, current_x + 100, current_y + 10), text_color, false, false, NULL, true /* ignoreRTL */); } } if (race_manager->getMinorMode() != RaceManager::MINOR_MODE_SOCCER) { // display lap count if (race_manager->modeHasLaps()) { core::stringw laps = _("Laps: %i", race_manager->getNumLaps()); current_y += int(m_distance_between_rows * 0.8f * 2); GUIEngine::getFont()->draw(laps, core::recti(x, current_y, 0, 0), white_color, false, false, nullptr, true); } // display difficulty const core::stringw& difficulty_name = race_manager->getDifficultyName(race_manager->getDifficulty()); core::stringw difficulty_string = _("Difficulty: %s", difficulty_name); current_y += int(m_distance_between_rows * 0.8f); GUIEngine::getFont()->draw(difficulty_string, core::recti(x, current_y, 0, 0), white_color, false, false, nullptr, true); // show fastest lap if (race_manager->modeHasLaps()) { float best_lap_time = static_cast(World::getWorld())->getFastestLap(); core::stringw best_lap_string = _("Best lap time: %s", StringUtils::timeToString(best_lap_time, time_precision).c_str()); current_y += int(m_distance_between_rows * 0.8f); GUIEngine::getFont()->draw(best_lap_string, core::recti(x, current_y, 0, 0), white_color, false, false, nullptr, true); } // if mode has laps } // if not soccer mode #endif } // ---------------------------------------------------------------------------- void RaceResultGUI::displayScreenShots() { const std::vector tracks = race_manager->getGrandPrix().getTrackNames(); int currentTrack = race_manager->getTrackNumber(); int n_sshot = 1; for (int i = m_start_track; i < m_end_track; i++) { Track* track = track_manager->getTrack(tracks[i]); GUIEngine::IconButtonWidget* sshot = getWidget( ("sshot_" + StringUtils::toString(n_sshot)).c_str()); GUIEngine::LabelWidget* label = getWidget( ("sshot_label_" + StringUtils::toString(n_sshot)).c_str()); assert(sshot != NULL && label != NULL); // Network grand prix chooses each track 1 by 1 if (track == NULL) { sshot->setImage(file_manager->getAsset(FileManager::GUI_ICON, "main_help.png")); } else sshot->setImage(track->getScreenshotFile()); if (i <= currentTrack) sshot->setBadge(GUIEngine::OK_BADGE); else sshot->resetAllBadges(); label->setText(StringUtils::toWString(i + 1), true); n_sshot++; } } // ---------------------------------------------------------------------------- int RaceResultGUI::getFontHeight() const { assert(m_font != NULL); return m_font->getDimension(L"A").Height; //Could be any capital letter }