In-game high scores management (#4483)
* Add in-game high score selection screens, based on the ghost replay screens Its functionality is basic for now, mainly to let players have a central place to view their high scores Other things to improve: * Allow sorting the high score entries by criteria * Allow deleting high score nodes and possibly entries * Use better icons * The string for the high scores title in the track info screen can now be translated * Refine in-game high score selection screens Includes: * High score info dialog now shows track and setup information * A race can be started with the displayed setup, using the current player and kart * Icon to access the screen now placed between the tutorial and achievements buttons * It is possible to delete a specific high score group or all of the high score groups * Change the order of some columns to make them easier to hide for non-linear modes * The list will now filter out enpty high score groups * Replace bomb icon (as used in the help menu) with the full object version from STK 0.8 It has been edited to remove most of the transparency in the object itself * Implement column clicking in the high score selection screen, and minor GUI fixes Note that high score entry sorting is not yet working properly Includes: * Top right buttons replaced by button bar containing them; fixes unreliable clicking * High score manager has some one-line functions moved into its header file * High scores can be sorted by some criteria * Sorting is done before every time high scores are saved * Fix header define names for the high score info dialog * Fix high score sorting, reorganize its associated code * More refinements to the high score selection screen Includes: * Clearing high scores no longer causes memory leaks * The manual refresh button has been removed, as it has been deemed useless * Remove unused header files for the high scores information dialog header * The high scores box in the track information screen no longer has '=' * Fix pressing escape key in the high score information dialog crashing the game Also remove unused widget variables and unnecessary function overrides * Do not write high scores for races that have 0 laps and/or have no real karts * Allow passing a parameter to prevent high scores from temporarily being written This setting lasts only as long as the game runs; it is useful during track and kart animation testing, where unwanted high score entries should not be written * Force update sources.cmake, as new source files are being added for high scores screens * Fix memory leak and strings Co-authored-by: Benau <Benau@users.noreply.github.com>
This commit is contained in:
parent
ee91cf31e2
commit
f136c6fe36
57
data/gui/dialogs/high_score_info_dialog.stkgui
Normal file
57
data/gui/dialogs/high_score_info_dialog.stkgui
Normal file
@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<stkgui>
|
||||
<div x="1%" y="4%" width="98%" height="92%" layout="vertical-row">
|
||||
|
||||
<div width="100%" height="55%" layout="vertical-row">
|
||||
<div width="100%" height="15%" align="center" layout="vertical-row" >
|
||||
<label id="name" width="100%" text_align="center"/>
|
||||
</div>
|
||||
<!-- This is filled in programmatically -->
|
||||
<div width="100%" height="85%" layout="horizontal-row">
|
||||
<box width="50%" height="100%" layout="vertical-row" padding="1">
|
||||
<list id="high_score_list" x="0" y="0" width="100%" height="100%"/>
|
||||
</box>
|
||||
|
||||
<spacer width="10" height="10%"/>
|
||||
|
||||
<div width="50%" height="100%" layout="vertical-row">
|
||||
<spacer width="10" height="5%"/>
|
||||
<label id="track-name" width="100%" text_align="left"/>
|
||||
<spacer width="10" height="5%"/>
|
||||
<label id="difficulty" width="100%" text_align="left"/>
|
||||
<spacer width="10" height="5%"/>
|
||||
<label id="num-karts" width="100%" text_align="left"/>
|
||||
<spacer width="10" height="5%"/>
|
||||
<label id="num-laps" width="100%" text_align="left"/>
|
||||
<spacer width="10" height="5%"/>
|
||||
<label id="reverse" width="100%" text_align="left"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div width="100%" height="45%" layout="horizontal-row">
|
||||
<div width="40%" height="100%" layout="vertical-row">
|
||||
<spacer width="10" height="5%"/>
|
||||
<icon-button proportion="1" width="100%" height="100%" id="track_screenshot" custom_ratio="1.33333"/>
|
||||
</div>
|
||||
|
||||
<div width="60%" height="50%" layout="vertical-row">
|
||||
<spacer width="5" height="45%"/>
|
||||
<div width="95%" height="100%" align="center">
|
||||
<buttonbar id="actions" x="0" y="0" height="100%" width="100%" align="center">
|
||||
<icon-button id="start" width="128" height="128"
|
||||
icon="gui/icons/green_check.png"
|
||||
I18N="High score info screen action" text="Start Race" word_wrap="true" />
|
||||
<icon-button id="remove" width="128" height="128"
|
||||
icon="gui/icons/remove.png"
|
||||
I18N="High score info screen action" text="Remove" word_wrap="true" />
|
||||
<icon-button id="back" width="128" height="128"
|
||||
icon="gui/icons/back.png"
|
||||
I18N="High score info screen action" text="Back" word_wrap="true" />
|
||||
</buttonbar>
|
||||
</div>
|
||||
<spacer width="10" height="10%"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</stkgui>
|
Binary file not shown.
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 10 KiB |
51
data/gui/screens/high_score_selection.stkgui
Normal file
51
data/gui/screens/high_score_selection.stkgui
Normal file
@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<stkgui>
|
||||
<div x="1%" y="0" width="98%" layout="horizontal-row" height="9%">
|
||||
<icon-button id="back" height="100%" icon_align="left" icon="gui/icons/back.png"/>
|
||||
<spacer proportion="1" height="1"/>
|
||||
<icon-button id="remove-all" y="5%" height="90%" icon_align="right" icon="gui/icons/bomb_icon.png"/>
|
||||
</div>
|
||||
|
||||
<div x="0%" y="1%" width="100%" height="98%" layout="vertical-row" >
|
||||
<header width="80%" height="8%" align="center" text_align="center" I18N="In the high score selection screen" text="High Score Selection"/>
|
||||
<spacer width="100%" height="1%"/>
|
||||
|
||||
<!-- This is filled in programmatically -->
|
||||
<box proportion="1" width="98%" align="center" layout="vertical-row" padding="6">
|
||||
<list id="high_scores_list" x="0" y="0" width="100%" height="100%" alternate_bg="true"/>
|
||||
</box>
|
||||
|
||||
<tabs id="race_mode" height="6%" width="98%" align="center">
|
||||
<icon-button id="tab_normal_race" width="128" height="128" icon="gui/icons/mode_normal.png"
|
||||
I18N="In the high score selection screen" text="Normal Race"/>
|
||||
<icon-button id="tab_time_trial" width="128" height="128" icon="gui/icons/mode_tt.png"
|
||||
I18N="In the high score selection screen" text="Time Trial"/>
|
||||
<icon-button id="tab_egg_hunt" width="128" height="128" icon="gui/icons/mode_easter.png"
|
||||
I18N="In the high score selection screen" text="Egg Hunt"/>
|
||||
</tabs>
|
||||
|
||||
<spacer width="100%" height="2%" />
|
||||
<!--
|
||||
<div width="98%" align="center" layout="horizontal-row" height="fit">
|
||||
<div width="60%" height="fit" layout="horizontal-row" >
|
||||
<checkbox width="fit" id="best_times_toggle" text_align="left"/>
|
||||
<spacer width="2%" height="fit"/>
|
||||
<label height="100%" text_align="left" I18N="In the high score selection screen" text="Only show the best times"/>
|
||||
</div>
|
||||
<div width="40%" height="fit" layout="horizontal-row" >
|
||||
<checkbox width="fit" id="compare_toggle" text_align="left"/>
|
||||
<spacer width="2%" height="fit"/>
|
||||
<label height="100%" id="compare-toggle-text" text_align="left" I18N="In the high score selection screen" text="Compare high scores"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div width="98%" align="center" layout="horizontal-row" height="fit">
|
||||
<div width="60%" height="fit" layout="horizontal-row" >
|
||||
<checkbox width="fit" id="high_scores_difficulty_toggle" text_align="left"/>
|
||||
<spacer width="2%" height="fit"/>
|
||||
<label height="100%" text_align="left" I18N="In the high score selection screen" text="Only show high scores matching the current difficulty"/>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
</stkgui>
|
68
data/gui/screens/highscore_info.stkgui
Normal file
68
data/gui/screens/highscore_info.stkgui
Normal file
@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<stkgui>
|
||||
<icon-button id="back" x="1%" y="0" height="9%" icon="gui/icons/back.png"/>
|
||||
|
||||
<div x="2%" y="1%" width="96%" height="98%" layout="vertical-row">
|
||||
<header id="name" height="8%" width="80%" align="center" text_align="center"/>
|
||||
<spacer width="1" height="1%"/>
|
||||
|
||||
<div width="100%" height="72%" padding="10" layout="horizontal-row">
|
||||
<box width="55%" height="100%" layout="vertical-row">
|
||||
<!-- Track screenshot -->
|
||||
<div width="100%" height="54%" layout="vertical-row">
|
||||
<icon-button proportion="1" width="100%" height="100%" id="screenshot" custom_ratio="1.33333"/>
|
||||
</div>
|
||||
<spacer width="1" height="1%"/>
|
||||
<!-- Options list -->
|
||||
<div width="100%" height="45%" layout="vertical-row">
|
||||
<div width="100%" height="fit" layout="horizontal-row" >
|
||||
<div width="45%" height="fit" layout="horizontal-row">
|
||||
<spinner id="ai-spinner" width="100%" min_value="1" max_value="20" align="center" wrap_around="true" />
|
||||
</div>
|
||||
<spacer width="3%"/>
|
||||
<label id="ai-text" width="52%" height="fit" I18N="In the grand prix info screen" text="AI karts" text_align="left"/>
|
||||
</div>
|
||||
<spacer width="1" height="1%"/>
|
||||
<div width="100%" height="fit" layout="horizontal-row" >
|
||||
<div width="45%" height="fit" layout="horizontal-row">
|
||||
<spinner id="reverse-spinner" width="100%" align="center" wrap_around="true" />
|
||||
</div>
|
||||
<spacer width="3%"/>
|
||||
<label id="reverse-text" width="52%" height="fit" I18N="In the grand prix info screen" text="Reverse" text_align="left"/>
|
||||
</div>
|
||||
<spacer width="1" height="1%"/>
|
||||
<div width="100%" height="fit" layout="horizontal-row">
|
||||
<div width="45%" height="fit" layout="horizontal-row">
|
||||
<spinner id="track-spinner" width="100%" min_value="1" max_value="20" align="center" wrap_around="true" />
|
||||
</div>
|
||||
<spacer width="3%"/>
|
||||
<label id="track-text" width="52%" height="fit" I18N="In the grand prix info screen" text="Tracks" text_align="left"/>
|
||||
</div>
|
||||
<spacer width="1" height="1%"/>
|
||||
<div width="100%" height="fit" layout="horizontal-row" >
|
||||
<div width="45%" height="fit" layout="horizontal-row">
|
||||
<spinner id="group-spinner" width="100%" align="center" wrap_around="true" />
|
||||
</div>
|
||||
<spacer width="3%"/>
|
||||
<label id="group-text" width="52%" height="fit" I18N="In the grand prix info screen" text="Track group" text_align="left"/>
|
||||
</div>
|
||||
</div>
|
||||
</box>
|
||||
<spacer width="2%" height="1"/>
|
||||
<!-- Track list -->
|
||||
<box width="43%" height="100%" layout="vertical-row">
|
||||
<list id="tracks" width="100%" height="100%"/>
|
||||
</box>
|
||||
</div>
|
||||
|
||||
<spacer width="1" height="1%"/>
|
||||
<buttonbar id="buttons" height="17%" width="100%" align="center">
|
||||
<icon-button id="start" width="64" height="64" icon="gui/icons/green_check.png"
|
||||
I18N="In the grand prix info screen" text="Start Race"/>
|
||||
|
||||
<icon-button id="continue" width="64" height="64" icon="gui/icons/green_check.png"
|
||||
I18N="In the grand prix info screen" text="Continue saved GP"/>
|
||||
</buttonbar>
|
||||
|
||||
</div>
|
||||
</stkgui>
|
@ -46,13 +46,15 @@
|
||||
<icon-button id="test_intro" width="64" height="64" icon="gui/icons/main_options.png"
|
||||
raw_text="TEST: Intro" label_location="hover"/>
|
||||
<icon-button id="test_outro" width="64" height="64" icon="gui/icons/main_options.png"
|
||||
raw_text="TEST: Outro" label_location="hover"/>
|
||||
raw_text="TEST: Outro" label_location="hover"/>
|
||||
<icon-button id="options" width="64" height="64" icon="gui/icons/main_options.png"
|
||||
I18N="In the main screen" text="Options" label_location="hover"/>
|
||||
<icon-button id="help" width="64" height="64" icon="gui/icons/main_help.png"
|
||||
I18N="In the main screen" text="Help" label_location="hover"/>
|
||||
<icon-button id="startTutorial" width="64" height="64" icon="gui/icons/tutorial.png"
|
||||
I18N="In the main screen" text="Tutorial" label_location="hover"/>
|
||||
<icon-button id="highscores" width="64" height="64" icon="gui/icons/crown.png"
|
||||
I18N="In the main screen" text="High Scores" label_location="hover"/>
|
||||
<icon-button id="achievements" width="64" height="64" icon="gui/icons/gp_copy.png"
|
||||
I18N="In the main screen" text="Achievements" label_location="hover"/>
|
||||
<icon-button id="gpEditor" width="64" height="64" icon="gui/icons/gpeditor.png"
|
||||
|
@ -11,7 +11,7 @@
|
||||
<!-- Left pane -->
|
||||
<div width="68%" height="100%" layout="vertical-row">
|
||||
<box width="100%" height="49%" padding="10" layout="vertical-row">
|
||||
<label id="highscores" width="100%" text_align="center" text="= Highscores ="/>
|
||||
<label id="highscores" width="100%" text_align="center"/>
|
||||
<spacer width="1" height="1%"/>
|
||||
<list id="highscore_entries" x="0" y="0" width="100%" proportion="1"/>
|
||||
</box><!-- Highscores box -->
|
||||
|
@ -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/*")
|
||||
|
@ -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)
|
||||
|
@ -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"))
|
||||
|
@ -285,9 +285,9 @@ void World::init()
|
||||
if (Camera::getNumCameras() == 0)
|
||||
{
|
||||
auto cl = LobbyProtocol::get<ClientLobby>();
|
||||
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()
|
||||
}
|
||||
} // i<kart_amount
|
||||
|
||||
// Update highscores, and retrieve the best highscore if relevant
|
||||
// to show it in the GUI
|
||||
/** Only update high scores when these conditions are met:
|
||||
* * The race is not over a network
|
||||
* * There is at least 1 real kart in play
|
||||
* * The number of laps is at least 1
|
||||
* * The command line parameter --no-high-scores has not been passed
|
||||
*
|
||||
* If they are met, retrieve the best highscore if relevant
|
||||
* to show it in the GUI
|
||||
*/
|
||||
int best_highscore_rank = -1;
|
||||
std::string highscore_who = "";
|
||||
if (!isNetworkWorld())
|
||||
if (!isNetworkWorld() && RaceManager::get()->getNumNonGhostKarts() > 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; i<stk_config->getPhysicsFPS(); i++)
|
||||
for(int i=0; i<stk_config->getPhysicsFPS(); 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();
|
||||
|
@ -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>(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>(highscores));
|
||||
return highscores;
|
||||
} // getHighscores
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
#include "race/highscores.hpp"
|
||||
|
||||
@ -36,25 +37,46 @@ class HighscoreManager
|
||||
public:
|
||||
private:
|
||||
static const unsigned int CURRENT_HSCORE_FILE_VERSION;
|
||||
typedef std::vector<Highscores*> type_all_scores;
|
||||
type_all_scores m_all_scores;
|
||||
std::vector<std::unique_ptr<Highscores> > 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;
|
||||
|
@ -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 <stdexcept>
|
||||
#include <fstream>
|
||||
|
||||
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; i<HIGHSCORE_LEN; i++)
|
||||
@ -232,3 +236,29 @@ void Highscores::getEntry(int number, std::string &kart_name,
|
||||
} // getEntry
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
bool Highscores::operator < (const Highscores& hi) const
|
||||
{
|
||||
switch (m_sort_order)
|
||||
{
|
||||
case SO_TRACK:
|
||||
{
|
||||
Track* a = track_manager->getTrack(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 <
|
||||
|
@ -18,28 +18,41 @@
|
||||
|
||||
#ifndef HEADER_HIGHSCORES_HPP
|
||||
#define HEADER_HIGHSCORES_HPP
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include "race/race_manager.hpp"
|
||||
|
||||
#include <irrString.h>
|
||||
#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<std::string, HIGHSCORE_LEN> m_kart_name;
|
||||
std::array<stringw, HIGHSCORE_LEN> m_name;
|
||||
std::array<float, HIGHSCORE_LEN> m_time;
|
||||
|
||||
static SortOrder m_sort_order;
|
||||
|
||||
public:
|
||||
bool operator < (const Highscores& hi) const;
|
||||
|
||||
static bool compare(const std::unique_ptr<Highscores>& a, const std::unique_ptr<Highscores>& 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
|
||||
|
249
src/states_screens/dialogs/high_score_info_dialog.cpp
Normal file
249
src/states_screens/dialogs/high_score_info_dialog.cpp
Normal file
@ -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<IconButtonWidget>("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<GUIEngine::ListWidget>("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<LabelWidget>("name");
|
||||
m_high_score_label->setText(_("Top %d High Scores", m_hs->HIGHSCORE_LEN), true);
|
||||
m_track_name_label = getWidget<LabelWidget>("track-name");
|
||||
m_track_name_label->setText(_("Track: %s",
|
||||
track_manager->getTrack(m_hs->m_track)->getName()), true);
|
||||
m_difficulty_label = getWidget<LabelWidget>("difficulty");
|
||||
m_difficulty_label->setText(_("Difficulty: %s", RaceManager::get()->
|
||||
getDifficultyName((RaceManager::Difficulty)
|
||||
m_hs->m_difficulty)), true);
|
||||
m_num_karts_label = getWidget<LabelWidget>("num-karts");
|
||||
m_reverse_label = getWidget<LabelWidget>("reverse");
|
||||
m_num_laps_label = getWidget<LabelWidget>("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<RibbonWidget>("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; i<kart_properties_manager->getNumberOfKarts(); 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<GUIEngine::ListWidget::ListCell> 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<HighScoreSelection*>(GUIEngine::getCurrentScreen())
|
||||
->onDeleteHighscores();
|
||||
return GUIEngine::EVENT_BLOCK;
|
||||
}
|
||||
else if (selection == "back")
|
||||
{
|
||||
ModalDialog::dismiss();
|
||||
return GUIEngine::EVENT_BLOCK;
|
||||
}
|
||||
}
|
||||
return GUIEngine::EVENT_LET;
|
||||
} // processEvent
|
59
src/states_screens/dialogs/high_score_info_dialog.hpp
Normal file
59
src/states_screens/dialogs/high_score_info_dialog.hpp
Normal file
@ -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
|
345
src/states_screens/high_score_selection.cpp
Normal file
345
src/states_screens/high_score_selection.cpp
Normal file
@ -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<GUIEngine::ListWidget>("high_scores_list");
|
||||
assert(m_high_scores_list_widget != NULL);
|
||||
m_high_scores_list_widget->setColumnListener(this);
|
||||
|
||||
m_mode_tabs = getWidget<GUIEngine::RibbonWidget>("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; i<kart_properties_manager->getNumberOfKarts(); 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<GUIEngine::ListWidget::ListCell> 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
|
102
src/states_screens/high_score_selection.hpp
Normal file
102
src/states_screens/high_score_selection.hpp
Normal file
@ -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<HighScoreSelection>,
|
||||
public GUIEngine::IListWidgetHeaderListener,
|
||||
public MessageDialog::IConfirmDialogListener
|
||||
|
||||
{
|
||||
friend class GUIEngine::ScreenSingleton<HighScoreSelection>;
|
||||
|
||||
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
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user