diff --git a/data/gui/dialogs/high_score_info_dialog.stkgui b/data/gui/dialogs/high_score_info_dialog.stkgui new file mode 100644 index 000000000..532f1c22d --- /dev/null +++ b/data/gui/dialogs/high_score_info_dialog.stkgui @@ -0,0 +1,57 @@ + + +
+ +
+
+
+ +
+ + + + + + +
+ +
+
+
+ +
+
+ + +
+ +
+ +
+ + + + + +
+ +
+
+
+
diff --git a/data/gui/icons/bomb_icon.png b/data/gui/icons/bomb_icon.png index 6e7238ea1..1aa4d3dec 100644 Binary files a/data/gui/icons/bomb_icon.png and b/data/gui/icons/bomb_icon.png differ diff --git a/data/gui/screens/high_score_selection.stkgui b/data/gui/screens/high_score_selection.stkgui new file mode 100644 index 000000000..61c7c3691 --- /dev/null +++ b/data/gui/screens/high_score_selection.stkgui @@ -0,0 +1,51 @@ + + +
+ + + +
+ +
+
+ + + + + + + + + + + + + + + +
+
diff --git a/data/gui/screens/highscore_info.stkgui b/data/gui/screens/highscore_info.stkgui new file mode 100644 index 000000000..babfa7fc9 --- /dev/null +++ b/data/gui/screens/highscore_info.stkgui @@ -0,0 +1,68 @@ + + + + +
+
+ + +
+ + +
+ +
+ + +
+
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+
+
+ + + + + +
+ + + + + + + + +
+
diff --git a/data/gui/screens/main_menu.stkgui b/data/gui/screens/main_menu.stkgui index 42d764b2f..9c14a5da5 100644 --- a/data/gui/screens/main_menu.stkgui +++ b/data/gui/screens/main_menu.stkgui @@ -46,13 +46,15 @@ + raw_text="TEST: Outro" label_location="hover"/> +
- diff --git a/sources.cmake b/sources.cmake index d4f28ae4d..ba4868d71 100644 --- a/sources.cmake +++ b/sources.cmake @@ -1,5 +1,5 @@ # Modify this file to change the last-modified date when you add/remove a file. -# This will then trigger a new cmake run automatically. +# This will then trigger a new cmake run automatically. file(GLOB_RECURSE STK_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.hpp") file(GLOB_RECURSE STK_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.cpp") file(GLOB_RECURSE STK_SHADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "data/shaders/*") diff --git a/src/config/user_config.hpp b/src/config/user_config.hpp index 3c4cf01e7..7c9ef79c3 100644 --- a/src/config/user_config.hpp +++ b/src/config/user_config.hpp @@ -724,6 +724,9 @@ namespace UserConfigParams &m_recording_group, "Specify the fps of recording video")); // ---- Debug - not saved to config file + /** If high scores will not be saved. For repeated testing on tracks. */ + PARAM_PREFIX bool m_no_high_scores PARAM_DEFAULT(false); + /** If gamepad debugging is enabled. */ PARAM_PREFIX bool m_unit_testing PARAM_DEFAULT(false); @@ -736,7 +739,7 @@ namespace UserConfigParams /** Wiimote debugging. */ PARAM_PREFIX bool m_wiimote_debug PARAM_DEFAULT( false ); - /** Debug gamepads by visualising their values. */ + /** Debug gamepads by visualising their values. */ PARAM_PREFIX bool m_gamepad_visualisation PARAM_DEFAULT( false ); /** If material debugging (printing terrain specific slowdown) diff --git a/src/main.cpp b/src/main.cpp index c05d4dd6f..35331eef8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -937,6 +937,8 @@ int handleCmdLine(bool has_server_config, bool has_parent_process) if (CommandLine::has("--unit-testing")) UserConfigParams::m_unit_testing = true; + if (CommandLine::has("--no-high-scores")) + UserConfigParams::m_no_high_scores=true; if (CommandLine::has("--gamepad-debug")) UserConfigParams::m_gamepad_debug=true; if (CommandLine::has("--keyboard-debug")) diff --git a/src/modes/world.cpp b/src/modes/world.cpp index bee2a8b2b..734591b34 100644 --- a/src/modes/world.cpp +++ b/src/modes/world.cpp @@ -285,9 +285,9 @@ void World::init() if (Camera::getNumCameras() == 0) { auto cl = LobbyProtocol::get(); - if ( (NetworkConfig::get()->isServer() && - !GUIEngine::isNoGraphics() ) || - RaceManager::get()->isWatchingReplay() || + if ((NetworkConfig::get()->isServer() && + !GUIEngine::isNoGraphics()) || + RaceManager::get()->isWatchingReplay() || (cl && cl->isSpectator())) { // In case that the server is running with gui, watching replay or @@ -581,8 +581,8 @@ Controller* World::loadAIController(AbstractKart* kart) { case 0: // If requested, start the test ai - if( (AIBaseController::getTestAI()!=0 ) && - ( (kart->getWorldKartId()+1) % AIBaseController::getTestAI() )==0) + if((AIBaseController::getTestAI()!=0) && + ((kart->getWorldKartId()+1) % AIBaseController::getTestAI()) == 0) controller = new TestAI(kart); else controller = new SkiddingAI(kart); @@ -748,11 +748,20 @@ void World::terminateRace() } } // igetNumNonGhostKarts() > 0 && + RaceManager::get()->getNumLaps() > 0 && + !(UserConfigParams::m_no_high_scores)) { updateHighscores(&best_highscore_rank); } @@ -870,7 +879,7 @@ void World::resetAllKarts() (*i)->getMaterial() && (*i)->getMaterial()->hasGravity() ? (*i)->getNormal() * -g : Vec3(0, -g, 0)); } - for(int i=0; igetPhysicsFPS(); i++) + for(int i=0; igetPhysicsFPS(); i++) Physics::get()->update(1); for ( KartList::iterator i=m_karts.begin(); i!=m_karts.end(); i++) @@ -1168,7 +1177,7 @@ void World::update(int ticks) PROFILER_PUSH_CPU_MARKER("World::update (Kart::upate)", 0x40, 0x7F, 0x00); // Update all the karts. This in turn will also update the controller, - // which causes all AI steering commands set. So in the following + // which causes all AI steering commands set. So in the following // physics update the new steering is taken into account. const int kart_amount = (int)m_karts.size(); for (int i = 0 ; i < kart_amount; ++i) @@ -1289,10 +1298,10 @@ void World::updateHighscores(int* best_highscore_rank) // Only record times for local player karts and only if // they finished the race - if(!m_karts[index[pos]]->getController()->isLocalPlayerController()) + if (!m_karts[index[pos]]->getController()->isLocalPlayerController() || + !m_karts[index[pos]]->hasFinishedRace() || + m_karts[index[pos]]->isEliminated()) continue; - if (!m_karts[index[pos]]->hasFinishedRace()) continue; - if (m_karts[index[pos]]->isEliminated()) continue; assert(index[pos] < m_karts.size()); Kart *k = (Kart*)m_karts[index[pos]].get(); @@ -1313,6 +1322,9 @@ void World::updateHighscores(int* best_highscore_rank) *best_highscore_rank = highscore_rank; } + Highscores::setSortOrder(Highscores::SO_DEFAULT); + highscore_manager->sortHighscores(false); + highscore_manager->saveHighscores(); } } // next position @@ -1618,7 +1630,7 @@ void World::setAITeam() { m_red_ai = RaceManager::get()->getNumberOfRedAIKarts(); m_blue_ai = RaceManager::get()->getNumberOfBlueAIKarts(); - + for (int i = 0; i < (int)RaceManager::get()->getNumLocalPlayers(); i++) { KartTeam team = RaceManager::get()->getKartInfo(i).getKartTeam(); diff --git a/src/race/highscore_manager.cpp b/src/race/highscore_manager.cpp index 50409004f..59b158aab 100644 --- a/src/race/highscore_manager.cpp +++ b/src/race/highscore_manager.cpp @@ -44,9 +44,8 @@ HighscoreManager::HighscoreManager() HighscoreManager::~HighscoreManager() { saveHighscores(); - for(type_all_scores::iterator i = m_all_scores.begin(); - i != m_all_scores.end(); i++) - delete *i; + clearHighscores(); + } // ~HighscoreManager // ----------------------------------------------------------------------------- @@ -124,7 +123,7 @@ void HighscoreManager::loadHighscores() Log::error("Highscore Manager", "Invalid highscore entry will be skipped : %s\n", e.what()); continue; } - m_all_scores.push_back(highscores); + m_all_scores.push_back(std::unique_ptr(highscores)); } // next entry if(UserConfigParams::logMisc()) @@ -186,20 +185,19 @@ Highscores* HighscoreManager::getHighscores(const Highscores::HighscoreType &hig Highscores *highscores = 0; // See if we already have a record for this type - for(type_all_scores::iterator i = m_all_scores.begin(); - i != m_all_scores.end(); i++) + for (auto& hs : m_all_scores) { - if((*i)->matches(highscore_type, num_karts, difficulty, trackName, + if (hs->matches(highscore_type, num_karts, difficulty, trackName, number_of_laps, reverse) ) { // we found one entry for this kind of race, return it - return (*i); + return hs.get(); } } // for i in m_all_scores // we don't have an entry for such a race currently. Create one. highscores = new Highscores(highscore_type, num_karts, difficulty, trackName, number_of_laps, reverse); - m_all_scores.push_back(highscores); + m_all_scores.push_back(std::unique_ptr(highscores)); return highscores; } // getHighscores diff --git a/src/race/highscore_manager.hpp b/src/race/highscore_manager.hpp index 898716479..c305cd892 100644 --- a/src/race/highscore_manager.hpp +++ b/src/race/highscore_manager.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include "race/highscores.hpp" @@ -36,25 +37,46 @@ class HighscoreManager public: private: static const unsigned int CURRENT_HSCORE_FILE_VERSION; - typedef std::vector type_all_scores; - type_all_scores m_all_scores; + std::vector > m_all_scores; std::string m_filename; bool m_can_write; - void loadHighscores(); - void setFilename(); + void setFilename(); public: HighscoreManager(); ~HighscoreManager(); + // ------------------------------------------------------------------------ + void loadHighscores(); + // ------------------------------------------------------------------------ void saveHighscores(); + // ------------------------------------------------------------------------ Highscores *getHighscores(const Highscores::HighscoreType &highscore_type, int num_karts, const RaceManager::Difficulty difficulty, const std::string &trackName, const int number_of_laps, const bool reverse); + // ------------------------------------------------------------------------ + void deleteHighscores(int i) { m_all_scores.erase + (m_all_scores.begin() + i); } + // ------------------------------------------------------------------------ + void clearHighscores() { m_all_scores.clear(); } + // ------------------------------------------------------------------------ + bool highscoresEmpty() { return m_all_scores.empty(); } + // ------------------------------------------------------------------------ + Highscores* getHighscoresAt(int i) { return m_all_scores.at(i).get(); } + // ------------------------------------------------------------------------ + int highscoresSize() { return m_all_scores.size(); } + // ------------------------------------------------------------------------ + void sortHighscores(bool reverse) + { + (reverse ? std::stable_sort(m_all_scores.rbegin(), + m_all_scores.rend(), Highscores::compare) : + std::stable_sort(m_all_scores.begin(), + m_all_scores.end(), Highscores::compare)); + } }; // HighscoreManager extern HighscoreManager* highscore_manager; diff --git a/src/race/highscores.cpp b/src/race/highscores.cpp index 4fd7e3283..5bffef6c1 100644 --- a/src/race/highscores.cpp +++ b/src/race/highscores.cpp @@ -21,11 +21,15 @@ #include "io/utf_writer.hpp" #include "io/xml_node.hpp" #include "race/race_manager.hpp" +#include "tracks/track.hpp" +#include "tracks/track_manager.hpp" #include "utils/log.hpp" #include #include +Highscores::SortOrder Highscores::m_sort_order = Highscores::SO_DEFAULT; + // ----------------------------------------------------------------------------- Highscores::Highscores(const HighscoreType &highscore_type, int num_karts, @@ -53,9 +57,9 @@ Highscores::Highscores(const XMLNode &node) { m_track = ""; m_highscore_type = "HST_UNDEFINED"; - m_number_of_karts = -1; + m_number_of_karts = 0; m_difficulty = -1; - m_number_of_laps = -1; + m_number_of_laps = 0; m_reverse = false; for(int i=0; igetTrack(m_track); + Track* b = track_manager->getTrack(hi.m_track); + std::wstring sort_name_a, sort_name_b; + if (a) + sort_name_a = a->getSortName().c_str(); + if (b) + sort_name_b = b->getSortName().c_str(); + return sort_name_a > sort_name_b; + } + case SO_KART_NUM: + return m_number_of_karts < hi.m_number_of_karts; + case SO_DIFF: + return m_difficulty < hi.m_difficulty; + case SO_LAPS: + return m_number_of_laps < hi.m_number_of_laps; + case SO_REV: + return m_reverse < hi.m_reverse; + } // switch + return true; +} // operator < diff --git a/src/race/highscores.hpp b/src/race/highscores.hpp index 892f52f3c..ac1337f36 100644 --- a/src/race/highscores.hpp +++ b/src/race/highscores.hpp @@ -18,28 +18,41 @@ #ifndef HEADER_HIGHSCORES_HPP #define HEADER_HIGHSCORES_HPP +#include #include #include #include #include "race/race_manager.hpp" -#include +#include "irrString.h" + +using namespace irr::core; class XMLNode; class UTFWriter; /** - * Represents one highscore entry, i.e. the (atm up to three) highscores + * Represents one highscore entry, i.e. the (atm up to five) highscores * for a particular setting (track, #karts, difficulty etc). * \ingroup race */ class Highscores { public: + /** Order of sort for Highscores */ + enum SortOrder + { + SO_DEFAULT, + SO_TRACK = SO_DEFAULT, // Sorted by internal track name + SO_KART_NUM, // Sorted by amount of karts used + SO_DIFF, // Sorted by difficulty level + SO_LAPS, // Sorted by number of laps + SO_REV // Sorted by if using reverse mode + }; + typedef std::string HighscoreType; -private: enum {HIGHSCORE_LEN = 5}; // It's a top 5 list std::string m_track; HighscoreType m_highscore_type; @@ -47,10 +60,18 @@ private: int m_difficulty; int m_number_of_laps; bool m_reverse; - std::string m_kart_name[HIGHSCORE_LEN]; - irr::core::stringw m_name[HIGHSCORE_LEN]; - float m_time[HIGHSCORE_LEN]; + +private: + std::array m_kart_name; + std::array m_name; + std::array m_time; + + static SortOrder m_sort_order; + public: + bool operator < (const Highscores& hi) const; + + static bool compare(const std::unique_ptr& a, const std::unique_ptr& b) { return (*a < *b); } /** Creates a new entry */ Highscores (const Highscores::HighscoreType &highscore_type, @@ -60,18 +81,25 @@ public: /** Creates an entry from a file */ Highscores (const XMLNode &node); - + // ------------------------------------------------------------------------ void readEntry (const XMLNode &node); + // ------------------------------------------------------------------------ void writeEntry(UTFWriter &writer); + // ------------------------------------------------------------------------ int matches (const HighscoreType &highscore_type, int num_karts, const RaceManager::Difficulty &difficulty, const std::string &track, const int number_of_laps, const bool reverse); + // ------------------------------------------------------------------------ int addData (const std::string& kart_name, const irr::core::stringw& name, const float time); + // ------------------------------------------------------------------------ int getNumberEntries() const; + // ------------------------------------------------------------------------ void getEntry (int number, std::string &kart_name, irr::core::stringw &name, float *const time) const; + // ------------------------------------------------------------------------ + static void setSortOrder(SortOrder so) { m_sort_order = so; } }; // Highscores #endif diff --git a/src/states_screens/dialogs/high_score_info_dialog.cpp b/src/states_screens/dialogs/high_score_info_dialog.cpp new file mode 100644 index 000000000..9d58d1ce5 --- /dev/null +++ b/src/states_screens/dialogs/high_score_info_dialog.cpp @@ -0,0 +1,249 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2016 SuperTuxKart-Team +// +// 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/dialogs/high_score_info_dialog.hpp" + +#include "config/player_manager.hpp" +#include "config/user_config.hpp" +#include "guiengine/CGUISpriteBank.hpp" +#include "graphics/stk_tex_manager.hpp" +#include "input/device_manager.hpp" +#include "input/input_manager.hpp" +#include "karts/kart_properties.hpp" +#include "karts/kart_properties_manager.hpp" +#include "race/highscores.hpp" +#include "race/highscore_manager.hpp" +#include "race/race_manager.hpp" +#include "states_screens/high_score_selection.hpp" +#include "states_screens/state_manager.hpp" +#include "tracks/track.hpp" +#include "tracks/track_manager.hpp" +#include "utils/string_utils.hpp" +#include "utils/translation.hpp" + +using namespace GUIEngine; +using namespace irr::core; + +// ----------------------------------------------------------------------------- +HighScoreInfoDialog::HighScoreInfoDialog(Highscores* highscore, bool is_linear) + : ModalDialog(0.75f,0.75f) +{ + m_hs = highscore; + + loadFromFile("high_score_info_dialog.stkgui"); + + Track* track = track_manager->getTrack(m_hs->m_track); + + m_track_screenshot_widget = getWidget("track_screenshot"); + m_track_screenshot_widget->setFocusable(false); + m_track_screenshot_widget->m_tab_stop = false; + + // temporary icon, will replace it just after (but it will be shown if the given icon is not found) + m_track_screenshot_widget->m_properties[PROP_ICON] = "gui/icons/main_help.png"; + + irr::video::ITexture* image = STKTexManager::getInstance() + ->getTexture(track->getScreenshotFile(), + "While loading screenshot for track '%s':", track->getFilename()); + if(!image) + { + image = STKTexManager::getInstance()->getTexture("main_help.png", + "While loading screenshot for track '%s':", track->getFilename()); + } + if (image != NULL) + m_track_screenshot_widget->setImage(image); + + // TODO : small refinement, add the possibility to tab stops for lists + // to make this unselectable by keyboard/mouse + m_high_score_list = getWidget("high_score_list"); + assert(m_high_score_list != NULL); + + /* Used to display kart icons for the entries */ + irr::gui::STKModifiedSpriteBank *icon_bank = HighScoreSelection::getInstance()->getIconBank(); + int icon_height = GUIEngine::getFontHeight() * 3 / 2; + + icon_bank->setScale(icon_height/128.0f); + icon_bank->setTargetIconSize(128, 128); + m_high_score_list->setIcons(icon_bank, (int)icon_height); + + updateHighscoreEntries(); + + //Setup static text labels + m_high_score_label = getWidget("name"); + m_high_score_label->setText(_("Top %d High Scores", m_hs->HIGHSCORE_LEN), true); + m_track_name_label = getWidget("track-name"); + m_track_name_label->setText(_("Track: %s", + track_manager->getTrack(m_hs->m_track)->getName()), true); + m_difficulty_label = getWidget("difficulty"); + m_difficulty_label->setText(_("Difficulty: %s", RaceManager::get()-> + getDifficultyName((RaceManager::Difficulty) + m_hs->m_difficulty)), true); + m_num_karts_label = getWidget("num-karts"); + m_reverse_label = getWidget("reverse"); + m_num_laps_label = getWidget("num-laps"); + + if (is_linear) + { + m_num_karts_label->setVisible(true); + m_num_karts_label->setText(_("Number of karts: %d", m_hs->m_number_of_karts), true); + + m_num_laps_label->setVisible(true); + m_num_laps_label->setText(_("Laps: %d", m_hs->m_number_of_laps), true); + + stringw is_reverse = m_hs->m_reverse ? _("Yes") : _("No"); + m_reverse_label->setVisible(true); + m_reverse_label->setText(_("Reverse: %s", is_reverse), true); + } + else + { + m_num_karts_label->setVisible(false); + m_num_laps_label->setVisible(false); + m_reverse_label->setVisible(false); + } + + m_action_widget = getWidget("actions"); + + m_action_widget->setFocusForPlayer(PLAYER_ID_GAME_MASTER); + m_action_widget->select("back", PLAYER_ID_GAME_MASTER); +} // HighScoreInfoDialog +// ----------------------------------------------------------------------------- +HighScoreInfoDialog::~HighScoreInfoDialog() +{ +} // ~HighScoreInfoDialog + +// ----------------------------------------------------------------------------- +void HighScoreInfoDialog::updateHighscoreEntries() +{ + m_high_score_list->clear(); + + const int amount = m_hs->getNumberEntries(); + + std::string kart_name; + core::stringw name; + float time; + + int time_precision = RaceManager::get()->currentModeTimePrecision(); + + // Fill highscore entries + for (int n = 0; n < m_hs->HIGHSCORE_LEN; n++) + { + irr::core::stringw line; + int icon = -1; + + // Check if this entry is filled or still empty + if (n < amount) + { + m_hs->getEntry(n, kart_name, name, &time); + + std::string time_string = StringUtils::timeToString(time, time_precision); + + for(unsigned int i=0; igetNumberOfKarts(); i++) + { + const KartProperties* prop = kart_properties_manager->getKartById(i); + if (kart_name == prop->getIdent()) + { + icon = i; + break; + } + } + + line = name + " " + core::stringw(time_string.c_str()); + } + else + { + //I18N: for empty highscores entries + line = _("(Empty)"); + } + + if (icon == -1) + { + icon = HighScoreSelection::getInstance()->getUnknownKartIcon(); + } + + std::vector row; + row.push_back(GUIEngine::ListWidget::ListCell(line.c_str(), icon, 5, false)); + m_high_score_list->addItem(StringUtils::toString(n), row); + } +} // updateHighscoreEntries + +// ----------------------------------------------------------------------------- +GUIEngine::EventPropagation + HighScoreInfoDialog::processEvent(const std::string& event_source) +{ + if (event_source == "actions") + { + const std::string& selection = + m_action_widget->getSelectionIDString(PLAYER_ID_GAME_MASTER); + + if (selection == "start") + { + // Use the last used device + InputDevice* device = input_manager->getDeviceManager()->getLatestUsedDevice(); + + // Create player and associate player with device + StateManager::get()->createActivePlayer(PlayerManager::getCurrentPlayer(), device); + + RaceManager::get()->setMinorMode(HighScoreSelection::getInstance()->getActiveMode()); + + bool reverse = m_hs->m_reverse; + std::string track_name = m_hs->m_track; + int laps = m_hs->m_number_of_laps; + + RaceManager::get()->setDifficulty((RaceManager::Difficulty) m_hs->m_difficulty); + + RaceManager::get()->setNumKarts(m_hs->m_number_of_karts); + RaceManager::get()->setNumPlayers(1); + + if (kart_properties_manager->getKart(UserConfigParams::m_default_kart) == NULL) + { + Log::warn("HighScoreInfoDialog", "Cannot find kart '%s', will revert to default", + UserConfigParams::m_default_kart.c_str()); + UserConfigParams::m_default_kart.revertToDefaults(); + } + RaceManager::get()->setPlayerKart(0, UserConfigParams::m_default_kart); + + // Disable accidentally unlocking of a challenge + PlayerManager::getCurrentPlayer()->setCurrentChallenge(""); + + RaceManager::get()->setReverseTrack(reverse); + + // ASSIGN should make sure that only input from assigned devices is read + input_manager->getDeviceManager()->setAssignMode(ASSIGN); + input_manager->getDeviceManager() + ->setSinglePlayer( StateManager::get()->getActivePlayer(0) ); + + ModalDialog::dismiss(); + + RaceManager::get()->startSingleRace(track_name, laps, false); + return GUIEngine::EVENT_BLOCK; + } + else if (selection == "remove") + { + ModalDialog::dismiss(); + + dynamic_cast(GUIEngine::getCurrentScreen()) + ->onDeleteHighscores(); + return GUIEngine::EVENT_BLOCK; + } + else if (selection == "back") + { + ModalDialog::dismiss(); + return GUIEngine::EVENT_BLOCK; + } + } + return GUIEngine::EVENT_LET; +} // processEvent diff --git a/src/states_screens/dialogs/high_score_info_dialog.hpp b/src/states_screens/dialogs/high_score_info_dialog.hpp new file mode 100644 index 000000000..dfffcae03 --- /dev/null +++ b/src/states_screens/dialogs/high_score_info_dialog.hpp @@ -0,0 +1,59 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2016 SuperTuxKart-Team +// +// 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. + +#ifndef HEADER_HIGH_SCORE_INFO_DIALOG_HPP +#define HEADER_HIGH_SCORE_INFO_DIALOG_HPP + +#include "guiengine/modaldialog.hpp" +#include "guiengine/widgets.hpp" +#include "race/highscores.hpp" + +/** \brief Dialog that allows a user to manage a high score + * \ingroup states_screens + */ +class HighScoreInfoDialog : public GUIEngine::ModalDialog +{ + +private: + + bool m_self_destroy; + + Highscores* m_hs; + + GUIEngine::RibbonWidget* m_action_widget; + + GUIEngine::LabelWidget* m_high_score_label; + GUIEngine::LabelWidget* m_track_name_label; + GUIEngine::LabelWidget* m_num_karts_label; + GUIEngine::LabelWidget* m_difficulty_label; + GUIEngine::LabelWidget* m_reverse_label; + GUIEngine::LabelWidget* m_num_laps_label; + + GUIEngine::ListWidget* m_high_score_list; + GUIEngine::IconButtonWidget* m_track_screenshot_widget; + + void updateHighscoreEntries(); + +public: + HighScoreInfoDialog(Highscores* highscore, bool is_linear); + ~HighScoreInfoDialog(); + + GUIEngine::EventPropagation processEvent(const std::string& eventSource); +}; // class HighScoreInfoDialog + +#endif diff --git a/src/states_screens/high_score_selection.cpp b/src/states_screens/high_score_selection.cpp new file mode 100644 index 000000000..94c80ae8c --- /dev/null +++ b/src/states_screens/high_score_selection.cpp @@ -0,0 +1,345 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2016 SuperTuxKart-Team +// +// 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/high_score_selection.hpp" + +#include "config/player_manager.hpp" +#include "config/user_config.hpp" +#include "graphics/material.hpp" +#include "guiengine/CGUISpriteBank.hpp" +#include "karts/kart_properties.hpp" +#include "karts/kart_properties_manager.hpp" +#include "race/highscores.hpp" +#include "race/highscore_manager.hpp" +#include "states_screens/dialogs/high_score_info_dialog.hpp" +#include "states_screens/state_manager.hpp" +#include "states_screens/online/tracks_screen.hpp" +#include "tracks/track.hpp" +#include "tracks/track_manager.hpp" +#include "utils/string_utils.hpp" +#include "utils/translation.hpp" + +using namespace GUIEngine; + +// ---------------------------------------------------------------------------- +/** Constructor, which loads the stkgui file. + */ +HighScoreSelection::HighScoreSelection() : Screen("high_score_selection.stkgui") +{ + m_selected_index = -1; +} // HighScoreSelection + +// ---------------------------------------------------------------------------- +/** Destructor. + */ +HighScoreSelection::~HighScoreSelection() +{ +} // HighScoreSelection + +// ---------------------------------------------------------------------------- +void HighScoreSelection::tearDown() +{ + m_high_scores_list_widget->setIcons(NULL); +} + +// ---------------------------------------------------------------------------- +void HighScoreSelection::unloaded() +{ + delete m_icon_bank; + m_icon_bank = NULL; +} // unloaded + +// ---------------------------------------------------------------------------- +/** Triggers a refresh of the high score list. + */ +void HighScoreSelection::refresh(bool forced_update, bool update_columns) +{ + m_selected_index = -1; + + if (highscore_manager->highscoresEmpty() || forced_update) + { + if (!highscore_manager->highscoresEmpty()) + { + highscore_manager->clearHighscores(); + } + highscore_manager->loadHighscores(); + } + defaultSort(); + + loadList(); + + if (update_columns) + { + m_high_scores_list_widget->clearColumns(); + beforeAddingWidget();//Reload the columns used + } +} // refresh + +// ---------------------------------------------------------------------------- +/** Set pointers to the various widgets. + */ +void HighScoreSelection::loadedFromFile() +{ + m_high_scores_list_widget = getWidget("high_scores_list"); + assert(m_high_scores_list_widget != NULL); + m_high_scores_list_widget->setColumnListener(this); + + m_mode_tabs = getWidget("race_mode"); + m_active_mode = RaceManager::MINOR_MODE_NORMAL_RACE; + m_active_mode_is_linear = true; + + m_icon_bank = new irr::gui::STKModifiedSpriteBank( GUIEngine::getGUIEnv()); + + for(unsigned int i=0; igetNumberOfKarts(); i++) + { + const KartProperties* prop = kart_properties_manager->getKartById(i); + m_icon_bank->addTextureAsSprite(prop->getIconMaterial()->getTexture()); + } + + video::ITexture* kart_not_found = irr_driver->getTexture( + file_manager->getAsset(FileManager::GUI_ICON, "random_kart.png")); + + m_icon_unknown_kart = m_icon_bank->addTextureAsSprite(kart_not_found); + + video::ITexture* lock = irr_driver->getTexture( file_manager->getAsset( + FileManager::GUI_ICON, "gui_lock.png")); + + m_icon_lock = m_icon_bank->addTextureAsSprite(lock); +} // loadedFromFile + +// ---------------------------------------------------------------------------- +/** Clear the high score entry list, which will be reloaded. + */ +void HighScoreSelection::beforeAddingWidget() +{ + m_high_scores_list_widget->addColumn(_C("column_name", "Track"), 7); + m_high_scores_list_widget->addColumn(_C("column_name", "Difficulty"), 4); + if (m_active_mode_is_linear) + { + m_high_scores_list_widget->addColumn(_C("column_name", "Number of karts"), 4); + m_high_scores_list_widget->addColumn(_C("column_name", "Laps"), 3); + m_high_scores_list_widget->addColumn(_C("column_name", "Reverse"), 3); + } + + m_high_scores_list_widget->createHeader(); +} // beforeAddingWidget + +// ---------------------------------------------------------------------------- +void HighScoreSelection::init() +{ + Screen::init(); + + int icon_height = GUIEngine::getFontHeight(); + int row_height = GUIEngine::getFontHeight() * 5 / 4; + + // 128 is the height of the image file + m_icon_bank->setScale(icon_height/128.0f); + m_icon_bank->setTargetIconSize(128, 128); + m_high_scores_list_widget->setIcons(m_icon_bank, (int)row_height); + + refresh(/*reload high score list*/ false, /* update columns */ true); +} // init + +// ---------------------------------------------------------------------------- +/** Loads the list of all high score entries. The gui element will be + * updated. + */ +void HighScoreSelection::loadList() +{ + m_high_scores_list_widget->clear(); + + if (highscore_manager->highscoresEmpty()) + return; + + for (int i = 0; i < highscore_manager->highscoresSize(); i++) + { + Highscores* hs = highscore_manager->getHighscoresAt(i); + + if (m_active_mode == RaceManager::MINOR_MODE_NORMAL_RACE && + hs->m_highscore_type != "HST_STANDARD") + continue; + else if (m_active_mode == RaceManager::MINOR_MODE_TIME_TRIAL && + hs->m_highscore_type != "HST_STD_TIMETRIAL") + continue; + else if (m_active_mode == RaceManager::MINOR_MODE_EASTER_EGG && + hs->m_highscore_type != "HST_EASTER_EGG_HUNT") + continue; + + Track* track = track_manager->getTrack(hs->m_track); + + if (track == NULL || hs->getNumberEntries() < 1) + continue; + + std::vector row; + //The third argument should match the numbers used in beforeAddingWidget + row.push_back(GUIEngine::ListWidget::ListCell(track->getName() , -1, 7)); + + bool display_lock = false; + if ((RaceManager::Difficulty)hs->m_difficulty == RaceManager::DIFFICULTY_BEST && + PlayerManager::getCurrentPlayer()->isLocked("difficulty_best")) + display_lock = true; + + row.push_back(GUIEngine::ListWidget::ListCell(RaceManager::get()-> + getDifficultyName((RaceManager::Difficulty) hs->m_difficulty), + display_lock ? m_icon_lock : -1, 4, true)); + + if (m_active_mode_is_linear) + { + row.push_back(GUIEngine::ListWidget::ListCell + (StringUtils::toWString(hs->m_number_of_karts), -1, 4, true)); + row.push_back(GUIEngine::ListWidget::ListCell + (StringUtils::toWString(hs->m_number_of_laps), -1, 3, true)); + row.push_back(GUIEngine::ListWidget::ListCell + (hs->m_reverse ? _("Yes") : _("No"), -1, 3, true)); + } + m_high_scores_list_widget->addItem(StringUtils::toString(i), row); + } +} // loadList + +// ---------------------------------------------------------------------------- +void HighScoreSelection::eventCallback(GUIEngine::Widget* widget, + const std::string& name, + const int playerID) +{ + if (name == "back") + { + StateManager::get()->escapePressed(); + } + else if (name == "remove-all") + { + onClearHighscores(); + } + else if (name == m_high_scores_list_widget->m_properties[GUIEngine::PROP_ID]) + { + m_selected_index = -1; + const bool success = StringUtils::fromString(m_high_scores_list_widget + ->getSelectionInternalName(), m_selected_index); + // This can happen e.g. when the list is empty and the user + // clicks somewhere. + if (m_selected_index >= (signed)highscore_manager->highscoresSize() || + m_selected_index < 0 || !success) + { + return; + } + if (PlayerManager::getCurrentPlayer()->isLocked("difficulty_best")) + { + Highscores* hs = highscore_manager->getHighscoresAt(m_selected_index); + if((RaceManager::Difficulty)hs->m_difficulty == RaceManager::DIFFICULTY_BEST) + return; + } + + new HighScoreInfoDialog(highscore_manager->getHighscoresAt(m_selected_index), m_active_mode_is_linear); + } // click on high score entry + else if (name == "race_mode") + { + std::string selection = ((RibbonWidget*)widget)->getSelectionIDString(PLAYER_ID_GAME_MASTER); + + if (selection == "tab_normal_race") + m_active_mode = RaceManager::MINOR_MODE_NORMAL_RACE; + else if (selection == "tab_time_trial") + m_active_mode = RaceManager::MINOR_MODE_TIME_TRIAL; + else if (selection == "tab_egg_hunt") + m_active_mode = RaceManager::MINOR_MODE_EASTER_EGG; + + m_active_mode_is_linear = RaceManager::get()->isLinearRaceMode(m_active_mode); + refresh(/*keep high score list*/ false, /* update columns */ true); + } +} // eventCallback + +// ---------------------------------------------------------------------------- +void HighScoreSelection::onDeleteHighscores() +{ + new MessageDialog( _("Are you sure you want to remove this high score entry?"), + MessageDialog::MESSAGE_DIALOG_CONFIRM, this, false); +} // onDeleteHighscores + +// ---------------------------------------------------------------------------- +void HighScoreSelection::onClearHighscores() +{ + m_selected_index = -1; + new MessageDialog( _("Are you sure you want to remove all of your high scores?"), + MessageDialog::MESSAGE_DIALOG_CONFIRM, this, false); +} // onClearHighscores + +// ---------------------------------------------------------------------------- +void HighScoreSelection::onConfirm() +{ + if (m_selected_index < 0) + { + highscore_manager->clearHighscores(); + } + else + { + highscore_manager->deleteHighscores(m_selected_index); + } + defaultSort(); + + highscore_manager->saveHighscores(); + + // Restore the previously used sort direction + highscore_manager->sortHighscores(m_reverse_sort); + + ModalDialog::dismiss(); + HighScoreSelection::getInstance()->refresh(); +} // onConfirm + +// ---------------------------------------------------------------------------- +/** Change the sort order if a column was clicked. + * \param column_id ID of the column that was clicked. + */ + +void HighScoreSelection::onColumnClicked(int column_id, bool sort_desc, bool sort_default) +{ + // Begin by resorting the list to default + defaultSort(); + + if (sort_default) + { + loadList(); + return; + } + + if (column_id == 0) + Highscores::setSortOrder(Highscores::SO_TRACK); + else if (column_id == 1) + Highscores::setSortOrder(Highscores::SO_DIFF); + else if (column_id == 2) + Highscores::setSortOrder(Highscores::SO_KART_NUM); + else if (column_id == 3) + Highscores::setSortOrder(Highscores::SO_LAPS); + else if (column_id == 4) + Highscores::setSortOrder(Highscores::SO_REV); + else + assert(0); + + m_reverse_sort = sort_desc; + highscore_manager->sortHighscores(sort_desc); + + loadList(); +} // onColumnClicked + +// ---------------------------------------------------------------------------- +/** Apply the default sorting to the high score list + */ + +void HighScoreSelection::defaultSort() +{ + m_reverse_sort = false; + Highscores::setSortOrder(Highscores::SO_DEFAULT); + highscore_manager->sortHighscores(false); +} // defaultSort diff --git a/src/states_screens/high_score_selection.hpp b/src/states_screens/high_score_selection.hpp new file mode 100644 index 000000000..4f583483b --- /dev/null +++ b/src/states_screens/high_score_selection.hpp @@ -0,0 +1,102 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2016 SuperTuxKart-Team +// +// 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. + +#ifndef HEADER_HIGH_SCORE_SELECTION_HPP +#define HEADER_HIGH_SCORE_SELECTION_HPP + +#include "guiengine/screen.hpp" +#include "guiengine/widgets.hpp" +#include "race/race_manager.hpp" +#include "states_screens/dialogs/message_dialog.hpp" + +namespace GUIEngine { class Widget; } + +/** + * \brief HighScoreSelection + * \ingroup states_screens + */ +class HighScoreSelection : public GUIEngine::Screen, + public GUIEngine::ScreenSingleton, + public GUIEngine::IListWidgetHeaderListener, + public MessageDialog::IConfirmDialogListener + +{ + friend class GUIEngine::ScreenSingleton; + +private: + HighScoreSelection(); + ~HighScoreSelection(); + + GUIEngine::ListWidget* m_high_scores_list_widget; + GUIEngine::RibbonWidget* m_mode_tabs; + bool m_active_mode_is_linear; + bool m_reverse_sort; + RaceManager::MinorRaceModeType m_active_mode; + int m_selected_index; + + irr::gui::STKModifiedSpriteBank *m_icon_bank; + + /** Icon for unknown karts */ + int m_icon_unknown_kart; + /** Icon for locked replays */ + int m_icon_lock; + + void defaultSort(); + +public: + irr::gui::STKModifiedSpriteBank* getIconBank() { return m_icon_bank; } + + int getUnknownKartIcon() { return m_icon_unknown_kart; } + + void refresh(bool forced_update = true, bool update_columns = false); + + /** Load the addons into the main list.*/ + void loadList(); + + void onDeleteHighscores(); + + void onClearHighscores(); + + const RaceManager::MinorRaceModeType getActiveMode() { return m_active_mode; } + + const bool isActiveModeLinear() { return m_active_mode_is_linear; } + + /** \brief implement callback from parent class GUIEngine::Screen */ + virtual void loadedFromFile() OVERRIDE; + + /** \brief implement callback from parent class GUIEngine::Screen */ + virtual void eventCallback(GUIEngine::Widget* widget, const std::string& name, + const int playerID) OVERRIDE; + + /** \brief implement callback from parent class GUIEngine::Screen */ + virtual void beforeAddingWidget() OVERRIDE; + + virtual void onColumnClicked(int column_id, bool sort_desc, bool sort_default) OVERRIDE; + + virtual void init() OVERRIDE; + + virtual void tearDown() OVERRIDE; + + virtual void unloaded() OVERRIDE; + + /** \brief Implement IConfirmDialogListener callback */ + virtual void onConfirm() OVERRIDE; + +}; // HighScoreSelection + +#endif diff --git a/src/states_screens/main_menu_screen.cpp b/src/states_screens/main_menu_screen.cpp index 45d8f5c79..1268032dd 100644 --- a/src/states_screens/main_menu_screen.cpp +++ b/src/states_screens/main_menu_screen.cpp @@ -46,6 +46,7 @@ #include "states_screens/cutscene_general.hpp" #include "states_screens/grand_prix_editor_screen.hpp" #include "states_screens/help_screen_1.hpp" +#include "states_screens/high_score_selection.hpp" #include "states_screens/offline_kart_selection.hpp" #include "states_screens/online/online_profile_achievements.hpp" #include "states_screens/online/online_profile_servers.hpp" @@ -594,6 +595,10 @@ void MainMenuScreen::eventCallback(Widget* widget, const std::string& name, { OnlineProfileAchievements::getInstance()->push(); } + else if (selection == "highscores") + { + HighScoreSelection::getInstance()->push(); + } #endif } // eventCallback diff --git a/src/states_screens/track_info_screen.cpp b/src/states_screens/track_info_screen.cpp index 1683970f5..ba1690f58 100644 --- a/src/states_screens/track_info_screen.cpp +++ b/src/states_screens/track_info_screen.cpp @@ -329,6 +329,7 @@ void TrackInfoScreen::init() m_ai_kart_spinner->setActive(true); // ---- High Scores + m_highscore_label->setText(_("High Scores"), false); m_highscore_label->setVisible(has_highscores); if (has_highscores)