diff --git a/data/gui/screens/arenas.stkgui b/data/gui/screens/arenas.stkgui index 9746e93081..6c71e81cf7 100644 --- a/data/gui/screens/arenas.stkgui +++ b/data/gui/screens/arenas.stkgui @@ -3,9 +3,16 @@
-
- +
+ +
+ + + + + +
- + -
diff --git a/src/config/favorite_track_status.cpp b/src/config/favorite_track_status.cpp new file mode 100644 index 0000000000..f5dedb4331 --- /dev/null +++ b/src/config/favorite_track_status.cpp @@ -0,0 +1,129 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2012-2015 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 "config/favorite_track_status.hpp" + +#include "config/player_manager.hpp" +#include "io/utf_writer.hpp" +#include "io/xml_node.hpp" +#include "utils/string_utils.hpp" + +const std::string FavoriteTrackStatus::DEFAULT_FAVORITE_GROUP_NAME = "Favorites"; + +//------------------------------------------------------------------------------ +FavoriteTrackStatus::FavoriteTrackStatus(const XMLNode* node) +{ + std::vector xml_favorite_tracks; + std::vector xml_favorite_groups; + + if (node) + { + node->getNodes("track", xml_favorite_tracks); + node->getNodes("group", xml_favorite_groups); + } + for (unsigned int i = 0; i < xml_favorite_tracks.size(); i++) + { + std::string temp_string; + xml_favorite_tracks[i]->get("ident", &temp_string); + m_favorite_tracks[DEFAULT_FAVORITE_GROUP_NAME].insert(temp_string); + } + for (unsigned int i = 0; i < xml_favorite_groups.size(); i++) + { + std::string temp_group_string; + std::vector temp_group; + + xml_favorite_groups[i]->get("name", &temp_group_string); + xml_favorite_groups[i]->getNodes("track", temp_group); + + for (unsigned int j = 0; j < temp_group.size(); j++) + { + std::string temp_string; + temp_group[j]->get("ident", &temp_string); + m_favorite_tracks[temp_group_string].insert(temp_string); + } + } +} // FavoriteTrackStatus + +//------------------------------------------------------------------------------ +FavoriteTrackStatus::~FavoriteTrackStatus() +{ + +} // ~FavoriteTrackStatus + +//------------------------------------------------------------------------------ +/** Adds a new favorite track to this player profile and to the group + * of favorite tracks of the Track Manager. + * To be used only if this player profile is the current player. + */ +bool FavoriteTrackStatus::isFavoriteTrack(std::string ident) +{ + return m_favorite_tracks[DEFAULT_FAVORITE_GROUP_NAME].find(ident) + != m_favorite_tracks[DEFAULT_FAVORITE_GROUP_NAME].end(); +} // addFavoriteTrack + +//------------------------------------------------------------------------------ +/** Adds a new favorite track to this player profile and to the group + * of favorite tracks of the Track Manager. + */ +void FavoriteTrackStatus::addFavoriteTrack(std::string ident, std::string group) +{ + m_favorite_tracks[group].insert(ident); +} // addFavoriteTrack + +//------------------------------------------------------------------------------ +/** Removes a favorite track from this player profile and from the group + * of favorite tracks of the Track Manager. + */ +void FavoriteTrackStatus::removeFavoriteTrack(std::string ident, std::string group) +{ + if (m_favorite_tracks[group].find(ident) != m_favorite_tracks[group].end()) + { + m_favorite_tracks[group].erase(ident); + } +} // removeFavoriteTrack + +//------------------------------------------------------------------------------ +/** Writes the data for this player to the specified UTFWriter. + * \param out The utf writer to write the data to. + */ +void FavoriteTrackStatus::save(UTFWriter &out) +{ + out << " \n"; + for (auto it_group = m_favorite_tracks.begin(); it_group != m_favorite_tracks.end(); it_group++) + { + std::string group_name = it_group->first; + + if (group_name == DEFAULT_FAVORITE_GROUP_NAME) + { + for (auto it_track = it_group->second.begin(); it_track != it_group->second.end(); it_track++) + { + out << " \n"; + } + } + else + { + out << " \n"; + for (auto it_track = it_group->second.begin(); it_track != it_group->second.end(); it_track++) + { + out << " \n"; + } + out << " \n"; + } + } + out << " \n"; +} // save diff --git a/src/config/favorite_track_status.hpp b/src/config/favorite_track_status.hpp new file mode 100644 index 0000000000..6defd245dc --- /dev/null +++ b/src/config/favorite_track_status.hpp @@ -0,0 +1,74 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2024 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_FAVORITE_TRACK_STATUS_HPP +#define HEADER_FAVORITE_TRACK_STATUS_HPP + +#include "utils/leak_check.hpp" + +#include + +#include +#include +#include + +using namespace irr; + +class TrackManager; +class UTFWriter; +class XMLNode; + +/** Class for managing player profiles (name, usage frequency, + * etc.). All PlayerProfiles are managed by the PlayerManager. + * A PlayerProfile keeps track of the story mode progress using an instance + * of StoryModeStatus, and achievements with AchievementsStatus. All data + * is saved in the players.xml file. + * This class also defines the interface for handling online data. All of + * the online handling is done in the derived class OnlinePlayerProfile, + * where the interface is fully implemented. + * \ingroup config + */ +class FavoriteTrackStatus +{ +private: + LEAK_CHECK() + + /** unordered_map > .*/ + std::unordered_map > m_favorite_tracks; + +public: + friend class TrackManager; + + static const std::string DEFAULT_FAVORITE_GROUP_NAME; + + FavoriteTrackStatus(const XMLNode *node); + + virtual ~FavoriteTrackStatus(); + + void save(UTFWriter &out); + + bool isFavoriteTrack(std::string ident); + + void addFavoriteTrack(std::string ident, std::string group = DEFAULT_FAVORITE_GROUP_NAME); + + void removeFavoriteTrack(std::string ident, std::string group = DEFAULT_FAVORITE_GROUP_NAME); +}; // class PlayerProfile + +#endif + +/*EOF*/ diff --git a/src/config/player_manager.cpp b/src/config/player_manager.cpp index 55e061b012..f3637e49fd 100644 --- a/src/config/player_manager.cpp +++ b/src/config/player_manager.cpp @@ -241,10 +241,6 @@ void PlayerManager::initRemainingData() // Sort player by frequency m_all_players.insertionSort(/*start*/0, /*desc*/true); - - // Load favorite tracks for the current player - if (m_current_player) - m_current_player->setFavoriteTracks(); } // initRemainingData // ---------------------------------------------------------------------------- @@ -486,7 +482,6 @@ void PlayerManager::setCurrentPlayer(PlayerProfile *player) if(m_current_player) { m_current_player->computeActive(); - m_current_player->setFavoriteTracks(); } if (player_has_changed) diff --git a/src/config/player_profile.cpp b/src/config/player_profile.cpp index 729450657e..52c4016f96 100644 --- a/src/config/player_profile.cpp +++ b/src/config/player_profile.cpp @@ -79,7 +79,7 @@ PlayerProfile::PlayerProfile(const XMLNode* node) m_remember_password = false; m_story_mode_status = NULL; m_achievements_status = NULL; - m_favorite_tracks.clear(); + m_favorite_track_status = NULL; m_default_kart_color = 0.0f; m_icon_filename = ""; @@ -106,6 +106,7 @@ PlayerProfile::~PlayerProfile() { delete m_story_mode_status; delete m_achievements_status; + delete m_favorite_track_status; #ifdef DEBUG m_magic_number = 0xDEADBEEF; #endif @@ -118,29 +119,20 @@ PlayerProfile::~PlayerProfile() */ void PlayerProfile::loadRemainingData(const XMLNode *node) { + assert(m_story_mode_status == NULL); const XMLNode *xml_story_mode = node->getNode("story-mode"); - m_story_mode_status = - unlock_manager->createStoryModeStatus(xml_story_mode); + m_story_mode_status = unlock_manager->createStoryModeStatus(xml_story_mode); + + assert(m_achievements_status == NULL); const XMLNode *xml_achievements = node->getNode("achievements"); m_achievements_status = AchievementsManager::get() - ->createAchievementsStatus(xml_achievements, m_unique_id == 1); + ->createAchievementsStatus(xml_achievements, m_unique_id == 1); // We first load the list of all favorite tracks // Some favorites may correspond to uninstalled addons, so we do not sanitize the strings - // TODO : handle Arena and Soccer fields + assert(m_favorite_track_status == NULL); const XMLNode *xml_favorites = node->getNode("favorites"); - std::vector xml_favorite_tracks; - xml_favorites->getNodes("track", xml_favorite_tracks); - for (unsigned int i = 0; i < xml_favorite_tracks.size(); i++) - { - std::string temp_string; - xml_favorite_tracks[i]->get("ident", &temp_string); - m_favorite_tracks.push_back(temp_string); - } - // Deduplicate the list just in case. - std::sort(m_favorite_tracks.begin(), m_favorite_tracks.end()); - auto it = std::unique(m_favorite_tracks.begin(), m_favorite_tracks.end()); - m_favorite_tracks.erase(it, m_favorite_tracks.end()); + m_favorite_track_status = new FavoriteTrackStatus(xml_favorites); // Fix up any potentially missing icons. addIcon(); @@ -155,49 +147,10 @@ void PlayerProfile::initRemainingData() m_story_mode_status = unlock_manager->createStoryModeStatus(); m_achievements_status = AchievementsManager::get()->createAchievementsStatus(); + m_favorite_track_status = new FavoriteTrackStatus(NULL); addIcon(); } // initRemainingData -//------------------------------------------------------------------------------ -/** Update the group of favorite tracks handled by the Track Manager. - * To be used only if this player profile is the current player. - */ -void PlayerProfile::setFavoriteTracks() -{ - // Update the group data from the Track Manager - track_manager->clearFavoriteTracks(); - for (unsigned int i = 0; i < m_favorite_tracks.size(); i++) - { - track_manager->addFavoriteTrack(m_favorite_tracks[i]); - } -} // setFavoriteTracks - -//------------------------------------------------------------------------------ -/** Adds a new favorite track to this player profile and to the group - * of favorite tracks of the Track Manager. - * To be used only if this player profile is the current player. - */ -void PlayerProfile::addFavoriteTrack(std::string ident) -{ - m_favorite_tracks.push_back(ident); - track_manager->addFavoriteTrack(ident); -} // addFavoriteTrack - -//------------------------------------------------------------------------------ -/** Removes a favorite track from this player profile and from the group - * of favorite tracks of the Track Manager. - * To be used only if this player profile is the current player. - */ -void PlayerProfile::removeFavoriteTrack(std::string ident) -{ - auto it = std::find(m_favorite_tracks.begin(), m_favorite_tracks.end(), ident); - if (it != m_favorite_tracks.end()) // the track to remove has been found - { - m_favorite_tracks.erase(it); - setFavoriteTracks(); - } -} // removeFavoriteTrack - //------------------------------------------------------------------------------ /** Creates an icon for a player if non exist so far. It takes the unique * player id modulo the number of karts to pick an icon from the karts. It @@ -282,18 +235,14 @@ void PlayerProfile::save(UTFWriter &out) if (player != NULL && (getName() == player->getName())) is_current_player = true; - if(m_story_mode_status) + if (m_story_mode_status) m_story_mode_status->save(out, is_current_player); - if(m_achievements_status) + if (m_achievements_status) m_achievements_status->save(out); - - out << " \n"; - for (unsigned int i=0; i < m_favorite_tracks.size(); i++) - { - out << " \n"; - } - out << " \n"; + + if (m_favorite_track_status) + m_favorite_track_status->save(out); } out << " \n"; } // save diff --git a/src/config/player_profile.hpp b/src/config/player_profile.hpp index 04fcd1f76a..d0ecf5c981 100644 --- a/src/config/player_profile.hpp +++ b/src/config/player_profile.hpp @@ -20,6 +20,7 @@ #define HEADER_PLAYER_PROFILE_HPP #include "challenges/story_mode_status.hpp" +#include "config/favorite_track_status.hpp" #include "network/remote_kart_info.hpp" #include "utils/leak_check.hpp" #include "utils/no_copy.hpp" @@ -114,8 +115,8 @@ class PlayerProfile : public NoCopy /** The complete achievement data. */ AchievementsStatus *m_achievements_status; - /** The list of identifiers of favorite tracks .*/ - std::vector m_favorite_tracks; + /** The favorite tracks selected by this player. */ + FavoriteTrackStatus *m_favorite_track_status; public: @@ -125,9 +126,6 @@ class PlayerProfile : public NoCopy void save(UTFWriter &out); void loadRemainingData(const XMLNode *node); void initRemainingData(); - void setFavoriteTracks(); - void addFavoriteTrack(std::string ident); - void removeFavoriteTrack(std::string ident); void incrementUseFrequency(); int getUseFrequency() const { return m_use_frequency; } bool operator<(const PlayerProfile &other); @@ -204,13 +202,6 @@ class PlayerProfile : public NoCopy return m_story_mode_status->isLocked(feature); } // isLocked // ---------------------------------------------------------------------------------------- - /** Returnes if the track is favorite. */ - bool isFavoriteTrack(const std::string &ident) const - { - return std::find(m_favorite_tracks.begin(), m_favorite_tracks.end(), ident) - != m_favorite_tracks.end(); - } // isFavoriteTrack - // ---------------------------------------------------------------------------------------- /** Returns all active challenges. */ void computeActive() { m_story_mode_status->computeActive(); } // ---------------------------------------------------------------------------------------- @@ -303,6 +294,23 @@ class PlayerProfile : public NoCopy // ---------------------------------------------------------------------------------------- StoryModeStatus* getStoryModeStatus() { return m_story_mode_status; } // ---------------------------------------------------------------------------------------- + FavoriteTrackStatus* getFavoriteTrackStatus() { return m_favorite_track_status; } + // ---------------------------------------------------------------------------------------- + bool isFavoriteTrack(std::string ident) + { + return m_favorite_track_status->isFavoriteTrack(ident); + } // getNumBestTrophies + void addFavoriteTrack(std::string ident, std::string group = + FavoriteTrackStatus::DEFAULT_FAVORITE_GROUP_NAME) + { + m_favorite_track_status->addFavoriteTrack(ident, group); + } // getNumBestTrophies + void removeFavoriteTrack(std::string ident, std::string group = + FavoriteTrackStatus::DEFAULT_FAVORITE_GROUP_NAME) + { + m_favorite_track_status->removeFavoriteTrack(ident, group); + } // getNumBestTrophies + // ---------------------------------------------------------------------------------------- /** If a session was saved, return the id of the saved user. */ int getSavedUserId() const { diff --git a/src/states_screens/arenas_screen.cpp b/src/states_screens/arenas_screen.cpp index fff9cc6e05..ec840e1d8e 100644 --- a/src/states_screens/arenas_screen.cpp +++ b/src/states_screens/arenas_screen.cpp @@ -20,6 +20,7 @@ #include "config/user_config.hpp" #include "graphics/irr_driver.hpp" #include "guiengine/widget.hpp" +#include "guiengine/widgets/check_box_widget.hpp" #include "guiengine/widgets/dynamic_ribbon_widget.hpp" #include "guiengine/widgets/icon_button_widget.hpp" #include "io/file_manager.hpp" @@ -57,6 +58,12 @@ void ArenasScreen::loadedFromFile() void ArenasScreen::beforeAddingWidget() { + // Add user-defined group to track groups + track_manager->setFavoriteTrackStatus(PlayerManager::getCurrentPlayer()->getFavoriteTrackStatus()); + + CheckBoxWidget* favorite_cb = getWidget("favorite"); + assert( favorite_cb != NULL ); + favorite_cb->setState(false); // Dynamically add tabs RibbonWidget* tabs = this->getWidget("trackgroups"); @@ -79,6 +86,8 @@ void ArenasScreen::beforeAddingWidget() //I18N: track group name FOR_GETTEXT_ONLY( _("All") ) //I18N: track group name + FOR_GETTEXT_ONLY( _("Favorites") ) + //I18N: track group name FOR_GETTEXT_ONLY( _("Standard") ) //I18N: track group name FOR_GETTEXT_ONLY( _("Add-Ons") ) @@ -204,8 +213,20 @@ void ArenasScreen::eventCallback(Widget* widget, const std::string& name, const Track* clicked_track = track_manager->getTrack(selection); if (clicked_track != NULL) { - TrackInfoScreen::getInstance()->setTrack(clicked_track); - TrackInfoScreen::getInstance()->push(); + if (getWidget("favorite")->getState()) + { + if(PlayerManager::getCurrentPlayer()->isFavoriteTrack(clicked_track->getIdent())) + PlayerManager::getCurrentPlayer()->removeFavoriteTrack(clicked_track->getIdent()); + else + PlayerManager::getCurrentPlayer()->addFavoriteTrack(clicked_track->getIdent()); + + buildTrackList(); + } + else + { + TrackInfoScreen::getInstance()->setTrack(clicked_track); + TrackInfoScreen::getInstance()->push(); + } } // clickedTrack != NULL } // if random_track @@ -225,6 +246,9 @@ void ArenasScreen::eventCallback(Widget* widget, const std::string& name, const void ArenasScreen::buildTrackList() { + // Add user-defined group to track groups + track_manager->setFavoriteTrackStatus(PlayerManager::getCurrentPlayer()->getFavoriteTrackStatus()); + DynamicRibbonWidget* w = this->getWidget("tracks"); assert( w != NULL ); @@ -237,6 +261,7 @@ void ArenasScreen::buildTrackList() bool soccer_mode = RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_SOCCER; bool arenas_have_navmesh = false; + PtrVector tracks; if (curr_group_name == ALL_ARENA_GROUPS_ID) { @@ -275,19 +300,8 @@ void ArenasScreen::buildTrackList() continue; } } - - if (PlayerManager::getCurrentPlayer()->isLocked(curr->getIdent())) - { - w->addItem( _("Locked : solve active challenges to gain access to more!"), - "locked", curr->getScreenshotFile(), LOCKED_BADGE ); - } - else - { - w->addItem(curr->getName(), curr->getIdent(), curr->getScreenshotFile(), 0, - IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE ); - } + tracks.push_back(curr); } - } else { @@ -327,17 +341,29 @@ void ArenasScreen::buildTrackList() continue; } } + tracks.push_back(curr); + } + } + tracks.insertionSort(); - if (PlayerManager::getCurrentPlayer()->isLocked(curr->getIdent())) - { - w->addItem( _("Locked : solve active challenges to gain access to more!"), - "locked", curr->getScreenshotFile(), LOCKED_BADGE ); - } - else - { - w->addItem(curr->getName(), curr->getIdent(), curr->getScreenshotFile(), 0, - IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE ); - } + for (unsigned int i = 0; i < tracks.size(); i++) + { + Track *curr = tracks.get(i); + if (PlayerManager::getCurrentPlayer()->isLocked(curr->getIdent())) + { + w->addItem( _("Locked : solve active challenges to gain access to more!"), + "locked", curr->getScreenshotFile(), LOCKED_BADGE ); + } + else if (PlayerManager::getCurrentPlayer()->isFavoriteTrack(curr->getIdent())) + { + w->addItem(curr->getName(), curr->getIdent(), + curr->getScreenshotFile(), HEART_BADGE, + IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE); + } + else + { + w->addItem(curr->getName(), curr->getIdent(), curr->getScreenshotFile(), 0, + IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE ); } } if (arenas_have_navmesh || RaceManager::get()->getNumLocalPlayers() > 1 || diff --git a/src/states_screens/easter_egg_screen.cpp b/src/states_screens/easter_egg_screen.cpp index 6c348c2f72..771a21f0d3 100644 --- a/src/states_screens/easter_egg_screen.cpp +++ b/src/states_screens/easter_egg_screen.cpp @@ -121,6 +121,9 @@ void EasterEggScreen::eventCallback(Widget* widget, const std::string& name, con void EasterEggScreen::beforeAddingWidget() { + // Add user-defined group to track groups + track_manager->setFavoriteTrackStatus(PlayerManager::getCurrentPlayer()->getFavoriteTrackStatus()); + Screen::init(); // Dynamically add tabs RibbonWidget* tabs = this->getWidget("trackgroups"); @@ -201,6 +204,9 @@ void EasterEggScreen::init() void EasterEggScreen::buildTrackList() { + // Add user-defined group to track groups + track_manager->setFavoriteTrackStatus(PlayerManager::getCurrentPlayer()->getFavoriteTrackStatus()); + DynamicRibbonWidget* tracks_widget = this->getWidget("tracks"); assert( tracks_widget != NULL ); @@ -213,6 +219,8 @@ void EasterEggScreen::buildTrackList() const std::string curr_group_name = tabs->getSelectionIDString(0); + PtrVector tracks; + // Build track list if (curr_group_name == ALL_TRACK_GROUPS_ID) { @@ -227,21 +235,8 @@ void EasterEggScreen::buildTrackList() if (curr->isArena() || curr->isSoccer()) continue; if (curr->isInternal()) continue; - if (PlayerManager::getCurrentPlayer()->isLocked(curr->getIdent())) - { - tracks_widget->addItem( _("Locked : solve active challenges to gain access to more!"), - "locked", curr->getScreenshotFile(), LOCKED_BADGE, - IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE); - } - else - { - tracks_widget->addItem(curr->getName(), curr->getIdent(), - curr->getScreenshotFile(), 0, - IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE ); - m_random_track_list.push_back(curr->getIdent()); - } + tracks.push_back(curr); } - } else { @@ -258,19 +253,34 @@ void EasterEggScreen::buildTrackList() if (curr->isSoccer()) continue; if (curr->isInternal()) continue; - if (PlayerManager::getCurrentPlayer()->isLocked(curr->getIdent())) - { - tracks_widget->addItem( _("Locked : solve active challenges to gain access to more!"), - "locked", curr->getScreenshotFile(), LOCKED_BADGE, - IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE); - } - else - { - tracks_widget->addItem(curr->getName(), curr->getIdent(), - curr->getScreenshotFile(), 0 /* no badge */, - IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE ); - m_random_track_list.push_back(curr->getIdent()); - } + tracks.push_back(curr); + } + } + tracks.insertionSort(); + + for (int n=0; nisLocked(curr->getIdent())) + { + tracks_widget->addItem( _("Locked : solve active challenges to gain access to more!"), + "locked", curr->getScreenshotFile(), LOCKED_BADGE, + IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE); + } + else if (PlayerManager::getCurrentPlayer()->isFavoriteTrack(curr->getIdent())) + { + tracks_widget->addItem(curr->getName(), curr->getIdent(), + curr->getScreenshotFile(), HEART_BADGE, + IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE ); + m_random_track_list.push_back(curr->getIdent()); + } + else + { + tracks_widget->addItem(curr->getName(), curr->getIdent(), + curr->getScreenshotFile(), 0, + IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE ); + m_random_track_list.push_back(curr->getIdent()); } } diff --git a/src/states_screens/online/tracks_screen.cpp b/src/states_screens/online/tracks_screen.cpp index 49d53a0a8a..434c8e9f65 100644 --- a/src/states_screens/online/tracks_screen.cpp +++ b/src/states_screens/online/tracks_screen.cpp @@ -219,6 +219,9 @@ void TracksScreen::beforeAddingWidget() { Screen::init(); + // Add user-defined group to track groups + track_manager->setFavoriteTrackStatus(PlayerManager::getCurrentPlayer()->getFavoriteTrackStatus()); + m_selected_track = NULL; m_search_track = NULL; m_timer = getWidget("timer"); @@ -567,6 +570,9 @@ void TracksScreen::init() */ void TracksScreen::buildTrackList() { + // Add user-defined group to track groups + track_manager->setFavoriteTrackStatus(PlayerManager::getCurrentPlayer()->getFavoriteTrackStatus()); + DynamicRibbonWidget* tracks_widget = this->getWidget("tracks"); RibbonWidget* tabs = this->getWidget("trackgroups"); @@ -575,7 +581,6 @@ void TracksScreen::buildTrackList() m_random_track_list.clear(); const std::string& curr_group_name = tabs->getSelectionIDString(0); - const int track_amount = (int)track_manager->getNumberOfTracks(); // First build a list of all tracks to be displayed // (e.g. exclude arenas, ...) @@ -587,35 +592,75 @@ void TracksScreen::buildTrackList() assert(clrp); } PtrVector tracks; - for (int n = 0; n < track_amount; n++) + if (curr_group_name == ALL_TRACK_GROUPS_ID) { - Track* curr = track_manager->getTrack(n); - core::stringw search_text; - if (m_search_track) - { - search_text = m_search_track->getText(); - search_text.make_lower(); - } - if (!search_text.empty() && - curr->getName().make_lower().find(search_text.c_str()) == -1) - continue; - if (RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_EASTER_EGG - && !curr->hasEasterEggs()) - continue; - if (!is_network && - (curr->isArena() || curr->isSoccer() || curr->isInternal())) - continue; - if (curr_group_name != ALL_TRACK_GROUPS_ID && - !curr->isInGroup(curr_group_name)) continue; - if (is_network && - clrp->getAvailableTracks().find(curr->getIdent()) == - clrp->getAvailableTracks().end()) + const int track_amount = (int)track_manager->getNumberOfTracks(); + for (int n = 0; n < track_amount; n++) { - continue; - } - tracks.push_back(curr); - } // for ngetTrack(n); + core::stringw search_text; + if (m_search_track) + { + search_text = m_search_track->getText(); + search_text.make_lower(); + } + if (!search_text.empty() && + curr->getName().make_lower().find(search_text.c_str()) == -1) + continue; + if (RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_EASTER_EGG + && !curr->hasEasterEggs()) + continue; + if (!is_network && + (curr->isArena() || curr->isSoccer() || curr->isInternal())) + continue; + if (is_network && + clrp->getAvailableTracks().find(curr->getIdent()) == + clrp->getAvailableTracks().end()) + { + continue; + } + tracks.push_back(curr); + } // for n curr_tracks = track_manager->getTracksInGroup(curr_group_name); + const std::vector& curr_arenas = track_manager->getArenasInGroup(curr_group_name, false); + const std::vector& curr_soccers = track_manager->getArenasInGroup(curr_group_name, true); + curr_tracks.insert(curr_tracks.end(), curr_arenas.begin(), curr_arenas.end()); + curr_tracks.insert(curr_tracks.end(), curr_soccers.begin(), curr_soccers.end()); + + const int track_amount = (int)curr_tracks.size(); + + for (int n = 0; n < track_amount; n++) + { + Track* curr = track_manager->getTrack(curr_tracks[n]); + core::stringw search_text; + if (m_search_track) + { + search_text = m_search_track->getText(); + search_text.make_lower(); + } + if (!search_text.empty() && + curr->getName().make_lower().find(search_text.c_str()) == -1) + continue; + if (RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_EASTER_EGG + && !curr->hasEasterEggs()) + continue; + if (!is_network && + (curr->isArena() || curr->isSoccer() || curr->isInternal())) + continue; + if (is_network && + clrp->getAvailableTracks().find(curr->getIdent()) == + clrp->getAvailableTracks().end()) + { + continue; + } + tracks.push_back(curr); + } // for ngetScreenshotFile(), LOCKED_BADGE, IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE); } + else if (PlayerManager::getCurrentPlayer()->isFavoriteTrack(curr->getIdent())) + { + tracks_widget->addItem(curr->getName(), curr->getIdent(), + curr->getScreenshotFile(), HEART_BADGE, + IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE ); + m_random_track_list.push_back(curr->getIdent()); + } else { tracks_widget->addItem(curr->getName(), diff --git a/src/states_screens/tracks_and_gp_screen.cpp b/src/states_screens/tracks_and_gp_screen.cpp index b0eca47a2f..a8ab6542ed 100644 --- a/src/states_screens/tracks_and_gp_screen.cpp +++ b/src/states_screens/tracks_and_gp_screen.cpp @@ -18,6 +18,7 @@ #include "states_screens/tracks_and_gp_screen.hpp" #include "challenges/unlock_manager.hpp" +#include "config/favorite_track_status.hpp" #include "config/player_manager.hpp" #include "config/user_config.hpp" #include "graphics/stk_tex_manager.hpp" @@ -90,7 +91,7 @@ void TracksAndGPScreen::eventCallback(Widget* widget, const std::string& name, // In favorite edit mode, switch the status of the selected track if (getWidget("favorite")->getState()) { - if(track->isInGroup("Favorites")) + if(PlayerManager::getCurrentPlayer()->isFavoriteTrack(track->getIdent())) PlayerManager::getCurrentPlayer()->removeFavoriteTrack(track->getIdent()); else PlayerManager::getCurrentPlayer()->addFavoriteTrack(track->getIdent()); @@ -154,6 +155,10 @@ void TracksAndGPScreen::eventCallback(Widget* widget, const std::string& name, void TracksAndGPScreen::beforeAddingWidget() { Screen::init(); + + // Add user-defined group to track groups + track_manager->setFavoriteTrackStatus(PlayerManager::getCurrentPlayer()->getFavoriteTrackStatus()); + RibbonWidget* tabs = getWidget("trackgroups"); tabs->clearAllChildren(); @@ -276,6 +281,9 @@ void TracksAndGPScreen::init() */ void TracksAndGPScreen::buildTrackList() { + // Add user-defined group to track groups + track_manager->setFavoriteTrackStatus(PlayerManager::getCurrentPlayer()->getFavoriteTrackStatus()); + DynamicRibbonWidget* tracks_widget = this->getWidget("tracks"); RibbonWidget* tabs = this->getWidget("trackgroups"); @@ -285,28 +293,49 @@ void TracksAndGPScreen::buildTrackList() const std::string& curr_group_name = tabs->getSelectionIDString(0); - const int track_amount = (int)track_manager->getNumberOfTracks(); - // First build a list of all tracks to be displayed // (e.g. exclude arenas, ...) PtrVector tracks; - for (int n = 0; n < track_amount; n++) + if (curr_group_name == ALL_TRACK_GROUPS_ID) { - Track* curr = track_manager->getTrack(n); - if (curr->isArena() || curr->isSoccer() || curr->isInternal()) continue; - if (RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_EASTER_EGG - && !curr->hasEasterEggs()) - continue; - core::stringw search_text = m_search_box->getText(); - search_text.make_lower(); - if (!search_text.empty() && - curr->getName().make_lower().find(search_text.c_str()) == -1) - continue; - if (curr_group_name != ALL_TRACK_GROUPS_ID && - !curr->isInGroup(curr_group_name)) continue; + const int track_amount = (int)track_manager->getNumberOfTracks(); + for (int n = 0; n < track_amount; n++) + { + Track* curr = track_manager->getTrack(n); + if (curr->isArena() || curr->isSoccer() || curr->isInternal()) continue; + if (RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_EASTER_EGG + && !curr->hasEasterEggs()) + continue; + core::stringw search_text = m_search_box->getText(); + search_text.make_lower(); + if (!search_text.empty() && + curr->getName().make_lower().find(search_text.c_str()) == -1) + continue; + + tracks.push_back(curr); + } // for n& curr_tracks = track_manager->getTracksInGroup(curr_group_name); + const int track_amount = (int)curr_tracks.size(); - tracks.push_back(curr); - } // for ngetTrack(curr_tracks[n]); + if (curr->isArena() || curr->isSoccer() || curr->isInternal()) continue; + if (RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_EASTER_EGG + && !curr->hasEasterEggs()) + continue; + core::stringw search_text = m_search_box->getText(); + search_text.make_lower(); + if (!search_text.empty() && + curr->getName().make_lower().find(search_text.c_str()) == -1) + continue; + + tracks.push_back(curr); + } // for ngetScreenshotFile(), LOCKED_BADGE, IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE); } - else if (curr->isInGroup("Favorites")) + else if (PlayerManager::getCurrentPlayer()->isFavoriteTrack(curr->getIdent())) { tracks_widget->addItem(curr->getName(), curr->getIdent(), curr->getScreenshotFile(), HEART_BADGE, diff --git a/src/tracks/track.cpp b/src/tracks/track.cpp index 63e4cef4ac..d5934ec0e6 100644 --- a/src/tracks/track.cpp +++ b/src/tracks/track.cpp @@ -23,6 +23,7 @@ #include "audio/music_manager.hpp" #include "challenges/challenge_status.hpp" #include "challenges/unlock_manager.hpp" +#include "config/favorite_track_status.hpp" #include "config/player_manager.hpp" #include "config/stk_config.hpp" #include "config/user_config.hpp" @@ -261,14 +262,6 @@ core::stringw Track::getSortName() const */ bool Track::isInGroup(const std::string &group_name) { - if (group_name == "Favorites") - { - int index = track_manager->getTrackIndexByIdent(m_ident); - std::vector favorite_list = track_manager->getTracksInGroup("Favorites"); - return std::find(favorite_list.begin(), favorite_list.end(), index) - != favorite_list.end(); - } - return std::find(m_groups.begin(), m_groups.end(), group_name) != m_groups.end(); } // isInGroup @@ -623,7 +616,7 @@ void Track::loadTrackInfo() m_all_modes.push_back(tm); } - if(m_groups.size()==0) m_groups.push_back(DEFAULT_GROUP_NAME); + if(m_groups.size()==0) m_groups.push_back(FavoriteTrackStatus::DEFAULT_FAVORITE_GROUP_NAME); const XMLNode *xml_node = root->getNode("curves"); if(xml_node) loadCurves(*xml_node); diff --git a/src/tracks/track_manager.cpp b/src/tracks/track_manager.cpp index 41a82da33d..677cbe9d8e 100644 --- a/src/tracks/track_manager.cpp +++ b/src/tracks/track_manager.cpp @@ -43,7 +43,9 @@ std::vector TrackManager::m_track_search_path; /** Constructor (currently empty). The real work happens in loadTrackList. */ TrackManager::TrackManager() -{} // TrackManager +{ + m_current_favorite_status = NULL; +} // TrackManager //----------------------------------------------------------------------------- /** Delete all tracks. @@ -151,12 +153,18 @@ std::vector TrackManager::getAllTrackIdentifiers() void TrackManager::loadTrackList() { m_all_track_dirs.clear(); + m_track_group_names.clear(); m_track_groups.clear(); m_arena_group_names.clear(); m_soccer_arena_group_names.clear(); m_arena_groups.clear(); m_soccer_arena_groups.clear(); + + m_track_groups_no_custom.clear(); + m_arena_groups_no_custom.clear(); + m_soccer_arena_groups_no_custom.clear(); + m_track_avail.clear(); // This function is called when install a new addons, delete previous // tracks @@ -164,9 +172,6 @@ void TrackManager::loadTrackList() delete track; m_tracks.clear(); - // Add the special favorites group - m_track_group_names.push_back("Favorites"); - for(unsigned int i=0; iisArena() ? m_arena_groups : (track->isSoccer() ? m_soccer_arena_groups : m_track_groups)); + + Group2Indices &group_2_indices_no_custom = + (track->isArena() ? m_arena_groups_no_custom : + (track->isSoccer() ? m_soccer_arena_groups_no_custom : + m_track_groups_no_custom)); std::vector &group_names = (track->isArena() ? m_arena_group_names : (track->isSoccer() ? m_soccer_arena_group_names : m_track_group_names)); - const std::vector& groups=track->getGroups(); + std::vector groups=track->getGroups(); + if (m_current_favorite_status) + { + for (auto it = m_current_favorite_status->m_favorite_tracks.begin(); + it != m_current_favorite_status->m_favorite_tracks.end(); it++) + { // User-defined groups + if (it->second.find(ident) != it->second.end()) + { + groups.push_back(it->first); + } + } + } + for(unsigned int i=0; i &indices = group_2_indices[groups[i]]; + std::vector &indices = group_2_indices_no_custom[groups[i]]; std::vector::iterator j; j = std::find(indices.begin(), indices.end(), index); + if (j != indices.end()) + indices.erase(j); + + // If the track was the last member of a group, + // completely remove the group + if(indices.size()==0) + { + group_2_indices_no_custom.erase(groups[i]); + } // if complete group must be removed + + indices = group_2_indices[groups[i]]; + j = std::find(indices.begin(), indices.end(), index); assert(j!=indices.end()); indices.erase(j); @@ -279,7 +313,7 @@ void TrackManager::removeTrack(const std::string &ident) group_2_indices.erase(groups[i]); std::vector::iterator it_g; it_g = std::find(group_names.begin(), group_names.end(), - groups[i]); + groups[i]); assert(it_g!=group_names.end()); group_names.erase(it_g); } // if complete group must be removed @@ -293,11 +327,19 @@ void TrackManager::removeTrack(const std::string &ident) Group2Indices &g2i = (i==0 ? m_soccer_arena_groups : (i==1 ? m_arena_groups : m_track_groups)); + Group2Indices &g2i_nc = (i==0 ? m_soccer_arena_groups_no_custom : + (i==1 ? m_arena_groups_no_custom : + m_track_groups_no_custom)); Group2Indices::iterator j; - for(j=g2i.begin(); j!=g2i.end(); j++) + for(j = g2i.begin(); j != g2i.end(); j++) + { + for(unsigned int i = 0; i < (*j).second.size(); i++) + if((*j).second[i] > index) (*j).second[i]--; + } // for j in group_2_indices + for(j = g2i_nc.begin(); j != g2i_nc.end(); j++) { - for(unsigned int i=0; i<(*j).second.size(); i++) - if((*j).second[i]>index) (*j).second[i]--; + for(unsigned int i = 0; i < (*j).second.size(); i++) + if((*j).second[i] > index) (*j).second[i]--; } // for j in group_2_indices } // for i in arenas, tracks @@ -315,13 +357,18 @@ void TrackManager::updateGroups(const Track* track) { if (track->isInternal()) return; - //TODO : ignore self-declarations from tracks as being in the "favorites" group - const std::vector& new_groups = track->getGroups(); + std::string ident = track->getIdent(); + std::vector new_groups = track->getGroups(); Group2Indices &group_2_indices = (track->isArena() ? m_arena_groups : (track->isSoccer() ? m_soccer_arena_groups : m_track_groups)); + + Group2Indices &group_2_indices_no_custom = + (track->isArena() ? m_arena_groups_no_custom : + (track->isSoccer() ? m_soccer_arena_groups_no_custom : + m_track_groups_no_custom)); std::vector &group_names = (track->isArena() ? m_arena_group_names : @@ -336,27 +383,91 @@ void TrackManager::updateGroups(const Track* track) if(!group_exists) group_names.push_back(new_groups[i]); group_2_indices[new_groups[i]].push_back((int)m_tracks.size()-1); + group_2_indices_no_custom[new_groups[i]].push_back((int)m_tracks.size()-1); + } + + if (m_current_favorite_status) + { + for (auto it = m_current_favorite_status->m_favorite_tracks.begin(); + it != m_current_favorite_status->m_favorite_tracks.end(); it++) + { // User-defined groups + if (it->second.find(ident) != it->second.end()) + { + bool group_exists = group_2_indices.find(ident) + != group_2_indices.end(); + if(!group_exists) + group_names.push_back(ident); + group_2_indices[ident].push_back((int)m_tracks.size()-1); + } + } } } // updateGroups // ---------------------------------------------------------------------------- -/** \brief Adds a track to the favorite track group +/** \brief Adds a player's favorite track status to define the custom group */ -void TrackManager::addFavoriteTrack(const std::string& ident) +void TrackManager::setFavoriteTrackStatus(FavoriteTrackStatus *status) { - // TODO : check if the ident corresponds to a race-track - int index = getTrackIndexByIdent(ident); - if (index >= 0) // index is negative if a track matching the index is missing - m_track_groups["Favorites"].push_back(index); + clearFavoriteTrackStatus(); + + m_current_favorite_status = status; + + if (status == NULL) + { + return; + } + + // Add all user-defined groups + for (auto it = status->m_favorite_tracks.begin(); it != status->m_favorite_tracks.end(); it++) + { + for (auto it_name = it->second.begin(); it_name != it->second.end(); it_name++) + { + int id = getTrackIndexByIdent(*it_name); + Track *track = m_tracks[id]; + + Group2Indices &group_2_indices = + (track->isArena() ? m_arena_groups : + (track->isSoccer() ? m_soccer_arena_groups : + m_track_groups)); + + group_2_indices[it->first].push_back(id); + } + } + for (int i = 0; i < 3; i++) + { + Group2Indices &g2i = (i==0 ? m_soccer_arena_groups : + (i==1 ? m_arena_groups : + m_track_groups)); + std::vector &gn = (i==0 ? m_soccer_arena_group_names : + (i==1 ? m_arena_group_names : + m_track_group_names)); + gn.clear(); + for (auto it = g2i.begin(); it != g2i.end(); it++) + { + std::sort(it->second.begin(), it->second.end()); + auto unique_end = std::unique(it->second.begin(), it->second.end()); + it->second.erase(unique_end, it->second.end()); + gn.push_back(it->first); + } + // Make sure the order of groups are right + std::sort(gn.begin(), gn.end(), [&, g2i](std::string &a, std::string &b)->bool{ + int x = g2i.find(a)->second[0], y = g2i.find(b)->second[0]; + return x == y ? a < b : x < y; + }); + } } // addFavoriteTrack // ---------------------------------------------------------------------------- /** \brief Clears the list of active favorite tracks, used e.g. when switching * between player profiles. */ -void TrackManager::clearFavoriteTracks() +void TrackManager::clearFavoriteTrackStatus() { - m_track_groups["Favorites"].clear(); + m_track_groups = m_track_groups_no_custom; + m_arena_groups = m_arena_groups_no_custom; + m_soccer_arena_groups = m_soccer_arena_groups_no_custom; + + m_current_favorite_status = NULL; } // clearFavoriteTracks // ---------------------------------------------------------------------------- diff --git a/src/tracks/track_manager.hpp b/src/tracks/track_manager.hpp index b0d49ee0ba..c95b1d14cf 100644 --- a/src/tracks/track_manager.hpp +++ b/src/tracks/track_manager.hpp @@ -19,6 +19,8 @@ #ifndef HEADER_TRACK_MANAGER_HPP #define HEADER_TRACK_MANAGER_HPP +#include "config/favorite_track_status.hpp" + #include #include #include @@ -62,11 +64,18 @@ class TrackManager /** List of the names of all groups containing soccer arenas */ std::vector m_soccer_arena_group_names; + /** Same as above but without user-defined groups. */ + Group2Indices m_track_groups_no_custom; + Group2Indices m_arena_groups_no_custom; + Group2Indices m_soccer_arena_groups_no_custom; + /** Flag if this track is available or not. Tracks are set unavailable * if they are not available on all clients (applies only to network mode) */ std::vector m_track_avail; + FavoriteTrackStatus *m_current_favorite_status; + void updateGroups(const Track* track); public: @@ -78,9 +87,9 @@ class TrackManager /** Adds a track to the special group of favorite tracks. * We need a special treatment, because the list of tracks in this group * depends on the player-profile, not on the track data. */ - void addFavoriteTrack(const std::string& ident); + void setFavoriteTrackStatus(FavoriteTrackStatus *status); - void clearFavoriteTracks(); + void clearFavoriteTrackStatus(); /** Returns a list of all track identifiers. */ std::vector getAllTrackIdentifiers();