From 2ae1dbeab25cd27ba1de702d1a06370448439ebe Mon Sep 17 00:00:00 2001 From: Yehonal Date: Tue, 19 Aug 2025 03:29:34 +0200 Subject: [PATCH] feat(core): add standalone functions for player settings (#22703) --- .../game/Entities/Player/PlayerSettings.cpp | 111 ++++++++++++++---- .../game/Entities/Player/PlayerSettings.h | 15 +++ 2 files changed, 101 insertions(+), 25 deletions(-) diff --git a/src/server/game/Entities/Player/PlayerSettings.cpp b/src/server/game/Entities/Player/PlayerSettings.cpp index dba4afaa5..89b719d71 100644 --- a/src/server/game/Entities/Player/PlayerSettings.cpp +++ b/src/server/game/Entities/Player/PlayerSettings.cpp @@ -18,11 +18,95 @@ #include "Player.h" #include "StringConvert.h" #include "Tokenize.h" +#include "CharacterDatabase.h" /*********************************************************/ /*** PLAYER SETTINGS SYSTEM ***/ /*********************************************************/ +namespace PlayerSettingsStore +{ + // Common helper: parse space-separated data string into PlayerSettingVector + PlayerSettingVector ParseSettingsData(std::string const& data) + { + PlayerSettingVector result; + std::vector tokens = Acore::Tokenize(data, ' ', false); + result.reserve(tokens.size()); + for (auto const& token : tokens) + { + if (token.empty()) + continue; + if (auto parsed = Acore::StringTo(token)) + result.emplace_back(*parsed); + } + return result; + } + + // Common helper: serialize PlayerSettingVector to space-separated string + std::string SerializeSettingsData(PlayerSettingVector const& settings) + { + if (settings.empty()) + return ""; + + std::ostringstream data; + data << settings[0].value; + for (size_t i = 1; i < settings.size(); ++i) + data << ' ' << settings[i].value; + return data.str(); + } + + // helper: load a single source row for a player and parse to vector + static PlayerSettingVector LoadPlayerSettings(uint32 playerLowGuid, std::string const& source) + { + PlayerSettingVector result; + + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_SETTINGS); + stmt->SetData(0, playerLowGuid); + PreparedQueryResult dbRes = CharacterDatabase.Query(stmt); + if (!dbRes) + return result; + + do + { + Field* fields = dbRes->Fetch(); + std::string rowSource = fields[0].Get(); + if (rowSource != source) + continue; + + std::string data = fields[1].Get(); + return ParseSettingsData(data); + } while (dbRes->NextRow()); + + return result; + } + + void UpdateSetting(uint32 playerLowGuid, std::string const& source, uint8 index, uint32 value) + { + if (!sWorld->getBoolConfig(CONFIG_PLAYER_SETTINGS_ENABLED)) + return; + + PlayerSettingVector settings = LoadPlayerSettings(playerLowGuid, source); + size_t const requiredSize = static_cast(index) + 1; + if (settings.size() < requiredSize) + settings.resize(requiredSize); // zero-initialized PlayerSetting::value + + settings[index].value = value; + + CharacterDatabasePreparedStatement* stmt = PlayerSettingsStore::PrepareReplaceStatement(playerLowGuid, source, settings); + CharacterDatabase.Execute(stmt); + } +} + +// Implementation of PrepareReplaceStatement +CharacterDatabasePreparedStatement* PlayerSettingsStore::PrepareReplaceStatement(uint32 playerLowGuid, std::string const& source, PlayerSettingVector const& settings) +{ + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CHAR_SETTINGS); + stmt->SetData(0, playerLowGuid); + stmt->SetData(1, source); + stmt->SetData(2, SerializeSettingsData(settings)); + return stmt; +} + void Player::_LoadCharacterSettings(PreparedQueryResult result) { m_charSettingsMap.clear(); @@ -39,21 +123,7 @@ void Player::_LoadCharacterSettings(PreparedQueryResult result) std::string source = fields[0].Get(); std::string data = fields[1].Get(); - std::vector tokens = Acore::Tokenize(data, ' ', false); - - PlayerSettingVector settings; - settings.reserve(tokens.size()); // reserve capacity but don't resize - - for (auto const& token : tokens) - { - if (token.empty()) - continue; - - // Try to parse the value safely - if (auto parsed = Acore::StringTo(token)) - settings.emplace_back(*parsed); - } - + PlayerSettingVector settings = PlayerSettingsStore::ParseSettingsData(data); m_charSettingsMap.emplace(std::move(source), std::move(settings)); } while (result->NextRow()); @@ -81,16 +151,7 @@ void Player::_SavePlayerSettings(CharacterDatabaseTransaction trans) if (settings.empty()) continue; - std::ostringstream data; - data << settings[0].value; - - for (size_t i = 1; i < settings.size(); ++i) - data << ' ' << settings[i].value; - - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CHAR_SETTINGS); - stmt->SetData(0, GetGUID().GetCounter()); - stmt->SetData(1, source); - stmt->SetData(2, data.str()); + CharacterDatabasePreparedStatement* stmt = PlayerSettingsStore::PrepareReplaceStatement(GetGUID().GetCounter(), source, settings); trans->Append(stmt); } } diff --git a/src/server/game/Entities/Player/PlayerSettings.h b/src/server/game/Entities/Player/PlayerSettings.h index 027062e3c..612225114 100644 --- a/src/server/game/Entities/Player/PlayerSettings.h +++ b/src/server/game/Entities/Player/PlayerSettings.h @@ -17,6 +17,7 @@ #ifndef _PLAYER_SETTINGS_H #define _PLAYER_SETTINGS_H +#include "DatabaseEnvFwd.h" class Player; @@ -51,4 +52,18 @@ struct PlayerSetting typedef std::vector PlayerSettingVector; typedef std::map PlayerSettingMap; +// Standalone API: update a player's setting directly on DB by GUID (low part) without requiring a Player instance +namespace PlayerSettingsStore +{ + // Update a single setting value for any player by GUID (works for online or offline players). + // This reads the existing "source" row from character_settings, adjusts the index, and REPLACE's it back. + void UpdateSetting(uint32 playerLowGuid, std::string const& source, uint8 index, uint32 value); + + // Common helpers for parsing and serializing settings data + PlayerSettingVector ParseSettingsData(std::string const& data); + std::string SerializeSettingsData(PlayerSettingVector const& settings); + // Prepare a REPLACE statement populated with given settings data. Caller may execute or append to a transaction. + CharacterDatabasePreparedStatement* PrepareReplaceStatement(uint32 playerLowGuid, std::string const& source, PlayerSettingVector const& settings); +} + #endif