-
+
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)