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
+11
View File
@@ -3,6 +3,17 @@
All notable changes to MetaHunt will be documented in this file.
## [2.0.0] - Unreleased
### Changed
- **Settings storage tidied into one clean layout**: The settings MetaHunt inherited from the older stand-alone addons it now replaces (Feed-O-Matic, ZHunter/zButtons, SmartAmmo, and the quest/anti-daze/auto-strip/minimap helpers) used to be scattered across many separate saved entries, several of them duplicated on disk. MetaHunt now keeps everything in one organised place, grouped per module. The clean-up runs automatically and once, the first time you log in after updating: all of your existing settings and layouts are carried over unchanged — nothing is reset — and the old duplicate copies are removed so your saved-variables file stays small and consistent.
### Fixed
- **Pets — duplicate and phantom entries in your pet collection**: MetaHunt could record the same tamed pet more than once — a fresh copy appeared after you logged out and back in, or after your pet leveled up — and a few very early pets you had long since dismissed lingered as though they were still with you. MetaHunt now recognises each pet reliably from one session to the next (and as it levels), automatically merges the duplicate entries it had already created while keeping the correct taming details (where, and at what level, you tamed each one), and tidies the leftover phantom pets away into your pet history. Your full taming history is preserved — nothing is deleted.
## [1.5.2] - 2026-09-08
Res on Octowow server.
+2 -2
View File
@@ -3,8 +3,8 @@
## Title: MetaHunt - |cff00ff00Hunter
## Author: Metasploit and his Copilot ;)
## Notes: Unified addon suite for huntards making old addons compatible with Twow, and adding an arsenal of never seen Hunter's utilities.
## SavedVariables: MTH_SavedVariables, FOM_Config, FOM_FoodQuality, FOM_AddedFoods, FOM_RemovedFoods, FOM_Cooking, FOM_QuestFood, FOM_LocaleInfo
## SavedVariablesPerCharacter: ZHunterMod_Saved, MTH_CharSavedVariables
## SavedVariables: MTH_SavedVariables
## SavedVariablesPerCharacter: MTH_CharSavedVariables
data\init.lua
+23 -16
View File
@@ -1,14 +1,15 @@
# MetaHunt
MetaHunt is a Twow Hunter toolkit.
It is a modern modular addon compiling brand new features, and improving a few old hunter add-ons that were broken on Twow.
It is a modern modular addon compiling brand new features, and improving a few old hunter add-ons that were broken on twow.
It provides a bunch of useful tools for all huntards, wheter they are still levelling, lone-wolf, or HL raider.
### Important notes
- Altough Nampower isn't mandatory for most of the features, it is a must-have for some.
- If you were using old versions of Feed-O-Matic, ICU, zHunterMod and HunterHelper, you dont need those with MetaHunt and you should disable them to avoid conflicts.
- MetaHunt is fully compatible with Quiver and is not a replacement for it.
- Zero support for Capycraft and Ravencraft mafia.
- MetaHunt is fully compatible with QUIVER, and is not a replacement for it.
- MetaHunt don't and will never support Capycraft/Ravencraft (mafia) servers, after the DDOS attacks they conducted on other servers, to only name that. Fuck you.
## Twow Data
@@ -23,6 +24,14 @@ MetaHunt ships with large Twow datastores :
You can browse all the beasts in game within the Book of Huntards, but also on the github site : https://DuvelCorp.github.io/MetaHunt-Web/
## The Great Book of Huntards
The Book is the main GUI of MetaHunt, it allows to display the data collected about you and your pet, and browse the addon's data to find anything a Hunter needs.
- Browse the MetaHunt datastores of NPCs (Beast, vendors, masters) and open the map to locate them like in pfQuest
- Track all info about the pet abilities that you know and don't know yet
- Track all info about your stabled pets
- Keep an history of all your pets after you abandon them or if they run away.
## Smart Ammo
@@ -31,7 +40,7 @@ You can browse all the beasts in game within the Book of Huntards, but also on t
- Auto equip the right ammo when you swap from a box/xbow to a gun and the other way around.
## MM Widget
Lets Make MM great again. Tracks the new 3-state of Experimental ammo cycle (Fire → Nature → Arcane), the Lock&Load procs, and has a dynamic cell that shows either Aimed Shot availability or the right shot to use following the current Experimental ammo proc if any.
Lets Make MM great again. Tracks the new 3-state of Experimental ammo cycle (Fire → Nature → Arcane), the Lock&Load procs, and has a dynamic cell that shows either Aimed Shot availability or the right shot to use following the current Experimental ammo proc if any. You can also bind it to turn it into a very efficient one-button rotation. No more excuse to not be amongst top DPS.
## Tooltips
@@ -45,11 +54,13 @@ You can browse all the beasts in game within the Book of Huntards, but also on t
Old zHunterMod addon on steroids. Fully compatible with Twow, and enhanced with new functionalities
- zAmmo : Track all your ammo Live and swap them in a click or bind
- zPet : Intelligent collapsible pet Bar regrouping all pet spells and providing an intelligent button that always show you the right spell to use depending on the situation
- zPet : Intelligent collapsible pet Bar regrouping all pet spells and providing a main button that always swap on the right spell to use depending on the situation
- zTrack: All your trackings in one collapsible bar
- zAspect: All your trackings in one collapsible bar
- zRanged : All the ranged weapons currently in your bags to swap them fast
- But also zBar, zCrafts, zMounts, zToys and zCompanions.
- But also zCrafts, zMounts, zToys and zCompanions.
Additionally, a zBar can regroup all the zButtons above into a single bar for people that like clean and ordered stuff.
## Feed-O-Matic
@@ -63,26 +74,22 @@ The good old Chronometer addon, purged of non-necessary things and reconfigured
Old addon used to display Targets' enhanced information when you click on your minimap tracking things. MetaHunt offers many more customization options for it.
## Ammo labels
You can display on all ammo in your bags and/or bank their type and/or dps to instantly see which of those damn same arrows icons really are.
## Auto-Buy
Define your own simple rules for auto buy ammo and pet food when you open a vendor.
Define your own simple rules for auto buy ammo and pet food when you open a vendor. You can also make a food-auto-buy planning based on all your stabled pet's diets.
## Auto-Quest
Just spam SHIFT-click on the NPC to validate quests Scorpok and Thorium arrows. Additionally you can have an enhanced tooltip that display your actual Scorpok-related items when you mouseover the mobs involved.
Just spam SHIFT-click on the NPC to validate quests Scorpok and Thorium arrows. Additionally you can have an enhanced tooltip that display your actual Scorpok-related items when you mouseover the Blasted Land mobs involved.
## Beast model Viewer
Display the model of all beasts in the world. Always know what you are going to tame exactly before running there ! You can also browse all skin models of the same pet family to find the best-looking pet according to your tastes.
## The Great Book of Huntards
The Book is the main GUI of MetaHunt, it allows to display the data constantly collected about you and your pet, and browse the addon's data to find anything a Hunter needs.
- Browse the MetaHunt datastores of NPCs (Beast, vendors, masters) and open the map to locate them like in pfQuest
- Track all info about the pet abilities that you know and don't know yet
- Track all info about your stabled pets
- Keep an history of all your pets after you abandon them or if they run away.
+16 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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()
+10
View File
@@ -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
View File
@@ -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
View File
@@ -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()
+4 -4
View File
@@ -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,
+434
View File
@@ -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
+1
View File
@@ -1,6 +1,7 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\FrameXML\UI.xsd">
<Include file="..\api\constants.lua"/>
<Include file="..\api\savedvariables.lua"/>
<Include file="..\api\minimap-button.lua"/>
<Include file="..\api\AutoStrip.xml"/>
<Include file="..\api\AutoStrip.lua"/>
+25 -80
View File
@@ -56,70 +56,33 @@ local function QA_GetOptionsStore()
if type(MTH_CharSavedVariables) ~= "table" then
MTH_CharSavedVariables = {}
end
if type(MTH_CharSavedVariables.autoquest) ~= "table" then
MTH_CharSavedVariables.autoquest = {}
end
local store = MTH_CharSavedVariables.autoquest
if store._migrated ~= true then
if type(MTH_CharSavedVariables.questautomation) == "table" then
local legacyStore = MTH_CharSavedVariables.questautomation
if legacyStore.scorpokDrazial ~= nil and store.scorpokDrazial == nil then
store.scorpokDrazial = legacyStore.scorpokDrazial and true or false
end
if legacyStore.scorpokTooltip ~= nil and store.scorpokTooltip == nil then
store.scorpokTooltip = legacyStore.scorpokTooltip and true or false
end
end
local legacyChar = nil
local legacyAccount = nil
if MTH and MTH.GetModuleCharSavedVariables then
legacyChar = MTH:GetModuleCharSavedVariables("autoquest")
if type(legacyChar) ~= "table" then
legacyChar = MTH:GetModuleCharSavedVariables("questautomation")
end
end
if MTH and MTH.GetModuleSavedVariables then
legacyAccount = MTH:GetModuleSavedVariables("autoquest")
if type(legacyAccount) ~= "table" then
legacyAccount = MTH:GetModuleSavedVariables("questautomation")
end
end
if type(legacyChar) == "table" and legacyChar.scorpokDrazial ~= nil and store.scorpokDrazial == nil then
store.scorpokDrazial = legacyChar.scorpokDrazial and true or false
elseif type(legacyAccount) == "table" and legacyAccount.scorpokDrazial ~= nil and store.scorpokDrazial == nil then
store.scorpokDrazial = legacyAccount.scorpokDrazial and true or false
elseif store.scorpokDrazial == nil and MTH and MTH.GetModuleCharSavedVariables then
local autobuyStore = MTH:GetModuleCharSavedVariables("autobuy")
if type(autobuyStore) == "table"
and type(autobuyStore.questAutomation) == "table"
and autobuyStore.questAutomation.scorpokDrazial ~= nil then
store.scorpokDrazial = autobuyStore.questAutomation.scorpokDrazial and true or false
end
end
store._migrated = true
end
if store.scorpokDrazial == nil then
store.scorpokDrazial = false
end
if store.arrowsForSissies == nil then
store.arrowsForSissies = false
end
if store.scorpokTooltip == nil then
store.scorpokTooltip = false
end
local store = nil
if MTH and MTH.GetModuleCharSavedVariables then
local moduleStore = MTH:GetModuleCharSavedVariables("autoquest")
if type(moduleStore) == "table" then
moduleStore.scorpokDrazial = store.scorpokDrazial and true or false
moduleStore.arrowsForSissies = store.arrowsForSissies and true or false
moduleStore.scorpokTooltip = store.scorpokTooltip and true or false
end
store = MTH:GetModuleCharSavedVariables("autoquest")
end
return store
if type(store) ~= "table" then
if type(MTH_CharSavedVariables.modules) ~= "table" then
MTH_CharSavedVariables.modules = {}
end
if type(MTH_CharSavedVariables.modules.autoquest) ~= "table" then
MTH_CharSavedVariables.modules.autoquest = {}
end
store = MTH_CharSavedVariables.modules.autoquest
end
if type(store.settings) ~= "table" then
store.settings = {}
end
local settings = store.settings
if settings.scorpokDrazial == nil then
settings.scorpokDrazial = false
end
if settings.arrowsForSissies == nil then
settings.arrowsForSissies = false
end
if settings.scorpokTooltip == nil then
settings.scorpokTooltip = false
end
return settings
end
function MTH_AutoQuest:GetStore()
@@ -129,36 +92,18 @@ end
function MTH_AutoQuest:SetScorpokDrazialEnabled(enabled)
local store = QA_GetOptionsStore()
store.scorpokDrazial = enabled and true or false
if MTH and MTH.GetModuleCharSavedVariables then
local moduleStore = MTH:GetModuleCharSavedVariables("autoquest")
if type(moduleStore) == "table" then
moduleStore.scorpokDrazial = store.scorpokDrazial and true or false
end
end
QA_Debug("option write: scorpokDrazial=" .. tostring(store.scorpokDrazial and true or false))
end
function MTH_AutoQuest:SetArrowsForSissiesEnabled(enabled)
local store = QA_GetOptionsStore()
store.arrowsForSissies = enabled and true or false
if MTH and MTH.GetModuleCharSavedVariables then
local moduleStore = MTH:GetModuleCharSavedVariables("autoquest")
if type(moduleStore) == "table" then
moduleStore.arrowsForSissies = store.arrowsForSissies and true or false
end
end
QA_Debug("option write: arrowsForSissies=" .. tostring(store.arrowsForSissies and true or false))
end
function MTH_AutoQuest:SetScorpokTooltipEnabled(enabled)
local store = QA_GetOptionsStore()
store.scorpokTooltip = enabled and true or false
if MTH and MTH.GetModuleCharSavedVariables then
local moduleStore = MTH:GetModuleCharSavedVariables("autoquest")
if type(moduleStore) == "table" then
moduleStore.scorpokTooltip = store.scorpokTooltip and true or false
end
end
QA_Debug("option write: scorpokTooltip=" .. tostring(store.scorpokTooltip and true or false))
end
-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
+35 -60
View File
@@ -1,7 +1,3 @@
local function MTHSmartAmmo_GetSaved()
return MTH_SA_GetSavedTable("MTHSmartAmmo")
end
local function MTHSmartAmmo_GetModuleStore()
if MTH and MTH.GetModuleCharSavedVariables then
return MTH:GetModuleCharSavedVariables("smartammo")
@@ -9,40 +5,43 @@ local function MTHSmartAmmo_GetModuleStore()
return nil
end
local function MTHSmartAmmo_IsSmartEnabled()
-- Single source of truth: moduleStore.settings.{smartEnabled,reloadEnabled,weaponSwapEnabled}
local function MTHSmartAmmo_GetSettings()
local moduleStore = MTHSmartAmmo_GetModuleStore()
if type(moduleStore) == "table" and moduleStore.smartEnabled ~= nil then
return moduleStore.smartEnabled and true or false
if type(moduleStore) ~= "table" then
return nil
end
local saved = MTHSmartAmmo_GetSaved()
if saved["enabled"] == nil then
return true
if type(moduleStore.settings) ~= "table" then
moduleStore.settings = {}
end
return saved["enabled"] and true or false
return moduleStore.settings
end
local function MTHSmartAmmo_ReadFlag(key)
local settings = MTHSmartAmmo_GetSettings()
if settings and settings[key] ~= nil then
return settings[key] and true or false
end
return true -- default enabled
end
local function MTHSmartAmmo_WriteFlag(key, enabled)
local settings = MTHSmartAmmo_GetSettings()
if settings then
settings[key] = enabled and true or false
end
end
local function MTHSmartAmmo_IsSmartEnabled()
return MTHSmartAmmo_ReadFlag("smartEnabled")
end
local function MTHSmartAmmo_IsReloadEnabled()
local moduleStore = MTHSmartAmmo_GetModuleStore()
if type(moduleStore) == "table" and moduleStore.reloadEnabled ~= nil then
return moduleStore.reloadEnabled and true or false
end
local saved = MTHSmartAmmo_GetSaved()
if saved["reload"] == nil then
return true
end
return saved["reload"] and true or false
return MTHSmartAmmo_ReadFlag("reloadEnabled")
end
local function MTHSmartAmmo_IsWeaponSwapEnabled()
local moduleStore = MTHSmartAmmo_GetModuleStore()
if type(moduleStore) == "table" and moduleStore.weaponSwapEnabled ~= nil then
return moduleStore.weaponSwapEnabled and true or false
end
local saved = MTHSmartAmmo_GetSaved()
if saved["weaponSwap"] == nil then
return true
end
return saved["weaponSwap"] and true or false
return MTHSmartAmmo_ReadFlag("weaponSwapEnabled")
end
local MTHSmartAmmo_InitializeHooks
@@ -58,9 +57,7 @@ local MTH_SA_SPELL_EVENTS = {
}
function MTHSmartAmmo_SetSmartEnabled(enabled, silent)
local saved = MTHSmartAmmo_GetSaved()
if enabled then
saved["enabled"] = 1
local evFrame = getglobal("MTH_SA_EventFrame")
if evFrame then
for _, ev in ipairs(MTH_SA_SPELL_EVENTS) do
@@ -77,7 +74,6 @@ function MTHSmartAmmo_SetSmartEnabled(enabled, silent)
MTH_SA_Print("Smart Ammo Enabled.")
end
else
saved["enabled"] = false
local evFrame = getglobal("MTH_SA_EventFrame")
if evFrame then
for _, ev in ipairs(MTH_SA_SPELL_EVENTS) do
@@ -89,10 +85,7 @@ function MTHSmartAmmo_SetSmartEnabled(enabled, silent)
end
end
local moduleStore = MTHSmartAmmo_GetModuleStore()
if type(moduleStore) == "table" then
moduleStore.smartEnabled = enabled and true or false
end
MTHSmartAmmo_WriteFlag("smartEnabled", enabled and true or false)
end
function MTHSmartAmmo_GetSmartEnabled()
@@ -100,16 +93,7 @@ function MTHSmartAmmo_GetSmartEnabled()
end
function MTHSmartAmmo_SetReloadEnabled(enabled, silent)
local saved = MTHSmartAmmo_GetSaved()
if enabled then
saved["reload"] = 1
else
saved["reload"] = false
end
local moduleStore = MTHSmartAmmo_GetModuleStore()
if type(moduleStore) == "table" then
moduleStore.reloadEnabled = enabled and true or false
end
MTHSmartAmmo_WriteFlag("reloadEnabled", enabled and true or false)
if not silent and DEFAULT_CHAT_FRAME then
MTH_SA_Print("Smart Ammo reload fallback " .. (enabled and "Enabled." or "Disabled."))
end
@@ -120,16 +104,7 @@ function MTHSmartAmmo_GetReloadEnabled()
end
function MTHSmartAmmo_SetWeaponSwapEnabled(enabled, silent)
local saved = MTHSmartAmmo_GetSaved()
if enabled then
saved["weaponSwap"] = 1
else
saved["weaponSwap"] = false
end
local moduleStore = MTHSmartAmmo_GetModuleStore()
if type(moduleStore) == "table" then
moduleStore.weaponSwapEnabled = enabled and true or false
end
MTHSmartAmmo_WriteFlag("weaponSwapEnabled", enabled and true or false)
if not silent and DEFAULT_CHAT_FRAME then
MTH_SA_Print("Smart Ammo weapon-swap auto-equip " .. (enabled and "Enabled." or "Disabled."))
end
@@ -807,15 +782,15 @@ SlashCmdList["MTHSmartAmmo"] = function(msg)
return
end
local saved = MTHSmartAmmo_GetSaved()
local smartEnabled = MTHSmartAmmo_GetSmartEnabled()
if msg == "status" then
local castHook, actionHook, byNameHook = MTHSmartAmmo_AreHooksInstalled()
MTH_SA_Print("Smart Ammo status: smart=" .. tostring(saved["enabled"] and true or false) .. ", module=" .. tostring(MTH_SA_IsModuleEnabled()) .. ", hooks=" .. tostring(castHook) .. "/" .. tostring(actionHook) .. "/" .. tostring(byNameHook))
MTH_SA_Print("Smart Ammo status: smart=" .. tostring(smartEnabled) .. ", module=" .. tostring(MTH_SA_IsModuleEnabled()) .. ", hooks=" .. tostring(castHook) .. "/" .. tostring(actionHook) .. "/" .. tostring(byNameHook))
return
end
if saved["enabled"] then
if smartEnabled then
MTHSmartAmmo_SetSmartEnabled(nil)
else
MTHSmartAmmo_SetSmartEnabled(1)
+11 -66
View File
@@ -15,39 +15,6 @@ local MTH_SmartAmmo = {
local MTH_SA_BOUNDARY_KEY = "smartammo.core"
function MTH_SA_GetSavedTable(tableName)
if not MTH or not MTH.GetModuleCharSavedVariables then
if type(_G[tableName]) ~= "table" then
_G[tableName] = {}
end
return _G[tableName]
end
local moduleStore = MTH:GetModuleCharSavedVariables("smartammo")
if type(moduleStore) ~= "table" then
moduleStore = {}
end
if not moduleStore.legacy then
moduleStore.legacy = {}
end
if type(moduleStore.legacy[tableName]) ~= "table" then
moduleStore.legacy[tableName] = {}
end
if type(_G[tableName]) == "table" then
local legacyTable = moduleStore.legacy[tableName]
if not next(legacyTable) then
for key, value in pairs(_G[tableName]) do
legacyTable[key] = value
end
end
end
_G[tableName] = moduleStore.legacy[tableName]
return moduleStore.legacy[tableName]
end
function MTH_SA_IsModuleEnabled()
if MTH and MTH.IsModuleEnabled then
return MTH:IsModuleEnabled("smartammo", true) and true or false
@@ -70,43 +37,22 @@ local function MTH_SA_ApplySavedState()
return
end
local saved = MTH_SA_GetSavedTable("MTHSmartAmmo")
local moduleStore = MTH and MTH.GetModuleCharSavedVariables and MTH:GetModuleCharSavedVariables("smartammo") or nil
if type(moduleStore) == "table" then
if moduleStore.smartEnabled ~= nil then
saved["enabled"] = moduleStore.smartEnabled and 1 or false
end
if moduleStore.reloadEnabled ~= nil then
saved["reload"] = moduleStore.reloadEnabled and 1 or false
end
if moduleStore.weaponSwapEnabled ~= nil then
saved["weaponSwap"] = moduleStore.weaponSwapEnabled and 1 or false
end
end
if saved["enabled"] == nil then
saved["enabled"] = 1
if type(moduleStore) == "table" then
moduleStore.smartEnabled = true
end
end
if saved["reload"] == nil and type(moduleStore) == "table" then
moduleStore.reloadEnabled = saved["reload"] ~= false
end
if saved["weaponSwap"] == nil then
saved["weaponSwap"] = 1
if type(moduleStore) == "table" then
moduleStore.weaponSwapEnabled = true
end
elseif type(moduleStore) == "table" and moduleStore.weaponSwapEnabled == nil then
moduleStore.weaponSwapEnabled = saved["weaponSwap"] ~= false
end
local settings = (type(moduleStore) == "table" and type(moduleStore.settings) == "table") and moduleStore.settings or {}
MTHSmartAmmo_SetSmartEnabled(saved["enabled"] and 1 or nil, 1)
local smart = settings.smartEnabled
if smart == nil then smart = true end
local reload = settings.reloadEnabled
if reload == nil then reload = true end
local swap = settings.weaponSwapEnabled
if swap == nil then swap = true end
MTHSmartAmmo_SetSmartEnabled(smart and 1 or nil, 1)
if type(MTHSmartAmmo_SetReloadEnabled) == "function" then
MTHSmartAmmo_SetReloadEnabled(saved["reload"] ~= false and 1 or nil, 1)
MTHSmartAmmo_SetReloadEnabled(reload and 1 or nil, 1)
end
if type(MTHSmartAmmo_SetWeaponSwapEnabled) == "function" then
MTHSmartAmmo_SetWeaponSwapEnabled(saved["weaponSwap"] ~= false and 1 or nil, 1)
MTHSmartAmmo_SetWeaponSwapEnabled(swap and 1 or nil, 1)
end
if type(MTHSmartAmmo_EnsureHooks) == "function" then
MTHSmartAmmo_EnsureHooks("module-apply")
@@ -154,7 +100,6 @@ local function MTH_SA_RestoreHooks()
end
function MTH_SmartAmmo:init()
MTH_SA_GetSavedTable("MTHSmartAmmo")
if self.enabled then
MTH_SA_ApplySavedState()
end
+12 -11
View File
@@ -115,23 +115,24 @@ local function MTH_TT_IsOwnPetTooltipsEnabled()
end
local function MTH_TT_GetAutoQuestStore()
if type(MTH_CharSavedVariables) ~= "table" then
return nil
end
local store = MTH_CharSavedVariables.autoquest
if type(store) ~= "table" then
store = MTH_CharSavedVariables.questautomation
local store = nil
if MTH and MTH.GetModuleCharSavedVariables then
store = MTH:GetModuleCharSavedVariables("autoquest")
end
if type(store) ~= "table" then
return nil
end
if store.scorpokDrazial == nil then
store.scorpokDrazial = false
if type(store.settings) ~= "table" then
store.settings = {}
end
if store.scorpokTooltip == nil then
store.scorpokTooltip = false
local settings = store.settings
if settings.scorpokDrazial == nil then
settings.scorpokDrazial = false
end
return store
if settings.scorpokTooltip == nil then
settings.scorpokTooltip = false
end
return settings
end
local function MTH_TT_IsScorpokAutomationEnabled()
+28 -3
View File
@@ -43,10 +43,35 @@ function MTH_ZH_IsModuleEnabled()
end
function MTH_ZH_GetSavedRoot()
if type(ZHunterMod_Saved) ~= "table" then
ZHunterMod_Saved = {}
-- Canonical store is MTH_CharSavedVariables.modules.zhunter. The old global
-- ZHunterMod_Saved is kept only as a RUNTIME alias so the many zButton files
-- and options-zbuttons.lua can keep referencing it unchanged.
local store = nil
if MTH and MTH.GetModuleCharSavedVariables then
store = MTH:GetModuleCharSavedVariables("zhunter")
end
return ZHunterMod_Saved
if type(store) ~= "table" then
-- Framework not ready yet: fall back to a temporary global holder;
-- MTH_ZH_SyncSavedVariables re-binds to the nested store later.
if type(ZHunterMod_Saved) ~= "table" then
ZHunterMod_Saved = {}
end
return ZHunterMod_Saved
end
-- Adopt a pre-existing global (legacy TOC copy) once, only if the nested
-- store is still empty, so no layout is lost during the transition.
if type(ZHunterMod_Saved) == "table" and ZHunterMod_Saved ~= store then
if next(store) == nil then
for k, v in pairs(ZHunterMod_Saved) do
store[k] = v
end
end
end
ZHunterMod_Saved = store -- pure runtime alias into the canonical store
return store
end
function MTH_ZH_GetSavedTable(key)
+5 -12
View File
@@ -165,18 +165,11 @@ MTH_ZH_ApplyEnabledRuntimeState = function(source)
end
MTH_ZH_SyncSavedVariables = function()
if not MTH or not MTH.GetModuleCharSavedVariables then
return
end
local moduleStore = MTH:GetModuleCharSavedVariables("zhunter")
if type(ZHunterMod_Saved) == "table" then
if MTH_CharSavedVariables and MTH_CharSavedVariables.modules then
MTH_CharSavedVariables.modules.zhunter = ZHunterMod_Saved
end
elseif type(moduleStore) == "table" then
ZHunterMod_Saved = moduleStore
-- Bind the ZHunterMod_Saved global as a runtime alias into the canonical
-- nested store (MTH_CharSavedVariables.modules.zhunter). GetSavedRoot does the
-- adoption + aliasing; there is no second on-disk copy anymore.
if type(MTH_ZH_GetSavedRoot) == "function" then
MTH_ZH_GetSavedRoot()
end
end