mirror of
https://codeberg.org/Duvelcorp/MetaHunt.git
synced 2026-09-26 17:46:07 +00:00
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:
+16
-3
@@ -10,10 +10,23 @@ local function AntiDaze_GetSaved()
|
||||
if type(MTH_CharSavedVariables) ~= "table" then
|
||||
MTH_CharSavedVariables = {}
|
||||
end
|
||||
if type(MTH_CharSavedVariables.antiDaze) ~= "table" then
|
||||
MTH_CharSavedVariables.antiDaze = {}
|
||||
local store
|
||||
if MTH and MTH.GetModuleCharSavedVariables then
|
||||
store = MTH:GetModuleCharSavedVariables("antidaze")
|
||||
end
|
||||
return MTH_CharSavedVariables.antiDaze
|
||||
if type(store) ~= "table" then
|
||||
if type(MTH_CharSavedVariables.modules) ~= "table" then
|
||||
MTH_CharSavedVariables.modules = {}
|
||||
end
|
||||
if type(MTH_CharSavedVariables.modules.antidaze) ~= "table" then
|
||||
MTH_CharSavedVariables.modules.antidaze = {}
|
||||
end
|
||||
store = MTH_CharSavedVariables.modules.antidaze
|
||||
end
|
||||
if type(store.settings) ~= "table" then
|
||||
store.settings = {}
|
||||
end
|
||||
return store.settings
|
||||
end
|
||||
|
||||
local function AntiDaze_IsEnabled()
|
||||
|
||||
+13
-3
@@ -33,10 +33,20 @@ function AutoStrip_GetSaved()
|
||||
if type(MTH_CharSavedVariables) ~= "table" then
|
||||
MTH_CharSavedVariables = {}
|
||||
end
|
||||
if type(MTH_CharSavedVariables.autoStrip) ~= "table" then
|
||||
MTH_CharSavedVariables.autoStrip = {}
|
||||
local store
|
||||
if MTH and MTH.GetModuleCharSavedVariables then
|
||||
store = MTH:GetModuleCharSavedVariables("autostrip")
|
||||
end
|
||||
return MTH_CharSavedVariables.autoStrip
|
||||
if type(store) ~= "table" then
|
||||
if type(MTH_CharSavedVariables.modules) ~= "table" then
|
||||
MTH_CharSavedVariables.modules = {}
|
||||
end
|
||||
if type(MTH_CharSavedVariables.modules.autostrip) ~= "table" then
|
||||
MTH_CharSavedVariables.modules.autostrip = {}
|
||||
end
|
||||
store = MTH_CharSavedVariables.modules.autostrip
|
||||
end
|
||||
return store
|
||||
end
|
||||
|
||||
local AUTO_STRIP_ORDER = {16, 17, 18, 5, 7, 1, 3, 10, 8, 6, 9}
|
||||
|
||||
+4
-1
@@ -38,7 +38,10 @@ local function MTH_ConfigEnsureStore(module_name)
|
||||
root.modules[module_name] = legacy
|
||||
end
|
||||
|
||||
root[module_name] = root.modules[module_name]
|
||||
-- NOTE: we intentionally do NOT mirror root[module_name] = root.modules[module_name].
|
||||
-- That old alias made WoW serialize a full DUPLICATE of every module store at the
|
||||
-- account root on disk. The canonical store is root.modules[module_name] only.
|
||||
root[module_name] = nil
|
||||
MTH_SavedVariables = root
|
||||
return root.modules[module_name]
|
||||
end
|
||||
|
||||
+18
-11
@@ -1187,8 +1187,6 @@ function MTH:InitSavedVariables()
|
||||
if not MTH_SavedVariables then
|
||||
MTH_SavedVariables = {
|
||||
modules = {},
|
||||
feedomatic = {},
|
||||
zhunter = {},
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1198,12 +1196,6 @@ function MTH:InitSavedVariables()
|
||||
if not MTH_SavedVariables.moduleStates then
|
||||
MTH_SavedVariables.moduleStates = {}
|
||||
end
|
||||
if not MTH_SavedVariables.modules.feedomatic then
|
||||
MTH_SavedVariables.modules.feedomatic = MTH_SavedVariables.feedomatic or {}
|
||||
end
|
||||
if not MTH_SavedVariables.modules.zhunter then
|
||||
MTH_SavedVariables.modules.zhunter = MTH_SavedVariables.zhunter or {}
|
||||
end
|
||||
if type(MTH_SavedVariables.messages) ~= "table" then
|
||||
MTH_SavedVariables.messages = {}
|
||||
end
|
||||
@@ -1215,9 +1207,6 @@ function MTH:InitSavedVariables()
|
||||
end
|
||||
end
|
||||
|
||||
MTH_SavedVariables.feedomatic = MTH_SavedVariables.modules.feedomatic
|
||||
MTH_SavedVariables.zhunter = MTH_SavedVariables.modules.zhunter
|
||||
|
||||
if not MTH_CharSavedVariables then
|
||||
MTH_CharSavedVariables = {}
|
||||
end
|
||||
@@ -1271,6 +1260,24 @@ function MTH:InitSavedVariables()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- NOTE: the legacy/scattered SavedVariables consolidation (MTH_SV_EnsureSchema)
|
||||
-- is intentionally NOT called here. InitSavedVariables also runs at file-load time
|
||||
-- (before WoW has loaded the SV file from disk), when the roots are still empty
|
||||
-- defaults. Running the migration there operated on throwaway tables and made it
|
||||
-- re-announce every session. The migration is now driven from an ADDON_LOADED
|
||||
-- handler in api/savedvariables.lua, which fires once the real saved data exists.
|
||||
|
||||
-- Account-root module aliases must NEVER persist: the canonical stores live under
|
||||
-- .modules.<id>. feedomatic/zhunter used to be mirrored here (config.lua alias +
|
||||
-- InitSavedVariables seeds), which made WoW serialize a full DUPLICATE on disk.
|
||||
-- Strip them unconditionally on every load — even after the one-shot migration gate
|
||||
-- has closed — so any stray copy re-created by older code, or already sitting in an
|
||||
-- on-disk file from a previous version, is removed.
|
||||
if type(MTH_SavedVariables) == "table" then
|
||||
MTH_SavedVariables.feedomatic = nil
|
||||
MTH_SavedVariables.zhunter = nil
|
||||
end
|
||||
end
|
||||
|
||||
function MTH:GetModuleSavedVariables(name)
|
||||
|
||||
+154
-21
@@ -2,7 +2,7 @@ if not MTH then
|
||||
error("MetaHunt core framework missing: api/core-framework.lua must load before api/core-scan-stablemaster.lua")
|
||||
end
|
||||
|
||||
local MTH_PETS_SCHEMA_VERSION = 2
|
||||
local MTH_PETS_SCHEMA_VERSION = 3
|
||||
local MTH_PETS_CORE_HOOK_BOUNDARY_KEY = "core-pet-rename-hook"
|
||||
local MTH_ST_FULL_DEBUG_TRACE = false
|
||||
MTH_PETS_TRACE_CONSISTENCY = false
|
||||
@@ -914,6 +914,21 @@ local function MTH_PETS_ParseCreatureIdFromGuid(guid)
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Canonical pet key: the pet GUID with its low 16 bits (last 4 hex chars) zeroed.
|
||||
-- On this core the low 16 bits change every session while the high portion stays
|
||||
-- stable per physical pet, so the full GUID cannot be used as a durable identity
|
||||
-- key. The canonical key is that durable identity, derived purely from the GUID.
|
||||
function MTH_PETS_CanonicalKey(guid)
|
||||
if type(guid) ~= "string" or guid == "" then
|
||||
return nil
|
||||
end
|
||||
local body = string.gsub(guid, "^0[xX]", "")
|
||||
if string.len(body) < 5 then
|
||||
return nil
|
||||
end
|
||||
return "0x" .. string.sub(body, 1, string.len(body) - 4) .. "0000"
|
||||
end
|
||||
|
||||
local function MTH_PETS_ParseBeastLevelBounds(levelField)
|
||||
local levelText = tostring(levelField or "")
|
||||
if levelText == "" then
|
||||
@@ -1252,10 +1267,11 @@ local function MTH_PETS_ResolveConfirmedTameBeastId(source, snapshot)
|
||||
end
|
||||
|
||||
local function MTH_PETS_MakeSignature(name, family, level)
|
||||
-- Level intentionally excluded: it mutates on level-up, so baking it into the
|
||||
-- identity signature made the same pet look like a new one after each level.
|
||||
local cleanName = MTH_PETS_SafeLower(MTH_PETS_NormalizeText(name))
|
||||
local cleanFamily = MTH_PETS_SafeLower(MTH_PETS_NormalizeText(family))
|
||||
local numericLevel = tonumber(level) or 0
|
||||
return cleanName .. "|" .. cleanFamily .. "|" .. tostring(numericLevel)
|
||||
return cleanName .. "|" .. cleanFamily
|
||||
end
|
||||
|
||||
local function MTH_PETS_RowHasAnyTameMetadata(row)
|
||||
@@ -1546,11 +1562,14 @@ local function MTH_PETS_RepairActiveRowsByGuid(pets)
|
||||
local groupedByGuid = {}
|
||||
for petId, row in pairs(pets.petStore.activeById) do
|
||||
if type(row) == "table" and type(row.guid) == "string" and row.guid ~= "" then
|
||||
local guid = row.guid
|
||||
if type(groupedByGuid[guid]) ~= "table" then
|
||||
groupedByGuid[guid] = {}
|
||||
-- Group by the CANONICAL key, not the full guid: the full guid is volatile
|
||||
-- across sessions, so grouping on it never catches cross-session duplicates
|
||||
-- of the same physical pet (the root cause of the duplicate-row bug).
|
||||
local key = MTH_PETS_CanonicalKey(row.guid) or row.guid
|
||||
if type(groupedByGuid[key]) ~= "table" then
|
||||
groupedByGuid[key] = {}
|
||||
end
|
||||
table.insert(groupedByGuid[guid], tostring(petId))
|
||||
table.insert(groupedByGuid[key], tostring(petId))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1627,6 +1646,9 @@ local function MTH_PETS_EnsurePetStoreSchema(pets)
|
||||
row.tameVerified = true
|
||||
end
|
||||
end
|
||||
-- Re-sign level-free every load so the signature index can never retain a
|
||||
-- level-baked key from older data.
|
||||
row.signature = MTH_PETS_MakeSignature(row.name, row.family)
|
||||
if type(row.signature) == "string" and row.signature ~= "" then
|
||||
if type(petStore.signatureIndex[row.signature]) ~= "table" then
|
||||
petStore.signatureIndex[row.signature] = {}
|
||||
@@ -1634,7 +1656,12 @@ local function MTH_PETS_EnsurePetStoreSchema(pets)
|
||||
table.insert(petStore.signatureIndex[row.signature], resolvedPetId)
|
||||
end
|
||||
if type(row.guid) == "string" and row.guid ~= "" then
|
||||
petStore.guidIndex[row.guid] = resolvedPetId
|
||||
petStore.guidIndex[MTH_PETS_CanonicalKey(row.guid) or row.guid] = resolvedPetId
|
||||
end
|
||||
-- beastId is mislabeled (per-pet number, not creature id); expose petNumber
|
||||
-- additively without breaking existing readers.
|
||||
if row.beastId ~= nil and row.petNumber == nil then
|
||||
row.petNumber = row.beastId
|
||||
end
|
||||
if row.loyaltyLevel == nil and type(row.loyalty) == "string" then
|
||||
row.loyaltyLevel = MTH_PETS_ParseLoyaltyLevelFromText(row.loyalty)
|
||||
@@ -1800,8 +1827,16 @@ local function MTH_PETS_ApplyCurrentPetFromActiveRow(cp, petId, row)
|
||||
end
|
||||
end
|
||||
|
||||
-- Forward declaration: the one-shot orphan cleanup is defined later (it reuses
|
||||
-- MTH_PETS_MoveToHistory, declared further down), but MTH_PETS_EnsureSchema needs
|
||||
-- to reference it here. By call time (a load-time event) it is assigned.
|
||||
local MTH_PETS_ArchiveOrphanActiveRows
|
||||
|
||||
local function MTH_PETS_EnsureSchema(pets)
|
||||
local now = time()
|
||||
-- Capture BEFORE the stamp block rewrites schemaVersion, so we know whether the
|
||||
-- one-time v3 orphan cleanup still needs to run for this character.
|
||||
local needsOrphanCleanup = (tonumber(pets.schemaVersion) or 0) < 3
|
||||
if pets.schemaVersion ~= MTH_PETS_SCHEMA_VERSION then
|
||||
pets.schemaVersion = MTH_PETS_SCHEMA_VERSION
|
||||
pets.schemaMigratedAt = now
|
||||
@@ -1810,7 +1845,7 @@ local function MTH_PETS_EnsureSchema(pets)
|
||||
end
|
||||
pets.schemaMigration.version = MTH_PETS_SCHEMA_VERSION
|
||||
pets.schemaMigration.at = now
|
||||
pets.schemaMigration.note = "beta canonical store"
|
||||
pets.schemaMigration.note = "canonical identity keys + orphan cleanup"
|
||||
end
|
||||
if pets.updatedAt == nil then
|
||||
pets.updatedAt = 0
|
||||
@@ -1839,6 +1874,11 @@ local function MTH_PETS_EnsureSchema(pets)
|
||||
end
|
||||
MTH_PETS_EnsureCurrentPetSchema(pets)
|
||||
MTH_PETS_EnsurePetStoreSchema(pets)
|
||||
-- After dedup + index rebuild (done inside EnsurePetStoreSchema), sweep legacy
|
||||
-- orphan rows (active but neither the current pet nor stabled) into history once.
|
||||
if needsOrphanCleanup and type(MTH_PETS_ArchiveOrphanActiveRows) == "function" then
|
||||
MTH_PETS_ArchiveOrphanActiveRows(pets, "migration-recovered")
|
||||
end
|
||||
end
|
||||
|
||||
local function MTH_PETS_MarkStableVisited(pets, source)
|
||||
@@ -2067,19 +2107,22 @@ local function MTH_PETS_SelectPetIdBySnapshot(store, snapshot, previousPetId)
|
||||
|
||||
if previousPetId and store.activeById[previousPetId] then
|
||||
local previousRow = store.activeById[previousPetId]
|
||||
if previousRow.guid and snapshot.guid and previousRow.guid == snapshot.guid then
|
||||
if previousRow.guid and snapshot.guid
|
||||
and MTH_PETS_CanonicalKey(previousRow.guid) == MTH_PETS_CanonicalKey(snapshot.guid) then
|
||||
return previousPetId
|
||||
end
|
||||
end
|
||||
|
||||
if snapshot.guid and store.guidIndex[snapshot.guid] and store.activeById[store.guidIndex[snapshot.guid]] then
|
||||
return store.guidIndex[snapshot.guid]
|
||||
local snapshotKey = snapshot.guid and MTH_PETS_CanonicalKey(snapshot.guid) or nil
|
||||
if snapshotKey and store.guidIndex[snapshotKey] and store.activeById[store.guidIndex[snapshotKey]] then
|
||||
return store.guidIndex[snapshotKey]
|
||||
end
|
||||
|
||||
if snapshot.guid and snapshot.guid ~= "" then
|
||||
if snapshotKey then
|
||||
for petId, row in pairs(store.activeById or {}) do
|
||||
if type(row) == "table" and row.guid == snapshot.guid then
|
||||
store.guidIndex[snapshot.guid] = tostring(petId)
|
||||
if type(row) == "table" and type(row.guid) == "string"
|
||||
and MTH_PETS_CanonicalKey(row.guid) == snapshotKey then
|
||||
store.guidIndex[snapshotKey] = tostring(petId)
|
||||
return tostring(petId)
|
||||
end
|
||||
end
|
||||
@@ -2240,16 +2283,18 @@ local function MTH_PETS_UpsertActivePetFromSnapshot(pets, snapshot, source, opti
|
||||
petStore.stableSlotIndex[row.stableSlot] = nil
|
||||
end
|
||||
row.stableSlot = nil
|
||||
if row.guid and row.guid ~= snapshot.guid and petStore.guidIndex[row.guid] == petId then
|
||||
petStore.guidIndex[row.guid] = nil
|
||||
local oldGuidKey = row.guid and MTH_PETS_CanonicalKey(row.guid) or nil
|
||||
local newGuidKey = snapshot.guid and MTH_PETS_CanonicalKey(snapshot.guid) or nil
|
||||
if oldGuidKey and oldGuidKey ~= newGuidKey and petStore.guidIndex[oldGuidKey] == petId then
|
||||
petStore.guidIndex[oldGuidKey] = nil
|
||||
end
|
||||
|
||||
local context = MTH_PETS_CaptureContext()
|
||||
MTH_PETS_ApplySnapshotToRow(row, snapshot, source, context)
|
||||
row.firstSeen = row.firstSeen or time()
|
||||
|
||||
if snapshot.guid and snapshot.guid ~= "" then
|
||||
petStore.guidIndex[snapshot.guid] = petId
|
||||
if newGuidKey then
|
||||
petStore.guidIndex[newGuidKey] = petId
|
||||
end
|
||||
if snapshot.signature and snapshot.signature ~= "" then
|
||||
MTH_PETS_AddSignatureIndex(petStore, snapshot.signature, petId)
|
||||
@@ -2573,8 +2618,9 @@ local function MTH_PETS_MoveToHistory(pets, petId, source, context, reason)
|
||||
if row.signature then
|
||||
MTH_PETS_RemoveSignatureIndex(petStore, row.signature, resolvedPetId)
|
||||
end
|
||||
if row.guid and petStore.guidIndex[row.guid] == resolvedPetId then
|
||||
petStore.guidIndex[row.guid] = nil
|
||||
local guidKey = row.guid and MTH_PETS_CanonicalKey(row.guid) or nil
|
||||
if guidKey and petStore.guidIndex[guidKey] == resolvedPetId then
|
||||
petStore.guidIndex[guidKey] = nil
|
||||
end
|
||||
if row.stableSlot and petStore.stableSlotIndex[row.stableSlot] == resolvedPetId then
|
||||
petStore.stableSlotIndex[row.stableSlot] = nil
|
||||
@@ -2597,6 +2643,56 @@ local function MTH_PETS_MoveToHistory(pets, petId, source, context, reason)
|
||||
return true
|
||||
end
|
||||
|
||||
-- One-shot legacy cleanup (v3 migration): move "orphan" active rows into history.
|
||||
-- An orphan is an active row that is neither the current pet nor associated with a
|
||||
-- stable slot in any way. In normal play a pet is always either summoned or stabled,
|
||||
-- so such rows are abandoned pets that older code failed to archive. They are moved
|
||||
-- (not deleted): all tame history is preserved. Assigned to the forward-declared
|
||||
-- local so MTH_PETS_EnsureSchema (defined earlier) can call it.
|
||||
function MTH_PETS_ArchiveOrphanActiveRows(pets, reason)
|
||||
local petStore = type(pets) == "table" and pets.petStore or nil
|
||||
if type(petStore) ~= "table" or type(petStore.activeById) ~= "table" then
|
||||
return 0
|
||||
end
|
||||
local currentId = tostring(pets.currentPetId or petStore.activeCurrentId or "")
|
||||
local orphanIds = {}
|
||||
for petId, row in pairs(petStore.activeById) do
|
||||
if type(row) == "table" then
|
||||
local id = tostring(petId)
|
||||
local isCurrent = (currentId ~= "" and id == currentId)
|
||||
local inSlotIndex = false
|
||||
if type(petStore.stableSlotIndex) == "table" then
|
||||
for _, mappedId in pairs(petStore.stableSlotIndex) do
|
||||
if tostring(mappedId or "") == id then
|
||||
inSlotIndex = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
local hasStableSlot = tonumber(row.stableSlot) and tonumber(row.stableSlot) > 0
|
||||
local hasStableInfo = type(row.stableInfo) == "table"
|
||||
if not isCurrent and not inSlotIndex and not hasStableSlot and not hasStableInfo then
|
||||
table.insert(orphanIds, id)
|
||||
end
|
||||
end
|
||||
end
|
||||
local archived = 0
|
||||
for i = 1, table.getn(orphanIds) do
|
||||
local id = orphanIds[i]
|
||||
local row = petStore.activeById[id]
|
||||
if type(row) == "table" then
|
||||
row.migrationNote = "Auto-archived by 2.0 pet migration (orphaned active row)."
|
||||
if MTH_PETS_MoveToHistory(pets, id, "migration-v3", nil, reason or "migration-recovered") then
|
||||
archived = archived + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
if archived > 0 and MTH and MTH.DebugPrint then
|
||||
MTH:DebugPrint("Pet migration: archived " .. tostring(archived) .. " orphan pet row(s) to history.")
|
||||
end
|
||||
return archived
|
||||
end
|
||||
|
||||
function MTH_PETS_RecordPetAbandon(source, explicitName)
|
||||
local pets = MTH_PETS_GetRootStore()
|
||||
if type(pets) ~= "table" then
|
||||
@@ -3952,6 +4048,43 @@ function MTH_CommandPetsReset()
|
||||
return true
|
||||
end
|
||||
|
||||
-- Dev-only: force the v3 pet-store migration to run again (dedup by canonical key,
|
||||
-- level-free re-sign, index rebuild, orphan cleanup). Resets the pet schema flag
|
||||
-- and re-runs EnsureSchema. Does NOT touch MTH.version, so nothing is broadcast.
|
||||
-- Non-destructive: history is preserved. Invoke via /mth dev petmigrate or /run.
|
||||
function MTH_CommandPetsMigrate()
|
||||
local pets = MTH_PETS_GetRootStore()
|
||||
if type(pets) ~= "table" then
|
||||
MTH:Print("Pet migration failed: datastore unavailable.")
|
||||
return false
|
||||
end
|
||||
local store = MTH_PETS_GetStoreTables(pets)
|
||||
local function countTable(t)
|
||||
local n = 0
|
||||
if type(t) == "table" then for _ in pairs(t) do n = n + 1 end end
|
||||
return n
|
||||
end
|
||||
local beforeActive = countTable(store and store.activeById)
|
||||
local beforeHistory = countTable(store and store.historyById)
|
||||
|
||||
-- Force the one-shot path to re-run regardless of current stamp.
|
||||
pets.schemaVersion = 2
|
||||
MTH_PETS_EnsureSchema(pets)
|
||||
|
||||
store = MTH_PETS_GetStoreTables(pets)
|
||||
local afterActive = countTable(store and store.activeById)
|
||||
local afterHistory = countTable(store and store.historyById)
|
||||
pets.updatedAt = time()
|
||||
if type(MTH_CharSavedVariables) == "table" then
|
||||
MTH_CharSavedVariables.MTH_Pets = pets
|
||||
MTH_CharSavedVariables.petStore = pets.petStore
|
||||
end
|
||||
MTH:Print("Pet migration (v3) complete: active " .. tostring(beforeActive) .. "->" .. tostring(afterActive)
|
||||
.. ", history " .. tostring(beforeHistory) .. "->" .. tostring(afterHistory)
|
||||
.. ", schemaVersion=" .. tostring(pets.schemaVersion))
|
||||
return true
|
||||
end
|
||||
|
||||
function MTH_CommandPetsDump()
|
||||
local pets = MTH_PETS_GetRootStore()
|
||||
MTH_PETS_RefreshCurrentPet()
|
||||
|
||||
@@ -422,6 +422,16 @@ function SlashCmdList.MTH(msg, editbox)
|
||||
MTH:Print("|cffaaffaaArmed.|r Open any vendor, trainer, or stable master to capture their ID.")
|
||||
end
|
||||
end
|
||||
elseif lowerCmd == "dev" then
|
||||
if lowerArg == "petmigrate" then
|
||||
if type(MTH_CommandPetsMigrate) == "function" then
|
||||
MTH_CommandPetsMigrate()
|
||||
else
|
||||
MTH:Print("Pet migration command unavailable.")
|
||||
end
|
||||
else
|
||||
MTH:Print("Dev commands: /mth dev petmigrate")
|
||||
end
|
||||
elseif lowerCmd == "food" then
|
||||
local sub = lowerArg
|
||||
local function MTH_FoodItemLabel(itemId)
|
||||
|
||||
+12
-3
@@ -25,10 +25,19 @@ local function MTH_MB_GetStore()
|
||||
if not MTH_CharSavedVariables then
|
||||
MTH_CharSavedVariables = {}
|
||||
end
|
||||
if type(MTH_CharSavedVariables.minimapButton) ~= "table" then
|
||||
MTH_CharSavedVariables.minimapButton = {}
|
||||
local store
|
||||
if MTH and MTH.GetModuleCharSavedVariables then
|
||||
store = MTH:GetModuleCharSavedVariables("minimapbutton")
|
||||
end
|
||||
if type(store) ~= "table" then
|
||||
if type(MTH_CharSavedVariables.modules) ~= "table" then
|
||||
MTH_CharSavedVariables.modules = {}
|
||||
end
|
||||
if type(MTH_CharSavedVariables.modules.minimapbutton) ~= "table" then
|
||||
MTH_CharSavedVariables.modules.minimapbutton = {}
|
||||
end
|
||||
store = MTH_CharSavedVariables.modules.minimapbutton
|
||||
end
|
||||
local store = MTH_CharSavedVariables.minimapButton
|
||||
if store.angle == nil then
|
||||
store.angle = 220
|
||||
end
|
||||
|
||||
+18
-14
@@ -16,26 +16,30 @@ local function MTH_AQ_EnsureStore()
|
||||
if type(MTH_CharSavedVariables) ~= "table" then
|
||||
MTH_CharSavedVariables = {}
|
||||
end
|
||||
if type(MTH_CharSavedVariables.autoquest) ~= "table" then
|
||||
MTH_CharSavedVariables.autoquest = {}
|
||||
local store = nil
|
||||
if MTH and MTH.GetModuleCharSavedVariables then
|
||||
store = MTH:GetModuleCharSavedVariables("autoquest")
|
||||
end
|
||||
local store = MTH_CharSavedVariables.autoquest
|
||||
if type(MTH_CharSavedVariables.questautomation) == "table" then
|
||||
local legacy = MTH_CharSavedVariables.questautomation
|
||||
if store.scorpokDrazial == nil and legacy.scorpokDrazial ~= nil then
|
||||
store.scorpokDrazial = legacy.scorpokDrazial and true or false
|
||||
if type(store) ~= "table" then
|
||||
if type(MTH_CharSavedVariables.modules) ~= "table" then
|
||||
MTH_CharSavedVariables.modules = {}
|
||||
end
|
||||
if store.scorpokTooltip == nil and legacy.scorpokTooltip ~= nil then
|
||||
store.scorpokTooltip = legacy.scorpokTooltip and true or false
|
||||
if type(MTH_CharSavedVariables.modules.autoquest) ~= "table" then
|
||||
MTH_CharSavedVariables.modules.autoquest = {}
|
||||
end
|
||||
store = MTH_CharSavedVariables.modules.autoquest
|
||||
end
|
||||
if store.scorpokDrazial == nil then
|
||||
store.scorpokDrazial = false
|
||||
if type(store.settings) ~= "table" then
|
||||
store.settings = {}
|
||||
end
|
||||
if store.arrowsForSissies == nil then
|
||||
store.arrowsForSissies = false
|
||||
local settings = store.settings
|
||||
if settings.scorpokDrazial == nil then
|
||||
settings.scorpokDrazial = false
|
||||
end
|
||||
return store
|
||||
if settings.arrowsForSissies == nil then
|
||||
settings.arrowsForSissies = false
|
||||
end
|
||||
return settings
|
||||
end
|
||||
|
||||
function MTH_SetupAutoQuestOptions()
|
||||
|
||||
@@ -129,7 +129,7 @@ function MTH_SetupCreditsOptions()
|
||||
|
||||
cursor = MTH_CreditsCreateText(content, cursor,
|
||||
MTH_CR_L("CREDITS_ABOUT_ZHUNTER",
|
||||
"- zBars, Antidaze and Autostrip were core functionalities of Vanilla zHunterMod addon. And I kept the \"z\" naming of bars/buttons to always remember it. On top of this I have created myself zAmmo, zCompanions, zMount, zToys.\nAlso the SmartAmmo feature idea is coming from Zhuntermod: there was a file in it with that functionality, but was like a work-in-progress, totally unfunctional and even \"dangerous\" in its current state, and was not active in the addon. Since it was a fucking great idea, I recoded it mostly from scratch and made it work reliably and safely."),
|
||||
"- zBars, Antidaze and Autostrip were core functionalities of Vanilla zHunterMod addon. And I kept the \"z\" naming of bars/buttons to always remember it. On top of this I have created myself zBars, zAmmo, zCompanions, zMount, zToys, zCraft.\nAlso the SmartAmmo feature idea is coming from Zhuntermod: there was a file in it with that functionality, but was like a work-in-progress, totally unfunctional and even \"dangerous\" in its current state, and was not active in the addon. Since it was a fucking great idea, I recoded it mostly from scratch and made it work reliably and safely."),
|
||||
"GameFontNormalSmall", -10)
|
||||
|
||||
cursor = MTH_CreditsCreateText(content, cursor,
|
||||
@@ -170,7 +170,7 @@ function MTH_SetupCreditsOptions()
|
||||
-4)
|
||||
|
||||
cursor = MTH_CreditsCreateText(content, cursor,
|
||||
MTH_CR_L("CREDITS_ABOUT_MAP", "- The Map/Marker system is the one of pfQuest from Master Shagu. Unfortunately he is no more reachable for some months and I could not have some talk with him about this. I mostly let it untouched, I just made slight modifications so it can integrate better within Metahunt, and doesn't conflict with pfQuest."),
|
||||
MTH_CR_L("CREDITS_ABOUT_MAP", "- The Map/Marker system is the one of pfQuest from Master Shagu. I mostly let it untouched, I just made slight modifications so it can integrate better within Metahunt, and doesn't conflict with pfQuest."),
|
||||
"GameFontNormalSmall", -10)
|
||||
|
||||
cursor = MTH_CreditsCreateText(content, cursor,
|
||||
@@ -179,8 +179,8 @@ function MTH_SetupCreditsOptions()
|
||||
|
||||
cursor = MTH_CreditsCreateText(content, cursor, MTH_CR_L("CREDITS_ABOUT_DATA_TITLE", "About data sources"), "GameFontNormal", -14, 1.00, 0.82, 0.00)
|
||||
cursor = MTH_CreditsCreateText(content, cursor,
|
||||
MTH_CR_L("CREDITS_ABOUT_DATA_INTRO", "Metahunt ships with its own Twow datastores, limited to hunter stuff only, and most of this data is up-to-date (Feb 2026).\n\n"
|
||||
.. "Sources used to build the data include:"),
|
||||
MTH_CR_L("CREDITS_ABOUT_DATA_INTRO", "Metahunt ships with its own Twow datastores, limited to hunter stuff only, and most of this data is up-to-date (twow 1.18.1). The beast information is coming directly from a extract of live twow DB, made for MetaHunt by twow dev lead in March 2026.\n\n"
|
||||
.. "Other sources used to build the data include:"),
|
||||
"GameFontNormalSmall", -10)
|
||||
|
||||
cursor = MTH_CreditsCreateText(content, cursor,
|
||||
|
||||
@@ -0,0 +1,434 @@
|
||||
------------------------------------------------------
|
||||
-- MetaHunt — Unified SavedVariables schema + migration engine
|
||||
------------------------------------------------------
|
||||
-- PURPOSE
|
||||
-- Collapse every legacy / scattered SavedVariable into just TWO persisted
|
||||
-- globals and one consistent shape, so old-addon names (FOM_*, ZHunterMod_Saved,
|
||||
-- MTHSmartAmmo) and ad-hoc key styles disappear.
|
||||
--
|
||||
-- TARGET SHAPE (canonical)
|
||||
-- MTH_SavedVariables (account-wide) = {
|
||||
-- schemaVersion = <MTH_SV_SCHEMA_VERSION>,
|
||||
-- modules = {
|
||||
-- <id> = { schemaVersion = N, settings = { ...camelCase... }, <dataTables> },
|
||||
-- feedomatic = { settings{...}, foodQuality, cooking, questFood,
|
||||
-- addedFoods, removedFoods, localeInfo },
|
||||
-- },
|
||||
-- messages = {...}, moduleStates = {...}, profiles = {...},
|
||||
-- charSnapshots = {...}, versionCheck = {...},
|
||||
-- }
|
||||
-- MTH_CharSavedVariables (per-character) = {
|
||||
-- schemaVersion = <MTH_SV_SCHEMA_VERSION>,
|
||||
-- modules = {
|
||||
-- <id> = { schemaVersion = N, settings{...}, <dataTables> },
|
||||
-- zhunter = { buttons{...}, bar{...}, widgetSpawnLayout },
|
||||
-- antidaze = { settings{ enabled } },
|
||||
-- autostrip = { settings{ enabled, display }, frame{ point, relativePoint, x, y } },
|
||||
-- minimapbutton = { settings{ angle } },
|
||||
-- },
|
||||
-- moduleStates = {...},
|
||||
-- -- pet-system core stays at root (out of scope, heavily referenced):
|
||||
-- MTH_Pets, feedTracking, petTraining, trainScan, stableScan, petSpellScan,
|
||||
-- }
|
||||
--
|
||||
-- RULES
|
||||
-- * One-shot, gated by schemaVersion on each root. Idempotent. Non-destructive
|
||||
-- (data is COPIED into the new location before the old location is cleared).
|
||||
-- * Lua 5.0-safe (no '#', no string.match/gmatch; pairs()/ipairs() only).
|
||||
-- * Testable offline: MTH_SV_EnsureSchema(account, char) accepts explicit roots
|
||||
-- (defaults to the live globals) and returns a report table.
|
||||
------------------------------------------------------
|
||||
|
||||
MTH_SV_SCHEMA_VERSION = 1
|
||||
|
||||
------------------------------------------------------
|
||||
-- Helpers
|
||||
------------------------------------------------------
|
||||
|
||||
local function MTH_SV_IsTable(v)
|
||||
return type(v) == "table"
|
||||
end
|
||||
|
||||
local function MTH_SV_DeepCopy(orig)
|
||||
if type(orig) ~= "table" then
|
||||
return orig
|
||||
end
|
||||
local copy = {}
|
||||
for k, v in pairs(orig) do
|
||||
copy[MTH_SV_DeepCopy(k)] = MTH_SV_DeepCopy(v)
|
||||
end
|
||||
return copy
|
||||
end
|
||||
|
||||
-- Ensure parent[key] is a table and return it.
|
||||
local function MTH_SV_Ensure(parent, key)
|
||||
if type(parent[key]) ~= "table" then
|
||||
parent[key] = {}
|
||||
end
|
||||
return parent[key]
|
||||
end
|
||||
|
||||
-- Pick the first non-nil value from a list of candidates; else default.
|
||||
local function MTH_SV_Coalesce(candidates, default)
|
||||
for _, v in ipairs(candidates) do
|
||||
if v ~= nil then
|
||||
return v
|
||||
end
|
||||
end
|
||||
return default
|
||||
end
|
||||
|
||||
-- Copy every key from src into dst, applying a rename map (oldKey -> newKey).
|
||||
-- Keys not present in the map keep their original name. Existing dst values win
|
||||
-- (so re-running never clobbers already-migrated data). Returns count copied.
|
||||
local function MTH_SV_CopyRenamed(src, dst, renameMap)
|
||||
local moved = 0
|
||||
if not MTH_SV_IsTable(src) then
|
||||
return moved
|
||||
end
|
||||
for oldKey, value in pairs(src) do
|
||||
local newKey = (renameMap and renameMap[oldKey]) or oldKey
|
||||
if dst[newKey] == nil then
|
||||
dst[newKey] = value
|
||||
moved = moved + 1
|
||||
end
|
||||
end
|
||||
return moved
|
||||
end
|
||||
|
||||
-- Count entries in a table (Lua 5.0-safe).
|
||||
local function MTH_SV_Count(t)
|
||||
local n = 0
|
||||
if MTH_SV_IsTable(t) then
|
||||
for _ in pairs(t) do
|
||||
n = n + 1
|
||||
end
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
------------------------------------------------------
|
||||
-- Per-module migrators
|
||||
-- Each takes the relevant module store table (already ensured) plus context,
|
||||
-- transforms in place, and records notes into the shared report.
|
||||
------------------------------------------------------
|
||||
|
||||
-- SMARTAMMO (per-character)
|
||||
-- OLD: modules.smartammo = { enabled, smartEnabled, reloadEnabled,
|
||||
-- weaponSwapEnabled, legacy = { MTHSmartAmmo = {
|
||||
-- enabled, reload, weaponSwap } } }
|
||||
-- + optional global MTHSmartAmmo = { enabled, reload, weaponSwap }
|
||||
-- NEW: modules.smartammo = { schemaVersion, settings = {
|
||||
-- smartEnabled, reloadEnabled, weaponSwapEnabled } }
|
||||
local function MTH_SV_Migrate_SmartAmmo(store, globalMTHSmartAmmo, report)
|
||||
if not MTH_SV_IsTable(store) then
|
||||
return
|
||||
end
|
||||
local legacy = MTH_SV_IsTable(store.legacy) and store.legacy.MTHSmartAmmo or nil
|
||||
local g = MTH_SV_IsTable(globalMTHSmartAmmo) and globalMTHSmartAmmo or nil
|
||||
|
||||
local settings = MTH_SV_Ensure(store, "settings")
|
||||
|
||||
-- smartEnabled: modern flat -> settings -> legacy.enabled -> global.enabled -> old `enabled` -> true
|
||||
if settings.smartEnabled == nil then
|
||||
settings.smartEnabled = MTH_SV_Coalesce({
|
||||
store.smartEnabled,
|
||||
legacy and legacy.enabled,
|
||||
g and g.enabled,
|
||||
store.enabled,
|
||||
}, true) and true or false
|
||||
end
|
||||
if settings.reloadEnabled == nil then
|
||||
settings.reloadEnabled = MTH_SV_Coalesce({
|
||||
store.reloadEnabled,
|
||||
legacy and legacy.reload,
|
||||
g and g.reload,
|
||||
}, true) and true or false
|
||||
end
|
||||
if settings.weaponSwapEnabled == nil then
|
||||
settings.weaponSwapEnabled = MTH_SV_Coalesce({
|
||||
store.weaponSwapEnabled,
|
||||
legacy and legacy.weaponSwap,
|
||||
g and g.weaponSwap,
|
||||
}, true) and true or false
|
||||
end
|
||||
|
||||
-- Clear the old flat/legacy shape now that settings holds the truth.
|
||||
store.legacy = nil
|
||||
store.enabled = nil
|
||||
store.smartEnabled = nil
|
||||
store.reloadEnabled = nil
|
||||
store.weaponSwapEnabled = nil
|
||||
store.schemaVersion = 1
|
||||
|
||||
report.smartammo = "settings{smartEnabled=" .. tostring(settings.smartEnabled)
|
||||
.. ",reloadEnabled=" .. tostring(settings.reloadEnabled)
|
||||
.. ",weaponSwapEnabled=" .. tostring(settings.weaponSwapEnabled) .. "}"
|
||||
end
|
||||
|
||||
-- AUTOQUEST (per-character)
|
||||
-- OLD (canonical): MTH_CharSavedVariables.autoquest = { scorpokDrazial,
|
||||
-- arrowsForSissies, scorpokTooltip, _migrated } mirrored into
|
||||
-- modules.autoquest = { scorpokDrazial, arrowsForSissies, scorpokTooltip, enabled }
|
||||
-- plus oldest legacy MTH_CharSavedVariables.questautomation.
|
||||
-- NEW: modules.autoquest = { schemaVersion, settings = {
|
||||
-- scorpokDrazial, arrowsForSissies, scorpokTooltip } }
|
||||
local function MTH_SV_Migrate_AutoQuest(char, report)
|
||||
local modules = MTH_SV_Ensure(char, "modules")
|
||||
local store = MTH_SV_Ensure(modules, "autoquest")
|
||||
local settings = MTH_SV_Ensure(store, "settings")
|
||||
|
||||
local root = MTH_SV_IsTable(char.autoquest) and char.autoquest or {}
|
||||
local qa = MTH_SV_IsTable(char.questautomation) and char.questautomation or {}
|
||||
|
||||
local keys = { "scorpokDrazial", "arrowsForSissies", "scorpokTooltip" }
|
||||
for _, k in ipairs(keys) do
|
||||
if settings[k] == nil then
|
||||
settings[k] = MTH_SV_Coalesce({
|
||||
store[k], -- flat mirror on modules.autoquest
|
||||
root[k], -- former canonical root .autoquest
|
||||
qa[k], -- oldest legacy .questautomation
|
||||
}, false) and true or false
|
||||
end
|
||||
end
|
||||
|
||||
-- Clear old / duplicate locations.
|
||||
store.scorpokDrazial = nil
|
||||
store.arrowsForSissies = nil
|
||||
store.scorpokTooltip = nil
|
||||
store._migrated = nil
|
||||
char.autoquest = nil
|
||||
char.questautomation = nil
|
||||
store.schemaVersion = 1
|
||||
|
||||
report.autoquest = "settings{scorpokDrazial=" .. tostring(settings.scorpokDrazial)
|
||||
.. ",arrowsForSissies=" .. tostring(settings.arrowsForSissies)
|
||||
.. ",scorpokTooltip=" .. tostring(settings.scorpokTooltip) .. "}"
|
||||
end
|
||||
|
||||
-- ANTIDAZE (per-character): char-root .antiDaze{enabled} -> modules.antidaze.settings{enabled}
|
||||
local function MTH_SV_Migrate_AntiDaze(char, report)
|
||||
local modules = MTH_SV_Ensure(char, "modules")
|
||||
local store = MTH_SV_Ensure(modules, "antidaze")
|
||||
local settings = MTH_SV_Ensure(store, "settings")
|
||||
local old = MTH_SV_IsTable(char.antiDaze) and char.antiDaze or {}
|
||||
if settings.enabled == nil and old.enabled ~= nil then
|
||||
settings.enabled = old.enabled and true or false
|
||||
end
|
||||
char.antiDaze = nil
|
||||
store.schemaVersion = 1
|
||||
report.antidaze = "settings{enabled=" .. tostring(settings.enabled) .. "}"
|
||||
end
|
||||
|
||||
-- AUTOSTRIP (per-character): char-root .autoStrip{autostrip,display,point,relativePoint,x,y}
|
||||
-- -> modules.autostrip{autostrip,display,point,relativePoint,x,y}
|
||||
-- (keys preserved verbatim; only the storage location moves under .modules)
|
||||
local function MTH_SV_Migrate_AutoStrip(char, report)
|
||||
local modules = MTH_SV_Ensure(char, "modules")
|
||||
local store = MTH_SV_Ensure(modules, "autostrip")
|
||||
local old = MTH_SV_IsTable(char.autoStrip) and char.autoStrip or {}
|
||||
local carry = { "autostrip", "display", "point", "relativePoint", "x", "y" }
|
||||
for _, k in ipairs(carry) do
|
||||
if store[k] == nil and old[k] ~= nil then
|
||||
store[k] = old[k]
|
||||
end
|
||||
end
|
||||
char.autoStrip = nil
|
||||
store.schemaVersion = 1
|
||||
report.autostrip = "store{autostrip=" .. tostring(store.autostrip)
|
||||
.. ",display=" .. tostring(store.display) .. "}"
|
||||
end
|
||||
|
||||
-- MINIMAPBUTTON (per-character): char-root .minimapButton{angle} -> modules.minimapbutton{angle}
|
||||
local function MTH_SV_Migrate_MinimapButton(char, report)
|
||||
local modules = MTH_SV_Ensure(char, "modules")
|
||||
local store = MTH_SV_Ensure(modules, "minimapbutton")
|
||||
local old = MTH_SV_IsTable(char.minimapButton) and char.minimapButton or {}
|
||||
if store.angle == nil and old.angle ~= nil then
|
||||
store.angle = old.angle
|
||||
end
|
||||
char.minimapButton = nil
|
||||
store.schemaVersion = 1
|
||||
report.minimapbutton = "store{angle=" .. tostring(store.angle) .. "}"
|
||||
end
|
||||
|
||||
-- FEEDOMATIC (account-wide): the 7 old FeedOMatic globals were stored under
|
||||
-- modules.feedomatic.legacy.FOM_* (with _G.FOM_* bound as runtime aliases) and
|
||||
-- duplicated at the account-root alias MTH_SavedVariables.feedomatic.
|
||||
-- NEW: modules.feedomatic = { schemaVersion, settings, foodQuality, addedFoods,
|
||||
-- removedFoods, cooking, questFood, localeInfo } — no .legacy wrapper, no root
|
||||
-- alias. The _G.FOM_* globals stay as RUNTIME aliases (re-bound to these nested
|
||||
-- tables by MTH_FeedOMatic_SyncSavedVariables) so FeedOMatic.lua is untouched.
|
||||
local MTH_SV_FOM_MAP = {
|
||||
{ global = "FOM_Config", key = "settings" },
|
||||
{ global = "FOM_FoodQuality", key = "foodQuality" },
|
||||
{ global = "FOM_AddedFoods", key = "addedFoods" },
|
||||
{ global = "FOM_RemovedFoods", key = "removedFoods" },
|
||||
{ global = "FOM_Cooking", key = "cooking" },
|
||||
{ global = "FOM_QuestFood", key = "questFood" },
|
||||
{ global = "FOM_LocaleInfo", key = "localeInfo" },
|
||||
}
|
||||
|
||||
local function MTH_SV_Migrate_FeedOMatic(account, report)
|
||||
local modules = MTH_SV_Ensure(account, "modules")
|
||||
local store = MTH_SV_Ensure(modules, "feedomatic")
|
||||
local legacy = MTH_SV_IsTable(store.legacy) and store.legacy or {}
|
||||
|
||||
for _, m in ipairs(MTH_SV_FOM_MAP) do
|
||||
if store[m.key] == nil then
|
||||
local src = legacy[m.global]
|
||||
if type(src) ~= "table" and _G then
|
||||
src = _G[m.global]
|
||||
end
|
||||
if type(src) == "table" then
|
||||
store[m.key] = src
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
store.legacy = nil
|
||||
account.feedomatic = nil -- drop the account-root alias duplicate
|
||||
store.schemaVersion = 1
|
||||
report.feedomatic = "keys{settings,foodQuality,addedFoods,removedFoods,cooking,questFood,localeInfo}"
|
||||
end
|
||||
|
||||
-- ZHUNTER (per-character): the zButton layout used to live in the TOC-declared
|
||||
-- global ZHunterMod_Saved AND was mirrored into modules.zhunter (two on-disk
|
||||
-- copies). NEW: modules.zhunter is the single canonical store (button tables,
|
||||
-- _zbar, enabled, _mth_widget_spawn_layout_v1). ZHunterMod_Saved becomes a pure
|
||||
-- RUNTIME alias re-bound by MTH_ZH_GetSavedRoot, and is dropped from the TOC.
|
||||
-- The vestigial account-root alias MTH_SavedVariables.zhunter is removed too.
|
||||
local function MTH_SV_Migrate_ZHunter(account, char, report)
|
||||
local modules = MTH_SV_Ensure(char, "modules")
|
||||
local store = MTH_SV_Ensure(modules, "zhunter")
|
||||
|
||||
-- If the nested store is still empty but a per-char global carries data
|
||||
-- (legacy TOC copy present during the transition), adopt it once.
|
||||
if _G and MTH_SV_IsTable(_G.ZHunterMod_Saved) and _G.ZHunterMod_Saved ~= store then
|
||||
if MTH_SV_Count(store) == 0 then
|
||||
for k, v in pairs(_G.ZHunterMod_Saved) do
|
||||
store[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
account.zhunter = nil -- drop the vestigial account-root alias duplicate
|
||||
report.zhunter = "modules.zhunter canonical; ZHunterMod_Saved -> runtime alias"
|
||||
end
|
||||
|
||||
------------------------------------------------------
|
||||
-- Orchestrator
|
||||
------------------------------------------------------
|
||||
|
||||
-- MTH_SV_EnsureSchema(account, char)
|
||||
-- account / char default to the live globals. Returns a report table.
|
||||
-- Gated by schemaVersion on each root so it only runs once; idempotent anyway.
|
||||
function MTH_SV_EnsureSchema(account, char)
|
||||
if account == nil then account = MTH_SavedVariables end
|
||||
if char == nil then char = MTH_CharSavedVariables end
|
||||
|
||||
local report = { ran = false }
|
||||
|
||||
if not MTH_SV_IsTable(account) or not MTH_SV_IsTable(char) then
|
||||
report.error = "roots unavailable"
|
||||
return report
|
||||
end
|
||||
|
||||
local accountDone = (tonumber(account.schemaVersion) or 0) >= MTH_SV_SCHEMA_VERSION
|
||||
local charDone = (tonumber(char.schemaVersion) or 0) >= MTH_SV_SCHEMA_VERSION
|
||||
if accountDone and charDone then
|
||||
report.skipped = true
|
||||
return report
|
||||
end
|
||||
report.ran = true
|
||||
|
||||
local accountModules = MTH_SV_Ensure(account, "modules")
|
||||
local charModules = MTH_SV_Ensure(char, "modules")
|
||||
|
||||
-- Detect whether there is genuine legacy data to relocate BEFORE the migrators
|
||||
-- consume/delete it. On a brand-new install there is nothing to move, so we still
|
||||
-- stamp the schema (to avoid re-checking) but skip the player-facing announcement.
|
||||
local smartLegacy = MTH_SV_IsTable(charModules.smartammo) and charModules.smartammo.legacy or nil
|
||||
local feedLegacy = MTH_SV_IsTable(accountModules.feedomatic) and accountModules.feedomatic.legacy or nil
|
||||
report.changed = (account.feedomatic ~= nil)
|
||||
or (account.zhunter ~= nil)
|
||||
or (char.antiDaze ~= nil)
|
||||
or (char.autoStrip ~= nil)
|
||||
or (char.minimapButton ~= nil)
|
||||
or (char.autoquest ~= nil)
|
||||
or (char.questautomation ~= nil)
|
||||
or (feedLegacy ~= nil)
|
||||
or (smartLegacy ~= nil)
|
||||
or (_G ~= nil and (_G.FOM_Config ~= nil or _G.ZHunterMod_Saved ~= nil or _G.MTHSmartAmmo ~= nil))
|
||||
or false
|
||||
|
||||
-- ---- account-wide modules ----
|
||||
MTH_SV_Migrate_FeedOMatic(account, report)
|
||||
|
||||
-- ---- per-character modules ----
|
||||
MTH_SV_Migrate_SmartAmmo(
|
||||
MTH_SV_Ensure(charModules, "smartammo"),
|
||||
_G and _G.MTHSmartAmmo or nil,
|
||||
report
|
||||
)
|
||||
if _G then _G.MTHSmartAmmo = nil end
|
||||
|
||||
MTH_SV_Migrate_AutoQuest(char, report)
|
||||
MTH_SV_Migrate_AntiDaze(char, report)
|
||||
MTH_SV_Migrate_AutoStrip(char, report)
|
||||
MTH_SV_Migrate_MinimapButton(char, report)
|
||||
MTH_SV_Migrate_ZHunter(account, char, report)
|
||||
|
||||
-- ---- stamp ----
|
||||
account.schemaVersion = MTH_SV_SCHEMA_VERSION
|
||||
char.schemaVersion = MTH_SV_SCHEMA_VERSION
|
||||
|
||||
-- ---- player-facing announcement (only when real legacy data was relocated) ----
|
||||
-- Guarded on MTH_Log so the offline test harness (no MetaHunt runtime) stays silent.
|
||||
if report.changed and type(MTH_Log) == "function" then
|
||||
local order = {
|
||||
"feedomatic", "smartammo", "autoquest",
|
||||
"antidaze", "autostrip", "minimapbutton", "zhunter",
|
||||
}
|
||||
local done = {}
|
||||
for _, id in ipairs(order) do
|
||||
if report[id] ~= nil then
|
||||
table.insert(done, id)
|
||||
end
|
||||
end
|
||||
MTH_Log("Tidying your saved settings into the new 2.0 layout (one-time)...")
|
||||
if table.getn(done) > 0 then
|
||||
MTH_Log(" consolidated: " .. table.concat(done, ", "))
|
||||
end
|
||||
MTH_Log("Saved-settings cleanup complete. Your options were carried over unchanged.")
|
||||
end
|
||||
|
||||
return report
|
||||
end
|
||||
|
||||
------------------------------------------------------
|
||||
-- Load trigger
|
||||
------------------------------------------------------
|
||||
-- Run the migration exactly once, AFTER WoW has loaded MetaHunt's SavedVariables
|
||||
-- from disk. ADDON_LOADED with arg1 == "MetaHunt" is the earliest point at which the
|
||||
-- real account + per-character saved data is actually available. Running it any earlier
|
||||
-- (e.g. from a top-level MTH:InitSavedVariables() at file-load) operates on the empty
|
||||
-- default tables WoW then overwrites when it loads the SV file — which is exactly why
|
||||
-- the migration used to re-announce every login and /reload. CreateFrame is guarded so
|
||||
-- the offline test harness (no WoW API) simply skips this.
|
||||
local MTH_SV_LoadFrame = CreateFrame and CreateFrame("Frame")
|
||||
if MTH_SV_LoadFrame then
|
||||
MTH_SV_LoadFrame:RegisterEvent("ADDON_LOADED")
|
||||
MTH_SV_LoadFrame:SetScript("OnEvent", function()
|
||||
if event ~= "ADDON_LOADED" or arg1 ~= "MetaHunt" then
|
||||
return
|
||||
end
|
||||
MTH_SV_LoadFrame:UnregisterEvent("ADDON_LOADED")
|
||||
if MTH and MTH.InitSavedVariables then
|
||||
MTH:InitSavedVariables()
|
||||
end
|
||||
if type(MTH_SV_EnsureSchema) == "function" then
|
||||
MTH_SV_EnsureSchema(MTH_SavedVariables, MTH_CharSavedVariables)
|
||||
end
|
||||
end)
|
||||
end
|
||||
Reference in New Issue
Block a user