From e21f808714c4ce423da94373bc1ea4e31280ee23 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 21 Mar 2026 02:46:21 -0700 Subject: [PATCH] feat: support SavedVariablesPerCharacter for per-character addon data Implement the SavedVariablesPerCharacter TOC directive that many addons use to store different settings per character (Bartender, Dominos, MoveAnything, WeakAuras, etc.). Without this, all characters share the same addon data file. Per-character files are stored as ..lua.saved alongside the existing account-wide .lua.saved files. The character name is resolved from the player GUID at world entry time. Changes: - TocFile::getSavedVariablesPerCharacter() parses the TOC directive - AddonManager loads/saves per-character vars alongside account-wide vars - Character name set from game handler before addon loading --- include/addons/addon_manager.hpp | 3 +++ include/addons/toc_parser.hpp | 1 + src/addons/addon_manager.cpp | 21 +++++++++++++++++++++ src/addons/toc_parser.cpp | 17 +++++++++++------ src/core/application.cpp | 15 +++++++++++++++ 5 files changed, 51 insertions(+), 6 deletions(-) diff --git a/include/addons/addon_manager.hpp b/include/addons/addon_manager.hpp index 681d3822..cfbfd297 100644 --- a/include/addons/addon_manager.hpp +++ b/include/addons/addon_manager.hpp @@ -26,6 +26,7 @@ public: bool isInitialized() const { return luaEngine_.isInitialized(); } void saveAllSavedVariables(); + void setCharacterName(const std::string& name) { characterName_ = name; } /// Re-initialize the Lua VM and reload all addons (used by /reload). bool reload(); @@ -38,6 +39,8 @@ private: bool loadAddon(const TocFile& addon); std::string getSavedVariablesPath(const TocFile& addon) const; + std::string getSavedVariablesPerCharacterPath(const TocFile& addon) const; + std::string characterName_; }; } // namespace wowee::addons diff --git a/include/addons/toc_parser.hpp b/include/addons/toc_parser.hpp index 7bfff469..b19b4a78 100644 --- a/include/addons/toc_parser.hpp +++ b/include/addons/toc_parser.hpp @@ -18,6 +18,7 @@ struct TocFile { std::string getInterface() const; bool isLoadOnDemand() const; std::vector getSavedVariables() const; + std::vector getSavedVariablesPerCharacter() const; }; std::optional parseTocFile(const std::string& tocPath); diff --git a/src/addons/addon_manager.cpp b/src/addons/addon_manager.cpp index e826097f..ca91e92d 100644 --- a/src/addons/addon_manager.cpp +++ b/src/addons/addon_manager.cpp @@ -68,6 +68,11 @@ std::string AddonManager::getSavedVariablesPath(const TocFile& addon) const { return addon.basePath + "/" + addon.addonName + ".lua.saved"; } +std::string AddonManager::getSavedVariablesPerCharacterPath(const TocFile& addon) const { + if (characterName_.empty()) return ""; + return addon.basePath + "/" + addon.addonName + "." + characterName_ + ".lua.saved"; +} + bool AddonManager::loadAddon(const TocFile& addon) { // Load SavedVariables before addon code (so globals are available at load time) auto savedVars = addon.getSavedVariables(); @@ -76,6 +81,15 @@ bool AddonManager::loadAddon(const TocFile& addon) { luaEngine_.loadSavedVariables(svPath); LOG_DEBUG("AddonManager: loaded saved variables for '", addon.addonName, "'"); } + // Load per-character SavedVariables + auto savedVarsPC = addon.getSavedVariablesPerCharacter(); + if (!savedVarsPC.empty()) { + std::string svpcPath = getSavedVariablesPerCharacterPath(addon); + if (!svpcPath.empty()) { + luaEngine_.loadSavedVariables(svpcPath); + LOG_DEBUG("AddonManager: loaded per-character saved variables for '", addon.addonName, "'"); + } + } bool success = true; for (const auto& filename : addon.files) { @@ -120,6 +134,13 @@ void AddonManager::saveAllSavedVariables() { std::string svPath = getSavedVariablesPath(addon); luaEngine_.saveSavedVariables(svPath, savedVars); } + auto savedVarsPC = addon.getSavedVariablesPerCharacter(); + if (!savedVarsPC.empty()) { + std::string svpcPath = getSavedVariablesPerCharacterPath(addon); + if (!svpcPath.empty()) { + luaEngine_.saveSavedVariables(svpcPath, savedVarsPC); + } + } } } diff --git a/src/addons/toc_parser.cpp b/src/addons/toc_parser.cpp index 3b5c03ab..523a164a 100644 --- a/src/addons/toc_parser.cpp +++ b/src/addons/toc_parser.cpp @@ -19,17 +19,12 @@ bool TocFile::isLoadOnDemand() const { return (it != directives.end()) && it->second == "1"; } -std::vector TocFile::getSavedVariables() const { +static std::vector parseVarList(const std::string& val) { std::vector result; - auto it = directives.find("SavedVariables"); - if (it == directives.end()) return result; - // Parse comma-separated variable names - std::string val = it->second; size_t pos = 0; while (pos <= val.size()) { size_t comma = val.find(',', pos); std::string name = (comma != std::string::npos) ? val.substr(pos, comma - pos) : val.substr(pos); - // Trim whitespace size_t start = name.find_first_not_of(" \t"); size_t end = name.find_last_not_of(" \t"); if (start != std::string::npos) @@ -40,6 +35,16 @@ std::vector TocFile::getSavedVariables() const { return result; } +std::vector TocFile::getSavedVariables() const { + auto it = directives.find("SavedVariables"); + return (it != directives.end()) ? parseVarList(it->second) : std::vector{}; +} + +std::vector TocFile::getSavedVariablesPerCharacter() const { + auto it = directives.find("SavedVariablesPerCharacter"); + return (it != directives.end()) ? parseVarList(it->second) : std::vector{}; +} + std::optional parseTocFile(const std::string& tocPath) { std::ifstream f(tocPath); if (!f.is_open()) return std::nullopt; diff --git a/src/core/application.cpp b/src/core/application.cpp index 8b4aeeb0..91d6d619 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -5182,6 +5182,21 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float // Load addons once per session on first world entry if (addonManager_ && !addonsLoaded_) { + // Set character name for per-character SavedVariables + if (gameHandler) { + const std::string& charName = gameHandler->lookupName(gameHandler->getPlayerGuid()); + if (!charName.empty()) { + addonManager_->setCharacterName(charName); + } else { + // Fallback: find name from character list + for (const auto& c : gameHandler->getCharacters()) { + if (c.guid == gameHandler->getPlayerGuid()) { + addonManager_->setCharacterName(c.name); + break; + } + } + } + } addonManager_->loadAllAddons(); addonsLoaded_ = true; addonManager_->fireEvent("VARIABLES_LOADED");