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 <AddonName>.<CharacterName>.lua.saved
alongside the existing account-wide <AddonName>.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
This commit is contained in:
Kelsi 2026-03-21 02:46:21 -07:00
parent 0d2fd02dca
commit e21f808714
5 changed files with 51 additions and 6 deletions

View file

@ -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

View file

@ -18,6 +18,7 @@ struct TocFile {
std::string getInterface() const;
bool isLoadOnDemand() const;
std::vector<std::string> getSavedVariables() const;
std::vector<std::string> getSavedVariablesPerCharacter() const;
};
std::optional<TocFile> parseTocFile(const std::string& tocPath);

View file

@ -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);
}
}
}
}

View file

@ -19,17 +19,12 @@ bool TocFile::isLoadOnDemand() const {
return (it != directives.end()) && it->second == "1";
}
std::vector<std::string> TocFile::getSavedVariables() const {
static std::vector<std::string> parseVarList(const std::string& val) {
std::vector<std::string> 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<std::string> TocFile::getSavedVariables() const {
return result;
}
std::vector<std::string> TocFile::getSavedVariables() const {
auto it = directives.find("SavedVariables");
return (it != directives.end()) ? parseVarList(it->second) : std::vector<std::string>{};
}
std::vector<std::string> TocFile::getSavedVariablesPerCharacter() const {
auto it = directives.find("SavedVariablesPerCharacter");
return (it != directives.end()) ? parseVarList(it->second) : std::vector<std::string>{};
}
std::optional<TocFile> parseTocFile(const std::string& tocPath) {
std::ifstream f(tocPath);
if (!f.is_open()) return std::nullopt;

View file

@ -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");