Unify SavedVariables into two clean roots with one-time migration

Collapse all legacy/scattered SavedVariables from absorbed standalone
addons (FeedOMatic, ZHunter/zButtons, SmartAmmo, AutoQuest, AntiDaze,
AutoStrip, MinimapButton) into two persisted globals with clean per-module
nesting under .modules.<id>.

Migration engine (api/savedvariables.lua):
- MTH_SV_EnsureSchema() with schemaVersion gate, idempotent + non-destructive
- Per-module migrators map old locations -> canonical nested stores
- Driven by an ADDON_LOADED("MetaHunt") handler so it runs only AFTER WoW
  loads the real SavedVariables (fixes announce-every-session bug that ran
  the migration on empty pre-load defaults)
- One-time player-facing chat announce, gated on real legacy data present

Framework/config:
- core-framework InitSavedVariables no longer runs migration at file-load;
  unconditionally strips account-root module aliases
- config.lua stops re-creating root[module] aliases (was duplicating data
  onto disk); canonical store stays under .modules.<id>

Modules:
- feedomatic: bind FOM_* globals as runtime aliases into nested store,
  drop persisted duplicates and .legacy blob
- zhunter: ZHunterMod_Saved re-pointed at .modules.zhunter
- smartammo/autoquest/antidaze/autostrip/minimapbutton read nested store

TOC trimmed to MTH_SavedVariables + MTH_CharSavedVariables only.
Profiles unchanged (already snapshot/apply the nested .modules structure).

Also includes pet-scan refinements: canonical pet key derivation and
level-independent pet signature (schema v3).
This commit is contained in:
DuvelCorp
2026-09-09 22:52:48 +02:00
parent de6621f41f
commit 060fc555fe
22 changed files with 855 additions and 503 deletions
-98
View File
@@ -650,58 +650,6 @@ function MTH_GetMerchantFoodsByDiet()
return result;
end
local function FOM_ItemQuality(itemIdOrLink)
if (type(GetItemInfo) ~= "function" or itemIdOrLink == nil) then
return nil;
end
local _, _, quality = GetItemInfo(itemIdOrLink);
return quality;
end
local function FOM_DebugLog(message)
if (not (FOM_Config and FOM_Config.Debug)) then
return;
end
local text = "[FOM] " .. tostring(message);
if (MTH and MTH.Print) then
MTH:Print(text, "debug");
elseif (DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.AddMessage) then
DEFAULT_CHAT_FRAME:AddMessage(text);
end
return;
end
local function FOM_DebugLogMerchantFoods(sourceEvent)
local merchantFoods = MTH_GetMerchantFoodsByDiet();
local eventLabel = tostring(sourceEvent or "MERCHANT");
if (merchantFoods == nil) then
FOM_DebugLog(eventLabel .. ": no merchant data available.");
return;
end
local knownCount = table.getn(merchantFoods.items or {});
local unknownCount = table.getn(merchantFoods.unknown or {});
local merchantName = UnitName("npc") or UnitName("target") or "unknown";
FOM_DebugLog(eventLabel .. ": " .. tostring(merchantName) .. " | known foods=" .. tostring(knownCount) .. " | unknown items=" .. tostring(unknownCount));
if (type(merchantFoods.byDiet) == "table") then
for dietName, foodRows in merchantFoods.byDiet do
local dietCount = table.getn(foodRows or {});
FOM_DebugLog("diet " .. tostring(dietName) .. ": " .. tostring(dietCount) .. " item(s)");
for _, row in foodRows do
FOM_DebugLog(" - [" .. tostring(row.id) .. "] " .. tostring(row.name) .. " (merchantIndex=" .. tostring(row.index) .. ")");
end
end
end
if (unknownCount > 0) then
for _, row in merchantFoods.unknown do
FOM_DebugLog("unknown food map: [" .. tostring(row.id) .. "] " .. tostring(row.name) .. " (merchantIndex=" .. tostring(row.index) .. ")");
end
end
end
function MTH_FOM_FeedButton_OnClick()
if (arg1 == "RightButton") then
if (MTH_OpenOptions) then
@@ -920,10 +868,6 @@ function FOM_OnEvent(event, arg1)
end
return;
elseif ( event == "MERCHANT_SHOW" or event == "MERCHANT_UPDATE" ) then
FOM_DebugLogMerchantFoods(event);
return;
elseif ( event == "PET_ATTACK_START" ) then
-- Set Flag
@@ -950,7 +894,6 @@ function FOM_OnEvent(event, arg1)
end
if (foodID) then
FOM_LastFood = GFWUtils.ItemLink(foodID);
FOM_DebugLog("Manually fed "..FOM_LastFood);
end
end
return;
@@ -1269,31 +1212,24 @@ end
function FOM_CanFeed()
local petInfo = MTH_FOM_GetCorePetInfo();
if ( not (petInfo and petInfo.liveExists) ) then
FOM_DebugLog("Can't feed; pet doesn't exist.");
return false;
end
if ( tonumber(petInfo.health) and tonumber(petInfo.health) <= 0 ) then
FOM_DebugLog("Can't feed; pet is dead.");
return false;
end
if ( UnitHealth("player") <= 0 ) then
FOM_DebugLog("Can't feed; I'm dead.");
return false;
end
if ( CastingBarFrameStatusBar:IsVisible() ) then
FOM_DebugLog("Can't feed; casting a spell / tradeksill.");
return false;
end
if ( UnitOnTaxi("player") ) then
FOM_DebugLog("Can't feed; flying.");
return false;
end
if ( FOM_State.InCombat ) or ( PlayerFrame.inCombat ) then
FOM_DebugLog("Can't feed; in combat.");
return false;
end
if ( LootFrame:IsVisible() ) then
FOM_DebugLog("Shouldn't feed; loot window is open.");
return false;
end
@@ -1315,14 +1251,12 @@ function FOM_CanFeed()
local name = GetSpellRecField(sid, "name")
if name then
if dontFeedNames[name] then
FOM_DebugLog("Can't feed; buff detected: " .. name)
return false
end
if UnitLevel("player") >= 40 then
local lname = string.lower(name)
for _, mountSub in FOM_MOUNT_NAME_SUBSTRINGS do
if string.find(lname, mountSub) then
FOM_DebugLog("Can't feed; mounted (" .. name .. ").")
return false
end
end
@@ -1351,7 +1285,6 @@ function FOM_CanFeed()
if ( buff ~= nil) then
for _, buffTexture in dontFeedBuffTextures do
if ( buff == buffTexture ) then
FOM_DebugLog("Can't feed; currently, eating, drinking, or feigning death.");
return false;
end
end
@@ -1364,7 +1297,6 @@ function FOM_CanFeed()
msg = string.lower(msg);
for _, mountName in FOM_MOUNT_NAME_SUBSTRINGS do
if (string.find(msg, mountName)) then
FOM_DebugLog("Can't feed; mounted.");
return false;
end
end
@@ -2387,17 +2319,6 @@ function FOM_Feed(aFood, options)
foodLevel = selectedFoodLevel,
})
FOM_DebugLog("Picked "..tostring(FOM_LastFood)
.." id="..tostring(selectedId)
.." foodLevel="..tostring(selectedFoodLevel)
.." itemQuality="..tostring(FOM_ItemQuality(selectedId))
.." (bag "..tostring(foodBag)..", slot "..tostring(foodItem)..")"
.." reason="..tostring(FOM_LastChoiceReason));
if (FOM_Config.Debug) then
-- don't actually feed anything, just show what we would choose
return false;
end
-- Actually feed the item to the pet
PickupContainerItem(foodBag, foodItem);
if ( CursorHasItem() ) then
@@ -2610,19 +2531,6 @@ function FOM_FlatFoodList()
if (table.getn(foodList) == 0 and table.getn(overflowFoodList) > 0) then
foodList = overflowFoodList;
end
if (FOM_Config and FOM_Config.Debug) then
FOM_DebugLog("Candidate foods for pet level "..tostring(petLevel).." ("..tostring(table.getn(foodList)).." in diet):");
for i = 1, table.getn(foodList) do
local c = foodList[i];
FOM_DebugLog(" ["..tostring(i).."] "..tostring(c.link)
.." id="..tostring(c.itemId)
.." qty="..tostring(c.count)
.." foodLevel="..tostring(c.quality)
.." itemQuality="..tostring(FOM_ItemQuality(c.itemId))
.." known="..tostring(c.knownLevel)
.." useful="..tostring(c.useful));
end
end
return foodList;
end
@@ -2786,12 +2694,10 @@ end
function FOM_IsUsefulFood(itemID, quantity)
local foodName = GetItemInfo(itemID);
if (foodName == nil) then
FOM_DebugLog("Can't get info for item ID "..itemID..", assuming it's OK to eat.");
return false;
end
if (FOM_Cooking and FOM_Cooking[FOM_RealmPlayer] and FOM_Cooking[FOM_RealmPlayer][itemID]) then
if (FOM_Cooking[FOM_RealmPlayer][itemID] >= FOM_Config.SaveForCookingLevel) then
FOM_DebugLog("Skipping "..quantity.."x "..foodName.."; is good for cooking.");
return true;
end
end
@@ -2803,19 +2709,15 @@ function FOM_IsUsefulFood(itemID, quantity)
FOM_Quantity[foodName] = FOM_Quantity[foodName] + quantity;
end
if (FOM_Quantity[foodName] > FOM_QuestFood[FOM_RealmPlayer][foodName]) then
FOM_DebugLog("Not skipping "..quantity.."x "..foodName.."; is needed for quest, but we have more than enough.");
return false;
else
FOM_DebugLog("Skipping "..quantity.."x "..foodName.."; is needed for quest.");
return true;
end
end
end
if (FOM_Config.AvoidBonusFood and FOM_IsInDiet(itemID, FOM_DIET_BONUS)) then
FOM_DebugLog("Skipping "..quantity.."x "..foodName.."; has bonus effect when eaten by player.");
return true;
end
--FOM_DebugLog("Not skipping "..quantity.."x "..foodName.."; doesn't have other uses.");
return false;
end
+19 -95
View File
@@ -8,8 +8,6 @@ local MTH_FeedOMatic = {
enabled = false,
events = {
"VARIABLES_LOADED",
"MERCHANT_SHOW",
"MERCHANT_UPDATE",
"MTH_PET_LIVE_STATE_CHANGED",
"PET_ATTACK_START",
"PET_ATTACK_STOP",
@@ -31,7 +29,6 @@ local MTH_FeedOMatic = {
}
local MTH_PETL_HOOK_BOUNDARY_KEY = "pet-lifecycle-hooks"
local MTH_FOM_MerchantProbe = nil
function MTH_FeedOMatic_OnUpdate(elapsed)
if not elapsed then
@@ -128,104 +125,34 @@ local function MTH_FeedOMatic_SyncSavedVariables()
end
local moduleStore = MTH:GetModuleSavedVariables("feedomatic")
if not moduleStore.legacy then
moduleStore.legacy = {}
end
local function bindLegacyTable(globalName)
local legacyValue = moduleStore.legacy[globalName]
local globalValue = _G and _G[globalName] or nil
if type(legacyValue) ~= "table" then
-- Bind each legacy FeedOMatic global to its clean nested location under
-- modules.feedomatic. The globals are runtime aliases only (NOT persisted via
-- the TOC any more); the real data lives in these nested tables inside
-- MTH_SavedVariables. Adopt any pre-existing runtime global on first bind.
local function bindGlobal(globalName, key)
local target = moduleStore[key]
if type(target) ~= "table" then
local globalValue = _G and _G[globalName] or nil
if type(globalValue) == "table" then
legacyValue = globalValue
target = globalValue
else
legacyValue = {}
target = {}
end
moduleStore.legacy[globalName] = legacyValue
moduleStore[key] = target
end
if _G then
_G[globalName] = legacyValue
_G[globalName] = target
end
end
bindLegacyTable("FOM_Config")
bindLegacyTable("FOM_FoodQuality")
bindLegacyTable("FOM_AddedFoods")
bindLegacyTable("FOM_RemovedFoods")
bindLegacyTable("FOM_Cooking")
bindLegacyTable("FOM_QuestFood")
bindLegacyTable("FOM_LocaleInfo")
end
local function MTH_FeedOMatic_Log(message, severity)
if type(MTH_Log) == "function" then
MTH_Log("[FeedOMatic] " .. tostring(message or ""), severity)
end
end
local function MTH_FeedOMatic_DebugMerchantScan(sourceEvent)
if type(MTH_GetMerchantFoodsByDiet) ~= "function" then
MTH_FeedOMatic_Log(tostring(sourceEvent) .. ": MTH_GetMerchantFoodsByDiet is not available yet")
return
end
local merchantFoods = MTH_GetMerchantFoodsByDiet()
local knownCount = 0
local unknownCount = 0
if type(merchantFoods) == "table" then
if type(merchantFoods.items) == "table" then
knownCount = table.getn(merchantFoods.items)
end
if type(merchantFoods.unknown) == "table" then
unknownCount = table.getn(merchantFoods.unknown)
end
end
local merchantName = UnitName("npc") or UnitName("target") or "unknown"
MTH_FeedOMatic_Log(tostring(sourceEvent) .. ": wrapper scan for " .. tostring(merchantName) .. " | known=" .. tostring(knownCount) .. " | unknown=" .. tostring(unknownCount))
if type(merchantFoods.byDiet) == "table" then
for dietName, rows in merchantFoods.byDiet do
local dietCount = 0
if type(rows) == "table" then
dietCount = table.getn(rows)
end
if type(rows) == "table" then
for _, row in rows do
end
end
end
end
if unknownCount > 0 and type(merchantFoods.unknown) == "table" then
for _, row in merchantFoods.unknown do
end
end
end
local function MTH_FeedOMatic_SetMerchantProbeEnabled(enabled)
if not enabled then
if MTH_FOM_MerchantProbe then
MTH_FOM_MerchantProbe:UnregisterEvent("MERCHANT_SHOW")
MTH_FOM_MerchantProbe:UnregisterEvent("MERCHANT_UPDATE")
end
return
end
if not MTH_FOM_MerchantProbe then
MTH_FOM_MerchantProbe = CreateFrame("Frame", "MTH_FOM_MerchantProbe")
MTH_FOM_MerchantProbe:SetScript("OnEvent", function()
local evt = event
if evt == "MERCHANT_SHOW" or evt == "MERCHANT_UPDATE" then
MTH_FeedOMatic_DebugMerchantScan(evt)
end
end)
end
MTH_FOM_MerchantProbe:RegisterEvent("MERCHANT_SHOW")
MTH_FOM_MerchantProbe:RegisterEvent("MERCHANT_UPDATE")
bindGlobal("FOM_Config", "settings")
bindGlobal("FOM_FoodQuality", "foodQuality")
bindGlobal("FOM_AddedFoods", "addedFoods")
bindGlobal("FOM_RemovedFoods", "removedFoods")
bindGlobal("FOM_Cooking", "cooking")
bindGlobal("FOM_QuestFood", "questFood")
bindGlobal("FOM_LocaleInfo", "localeInfo")
end
function MTH_FeedOMatic:init()
@@ -265,7 +192,6 @@ function MTH_FeedOMatic:setEnabled(enabled)
MTH_FeedOMatic_CaptureHookBoundary()
MTH_FeedOMatic_SyncSavedVariables()
MTH_FeedOMatic_EnsureVariablesLoaded(self)
MTH_FeedOMatic_SetMerchantProbeEnabled(false)
if MTH_FOM_FeedButton then
MTH_FOM_FeedButton:Show()
end
@@ -275,7 +201,6 @@ function MTH_FeedOMatic:setEnabled(enabled)
if type(FOM_State) == "table" then
FOM_State.ShouldFeed = false
end
MTH_FeedOMatic_SetMerchantProbeEnabled(false)
if MTH_FOM_FeedButton then
MTH_FOM_FeedButton:Hide()
end
@@ -304,7 +229,6 @@ function MTH_FeedOMatic:cleanup()
if type(FOM_State) == "table" then
FOM_State.ShouldFeed = false
end
MTH_FeedOMatic_SetMerchantProbeEnabled(false)
if MTH_FOM_FeedButton then
MTH_FOM_FeedButton:Hide();
end