This commit is contained in:
DuvelCorp
2026-03-31 21:25:35 +02:00
parent 680ddcba8a
commit 51d4c785fa
55 changed files with 5157 additions and 2530 deletions
+46 -4
View File
@@ -25,16 +25,21 @@ local function AntiDaze_RegisterCombatEvents()
if not AntiDazeFrame then
return
end
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE")
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE")
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PARTY_DAMAGE")
if MTH and MTH.nampower then
AntiDazeFrame:RegisterEvent("DEBUFF_ADDED_SELF")
else
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE")
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE")
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PARTY_DAMAGE")
end
end
local function AntiDaze_UnregisterCombatEvents()
if not AntiDazeFrame then
return
end
AntiDazeFrame:UnregisterEvent("DEBUFF_ADDED_SELF")
AntiDazeFrame:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE")
AntiDazeFrame:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE")
AntiDazeFrame:UnregisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
@@ -87,10 +92,31 @@ local function AntiDaze_ResolvePlayerFromMessage(msg)
return nil
end
local AntiDaze_DazeSpellIdCache = {} -- [spellId] = true/false
local function AntiDaze_IsDazeSpellId(spellId)
local cached = AntiDaze_DazeSpellIdCache[spellId]
if cached ~= nil then return cached end
if type(GetSpellRecField) ~= "function" then return false end
local name = GetSpellRecField(spellId, "name")
if name and (name == "Daze" or name == "Dazed") then
AntiDaze_DazeSpellIdCache[spellId] = true
return true
end
AntiDaze_DazeSpellIdCache[spellId] = false
return false
end
local function AntiDaze_CancelRelevantAspect(isSelfDazed)
local packName = _G["ZHUNTER_ASPECT_PACK"] or "Aspect of the Pack"
local cheetahName = _G["ZHUNTER_ASPECT_CHEETAH"] or "Aspect of the Cheetah"
-- Always use GetPlayerBuff + tooltip scan for cancellation.
-- The aura-array slot index from GetUnitField does NOT map 1:1
-- to the sequential buff index that CancelPlayerBuff expects
-- (empty slots create gaps), so we cannot use the NamPower
-- array for the cancel call. NamPower is still used for daze
-- DETECTION via DEBUFF_ADDED_SELF in the event handler.
for i = 0, 32 do
if GetPlayerBuff(i, "HELPFUL") < 0 then
break
@@ -118,6 +144,12 @@ if AntiDazeFrame then
AntiDazeFrame:RegisterEvent("VARIABLES_LOADED")
AntiDazeFrame:SetScript("OnEvent", function()
if event == "VARIABLES_LOADED" then
-- Class gate: only hunters use AntiDaze.
if MTH and MTH.IsClassGateBlocked and MTH:IsClassGateBlocked() then
AntiDazeFrame:UnregisterAllEvents()
AntiDazeFrame:SetScript("OnEvent", nil)
return
end
if AntiDaze_IsEnabled() then
AntiDaze_RegisterCombatEvents()
else
@@ -126,6 +158,16 @@ if AntiDazeFrame then
return
end
-- NamPower path: DEBUFF_ADDED_SELF fires with spellId in arg3
if event == "DEBUFF_ADDED_SELF" then
local spellId = tonumber(arg3)
if spellId and AntiDaze_IsDazeSpellId(spellId) then
AntiDaze_CancelRelevantAspect(true)
end
return
end
-- Legacy path: parse chat messages
local player = AntiDaze_ResolvePlayerFromMessage(arg1)
if player then
local you = _G["ZHUNTER_YOU"] or "You"
+6
View File
@@ -267,6 +267,12 @@ local function AutoStrip_OnEvent()
.. " playerInCombat=" .. tostring((type(UnitAffectingCombat) == "function" and UnitAffectingCombat("player")) and 1 or 0))
end
if event == "VARIABLES_LOADED" then
-- Class gate: only hunters use AutoStrip.
if MTH and MTH.IsClassGateBlocked and MTH:IsClassGateBlocked() then
AutoStrip_Frame:UnregisterAllEvents()
AutoStrip_Frame:SetScript("OnEvent", nil)
return
end
local saved = AutoStrip_GetSaved()
AutoStrip_Trace("VARIABLES_LOADED saved.autostrip=" .. tostring(saved["autostrip"] and 1 or 0)
.. " saved.display=" .. tostring(saved["display"] and 1 or 0))
+5 -540
View File
@@ -1,543 +1,8 @@
------------------------------------------------------
-- MetaHunt: Beast Lore Scanner
-- TWoW 1.18.1 — "Nightmares of Ursol"
-- MetaHunt: Beast Lore Scanner (removed)
------------------------------------------------------
-- Fires when the player channels Beast Lore on a target.
-- Captures all available data and stores it in
-- MTH_SavedVariables.beastLoreScan.entries (account-wide).
--
-- Dedup rule: skips recording if the same creature name
-- already exists in this zone within 30 yards of current
-- player position (uses ds-minimap-sizes for yard scale).
-- A chat message is always printed either way.
-- This file previously contained a feature that recorded
-- unknown beasts to SavedVariables when scanned with
-- Beast Lore. The feature has been removed; the static
-- beast database (ds-beasts) is now the sole data source.
------------------------------------------------------
local SPELL_BEAST_LORE = "Beast Lore"
local DEDUP_YARDS = 30
local FALLBACK_DEDUP_PCT = 0.015 -- ~1.5% of map width if zone size unknown
-- ── Tooltip probe ────────────────────────────────────────────
local MTH_BLS_Probe = nil
local function MTH_BLS_EnsureProbe()
if not MTH_BLS_Probe then
MTH_BLS_Probe = CreateFrame("GameTooltip", "MTH_BLSProbe", UIParent, "GameTooltipTemplate")
MTH_BLS_Probe:SetOwner(UIParent, "ANCHOR_NONE")
end
return MTH_BLS_Probe
end
-- Read left/right text lines from the probe (Lua 5.0 compatible: getglobal)
local function MTH_BLS_GetProbeLines()
local lines = {}
for i = 1, 24 do
local L = getglobal("MTH_BLSProbeTextLeft" .. i)
local R = getglobal("MTH_BLSProbeTextRight" .. i)
local lText = L and L:GetText() or nil
local rText = R and R:GetText() or nil
if (not lText or lText == "") and (not rText or rText == "") then
break
end
table.insert(lines, { left = lText or "", right = rText or "" })
end
return lines
end
-- Parse Beast Lore tooltip lines into structured fields.
-- Uses string.find with captures (Lua 5.0, no string.match).
local function MTH_BLS_ParseTooltip(lines)
local data = {
tameable = nil, -- true / false / nil (unknown)
diet = nil,
abilities = nil,
armor = nil,
health = nil,
minDmg = nil,
maxDmg = nil,
}
for _, line in ipairs(lines) do
local L = line.left or ""
local R = line.right or ""
-- Tameability ──────────────────────────────────────────
if string.find(L, "Cannot Be Tamed", 1, true) or string.find(R, "Cannot Be Tamed", 1, true) then
data.tameable = false
elseif string.find(L, "Can Be Tamed", 1, true) or string.find(R, "Can Be Tamed", 1, true)
or string.find(L, "Tameable", 1, true) or string.find(R, "Tameable", 1, true) then
data.tameable = true
end
-- Diet ─────────────────────────────────────────────────
if not data.diet then
local _, _, v = string.find(L, "^Diet:%s*(.*)")
if not v then _, _, v = string.find(R, "^Diet:%s*(.*)") end
if v and v ~= "" then data.diet = v end
end
-- Tamed Abilities ──────────────────────────────────────
if not data.abilities then
local _, _, v = string.find(L, "^Tamed Abilities:%s+(.*)")
if not v then _, _, v = string.find(R, "^Tamed Abilities:%s+(.*)") end
if v and v ~= "" then data.abilities = v end
end
-- Damage range ("107 - 133 Damage") ────────────────────
if not data.minDmg then
local _, _, lo, hi = string.find(L, "(%d+) %- (%d+) Damage")
if not lo then _, _, lo, hi = string.find(R, "(%d+) %- (%d+) Damage") end
if lo and hi then
data.minDmg = tonumber(lo)
data.maxDmg = tonumber(hi)
end
end
-- Armor ────────────────────────────────────────────────
if not data.armor then
local _, _, v = string.find(L, "Armor:%s*(%d+)")
if not v then _, _, v = string.find(R, "Armor:%s*(%d+)") end
if v then data.armor = tonumber(v) end
end
-- Health ───────────────────────────────────────────────
if not data.health then
local _, _, v = string.find(L, "Health:%s*(%d+)")
if not v then _, _, v = string.find(R, "Health:%s*(%d+)") end
if v then data.health = tonumber(v) end
end
end
return data
end
-- ── Location helpers ─────────────────────────────────────────
-- pfQuest AreaTable ID → WMA ID bridge (GetCurrentMapAreaID returns pfQ IDs)
local MTH_PFQ_TO_WMA = {
[1]=27,[3]=17,[4]=19,[8]=38,[10]=34,[11]=40,[12]=30,[14]=4,[15]=141,[16]=181,
[17]=11,[25]=519,[28]=22,[33]=37,[36]=15,[38]=35,[40]=39,[41]=32,[44]=36,
[45]=16,[46]=29,[47]=26,[51]=28,[85]=20,[130]=21,[139]=23,[141]=41,[148]=42,
[209]=650,[215]=9,[267]=24,[331]=43,[357]=121,[361]=182,[400]=61,[405]=101,
[406]=81,[408]=505,[409]=504,[440]=161,[490]=201,[491]=641,[493]=241,
[616]=501,[618]=281,[717]=611,[718]=516,[719]=609,[721]=518,[722]=639,
[796]=520,[876]=500,[1176]=605,[1337]=521,[1377]=261,[1477]=607,[1497]=382,
[1519]=301,[1537]=341,[1581]=514,[1583]=629,[1584]=623,[1637]=321,[1638]=362,
[1657]=381,[1769]=700,[1941]=523,[1977]=619,[2017]=652,[2040]=509,[2057]=648,
[2100]=517,[2159]=627,[2257]=659,[2366]=663,[2437]=603,[2557]=689,[2597]=401,
[2677]=635,[2717]=617,[3277]=443,[3358]=461,[3428]=654,[3429]=625,[3456]=667,
[3457]=682,[3478]=522,[4012]=502,[5023]=503,[5024]=511,[5053]=680,[5077]=670,
[5086]=674,[5087]=676,[5097]=678,[5103]=672,[5121]=507,[5130]=601,[5135]=644,
[5136]=643,[5138]=637,[5147]=655,[5148]=668,[5153]=645,[5163]=646,[5179]=510,
[5204]=662,[5208]=665,[5225]=513,[5536]=512,[5557]=683,[5561]=685,[5581]=686,
[5601]=691,[5602]=684,[5628]=693,[5640]=701,[5641]=698,[5642]=699,[5722]=702,
[5723]=657,[5731]=705,[5734]=704,
}
local function MTH_BLS_GetZoneId()
if type(SetMapToCurrentZone) == "function" then
pcall(SetMapToCurrentZone)
end
if type(GetCurrentMapAreaID) == "function" then
local ok, id = pcall(GetCurrentMapAreaID)
if ok and tonumber(id) and tonumber(id) > 0 then
local pfq = tonumber(id)
return MTH_PFQ_TO_WMA[pfq] or pfq
end
end
return 0
end
local function MTH_BLS_GetPlayerCoords()
if type(SetMapToCurrentZone) == "function" then
pcall(SetMapToCurrentZone)
end
if type(GetPlayerMapPosition) ~= "function" then return nil, nil end
local ok, x, y = pcall(GetPlayerMapPosition, "player")
if ok and x and y and (x ~= 0 or y ~= 0) then return x, y end
return nil, nil
end
-- Returns true if a beast with this name is already in the static MTH_DS_Beasts database.
-- We skip SavedVariables recording for beasts already catalogued in the DB.
local function MTH_BLS_IsInStaticDB(name)
if type(MTH_DS_Beasts) ~= "table" or not name or name == "" then return false end
for _, row in pairs(MTH_DS_Beasts) do
if type(row) == "table" and row.name == name then
return true
end
end
return false
end
-- ── Storage ──────────────────────────────────────────────────
local function MTH_BLS_EnsureStore()
if type(MTH_SavedVariables) ~= "table" then
MTH_SavedVariables = {}
end
if type(MTH_SavedVariables.beastLoreScan) ~= "table" then
MTH_SavedVariables.beastLoreScan = {}
end
if type(MTH_SavedVariables.beastLoreScan.entries) ~= "table" then
MTH_SavedVariables.beastLoreScan.entries = {}
end
return MTH_SavedVariables.beastLoreScan
end
-- ── Dedup ────────────────────────────────────────────────────
-- Returns the existing entry for a same-named beast in the same zone,
-- plus whether any of its recorded coords is within DEDUP_YARDS of (x, y).
local function MTH_BLS_FindEntry(name, zoneId, x, y)
local store = MTH_BLS_EnsureStore()
-- Normalize: treat 0 as unknown (entries are stored with nil for unknown zones)
if not zoneId or zoneId == 0 then zoneId = nil end
local mapW, mapH = nil, nil
if type(MTH_DS_MinimapSizes) == "table" and zoneId then
local sz = MTH_DS_MinimapSizes[zoneId]
if type(sz) == "table" then
mapW = tonumber(sz[1])
mapH = tonumber(sz[2])
end
end
local function IsNearby(ex, ey)
if not ex or not ey or not x or not y then return false end
local dx = ex - x
local dy = ey - y
if mapW and mapH then
return math.sqrt((dx * mapW)^2 + (dy * mapH)^2) < DEDUP_YARDS
else
-- No map size data: use a generous fallback (~5% of map width)
return math.sqrt(dx * dx + dy * dy) < 0.05
end
end
local entries = store.entries
for i = 1, table.getn(entries) do
local e = entries[i]
-- Match by name and zone. When either side has no zone, match on name alone
-- (coords will still be checked below; cross-zone false positives are
-- unlikely given players don't share coordinate spaces across zones).
local zoneMatch = (e.zoneId == zoneId) or (not e.zoneId) or (not zoneId)
if type(e) == "table"
and e.name == name
and zoneMatch
then
-- Check all recorded coords for this beast.
if type(e.coords) == "table" then
for j = 1, table.getn(e.coords) do
local c = e.coords[j]
if type(c) == "table" and IsNearby(c.x, c.y) then
return e, true -- entry found, nearby coord found
end
end
elseif IsNearby(e.x, e.y) then
-- Legacy single-coord entry
return e, true
end
return e, false -- entry found, but no coord nearby
end
end
return nil, false -- no entry for this beast in this zone
end
-- ── Main scan ────────────────────────────────────────────────
-- Per-session cooldown: prevents re-recording the same beast in the
-- same zone within SCAN_COOLDOWN seconds, as a safety net on top of
-- the coordinate-based dedup.
local MTH_BLS_ScanCooldowns = {}
local SCAN_COOLDOWN = 120 -- 2 minutes
local function MTH_BLS_Scan()
-- Basic target guards
if type(UnitExists) == "function" and not UnitExists("target") then return end
if type(UnitIsPlayer) == "function" and UnitIsPlayer("target") then return end
local name = (type(UnitName) == "function") and UnitName("target") or nil
if not name or name == "" then return end
-- ── Unit data ───────────────────────────────────────────
local level = (type(UnitLevel) == "function") and UnitLevel("target") or nil
local family = (type(UnitCreatureFamily) == "function") and UnitCreatureFamily("target") or nil
local healthMax = (type(UnitHealthMax) == "function") and UnitHealthMax("target") or nil
local minDmg, maxDmg, mainSpeed = nil, nil, nil
if type(UnitDamage) == "function" then
local ok, a, b, _, _, spd = pcall(UnitDamage, "target")
if ok then minDmg, maxDmg, mainSpeed = a, b, spd end
end
if not mainSpeed and type(UnitAttackSpeed) == "function" then
local ok, spd = pcall(UnitAttackSpeed, "target")
if ok then mainSpeed = spd end
end
local guid = nil
if type(UnitGUID) == "function" then
local ok, g = pcall(UnitGUID, "target")
if ok and g then guid = g end
end
-- ── Location ────────────────────────────────────────────
local zoneId = MTH_BLS_GetZoneId()
local x, y = MTH_BLS_GetPlayerCoords()
local zone = (type(GetZoneText) == "function") and GetZoneText() or ""
local subzone = (type(GetSubZoneText) == "function") and GetSubZoneText() or ""
-- Session cooldown: hard block on re-scanning the same beast in same zone
local cooldownKey = tostring(name) .. "|" .. tostring(zone)
local lastScan = MTH_BLS_ScanCooldowns[cooldownKey]
if lastScan and (GetTime() - lastScan) < SCAN_COOLDOWN then
MTH:Log("|cff888888Beast Lore: " .. tostring(name) .. " scanned recently — skipped|r")
return
end
-- ── Tooltip probe ───────────────────────────────────────
local probe = MTH_BLS_EnsureProbe()
probe:ClearLines()
local probeOk = pcall(function() probe:SetUnit("target") end)
local parsed = { tameable = nil, diet = nil, abilities = nil, armor = nil, health = nil }
if probeOk then
local lines = MTH_BLS_GetProbeLines()
parsed = MTH_BLS_ParseTooltip(lines)
end
-- Tooltip values override unit API (Beast Lore reveals more accurate data)
if parsed.minDmg then minDmg = parsed.minDmg end
if parsed.maxDmg then maxDmg = parsed.maxDmg end
if parsed.health then healthMax = parsed.health end
-- Fallback tameability: family present = tameable
if parsed.tameable == nil then
parsed.tameable = (family and family ~= "") and true or false
end
-- ── Dedup / upsert ──────────────────────────────────────
local function R3(v)
if type(v) ~= "number" then return nil end
return math.floor(v * 1000 + 0.5) / 1000
end
local function R2(v)
if type(v) ~= "number" then return nil end
return math.floor(v * 100 + 0.5) / 100
end
local rx, ry = R3(x), R3(y)
-- Mark scanned now (before storing) so any rapid re-trigger is blocked
MTH_BLS_ScanCooldowns[cooldownKey] = GetTime()
local existing, nearbyFound = MTH_BLS_FindEntry(name, zoneId, x, y)
if nearbyFound then
-- Already recorded at this location — do nothing.
MTH:Log("|cff888888Beast Lore: " .. tostring(name)
.. " already recorded nearby — skipped|r")
return
end
local store = MTH_BLS_EnsureStore()
if existing then
-- Known beast, new location — add coord to its list.
if type(existing.coords) ~= "table" then
-- Migrate legacy single coord into coords list.
existing.coords = {}
if existing.x or existing.y then
table.insert(existing.coords, { x = existing.x, y = existing.y, timestamp = existing.timestamp })
end
existing.x = nil
existing.y = nil
end
table.insert(existing.coords, { x = rx, y = ry, timestamp = (type(time) == "function") and time() or nil })
existing.lastUpdated = (type(time) == "function") and time() or nil
-- Update abilities/data if newly revealed by this cast.
if parsed.abilities and parsed.abilities ~= "" then existing.abilities = parsed.abilities end
local coordStr = (rx and ry)
and string.format(" (%.1f, %.1f)", rx * 100, ry * 100)
or ""
local abilitiesStr = (existing.abilities and existing.abilities ~= "")
and (" |cff88ddff[" .. existing.abilities .. "]|r")
or ""
MTH:Log("Beast Lore: |cffffaa00" .. tostring(name) .. "|r lv" .. (existing.level and tostring(existing.level) or "?")
.. " — new coord added" .. coordStr
.. abilitiesStr
.. " |cff888888[#" .. tostring(table.getn(store.entries)) .. " +" .. tostring(table.getn(existing.coords)) .. " loc]|r")
return
end
-- Skip recording if this beast is already in the static database.
if MTH_BLS_IsInStaticDB(name) then
MTH:Log("|cff888888Beast Lore: " .. tostring(name) .. " is already in the MetaHunt database — skipped|r")
return
end
-- New beast entirely.
local entry = {
name = name,
guid = guid,
level = level,
family = family,
tameable = parsed.tameable,
minDmg = minDmg and math.floor(minDmg + 0.5) or nil,
maxDmg = maxDmg and math.floor(maxDmg + 0.5) or nil,
attackSpeed = mainSpeed and R2(mainSpeed) or nil,
armor = parsed.armor or nil,
health = healthMax or nil,
diet = parsed.diet or nil,
abilities = parsed.abilities or nil,
coords = { { x = rx, y = ry, timestamp = (type(time) == "function") and time() or nil } },
zoneId = (zoneId and zoneId > 0) and zoneId or nil,
zone = (zone and zone ~= "") and zone or nil,
subzone = (subzone and subzone ~= "") and subzone or nil,
timestamp = (type(time) == "function") and time() or nil,
}
table.insert(store.entries, entry)
-- ── Chat confirmation ────────────────────────────────────
local tameStr = parsed.tameable
and "|cff33ff55Tameable|r"
or "|cffff4444Cannot be Tamed|r"
local famStr = (family and family ~= "")
and ("|cffffcc00" .. family .. "|r")
or "|cff888888unknown family|r"
local lvlStr = level and tostring(level) or "?"
local coordStr = (x and y)
and string.format(" (%.1f, %.1f)", x * 100, y * 100)
or ""
local zoneStr = (zone and zone ~= "") and (", " .. zone .. coordStr) or coordStr
local idx = table.getn(store.entries)
local abilitiesStr = ""
if parsed.abilities and parsed.abilities ~= "" then
abilitiesStr = " |cff88ddff[" .. parsed.abilities .. "]|r"
end
MTH:Log("|cff00ff88YOU FOUND AND RECORDED A NEW BEAST!|r |cffffaa00" .. tostring(name) .. "|r lv" .. lvlStr
.. " — " .. tameStr .. " • " .. famStr
.. zoneStr
.. abilitiesStr
.. " |cff888888[#" .. tostring(idx) .. "]|r")
end
-- ── Detection: event-gated tooltip poll ─────────────────────
-- SPELLCAST_CHANNEL_* events do not reliably fire in TurtleWoW 1.18.1.
-- Strategy: spellcast events open a short window (MTH_BLS_WindowEnd).
-- OnUpdate only runs during that window, checking the target tooltip
-- for Beast Lore-specific lines ("Diet:", "Tamed Abilities:").
-- If no event fires at all, the window stays closed and nothing runs.
local MTH_BLS_Frame = CreateFrame("Frame", "MTH_BeastLoreScanFrame")
local MTH_BLS_PollAccum = 0
local MTH_BLS_WindowEnd = 0 -- GetTime() deadline; 0 = window closed
local MTH_BLS_PollLast = nil -- target name already scanned this window
local WINDOW_DURATION = 6 -- seconds to probe after Beast Lore cast
local function MTH_BLS_OpenWindow()
MTH_BLS_WindowEnd = GetTime() + WINDOW_DURATION
MTH_BLS_PollLast = nil
end
-- [DISABLED] Beast Lore scan activation — not needed while full beast DB is shipped.
-- Re-enable by removing the `if false then` / `end` wrapper below.
if false then
-- Register all known spellcast event variants.
MTH_BLS_Frame:RegisterEvent("SPELLCAST_CHANNEL_START")
MTH_BLS_Frame:RegisterEvent("SPELLCAST_CHANNEL_STOP")
MTH_BLS_Frame:RegisterEvent("SPELLCAST_START")
MTH_BLS_Frame:RegisterEvent("SPELLCAST_STOP")
MTH_BLS_Frame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
MTH_BLS_Frame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
MTH_BLS_Frame:RegisterEvent("UNIT_SPELLCAST_START")
MTH_BLS_Frame:RegisterEvent("UNIT_SPELLCAST_STOP")
MTH_BLS_Frame:RegisterEvent("PLAYER_TARGET_CHANGED")
MTH_BLS_Frame:SetScript("OnEvent", function()
local evt = event or ""
if evt == "PLAYER_TARGET_CHANGED" then
-- New target: reset so re-targeting after a cast works.
MTH_BLS_PollLast = nil
return
end
-- Determine spell name from event args.
local isUnitEvt = string.sub(evt, 1, 15) == "UNIT_SPELLCAST_"
local spellName
if isUnitEvt then
if (arg1 or "") ~= "player" then return end
spellName = arg2 or ""
else
spellName = arg1 or ""
end
if string.find(spellName, SPELL_BEAST_LORE, 1, true) then
MTH_BLS_OpenWindow()
end
end)
MTH_BLS_Frame:SetScript("OnUpdate", function()
MTH_BLS_PollAccum = MTH_BLS_PollAccum + (arg1 or 0)
if MTH_BLS_PollAccum < 2.0 then return end
MTH_BLS_PollAccum = 0
if not (type(UnitExists) == "function" and UnitExists("target")) then return end
if type(UnitIsPlayer) == "function" and UnitIsPlayer("target") then return end
local name = (type(UnitName) == "function") and UnitName("target") or nil
if not name or name == "" then return end
if name == MTH_BLS_PollLast then return end
-- Probe the target tooltip for Beast Lore-specific lines.
local probe = MTH_BLS_EnsureProbe()
probe:ClearLines()
local probeOk = pcall(function() probe:SetUnit("target") end)
if not probeOk then return end
local hasData = false
for i = 1, 20 do
local L = getglobal("MTH_BLSProbeTextLeft" .. i)
if not L then break end
local txt = (L.GetText and L:GetText()) or ""
if string.find(txt, "Diet:", 1, true)
or string.find(txt, "Tamed Abilities:", 1, true)
or string.find(txt, "Cannot Be Tamed", 1, true)
then
hasData = true
break
end
end
if not hasData then return end
-- Beast Lore data confirmed — record and close window.
MTH_BLS_PollLast = name
MTH_BLS_WindowEnd = 0
local ok, err = pcall(MTH_BLS_Scan)
if not ok and MTH and MTH.Print then
MTH:Print("[BeastLore Scan] error: " .. tostring(err), "error")
end
end)
end -- [DISABLED] if false
-- ── Public accessors ─────────────────────────────────────────
-- Returns the first saved entry whose name matches (case-insensitive),
-- or nil if none found.
function MTH_BLS_FindSavedBeastByName(name)
local store = MTH_BLS_EnsureStore()
if not store or type(store.entries) ~= "table" then return nil end
local wantLower = string.lower(name or "")
if wantLower == "" then return nil end
for i = 1, table.getn(store.entries) do
local e = store.entries[i]
if type(e) == "table" and string.lower(e.name or "") == wantLower then
return e
end
end
return nil
end
+1 -1
View File
@@ -1,5 +1,5 @@
MTH_CONST = MTH_CONST or {}
MTH_CONST.version = MTH_CONST.version or "1.4.1"
MTH_CONST.version = MTH_CONST.version or "1.5.0"
MTH_CONST.WEAPON_TYPES = {
BOWS = "Bows",
+1 -1
View File
@@ -167,7 +167,7 @@ local function MTH_FEED_IndexBagFoods()
return true
end
local function MTH_FEED_HasPetFeedBuff()
function MTH_FEED_HasPetFeedBuff()
if type(UnitBuff) ~= "function" then
return false
end
+12 -2
View File
@@ -1,5 +1,5 @@
MTH = MTH or {
version = "1.4.1",
version = "1.5.0",
name = "MetaHunt",
modules = {},
config = {},
@@ -1311,7 +1311,17 @@ function MTH:AnnounceLoadComplete()
moduleList = table.concat(enabled, ", ")
end
self:Print("Check ignition: OK! Version " .. tostring(self.version or "unknown") .. " loaded.")
local namPowerStatus = ""
if type(GetNampowerVersion) == "function" then
local maj, min = GetNampowerVersion()
maj = tonumber(maj) or 0
min = tonumber(min) or 0
if maj > 2 or (maj == 2 and min >= 40) then
namPowerStatus = " | NamPower detected"
self.nampower = true
end
end
self:Print("Check Ignition.... OK! Version " .. tostring(self.version or "unknown") .. " loaded." .. namPowerStatus)
if self:IsMessageEnabled("initModulesLoaded", false) then
self:Print("Modules enabled : " .. moduleList .. ".")
end
+425 -103
View File
@@ -4,6 +4,7 @@ end
local MTH_PETS_SCHEMA_VERSION = 2
local MTH_PETS_CORE_HOOK_BOUNDARY_KEY = "core-pet-rename-hook"
local MTH_ST_FULL_DEBUG_TRACE = false
MTH_PETS_TRACE_CONSISTENCY = false
local MTH_PetLifecycleEventFrame = nil
@@ -24,8 +25,8 @@ local MTH_ST_Profile = {
runawayMaxMs = 0,
}
local MTH_ST_Trace = {
enabled = false,
verbose = false,
enabled = MTH_ST_FULL_DEBUG_TRACE and true or false,
verbose = MTH_ST_FULL_DEBUG_TRACE and true or false,
windowStart = 0,
windowSeconds = 5,
eventCount = 0,
@@ -36,7 +37,7 @@ local MTH_ST_Trace = {
local MTH_ST_Diag = {
lines = {},
maxLines = 200,
chatEcho = false,
chatEcho = MTH_ST_FULL_DEBUG_TRACE and true or false,
}
local MTH_ST_DiagCopyFrame = nil
local MTH_ST_PrintStableDiag
@@ -268,21 +269,27 @@ local function MTH_ST_EnsureDiagCopyFrame()
end
function MTH_CommandStableDiag(mode)
local lowerMode = string.lower(tostring(mode or "on"))
MTH_ST_Diag.lines = {}
MTH_ST_Diag.chatEcho = false
MTH_ST_Diag.chatEcho = (lowerMode ~= "off") and true or false
if MTH and MTH.Print then
MTH:Print("Stable diagnostics are disabled.")
MTH:Print("Stable diagnostics chat echo " .. (MTH_ST_Diag.chatEcho and "enabled" or "disabled") .. ".", "debug")
end
return false
return MTH_ST_Diag.chatEcho
end
function MTH_CommandStableTrace(mode)
MTH_ST_Trace.enabled = false
MTH_ST_Trace.verbose = false
if MTH and MTH.Print then
MTH:Print("Stable trace is disabled.")
local lowerMode = string.lower(tostring(mode or "verbose"))
MTH_ST_Trace.enabled = (lowerMode ~= "off") and true or false
MTH_ST_Trace.verbose = (lowerMode == "verbose" or lowerMode == "on") and MTH_ST_Trace.enabled or false
if MTH_ST_Trace.enabled then
MTH_ST_TraceResetCounters()
end
return false
if MTH and MTH.Print then
MTH:Print("Stable trace " .. (MTH_ST_Trace.enabled and "enabled" or "disabled")
.. " (verbose=" .. tostring(MTH_ST_Trace.verbose) .. ").", "debug")
end
return MTH_ST_Trace.enabled
end
local function MTH_ST_ProfileNow()
@@ -418,18 +425,29 @@ local function MTH_ST_ProfileFlushIfDue(force)
end
function MTH_CommandStableProfile(mode)
MTH_ST_Profile.enabled = false
if MTH and MTH.Print then
MTH:Print("Stable profiler is disabled.")
local lowerMode = string.lower(tostring(mode or "off"))
MTH_ST_Profile.enabled = (lowerMode ~= "off") and true or false
if MTH_ST_Profile.enabled then
MTH_ST_ProfileResetCounters()
end
return false
if MTH and MTH.Print then
MTH:Print("Stable profiler " .. (MTH_ST_Profile.enabled and "enabled" or "disabled") .. ".", "debug")
end
return MTH_ST_Profile.enabled
end
local function MTH_PETS_LogConsistency(line)
return
if not MTH_PETS_TRACE_CONSISTENCY then
return
end
if MTH and MTH.Print then
MTH:Print("[PETS] " .. tostring(line or ""), "debug")
end
end
local function MTH_PETS_LogTame(_) end
local function MTH_PETS_LogTame(_)
return
end
local function MTH_PETS_FormatRowConsistency(row)
if type(row) ~= "table" then
@@ -814,6 +832,30 @@ local function MTH_PETS_NormalizeSystemMessageKey(rawMessage)
return message
end
local function MTH_PETS_GetUnitGuid(unitToken)
local token = tostring(unitToken or "")
if token == "" then
return nil
end
local getUnitGuid = (type(getglobal) == "function" and getglobal("GetUnitGUID")) or (_G and _G["GetUnitGUID"])
if type(getUnitGuid) == "function" then
local ok, guid = pcall(getUnitGuid, token)
if ok and type(guid) == "string" and guid ~= "" then
return guid
end
end
if type(UnitGUID) == "function" then
local ok, guid = pcall(UnitGUID, token)
if ok and type(guid) == "string" and guid ~= "" then
return guid
end
end
return nil
end
local function MTH_PETS_IsRunawaySystemMessage(rawMessage)
local message = MTH_PETS_NormalizeSystemMessage(rawMessage)
if message == "" then
@@ -979,7 +1021,7 @@ local function MTH_PETS_CaptureTargetSnapshot()
if MTH_PETS_IsPlaceholderPetName(targetName) then
return nil
end
local targetGuid = (type(UnitGUID) == "function") and UnitGUID("target") or nil
local targetGuid = MTH_PETS_GetUnitGuid("target")
local beastId = MTH_PETS_ParseCreatureIdFromGuid(targetGuid)
local targetFamily = (type(UnitCreatureFamily) == "function") and UnitCreatureFamily("target") or nil
local targetLevel = (type(UnitLevel) == "function") and UnitLevel("target") or nil
@@ -1193,6 +1235,22 @@ local function MTH_PETS_ResolveTameBeastIdForRow(row, snapshot)
return nil
end
local function MTH_PETS_IsTameConfirmationSource(source)
local sourceText = tostring(source or "")
return sourceText == "unit-pet-acquire" or sourceText == "refresh-current-pet"
end
local function MTH_PETS_ResolveConfirmedTameBeastId(source, snapshot)
if not MTH_PETS_IsTameConfirmationSource(source) then
return nil
end
local resolved = MTH_PETS_GetPendingTameBeastId(snapshot)
if resolved and resolved > 0 then
return resolved
end
return nil
end
local function MTH_PETS_MakeSignature(name, family, level)
local cleanName = MTH_PETS_SafeLower(MTH_PETS_NormalizeText(name))
local cleanFamily = MTH_PETS_SafeLower(MTH_PETS_NormalizeText(family))
@@ -1200,7 +1258,7 @@ local function MTH_PETS_MakeSignature(name, family, level)
return cleanName .. "|" .. cleanFamily .. "|" .. tostring(numericLevel)
end
local function MTH_PETS_RowHasTameRecord(row)
local function MTH_PETS_RowHasAnyTameMetadata(row)
if type(row) ~= "table" then
return false
end
@@ -1219,11 +1277,33 @@ local function MTH_PETS_RowHasTameRecord(row)
return false
end
function MTH_PETS_HasVerifiedTameRecord(row)
if type(row) ~= "table" then
return false
end
if row.tameVerified == true then
return true
end
local legacyTameContext = type(row.tameContext) == "table" and row.tameContext or nil
if legacyTameContext and (legacyTameContext.name or legacyTameContext.zone or legacyTameContext.timestamp) then
return true
end
if type(row.events) == "table" then
for i = 1, table.getn(row.events) do
local ev = row.events[i]
if type(ev) == "table" and tostring(ev.type or "") == "pet-tamed" then
return true
end
end
end
return false
end
local function MTH_PETS_BackfillTameMetadataFromEvents(row)
if type(row) ~= "table" then
return false
end
if MTH_PETS_RowHasTameRecord(row) then
if MTH_PETS_HasVerifiedTameRecord(row) then
return false
end
if type(row.events) ~= "table" then
@@ -1234,7 +1314,7 @@ local function MTH_PETS_BackfillTameMetadataFromEvents(row)
local stableFirstSeenAt = tonumber(row.stableFirstSeenAt) or tonumber(row.stabledAt) or 0
for i = 1, table.getn(row.events) do
local ev = row.events[i]
if type(ev) == "table" and tostring(ev.type or "") == "pet-acquired" then
if type(ev) == "table" and tostring(ev.type or "") == "pet-tamed" then
local evAt = tonumber(ev.at) or 0
local timelineOk = true
if stableFirstSeenAt > 0 and evAt > 0 and evAt > stableFirstSeenAt then
@@ -1302,12 +1382,204 @@ local function MTH_PETS_BackfillTameMetadataFromEvents(row)
if changed then
row.tameRecorded = true
row.tameVerified = true
row.lastUpdated = time()
end
return changed
end
local function MTH_PETS_ParseNumericPetId(petId)
local _, _, numericId = string.find(tostring(petId or ""), "^pet%-(%d+)$")
return tonumber(numericId)
end
local function MTH_PETS_GetActiveRowRepairScore(pets, petId, row)
if type(row) ~= "table" then
return -1, 0
end
local score = 0
local resolvedPetId = tostring(petId or "")
local petStore = type(pets) == "table" and pets.petStore or nil
if resolvedPetId ~= "" and tostring((pets and pets.currentPetId) or "") == resolvedPetId then
score = score + 1000000
end
if resolvedPetId ~= "" and type(petStore) == "table" and tostring(petStore.activeCurrentId or "") == resolvedPetId then
score = score + 900000
end
if tonumber(row.stableSlot) and tonumber(row.stableSlot) > 0 then
score = score + 500000
end
if type(row.guid) == "string" and row.guid ~= "" then
score = score + 100000
end
if row.tameVerified == true then
score = score + 50000
end
if row.tameRecorded == true then
score = score + 10000
end
score = score + (tonumber(row.lastSeen) or tonumber(row.lastUpdated) or 0)
return score, tonumber(MTH_PETS_ParseNumericPetId(resolvedPetId)) or 0
end
local function MTH_PETS_MergeActiveRows(pets, winnerId, loserId)
if type(pets) ~= "table" or type(pets.petStore) ~= "table" then
return false
end
local petStore = pets.petStore
local winnerRow = type(petStore.activeById) == "table" and petStore.activeById[winnerId] or nil
local loserRow = type(petStore.activeById) == "table" and petStore.activeById[loserId] or nil
if type(winnerRow) ~= "table" or type(loserRow) ~= "table" or tostring(winnerId) == tostring(loserId) then
return false
end
local copyIfMissingFields = {
"guid", "signature", "beastId", "name", "family", "level",
"happiness", "loyalty", "loyaltyLevel", "xp", "xpMax", "xpPercent",
"origin", "originContext", "previousName", "stableRaw", "stableInfo",
"zone", "subZone", "x", "y", "hunterLevel", "lastSource",
"tameHunterLevel", "tameZone", "tameSubZone", "tameX", "tameY",
"tameBeastId", "tameContext",
}
for i = 1, table.getn(copyIfMissingFields) do
local field = copyIfMissingFields[i]
local winnerValue = winnerRow[field]
if winnerValue == nil or winnerValue == "" then
local loserValue = loserRow[field]
if loserValue ~= nil and loserValue ~= "" then
winnerRow[field] = loserValue
end
end
end
if type(winnerRow.petSpellbook) ~= "table" and type(loserRow.petSpellbook) == "table" then
winnerRow.petSpellbook = loserRow.petSpellbook
winnerRow.lastPetSpellbookSkipAt = winnerRow.lastPetSpellbookSkipAt or loserRow.lastPetSpellbookSkipAt
winnerRow.lastPetSpellbookSkipSource = winnerRow.lastPetSpellbookSkipSource or loserRow.lastPetSpellbookSkipSource
winnerRow.lastPetSpellbookSkipReason = winnerRow.lastPetSpellbookSkipReason or loserRow.lastPetSpellbookSkipReason
end
if type(winnerRow.abilities) ~= "table" then
winnerRow.abilities = {}
end
if type(loserRow.abilities) == "table" then
for token, ability in pairs(loserRow.abilities) do
if winnerRow.abilities[token] == nil then
winnerRow.abilities[token] = ability
end
end
end
if type(winnerRow.events) ~= "table" then
winnerRow.events = {}
end
if type(loserRow.events) == "table" then
for i = 1, table.getn(loserRow.events) do
table.insert(winnerRow.events, loserRow.events[i])
end
end
local winnerCreatedAt = tonumber(winnerRow.createdAt) or 0
local loserCreatedAt = tonumber(loserRow.createdAt) or 0
if winnerCreatedAt <= 0 or (loserCreatedAt > 0 and loserCreatedAt < winnerCreatedAt) then
winnerRow.createdAt = loserCreatedAt > 0 and loserCreatedAt or winnerRow.createdAt
end
local winnerFirstSeen = tonumber(winnerRow.firstSeen) or 0
local loserFirstSeen = tonumber(loserRow.firstSeen) or 0
if winnerFirstSeen <= 0 or (loserFirstSeen > 0 and loserFirstSeen < winnerFirstSeen) then
winnerRow.firstSeen = loserFirstSeen > 0 and loserFirstSeen or winnerRow.firstSeen
end
local winnerLastSeen = tonumber(winnerRow.lastSeen) or 0
local loserLastSeen = tonumber(loserRow.lastSeen) or 0
if loserLastSeen > winnerLastSeen then
winnerRow.lastSeen = loserLastSeen
end
local winnerLastUpdated = tonumber(winnerRow.lastUpdated) or 0
local loserLastUpdated = tonumber(loserRow.lastUpdated) or 0
if loserLastUpdated > winnerLastUpdated then
winnerRow.lastUpdated = loserLastUpdated
end
if winnerRow.tameVerified ~= true and loserRow.tameVerified == true then
winnerRow.tameVerified = true
winnerRow.tameRecorded = true
winnerRow.tamedAt = winnerRow.tamedAt or loserRow.tamedAt
winnerRow.tameHunterLevel = winnerRow.tameHunterLevel or loserRow.tameHunterLevel
winnerRow.tameZone = winnerRow.tameZone or loserRow.tameZone
winnerRow.tameSubZone = winnerRow.tameSubZone or loserRow.tameSubZone
winnerRow.tameX = winnerRow.tameX or loserRow.tameX
winnerRow.tameY = winnerRow.tameY or loserRow.tameY
winnerRow.tameBeastId = winnerRow.tameBeastId or loserRow.tameBeastId
end
if (winnerRow.stableSlot == nil or tonumber(winnerRow.stableSlot) == nil or tonumber(winnerRow.stableSlot) <= 0)
and tonumber(loserRow.stableSlot) and tonumber(loserRow.stableSlot) > 0 then
winnerRow.stableSlot = tonumber(loserRow.stableSlot)
end
if type(petStore.stableSlotIndex) == "table" then
for slotNumber, mappedPetId in pairs(petStore.stableSlotIndex) do
if tostring(mappedPetId or "") == tostring(loserId) then
petStore.stableSlotIndex[slotNumber] = tostring(winnerId)
end
end
end
if tostring(petStore.activeCurrentId or "") == tostring(loserId) then
petStore.activeCurrentId = tostring(winnerId)
end
if tostring(pets.currentPetId or "") == tostring(loserId) then
pets.currentPetId = tostring(winnerId)
end
if type(pets.currentPet) == "table" and tostring(pets.currentPet.id or "") == tostring(loserId) then
pets.currentPet.id = tostring(winnerId)
end
petStore.activeById[loserId] = nil
return true
end
local function MTH_PETS_RepairActiveRowsByGuid(pets)
if type(pets) ~= "table" or type(pets.petStore) ~= "table" or type(pets.petStore.activeById) ~= "table" then
return
end
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] = {}
end
table.insert(groupedByGuid[guid], tostring(petId))
end
end
for _, petIds in pairs(groupedByGuid) do
if type(petIds) == "table" and table.getn(petIds) > 1 then
table.sort(petIds, function(a, b)
local rowA = pets.petStore.activeById[a]
local rowB = pets.petStore.activeById[b]
local scoreA, numericA = MTH_PETS_GetActiveRowRepairScore(pets, a, rowA)
local scoreB, numericB = MTH_PETS_GetActiveRowRepairScore(pets, b, rowB)
if scoreA ~= scoreB then
return scoreA > scoreB
end
if numericA ~= numericB then
return numericA < numericB
end
return tostring(a) < tostring(b)
end)
local winnerId = petIds[1]
for i = 2, table.getn(petIds) do
local loserId = petIds[i]
if pets.petStore.activeById[winnerId] and pets.petStore.activeById[loserId] then
MTH_PETS_MergeActiveRows(pets, winnerId, loserId)
end
end
end
end
end
local function MTH_PETS_EnsurePetStoreSchema(pets)
if type(pets.petStore) ~= "table" then
pets.petStore = {}
@@ -1334,9 +1606,36 @@ local function MTH_PETS_EnsurePetStoreSchema(pets)
if petStore.activeCurrentId == nil then
petStore.activeCurrentId = nil
end
MTH_PETS_RepairActiveRowsByGuid(pets)
petStore.signatureIndex = {}
petStore.guidIndex = {}
local highestPetNumericId = 0
for _, row in pairs(petStore.activeById) do
for petId, row in pairs(petStore.activeById) do
if type(row) == "table" then
local resolvedPetId = tostring(petId)
if row.id == nil or tostring(row.id) == "" then
row.id = resolvedPetId
end
local numericPetId = MTH_PETS_ParseNumericPetId(resolvedPetId)
if numericPetId and numericPetId > highestPetNumericId then
highestPetNumericId = numericPetId
end
if row.tameVerified ~= true then
local legacyTameContext = type(row.tameContext) == "table" and row.tameContext or nil
if legacyTameContext and (legacyTameContext.name or legacyTameContext.zone or legacyTameContext.timestamp) then
row.tameVerified = true
end
end
if type(row.signature) == "string" and row.signature ~= "" then
if type(petStore.signatureIndex[row.signature]) ~= "table" then
petStore.signatureIndex[row.signature] = {}
end
table.insert(petStore.signatureIndex[row.signature], resolvedPetId)
end
if type(row.guid) == "string" and row.guid ~= "" then
petStore.guidIndex[row.guid] = resolvedPetId
end
if row.loyaltyLevel == nil and type(row.loyalty) == "string" then
row.loyaltyLevel = MTH_PETS_ParseLoyaltyLevelFromText(row.loyalty)
end
@@ -1351,9 +1650,21 @@ local function MTH_PETS_EnsurePetStoreSchema(pets)
end
row.stableInfo.loyalty = nil
end
if row.tameVerified ~= true and MTH_PETS_RowHasAnyTameMetadata(row) then
row.tameRecorded = nil
end
MTH_PETS_BackfillTameMetadataFromEvents(row)
end
end
if highestPetNumericId >= tonumber(petStore.nextId) then
petStore.nextId = highestPetNumericId + 1
end
if petStore.activeCurrentId ~= nil and petStore.activeById[tostring(petStore.activeCurrentId)] == nil then
petStore.activeCurrentId = nil
end
if pets.currentPetId ~= nil and petStore.activeById[tostring(pets.currentPetId)] == nil then
pets.currentPetId = petStore.activeCurrentId
end
end
local function MTH_PETS_EnsureCurrentPetSchema(pets)
@@ -1727,10 +2038,10 @@ local function MTH_PETS_MakeSnapshotFromLivePet()
xpPercent = math.floor((xp / xpMax) * 1000 + 0.5) / 10
end
end
local petGuid = (type(UnitGUID) == "function") and UnitGUID("pet") or nil
local petGuid = MTH_PETS_GetUnitGuid("pet")
local beastId = MTH_PETS_ParseCreatureIdFromGuid(petGuid)
if beastId == nil and type(UnitGUID) == "function" then
beastId = MTH_PETS_ParseCreatureIdFromGuid(UnitGUID("target"))
if beastId == nil then
beastId = MTH_PETS_ParseCreatureIdFromGuid(MTH_PETS_GetUnitGuid("target"))
end
local signature = MTH_PETS_MakeSignature(petName, petFamily, petLevel)
return {
@@ -1759,15 +2070,28 @@ local function MTH_PETS_SelectPetIdBySnapshot(store, snapshot, previousPetId)
if previousRow.guid and snapshot.guid and previousRow.guid == snapshot.guid then
return previousPetId
end
if previousRow.signature and snapshot.signature and previousRow.signature == snapshot.signature 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]
end
if snapshot.guid and snapshot.guid ~= "" 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)
return tostring(petId)
end
end
end
if previousPetId and store.activeById[previousPetId] then
local previousRow = store.activeById[previousPetId]
if previousRow.signature and snapshot.signature and previousRow.signature == snapshot.signature then
return previousPetId
end
end
if snapshot.signature and snapshot.signature ~= "" then
local list = store.signatureIndex[snapshot.signature]
if type(list) == "table" then
@@ -1831,21 +2155,17 @@ local function MTH_PETS_ApplySnapshotToRow(row, snapshot, source, context)
end
row.guid = snapshot.guid or row.guid
row.beastId = snapshot.beastId or row.beastId
local hasPendingTame = type(MTH_PETS_LastTameAttempt) == "table"
if hasPendingTame and (row.tameBeastId == nil or tonumber(row.tameBeastId) == nil) then
local resolvedTameBeastId = MTH_PETS_ResolveTameBeastIdForRow(row, snapshot)
if resolvedTameBeastId and resolvedTameBeastId > 0 then
row.tameBeastId = resolvedTameBeastId
end
end
if hasPendingTame and (source == "unit-pet-acquire" or source == "refresh-current-pet") and type(context) == "table" then
local confirmedTameBeastId = MTH_PETS_ResolveConfirmedTameBeastId(source, snapshot)
if confirmedTameBeastId and type(context) == "table" then
row.tameRecorded = true
row.tameVerified = true
row.tamedAt = row.tamedAt or context.timestamp
row.tameHunterLevel = row.tameHunterLevel or context.hunterLevel
row.tameZone = row.tameZone or context.zone
row.tameSubZone = row.tameSubZone or context.subZone
row.tameX = row.tameX or context.x
row.tameY = row.tameY or context.y
row.tameBeastId = row.tameBeastId or confirmedTameBeastId
end
row.signature = snapshot.signature
row.lastUpdated = now
@@ -1870,30 +2190,6 @@ local function MTH_PETS_ApplySnapshotToRow(row, snapshot, source, context)
row.hunterLevel = context.hunterLevel or row.hunterLevel
end
end
if not isStableScan and type(context) == "table" then
local hasTameRecord = (row.tameRecorded == true)
or tonumber(row.tamedAt) ~= nil
or tonumber(row.tameBeastId) ~= nil
or (type(row.tameZone) == "string" and row.tameZone ~= "")
if not hasTameRecord and hasPendingTame then
local sourceText = tostring(source or "")
if sourceText == "unit-pet-acquire" or sourceText == "refresh-current-pet" then
row.tameRecorded = true
row.tamedAt = row.tamedAt or context.timestamp
row.tameHunterLevel = row.tameHunterLevel or context.hunterLevel
row.tameZone = row.tameZone or context.zone
row.tameSubZone = row.tameSubZone or context.subZone
row.tameX = row.tameX or context.x
row.tameY = row.tameY or context.y
if tonumber(row.tameBeastId) == nil then
local resolvedTameBeastId = MTH_PETS_ResolveTameBeastIdForRow(row, snapshot)
if resolvedTameBeastId and resolvedTameBeastId > 0 then
row.tameBeastId = resolvedTameBeastId
end
end
end
end
end
if type(row.abilities) ~= "table" then
row.abilities = {}
end
@@ -1962,18 +2258,6 @@ local function MTH_PETS_UpsertActivePetFromSnapshot(pets, snapshot, source, opti
if created then
row.origin = source or "unknown"
row.originContext = context
local hasPendingTame = type(MTH_PETS_LastTameAttempt) == "table"
local shouldRecordTame = hasPendingTame and (source == "unit-pet-acquire" or source == "refresh-current-pet")
if shouldRecordTame then
row.tamedAt = context.timestamp
row.tameHunterLevel = context.hunterLevel
row.tameZone = context.zone
row.tameSubZone = context.subZone
row.tameX = context.x
row.tameY = context.y
row.tameBeastId = snapshot.beastId
row.tameRecorded = true
end
if type(options) == "table" and options.stableSlot then
row.origin = "stable-slot"
end
@@ -2131,6 +2415,45 @@ local function MTH_PETS_RecordStableSlot(pets, slot, raw1, raw2, raw3, raw4, raw
pets.updatedAt = time()
end
local function MTH_PETS_ReconcileStableSlots(pets, slotCount)
if type(pets) ~= "table" or type(pets.petStore) ~= "table" then
return
end
local petStore = pets.petStore
if type(petStore.activeById) ~= "table" or type(petStore.stableSlotIndex) ~= "table" then
return
end
local authoritativeByPetId = {}
for slotNumber, petId in pairs(petStore.stableSlotIndex) do
local numericSlot = tonumber(slotNumber)
local mappedPetId = tostring(petId or "")
if numericSlot and numericSlot > 0 and mappedPetId ~= "" and type(petStore.activeById[mappedPetId]) == "table" then
if not slotCount or numericSlot <= slotCount then
authoritativeByPetId[mappedPetId] = numericSlot
else
petStore.stableSlotIndex[slotNumber] = nil
end
else
petStore.stableSlotIndex[slotNumber] = nil
end
end
for petId, row in pairs(petStore.activeById) do
if type(row) == "table" then
local resolvedPetId = tostring(petId)
local authoritativeSlot = authoritativeByPetId[resolvedPetId]
if authoritativeSlot then
row.stableSlot = authoritativeSlot
elseif tonumber(row.stableSlot) and tonumber(row.stableSlot) > 0 then
row.stableSlot = nil
end
end
end
pets.updatedAt = time()
end
function MTH_PETS_RecordCurrentPetLearnedAbility(abilityName, rankNumber, source)
local pets = MTH_PETS_GetRootStore()
if type(pets) ~= "table" then
@@ -2661,13 +2984,16 @@ function MTH_PETS_RefreshCurrentPet()
row.events = {}
end
local eventType = "pet-acquired"
if not (created and (not hadPetBeforeRefresh)) then
if cp.id ~= nil and previousCurrentId ~= nil and cp.id ~= previousCurrentId then
eventType = "pet-swapped"
local eventType = nil
local confirmedTameBeastId = MTH_PETS_ResolveConfirmedTameBeastId("refresh-current-pet", snapshot)
if created and (not hadPetBeforeRefresh) then
if confirmedTameBeastId then
eventType = "pet-tamed"
else
eventType = nil -- routine update, don't record
eventType = "pet-acquired"
end
elseif cp.id ~= nil and previousCurrentId ~= nil and cp.id ~= previousCurrentId then
eventType = "pet-swapped"
end
local eventContext = nil
@@ -2681,26 +3007,6 @@ function MTH_PETS_RefreshCurrentPet()
})
end
if eventType == "pet-acquired" then
local hasTameRecord = (row.tameRecorded == true)
or (tonumber(row.tamedAt) and tonumber(row.tamedAt) > 0)
or (type(row.tameZone) == "string" and row.tameZone ~= "")
if not hasTameRecord and type(eventContext) == "table" then
row.tameRecorded = true
row.tamedAt = row.tamedAt or eventContext.timestamp or now
row.tameHunterLevel = row.tameHunterLevel or eventContext.hunterLevel
row.tameZone = row.tameZone or eventContext.zone
row.tameSubZone = row.tameSubZone or eventContext.subZone
row.tameX = row.tameX or eventContext.x
row.tameY = row.tameY or eventContext.y
if tonumber(row.tameBeastId) == nil then
local resolvedTameBeastId = MTH_PETS_ResolveTameBeastIdForRow(row, snapshot)
if resolvedTameBeastId and resolvedTameBeastId > 0 then
row.tameBeastId = resolvedTameBeastId
end
end
end
end
end
if hadPendingTame and cp.id and cp.id ~= previousCurrentId then
@@ -3117,6 +3423,10 @@ function MTH_ST_Scan(reason)
if currentPetId and type(petsRoot.petStore) == "table" and type(petsRoot.petStore.activeById) == "table" then
local currentRow = petsRoot.petStore.activeById[currentPetId]
if type(currentRow) == "table" then
MTH_ST_PrintStableDiag("scan currentRow petId=" .. tostring(currentPetId)
.. " guid='" .. tostring(currentRow.guid or "") .. "'"
.. " signature='" .. tostring(currentRow.signature or "") .. "'"
.. " beastId='" .. tostring(currentRow.beastId or "") .. "'")
local currentIcon = tostring(c1 or "")
if currentIcon ~= "" then
currentRow.icon = currentIcon
@@ -3141,6 +3451,8 @@ function MTH_ST_Scan(reason)
MTH_ST_PrintStableDiag("scan slots " .. table.concat(slotDiagParts, " | "))
end
MTH_PETS_ReconcileStableSlots(petsRoot, slotCount)
MTH_PETS_MarkStableVisited(petsRoot, tostring(reason or "stable-scan"))
MTH_PETS_RefreshCurrentPet()
@@ -3308,7 +3620,11 @@ local function MTH_PETS_HandleUnitPetTransition(source)
if type(row.events) ~= "table" then
row.events = {}
end
local confirmedTameBeastId = MTH_PETS_ResolveConfirmedTameBeastId(transitionSource, snapshot)
local eventType = (not hadPet) and "pet-acquired" or "pet-updated"
if not hadPet and confirmedTameBeastId then
eventType = "pet-tamed"
end
-- Only record lifecycle events (acquire, rename, abandon), not routine updates
if eventType ~= "pet-updated" then
table.insert(row.events, {
@@ -3777,8 +4093,20 @@ function MTH_ST_HandleSpellcastEvent(evt, eventArg1, eventArg2)
local isUnitSpellcast = (evt == "UNIT_SPELLCAST_START" or evt == "UNIT_SPELLCAST_STOP"
or evt == "UNIT_SPELLCAST_FAILED" or evt == "UNIT_SPELLCAST_INTERRUPTED"
or evt == "UNIT_SPELLCAST_CHANNEL_START" or evt == "UNIT_SPELLCAST_CHANNEL_STOP")
local isSpellcast = isUnitSpellcast
or evt == "SPELLCAST_START" or evt == "SPELLCAST_STOP"
or evt == "SPELLCAST_FAILED" or evt == "SPELLCAST_INTERRUPTED"
or evt == "SPELLCAST_CHANNEL_START" or evt == "SPELLCAST_CHANNEL_STOP"
if not isSpellcast then
return false
end
local castSpellName = eventArg1
local hasPendingTame = type(MTH_PETS_LastTameAttempt) == "table"
MTH_PETS_LogTame("Spellcast evt=" .. tostring(evt)
.. " arg1='" .. tostring(eventArg1 or "") .. "'"
.. " arg2='" .. tostring(eventArg2 or "") .. "'"
.. " unitCast=" .. tostring(isUnitSpellcast)
.. " pending=" .. tostring(hasPendingTame))
if isUnitSpellcast then
if eventArg1 ~= "player" then
return true
@@ -4115,12 +4443,6 @@ local function MTH_ST_OnEvent(frame, evt, eventArg1, eventArg2)
if isStableUiEvt then
stableDiagStart = MTH_ST_ProfileNow()
MTH_ST_PrintStableDiag("evt=" .. tostring(evt) .. " phase=begin")
if MTH_ST_Trace and not MTH_ST_Trace.enabled then
MTH_ST_Trace.enabled = true
MTH_ST_Trace.verbose = false
MTH_ST_TraceResetCounters()
MTH_ST_PrintStableDiag("trace=auto-enabled")
end
end
MTH_ST_DispatchEvent(frame, evt, eventArg1, eventArg2)
+119 -9
View File
@@ -312,7 +312,7 @@ function SlashCmdList.MTH(msg, editbox)
local lowerMsg = string.lower(msg)
local _, _, lowerCmd, lowerArg = string.find(lowerMsg, "^(%S+)%s*(.-)%s*$")
if msg == "" then
MTH:Print("Available: /mth options, /mth book")
MTH:Print("Available: /mth options, /mth book, /mth food")
elseif lowerMsg == "err" or lowerMsg == "errors" or lowerMsg == "debug" then
if MTH_DebugFrame and MTH_DebugFrame.Toggle then
MTH_DebugFrame:Toggle()
@@ -422,18 +422,128 @@ function SlashCmdList.MTH(msg, editbox)
MTH:Print("|cffaaffaaArmed.|r Open any vendor, trainer, or stable master to capture their ID.")
end
end
elseif lowerCmd == "bls" then
if lowerArg == "reset" then
if type(MTH_SavedVariables) == "table" then
MTH_SavedVariables.beastLoreScan = { entries = {} }
elseif lowerCmd == "food" then
local sub = lowerArg
local function MTH_FoodItemLabel(itemId)
local id = tonumber(itemId)
if not id then return tostring(itemId) end
if type(GetItemInfo) == "function" then
local name = GetItemInfo(id)
if name and name ~= "" then
return "|cffffcc00" .. name .. "|r |cffaaaaaa(" .. id .. ")|r"
end
end
MTH:Print("|cffff4444Beast Lore scan data wiped.|r All recorded beasts cleared.")
else
MTH:Print("Beast Lore Scan: |cffffff00/mth bls reset|r — wipe all recorded beast data")
return "|cffaaaaaa" .. tostring(id) .. "|r"
end
if sub == "" or sub == "status" then
-- Show quarantine and exception state
local feedStore = type(MTH_CharSavedVariables) == "table" and MTH_CharSavedVariables.feedTracking or nil
local quarantine = feedStore and feedStore.fomQuarantine and feedStore.fomQuarantine.byFamily or nil
local exceptions = feedStore and feedStore.exceptions and feedStore.exceptions.byItemId or nil
MTH:Print("|cffffff00=== Feed-O-Matic Status ===|r")
-- Quarantine
local qCount = 0
if type(quarantine) == "table" then
for family, byItem in pairs(quarantine) do
if type(byItem) == "table" then
for itemId, _ in pairs(byItem) do
qCount = qCount + 1
MTH:Print(" |cffff8800Quarantine:|r " .. MTH_FoodItemLabel(itemId) .. " (family=" .. tostring(family) .. ")")
end
end
end
end
if qCount == 0 then MTH:Print(" Quarantine: |cff00ff00empty|r") end
-- Core exceptions
local eCount = 0
if type(exceptions) == "table" then
for itemId, _ in pairs(exceptions) do
if type(itemId) == "number" then -- skip string-key dupes
eCount = eCount + 1
MTH:Print(" |cffff4444Exception block:|r " .. MTH_FoodItemLabel(itemId))
end
end
end
if eCount == 0 then MTH:Print(" Exception blocks: |cff00ff00none|r") end
-- FOM_RemovedFoods
local rCount = 0
if type(FOM_RemovedFoods) == "table" then
for diet, list in pairs(FOM_RemovedFoods) do
if type(list) == "table" then
for _, itemId in ipairs(list) do
rCount = rCount + 1
MTH:Print(" |cffaaaaaaRemoved food:|r " .. MTH_FoodItemLabel(itemId) .. " (diet=" .. tostring(diet) .. ")")
end
end
end
end
if rCount == 0 then MTH:Print(" Removed foods: |cff00ff00none|r") end
MTH:Print("|cffaaaaaa/mth food unban <id>|r — unban one item |cffaaaaaa/mth food reset|r — clear all bans")
elseif string.find(sub, "^unban%s") or sub == "unban" then
local _, _, itemArg = string.find(lowerArg, "^unban%s+(%S+)")
local itemId = tonumber(itemArg)
if not itemId then
MTH:Print("|cffff4444Usage:|r /mth food unban <itemId>")
MTH:Print("Example: /mth food unban 8952")
elseif type(FOM_ClearItemBans) ~= "function" then
MTH:Print("|cffff4444Error:|r Feed-O-Matic not loaded.")
else
local label = MTH_FoodItemLabel(itemId)
FOM_ClearItemBans(itemId)
MTH:Print("|cff00ff00Unbanned " .. label .. "|r — removed from quarantine, exceptions and removed-foods list.")
end
elseif sub == "reset" then
local feedStore = type(MTH_CharSavedVariables) == "table" and MTH_CharSavedVariables.feedTracking or nil
if feedStore then
if feedStore.fomQuarantine then feedStore.fomQuarantine = { byFamily = {} } end
if feedStore.exceptions then feedStore.exceptions = { byItemId = {} } end
end
if type(FOM_RemovedFoods) == "table" then
for diet, _ in pairs(FOM_RemovedFoods) do FOM_RemovedFoods[diet] = {} end
end
if type(FOM_FEED_SCAN_CACHE) ~= "nil" then FOM_FEED_SCAN_CACHE = nil end
MTH:Print("|cff00ff00All Feed-O-Matic bans and quarantines cleared.|r")
else
MTH:Print("|cffffff00/mth food|r — show quarantine/ban status")
MTH:Print("|cffffff00/mth food unban <itemId>|r — remove one item from all bans")
MTH:Print("|cffffff00/mth food reset|r — clear ALL bans and quarantines")
end
elseif lowerCmd == "zbar" and lowerArg == "debug" then
-- Dump the live state of every zBar button to chat so we can see which one is the ghost.
local p = function(s) MTH:Print("[zBar] " .. tostring(s), "debug") end
local root = (type(MTH_ZH_GetSavedRoot) == "function") and MTH_ZH_GetSavedRoot() or ZHunterMod_Saved
local zs = root and root["_zbar"]
p("=== zBar debug ===")
p("enabled=" .. tostring(zs and zs.enabled) .. " anchor=" .. tostring(getglobal("MTH_ZBar_Anchor") ~= nil))
p("direction=" .. tostring(zs and zs.direction) .. " childexpand=" .. tostring(zs and zs.childexpand) .. " childarrange=" .. tostring(zs and zs.childarrange))
local allButtons = {
"zButtonAspect","zButtonAmmo","zButtonTrack","zButtonTrap",
"zButtonPet","zButtonRanged","zButtonMounts","zButtonCompanions","zButtonToys",
}
for _, bname in ipairs(allButtons) do
local btn = getglobal(bname)
local shown = btn and btn.IsShown and btn:IsShown()
local bsaved = root and type(root[bname]) == "table" and root[bname]
local enabled = bsaved and bsaved["enabled"]
local zvis = zs and zs.visible and zs.visible[bname]
local x, y, point = "?", "?", "?"
if btn and btn.GetPoint then
local pt, _, _, bx, by = btn:GetPoint()
point = tostring(pt)
x = tostring(bx and math.floor(bx + 0.5) or "?")
y = tostring(by and math.floor(by + 0.5) or "?")
end
local bmtname = btn and tostring(btn.name) or "nil"
p(bname .. ": shown=" .. tostring(shown) ..
" enabled=" .. tostring(enabled) ..
" zbarVisible=" .. tostring(zvis) ..
" btn.name=" .. bmtname ..
" anchor=(" .. point .. " " .. x .. "," .. y .. ")")
end
p("=== end ===")
else
MTH:Print("Unknown command: " .. tostring(msg))
MTH:Print("Available: /mth options, /mth book, /mth npcid, /mth bls reset")
MTH:Print("Available: /mth options, /mth book, /mth npcid, /mth food")
end
end
+157 -23
View File
@@ -2,13 +2,16 @@ if type(MTH_HUNTERBOOK_TABS) ~= "table" then MTH_HUNTERBOOK_TABS = {} end
MTH_HUNTERBOOK_TABS.families = {
headerLabel = "Families",
columnLabels = { "Family", "Named", "Coords", "Abilities", "Diet" },
columnLabels = { "Family", "Named", "Coords", "Abilities", "Diet", "HP", "dmg", "Arm" },
columnLayout = {
{ x = 10, width = 106, align = "LEFT" },
{ x = 122, width = 52, align = "LEFT" },
{ x = 176, width = 52, align = "LEFT" },
{ x = 232, width = 364, align = "LEFT" },
{ x = 532, width = 112, align = "LEFT" },
{ x = 10, width = 100, align = "LEFT" },
{ x = 114, width = 38, align = "LEFT" },
{ x = 154, width = 38, align = "LEFT" },
{ x = 196, width = 364, align = "LEFT" },
{ x = 496, width = 102, align = "LEFT" },
{ x = 602, width = 30, align = "CENTER" },
{ x = 636, width = 36, align = "CENTER" },
{ x = 674, width = 38, align = "CENTER" },
},
}
@@ -26,6 +29,28 @@ local function MTH_BOOKTAB_FamiliesTrim(value)
return text
end
local function MTH_BOOKTAB_FormatFamilyStat(value)
local numeric = tonumber(value)
if not numeric then
return "-"
end
local colorPrefix = "|cFF4DA6FF"
if numeric < 1 then
colorPrefix = "|cFFFF4040"
elseif numeric > 1 then
colorPrefix = "|cFF40FF40"
end
local text = nil
if MTH_BOOK_STATE and MTH_BOOK_STATE.familyStatsPercent == true then
text = tostring(math.floor((numeric * 100) + 0.5)) .. "%"
return colorPrefix .. text .. "|r"
end
text = string.format("%.3f", numeric)
text = string.gsub(text, "(%..-)0+$", "%1")
text = string.gsub(text, "%.$", "")
return colorPrefix .. text .. "|r"
end
local function MTH_BOOKTAB_ShouldDisplayAllDiet(familyName)
local token = MTH_BOOKTAB_FamiliesSafeLower(MTH_BOOKTAB_FamiliesTrim(familyName))
return token == "bears" or token == "boars"
@@ -51,12 +76,41 @@ end
function MTH_BOOKTAB_BuildFamiliesRows()
local results = {}
local families = MTH_DS_Families
local beasts = MTH_DS_Beasts
if type(families) ~= "table" then
return results
end
local abilityCounts = {}
local canonicalMap = {}
local familyCounts = {}
if type(beasts) == "table" then
for _, beastRow in pairs(beasts) do
if type(beastRow) == "table" then
local familyName = MTH_BOOKTAB_FamiliesTrim(beastRow.family)
if familyName ~= "" then
local counts = familyCounts[familyName]
if not counts then
counts = { named = 0, coords = 0 }
familyCounts[familyName] = counts
end
counts.named = counts.named + 1
if type(beastRow.coords) == "table" then
for coordIndex = 1, table.getn(beastRow.coords) do
local coord = beastRow.coords[coordIndex]
if type(coord) == "table" then
if coord[1] ~= nil and coord[2] ~= nil then
counts.coords = counts.coords + 1
end
end
end
end
end
end
end
end
for _, familyRow in pairs(families) do
if type(familyRow) == "table" and type(familyRow.abilities) == "table" then
@@ -82,6 +136,7 @@ function MTH_BOOKTAB_BuildFamiliesRows()
for familyName, familyRow in pairs(families) do
if type(familyRow) == "table" then
local beastCounts = familyCounts[familyName] or nil
MTH_BOOKTAB_FamiliesTrace("BUILD " .. tostring(familyName) .. " icon=" .. tostring(familyRow.icon))
local abilities = {}
local seen = {}
@@ -171,6 +226,10 @@ function MTH_BOOKTAB_BuildFamiliesRows()
end)
local dietText = ""
local stats = type(familyRow.stats) == "table" and familyRow.stats or nil
local healthStat = stats and tonumber(stats.health) or nil
local damageStat = stats and tonumber(stats.damage) or nil
local armorStat = stats and tonumber(stats.armor) or nil
if MTH_BOOKTAB_ShouldDisplayAllDiet(familyName) then
dietText = "ALL"
else
@@ -178,7 +237,8 @@ function MTH_BOOKTAB_BuildFamiliesRows()
if type(familyRow.food) == "table" then
for i = 1, table.getn(familyRow.food) do
local food = MTH_BOOKTAB_FamiliesTrim(familyRow.food[i])
if food ~= "" then
local token = MTH_BOOKTAB_FamiliesSafeLower(food)
if food ~= "" and token ~= "raw fish" and token ~= "raw meat" then
table.insert(dietItems, string.upper(string.sub(food, 1, 1)) .. string.sub(food, 2))
end
end
@@ -186,16 +246,22 @@ function MTH_BOOKTAB_BuildFamiliesRows()
return MTH_BOOKTAB_FamiliesSafeLower(a) < MTH_BOOKTAB_FamiliesSafeLower(b)
end)
end
dietText = table.concat(dietItems, ", ")
dietText = table.concat(dietItems, "|")
end
table.insert(results, {
family = tostring(familyName),
icon = familyRow.icon or nil,
named = tonumber(familyRow.named) or 0,
coords = tonumber(familyRow.coords) or 0,
named = beastCounts and (tonumber(beastCounts.named) or 0) or 0,
coords = beastCounts and (tonumber(beastCounts.coords) or 0) or 0,
abilities = abilities,
dietText = dietText,
healthStat = healthStat,
healthText = MTH_BOOKTAB_FormatFamilyStat(healthStat),
damageStat = damageStat,
damageText = MTH_BOOKTAB_FormatFamilyStat(damageStat),
armorStat = armorStat,
armorText = MTH_BOOKTAB_FormatFamilyStat(armorStat),
})
end
end
@@ -253,31 +319,76 @@ function MTH_BOOKTAB_EnsureFamiliesUI()
ui.frame:SetPoint("BOTTOMRIGHT", listParent, "BOTTOMRIGHT", -6, 6)
ui.frame:Hide()
local statsText = getglobal("MTH_BOOK_StatsText")
local statsParent = statsText and statsText:GetParent() or nil
if statsParent then
ui.percentCheckbox = CreateFrame("CheckButton", "MTH_BOOK_FamiliesPercentCheckbox", statsParent, "UICheckButtonTemplate")
ui.percentCheckbox:SetFrameStrata(statsParent:GetFrameStrata())
ui.percentCheckbox:SetFrameLevel((statsParent:GetFrameLevel() or 0) + 20)
ui.percentCheckbox:ClearAllPoints()
if statsText then
ui.percentCheckbox:SetPoint("LEFT", statsText, "RIGHT", 260, 0)
else
ui.percentCheckbox:SetPoint("TOPLEFT", statsParent, "TOPLEFT", 538, -526)
end
ui.percentCheckbox.label = ui.percentCheckbox:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.percentCheckbox.label:SetPoint("LEFT", ui.percentCheckbox, "RIGHT", 4, 0)
ui.percentCheckbox.label:SetTextColor(1.00, 0.82, 0.00)
ui.percentCheckbox.label:SetText("Show modifiers in %")
ui.percentCheckbox:SetScript("OnClick", function()
if not this then return end
MTH_BOOK_STATE.familyStatsPercent = this:GetChecked() == 1
MTH_BOOKTAB_RenderFamiliesList()
end)
ui.percentCheckbox:Hide()
end
ui.headerFamily = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerFamily:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 4, -2)
ui.headerFamily:SetTextColor(1.00, 0.82, 0.00)
ui.headerFamily:SetText("Family")
ui.headerNamed = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerNamed:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 122, -2)
ui.headerNamed:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 114, -2)
ui.headerNamed:SetTextColor(1.00, 0.82, 0.00)
ui.headerNamed:SetText("Named")
ui.headerCoords = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerCoords:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 176, -2)
ui.headerCoords:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 154, -2)
ui.headerCoords:SetTextColor(1.00, 0.82, 0.00)
ui.headerCoords:SetText("Coords")
ui.headerAbilities = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerAbilities:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 232, -2)
ui.headerAbilities:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 196, -2)
ui.headerAbilities:SetTextColor(1.00, 0.82, 0.00)
ui.headerAbilities:SetText("Abilities")
ui.headerDiet = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerDiet:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 532, -2)
ui.headerDiet:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 496, -2)
ui.headerDiet:SetTextColor(1.00, 0.82, 0.00)
ui.headerDiet:SetText("Diet")
ui.headerHP = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerHP:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 602, -2)
ui.headerHP:SetWidth(30)
ui.headerHP:SetJustifyH("CENTER")
ui.headerHP:SetTextColor(1.00, 0.82, 0.00)
ui.headerHP:SetText("HP")
ui.headerDmg = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerDmg:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 636, -2)
ui.headerDmg:SetWidth(36)
ui.headerDmg:SetJustifyH("CENTER")
ui.headerDmg:SetTextColor(1.00, 0.82, 0.00)
ui.headerDmg:SetText("dmg")
ui.headerArm = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerArm:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 674, -2)
ui.headerArm:SetWidth(38)
ui.headerArm:SetJustifyH("CENTER")
ui.headerArm:SetTextColor(1.00, 0.82, 0.00)
ui.headerArm:SetText("Arm")
ui.rows = {}
for i = 1, 28 do
local row = CreateFrame("Frame", nil, ui.frame)
@@ -298,24 +409,39 @@ function MTH_BOOKTAB_EnsureFamiliesUI()
row.family = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.family:SetPoint("LEFT", row, "LEFT", 24, 0)
row.family:SetWidth(88)
row.family:SetWidth(82)
row.family:SetJustifyH("LEFT")
row.named = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.named:SetPoint("LEFT", row, "LEFT", 122, 0)
row.named:SetWidth(52)
row.named:SetPoint("LEFT", row, "LEFT", 114, 0)
row.named:SetWidth(38)
row.named:SetJustifyH("LEFT")
row.coords = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.coords:SetPoint("LEFT", row, "LEFT", 176, 0)
row.coords:SetWidth(52)
row.coords:SetPoint("LEFT", row, "LEFT", 154, 0)
row.coords:SetWidth(38)
row.coords:SetJustifyH("LEFT")
row.diet = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.diet:SetPoint("LEFT", row, "LEFT", 532, 0)
row.diet:SetWidth(112)
row.diet:SetPoint("LEFT", row, "LEFT", 496, 0)
row.diet:SetWidth(102)
row.diet:SetJustifyH("LEFT")
row.hp = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.hp:SetPoint("LEFT", row, "LEFT", 602, 0)
row.hp:SetWidth(30)
row.hp:SetJustifyH("CENTER")
row.dmg = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.dmg:SetPoint("LEFT", row, "LEFT", 636, 0)
row.dmg:SetWidth(36)
row.dmg:SetJustifyH("CENTER")
row.arm = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.arm:SetPoint("LEFT", row, "LEFT", 674, 0)
row.arm:SetWidth(38)
row.arm:SetJustifyH("CENTER")
row.abilityButtons = {}
for b = 1, 16 do
local button = CreateFrame("Button", nil, row)
@@ -379,9 +505,14 @@ function MTH_BOOKTAB_SetFamiliesUIVisible(visible)
if detailParent then detailParent:Hide() end
if detailText then detailText:Hide() end
if openMapButton then openMapButton:Hide() end
if ui.percentCheckbox then
ui.percentCheckbox:SetChecked(MTH_BOOK_STATE and MTH_BOOK_STATE.familyStatsPercent == true and 1 or nil)
ui.percentCheckbox:Show()
end
ui.frame:Show()
else
ui.frame:Hide()
if ui.percentCheckbox then ui.percentCheckbox:Hide() end
if listParent then listParent:Show() end
if detailParent then detailParent:Show() end
if detailText then detailText:Show() end
@@ -411,9 +542,12 @@ function MTH_BOOKTAB_RenderFamiliesList()
rowFrame.named:SetText(tostring(row.named or 0))
rowFrame.coords:SetText(tostring(row.coords or 0))
rowFrame.diet:SetText(tostring(row.dietText or "-"))
rowFrame.hp:SetText(MTH_BOOKTAB_FormatFamilyStat(row.healthStat))
rowFrame.dmg:SetText(MTH_BOOKTAB_FormatFamilyStat(row.damageStat))
rowFrame.arm:SetText(MTH_BOOKTAB_FormatFamilyStat(row.armorStat))
local offsetX = 232
local abilityMaxRight = 526
local offsetX = 196
local abilityMaxRight = 490
for b = 1, table.getn(rowFrame.abilityButtons) do
local button = rowFrame.abilityButtons[b]
local ability = row.abilities and row.abilities[b] or nil
+99 -80
View File
@@ -547,7 +547,75 @@ MTH_BOOK_NormalizeStableFoodLink = function(itemId, itemLink, itemName)
return nil
end
local function MTH_BOOK_FindStableFoodBagIcon(itemId, itemName)
local getContainerNumSlots = (type(getglobal) == "function" and getglobal("GetContainerNumSlots")) or (_G and _G["GetContainerNumSlots"])
local getContainerItemLink = (type(getglobal) == "function" and getglobal("GetContainerItemLink")) or (_G and _G["GetContainerItemLink"])
local getContainerItemInfo = (type(getglobal) == "function" and getglobal("GetContainerItemInfo")) or (_G and _G["GetContainerItemInfo"])
if type(getContainerNumSlots) ~= "function" or type(getContainerItemLink) ~= "function" or type(getContainerItemInfo) ~= "function" then
return nil
end
local wantedItemId = tonumber(itemId)
local wantedName = string.lower(tostring(itemName or ""))
for bag = 0, 4 do
local slotCount = tonumber(getContainerNumSlots(bag)) or 0
for slot = 1, slotCount do
local link = getContainerItemLink(bag, slot)
if link then
local matched = false
if wantedItemId then
local _, _, parsedId = string.find(tostring(link), "item:(%d+)")
if tonumber(parsedId) == wantedItemId then
matched = true
end
end
if not matched and wantedName ~= "" then
local _, _, parsedName = string.find(tostring(link), "|h%[([^%]]+)%]|h")
if parsedName and string.lower(tostring(parsedName)) == wantedName then
matched = true
end
end
if matched then
local bagTexture = getContainerItemInfo(bag, slot)
if bagTexture and bagTexture ~= "" then
if type(bagTexture) == "string" and string.find(bagTexture, "\\", 1, true) then
return bagTexture
end
end
end
end
end
end
return nil
end
local function MTH_BOOK_ResolveStableFoodIcon(itemId, iconHint, itemName, itemLink)
local function resolveTextureCandidate(value)
if value == nil or value == "" then
return nil
end
if type(value) == "number" then
return nil
end
local text = tostring(value)
if text == "" then
return nil
end
if string.find(text, "|Hitem:", 1, true) or string.find(text, "^item:%d+") then
return nil
end
if string.find(text, "^table:", 1, true) then
return nil
end
if string.find(text, "\\", 1, true) then
return text
end
local asNumber = tonumber(text)
if asNumber then
return nil
end
return "Interface\\Icons\\" .. text
end
local linkText = tostring(itemLink or "")
local linkItemId = nil
if linkText ~= "" then
@@ -559,104 +627,55 @@ local function MTH_BOOK_ResolveStableFoodIcon(itemId, iconHint, itemName, itemLi
local numericItemId = tonumber(itemId) or linkItemId
if linkText ~= "" and type(GetItemInfo) == "function" then
local _, _, _, _, _, _, _, _, _, linkTexture = GetItemInfo(linkText)
if linkTexture and linkTexture ~= "" then
if type(linkTexture) == "number" then
return linkTexture
end
if string.find(tostring(linkTexture), "\\", 1, true) then
return tostring(linkTexture)
end
local asNumber = tonumber(tostring(linkTexture))
if asNumber then
return asNumber
end
return "Interface\\Icons\\" .. tostring(linkTexture)
end
end
if type(iconHint) == "number" then
return iconHint
end
local iconHintText = tostring(iconHint or "")
if iconHintText ~= "" then
local numericHint = tonumber(iconHintText)
if numericHint then
return numericHint
end
end
local iconPath = iconHintText
if iconPath ~= "" then
if string.find(iconPath, "\\", 1, true) then
return iconPath
end
return "Interface\\Icons\\" .. iconPath
end
if numericItemId and type(GetItemInfo) == "function" then
local _, _, _, _, _, _, _, _, _, infoTexture = GetItemInfo(numericItemId)
if infoTexture and infoTexture ~= "" then
if type(infoTexture) == "number" then
return infoTexture
end
if string.find(tostring(infoTexture), "\\", 1, true) then
return tostring(infoTexture)
end
local asNumber = tonumber(tostring(infoTexture))
if asNumber then
return asNumber
end
return "Interface\\Icons\\" .. tostring(infoTexture)
local resolvedInfoTexture = resolveTextureCandidate(infoTexture)
if resolvedInfoTexture then
return resolvedInfoTexture
end
end
if numericItemId and type(GetItemIcon) == "function" then
local directIcon = GetItemIcon(numericItemId)
if directIcon and directIcon ~= "" then
if type(directIcon) == "number" then
return directIcon
end
if string.find(tostring(directIcon), "\\", 1, true) then
return tostring(directIcon)
end
local asNumber = tonumber(tostring(directIcon))
if asNumber then
return asNumber
end
return "Interface\\Icons\\" .. tostring(directIcon)
local resolvedDirectIcon = resolveTextureCandidate(directIcon)
if resolvedDirectIcon then
return resolvedDirectIcon
end
end
local resolvedHintIcon = resolveTextureCandidate(iconHint)
if resolvedHintIcon then
return resolvedHintIcon
end
if linkText ~= "" and type(GetItemInfo) == "function" then
local _, _, _, _, _, _, _, _, _, linkTexture = GetItemInfo(linkText)
local resolvedLinkTexture = resolveTextureCandidate(linkTexture)
if resolvedLinkTexture then
return resolvedLinkTexture
end
end
if itemName and itemName ~= "" and type(GetItemIcon) == "function" then
local namedIcon = GetItemIcon(itemName)
if namedIcon and namedIcon ~= "" then
if type(namedIcon) == "number" then
return namedIcon
end
if string.find(tostring(namedIcon), "\\", 1, true) then
return tostring(namedIcon)
end
local asNumber = tonumber(tostring(namedIcon))
if asNumber then
return asNumber
end
return "Interface\\Icons\\" .. tostring(namedIcon)
local resolvedNamedIcon = resolveTextureCandidate(namedIcon)
if resolvedNamedIcon then
return resolvedNamedIcon
end
end
if numericItemId and type(MTH_DS_Items) == "table" and type(MTH_DS_Items[numericItemId]) == "table" then
local dsIcon = tostring(MTH_DS_Items[numericItemId].icon or "")
if dsIcon ~= "" then
if string.find(dsIcon, "\\", 1, true) then
return dsIcon
end
return "Interface\\Icons\\" .. dsIcon
local resolvedDsIcon = resolveTextureCandidate(MTH_DS_Items[numericItemId].icon)
if resolvedDsIcon then
return resolvedDsIcon
end
end
local bagIcon = MTH_BOOK_FindStableFoodBagIcon(numericItemId, itemName)
if bagIcon then
return bagIcon
end
return nil
end
@@ -1566,7 +1585,7 @@ function MTH_BOOKTAB_RenderStableCards()
local abilitiesCount = MTH_BOOK_CountPetAbilitiesMap(row.abilities)
local abilitiesText = MTH_BOOK_GetStableAbilitySummary(row.abilities)
local legacyTameContext = type(row.tameContext) == "table" and row.tameContext or nil
local hasRecordedTame = row.tameRecorded == true
local hasRecordedTame = type(MTH_PETS_HasVerifiedTameRecord) == "function" and MTH_PETS_HasVerifiedTameRecord(row) or (row.tameRecorded == true)
if not hasRecordedTame and legacyTameContext then
if legacyTameContext.name or legacyTameContext.zone or legacyTameContext.timestamp then
hasRecordedTame = true
+85 -3
View File
@@ -76,6 +76,7 @@ local MTH_BOOK_STATE = {
npcZone = "all",
npcInZoneOnly = false,
npcHideNoZone = true,
familyStatsPercent = false,
petHideNoAbilities = true,
petHideUnknown = true,
forcedBeastId = nil,
@@ -1261,6 +1262,22 @@ function MTH_BOOK_ResolveCurrentPetId(store)
return nil
end
local function MTH_BOOK_GetAuthoritativeStableSlot(petId, store)
if petId == nil or type(store) ~= "table" or type(store.stableSlotIndex) ~= "table" then
return nil
end
local wantedId = tostring(petId)
for slotNumber, mappedPetId in pairs(store.stableSlotIndex) do
if tostring(mappedPetId or "") == wantedId then
local numericSlot = tonumber(slotNumber)
if numericSlot and numericSlot > 0 then
return numericSlot
end
end
end
return nil
end
function MTH_BOOK_GetStableDisplaySlot(petId, row, store)
if type(row) ~= "table" then return nil end
if type(store) ~= "table" then
@@ -1269,7 +1286,7 @@ function MTH_BOOK_GetStableDisplaySlot(petId, row, store)
if MTH_BOOK_IsCurrentActivePetId(store, petId) then
return 0
end
local slotNumber = tonumber(row.stableSlot)
local slotNumber = MTH_BOOK_GetAuthoritativeStableSlot(petId, store)
if slotNumber and slotNumber > 0 then
return slotNumber
end
@@ -1328,6 +1345,9 @@ local function MTH_BOOK_GetSortKey(entry, col)
if col == 3 then return tonumber(entry.coords) or 0 end
if col == 4 then return tonumber(entry.abilities and table.getn(entry.abilities) or 0) end
if col == 5 then return MTH_BOOK_SafeLower(entry.dietText or "") end
if col == 6 then return tonumber(entry.healthStat) end
if col == 7 then return tonumber(entry.damageStat) end
if col == 8 then return tonumber(entry.armorStat) end
end
if MTH_BOOK_STATE.mode == "npcs" then
@@ -1429,6 +1449,56 @@ local function MTH_BOOK_DefaultCompare(a, b)
return MTH_BOOK_ItemSort(a, b)
end
local function MTH_BOOK_FormatFamilyStatValue(value)
local numeric = tonumber(value)
if not numeric then
return "-"
end
local colorPrefix = "|cFF4DA6FF"
if numeric < 1 then
colorPrefix = "|cFFFF4040"
elseif numeric > 1 then
colorPrefix = "|cFF40FF40"
end
local text = nil
if MTH_BOOK_STATE.familyStatsPercent == true then
text = tostring(math.floor((numeric * 100) + 0.5)) .. "%"
return colorPrefix .. text .. "|r"
end
text = string.format("%.3f", numeric)
text = string.gsub(text, "(%..-)0+$", "%1")
text = string.gsub(text, "%.$", "")
return colorPrefix .. text .. "|r"
end
local function MTH_BOOK_EnsureFamiliesPercentCheckbox()
local statsText = getglobal("MTH_BOOK_StatsText")
if not statsText then return nil end
if MTH_BOOK_STATE.familiesPercentCheckbox then
return MTH_BOOK_STATE.familiesPercentCheckbox
end
local parent = statsText:GetParent()
if not parent then return nil end
local check = CreateFrame("CheckButton", "MTH_BOOK_FamiliesPercentCheckbox", parent, "UICheckButtonTemplate")
if not check then return nil end
check:SetFrameStrata(parent:GetFrameStrata())
check:SetFrameLevel((parent:GetFrameLevel() or 0) + 20)
check:ClearAllPoints()
check:SetPoint("LEFT", parent, "TOPLEFT", 604, -540)
check.label = check:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
check.label:SetPoint("LEFT", check, "RIGHT", 4, 0)
check.label:SetTextColor(1.00, 0.82, 0.00)
check.label:SetText("Show modifiers in %")
check:SetScript("OnClick", function()
if not this then return end
MTH_BOOK_STATE.familyStatsPercent = this:GetChecked() == 1
MTH_BOOK_UpdateResults()
end)
check:Hide()
MTH_BOOK_STATE.familiesPercentCheckbox = check
return check
end
local function MTH_BOOK_ApplyActiveSort(results)
local state = MTH_BOOK_GetSortState()
if not state.col then return end
@@ -3521,7 +3591,7 @@ function MTH_BOOK_UpdateDetail()
end
local legacyTameContext = type(row.tameContext) == "table" and row.tameContext or nil
local hasRecordedTame = row.tameRecorded == true
local hasRecordedTame = type(MTH_PETS_HasVerifiedTameRecord) == "function" and MTH_PETS_HasVerifiedTameRecord(row) or (row.tameRecorded == true)
if not hasRecordedTame and legacyTameContext then
if legacyTameContext.name or legacyTameContext.zone or legacyTameContext.timestamp then
hasRecordedTame = true
@@ -3630,6 +3700,7 @@ end
local function MTH_BOOK_UpdateHeader()
local stats = getglobal("MTH_BOOK_StatsText")
if not stats then return end
local familiesPercentCheckbox = MTH_BOOK_EnsureFamiliesPercentCheckbox()
local mode = MTH_BOOK_STATE.mode or "pets"
local tabDef = MTH_BOOK_GetTabDefinition(mode)
local modeLabel = (tabDef and tabDef.headerLabel) or "Beasts"
@@ -3647,6 +3718,7 @@ local function MTH_BOOK_UpdateHeader()
local filtered = table.getn(MTH_BOOK_STATE.results)
local pages = math.max(1, math.ceil(filtered / MTH_BOOK_STATE.pageSize))
if mode == "families" then
local familyTotal = table.getn(MTH_BOOK_STATE.results or {})
local namedTotal = 0
local coordsTotal = 0
for i = 1, table.getn(MTH_BOOK_STATE.results or {}) do
@@ -3656,9 +3728,16 @@ local function MTH_BOOK_UpdateHeader()
coordsTotal = coordsTotal + (tonumber(row.coords) or 0)
end
end
stats:SetText("Named: " .. tostring(namedTotal) .. " | Coords: " .. tostring(coordsTotal))
stats:SetText("Families: " .. tostring(familyTotal) .. " | Named: " .. tostring(namedTotal) .. " | Coords: " .. tostring(coordsTotal))
if familiesPercentCheckbox then
familiesPercentCheckbox:SetChecked(MTH_BOOK_STATE.familyStatsPercent == true and 1 or nil)
familiesPercentCheckbox:Show()
end
return
end
if familiesPercentCheckbox then
familiesPercentCheckbox:Hide()
end
stats:SetText(modeLabel .. ": " .. total .. " | Filtered: " .. filtered .. " | Page " .. MTH_BOOK_STATE.page .. "/" .. pages)
end
@@ -3946,6 +4025,9 @@ local function MTH_BOOK_GetRowValues(entry)
tostring(entry.coords or 0),
abilitiesText,
dietText,
MTH_BOOK_FormatFamilyStatValue(entry.healthStat),
MTH_BOOK_FormatFamilyStatValue(entry.damageStat),
MTH_BOOK_FormatFamilyStatValue(entry.armorStat),
}
end
+2
View File
@@ -56,6 +56,8 @@ BINDING_HEADER_ZTrackHeader = MTH_L("BINDING_HEADER_ZTrackHeader", "[MetaHunt] Z
BINDING_HEADER_ZTrapHeader = MTH_L("BINDING_HEADER_ZTrapHeader", "[MetaHunt] ZTrap Buttons")
BINDING_HEADER_ZPetHeader = MTH_L("BINDING_HEADER_ZPetHeader", "[MetaHunt] ZPet Buttons")
BINDING_HEADER_ZAmmoHeader = MTH_L("BINDING_HEADER_ZAmmoHeader", "[MetaHunt] ZAmmo Buttons")
BINDING_HEADER_ExpAmmoHeader = MTH_L("BINDING_HEADER_ExpAmmoHeader", "[MetaHunt] MM Widget")
BINDING_NAME_EXPAMMOACTION = MTH_L("BINDING_NAME_EXPAMMOACTION", "MM Widget Action")
function MTH:GetLocalization(key, default)
return MTH_L(key, default)
+116 -45
View File
@@ -5,6 +5,7 @@ MTH_Map = {
activeSource = "focus",
providers = {},
nodesByZone = {},
minimapNodesByZone = {},
worldPins = {},
minimapPins = {},
zoneNameToId = {},
@@ -24,7 +25,7 @@ MTH_Map = {
nodeRevision = 0,
minimapForceRefreshInterval = 1.0,
verboseMiniReasons = false,
minimapTickActive = 0.20,
minimapTickActive = 0.05,
minimapTickIdle = 0.75,
}
@@ -122,15 +123,47 @@ local function MTH_Map_StripThePrefix(name)
return nil
end
local function MTH_Map_WmaToPfq(zoneId)
if type(MTH_MAP_WMA_TO_PFQ) == "table" then
return MTH_MAP_WMA_TO_PFQ[zoneId] or zoneId
end
return zoneId
end
local function MTH_Map_PfqToWma(pfqId)
if type(MTH_MAP_PFQ_TO_WMA) == "table" then
return MTH_MAP_PFQ_TO_WMA[pfqId] or pfqId
end
return pfqId
end
local function MTH_Map_GetMinimapSizes()
if pfDB and pfDB["minimap"] and MTH_DS_MinimapSizes then
if not MTH_Map._mergedMinimapSizes then
local merged = {}
for zoneId, size in pairs(pfDB["minimap"]) do
merged[zoneId] = size
end
for zoneId, size in pairs(MTH_DS_MinimapSizes) do
if merged[zoneId] == nil then
merged[zoneId] = size
end
end
MTH_Map._mergedMinimapSizes = merged
end
return MTH_Map._mergedMinimapSizes
elseif pfDB and pfDB["minimap"] then
return pfDB["minimap"]
end
return MTH_DS_MinimapSizes or {}
end
local function MTH_Map_GetZoneNameById(zoneId)
local normalizedId = tonumber(zoneId) or zoneId
if not normalizedId then return nil end
-- Translate WMA→pfQ first so WMA IDs don't accidentally hit wrong pfQ entries.
local pfqId = normalizedId
if type(MTH_MAP_WMA_TO_PFQ) == "table" then
pfqId = MTH_MAP_WMA_TO_PFQ[normalizedId] or normalizedId
end
local pfqId = MTH_Map_WmaToPfq(normalizedId)
if MTH_DS_ZoneNamesFallback and MTH_DS_ZoneNamesFallback[pfqId] then
return MTH_DS_ZoneNamesFallback[pfqId]
@@ -364,6 +397,8 @@ end
function MTH_Map:ResolveZoneHierarchy(zoneId)
local inputId = tonumber(zoneId)
local pfqInputId = MTH_Map_WmaToPfq(inputId)
local inputIsWma = inputId and pfqInputId ~= inputId or false
local resolved = {
inputId = inputId,
zoneId = inputId,
@@ -377,9 +412,9 @@ function MTH_Map:ResolveZoneHierarchy(zoneId)
return resolved
end
local sizes = MTH_DS_MinimapSizes or (pfDB and pfDB["minimap"]) or {}
local sizes = MTH_Map_GetMinimapSizes()
local visited = {}
local cursor = inputId
local cursor = pfqInputId
local depth = 0
while cursor and not visited[cursor] and depth < 8 do
@@ -390,15 +425,17 @@ function MTH_Map:ResolveZoneHierarchy(zoneId)
end
local parent = tonumber(row.parent or row.continent)
local outputCursor = inputIsWma and MTH_Map_PfqToWma(cursor) or cursor
local outputParent = parent and (inputIsWma and MTH_Map_PfqToWma(parent) or parent) or nil
if parent and not resolved.continentId then
resolved.continentId = parent
resolved.continentId = outputParent
end
if parent and parent > 0 and parent ~= cursor then
resolved.subzoneId = resolved.subzoneId or cursor
resolved.parentId = parent
resolved.subzoneId = resolved.subzoneId or outputCursor
resolved.parentId = outputParent
cursor = parent
resolved.zoneId = cursor
resolved.zoneId = inputIsWma and MTH_Map_PfqToWma(cursor) or cursor
depth = depth + 1
else
break
@@ -406,11 +443,17 @@ function MTH_Map:ResolveZoneHierarchy(zoneId)
end
local renderZoneId = resolved.zoneId
local pfqRenderId = tonumber(cursor or pfqInputId)
local pfqParentId = resolved.parentId and MTH_Map_WmaToPfq(resolved.parentId) or nil
if not sizes[renderZoneId] then
if sizes[inputId] then
renderZoneId = inputId
elseif resolved.parentId and sizes[resolved.parentId] then
renderZoneId = resolved.parentId
elseif pfqRenderId and sizes[pfqRenderId] then
renderZoneId = pfqRenderId
elseif pfqParentId and sizes[pfqParentId] then
renderZoneId = pfqParentId
end
end
@@ -419,21 +462,13 @@ function MTH_Map:ResolveZoneHierarchy(zoneId)
return resolved
end
-- Translate a pfQ AreaTable ID to a WMA ID (for zone matching against coord tuples).
-- Falls back to the pfQ ID itself if no mapping exists (sub-zones, custom zones).
local function MTH_Map_PfqToWma(pfqId)
if type(MTH_MAP_PFQ_TO_WMA) == "table" then
return MTH_MAP_PFQ_TO_WMA[pfqId] or pfqId
end
return pfqId
end
function MTH_Map:GetCurrentMapID()
function MTH_Map:GetCurrentMapID(space)
local continent = GetCurrentMapContinent and GetCurrentMapContinent() or nil
local zone = GetCurrentMapZone and GetCurrentMapZone() or nil
local zoneName = nil
local mapId = nil
local worldMapShown = WorldMapFrame and WorldMapFrame.IsShown and WorldMapFrame:IsShown() or false
local preferPfq = space == "pfq"
if continent and zone and continent > 0 and zone > 0 and GetMapZones then
if not self.mapZoneCache[continent] then
@@ -444,7 +479,9 @@ function MTH_Map:GetCurrentMapID()
if mapId then
local resolved = self:ResolveZoneHierarchy(mapId)
local normalized = resolved.zoneId or mapId
normalized = MTH_Map_PfqToWma(normalized)
if not preferPfq then
normalized = MTH_Map_PfqToWma(normalized)
end
self.lastMapContext = string.format("continent=%s zone=%s mapZone='%s' real=nil mapId=%s normalized=%s", tostring(continent), tostring(zone), tostring(zoneName), tostring(mapId), tostring(normalized))
return normalized
end
@@ -460,7 +497,9 @@ function MTH_Map:GetCurrentMapID()
mapId = self:GetMapIDByName(real)
local resolved = self:ResolveZoneHierarchy(mapId)
local normalized = resolved.zoneId or mapId
normalized = MTH_Map_PfqToWma(normalized)
if not preferPfq then
normalized = MTH_Map_PfqToWma(normalized)
end
self.lastMapContext = string.format("continent=%s zone=%s mapZone='%s' real='%s' mapId=%s normalized=%s", tostring(continent), tostring(zone), tostring(zoneName), tostring(real), tostring(mapId), tostring(normalized))
return normalized
end
@@ -470,6 +509,30 @@ function MTH_Map:GetCurrentMapID()
return nil
end
function MTH_Map:GetCurrentMinimapMapID()
local realZoneName = GetRealZoneText and GetRealZoneText() or nil
if realZoneName and realZoneName ~= "" then
local mapId = self:GetMapIDByName(realZoneName)
if mapId then
return mapId
end
end
local continent = GetCurrentMapContinent and GetCurrentMapContinent() or nil
local zone = GetCurrentMapZone and GetCurrentMapZone() or nil
if continent and zone and continent > 0 and zone > 0 and GetMapZones then
if not self.mapZoneCache[continent] then
self.mapZoneCache[continent] = { GetMapZones(continent) }
end
local zoneName = self.mapZoneCache[continent][zone]
if zoneName and zoneName ~= "" then
return self:GetMapIDByName(zoneName)
end
end
return nil
end
function MTH_Map:RegisterProvider(key, provider)
if not key or type(key) ~= "string" then return false end
if type(provider) ~= "table" or type(provider.buildNodes) ~= "function" then return false end
@@ -886,6 +949,7 @@ end
function MTH_Map:RebuildNodes()
self:BuildZoneLookup()
self.nodesByZone = {}
self.minimapNodesByZone = {}
self.nodeRevision = (tonumber(self.nodeRevision) or 0) + 1
self._lastWorldRenderKey = nil
self._lastMiniState = nil
@@ -899,15 +963,22 @@ function MTH_Map:RebuildNodes()
local node = allNodes[i]
if node and node.zoneId then
local sourceZoneId = tonumber(node.zoneId)
local resolved = self:ResolveZoneHierarchy(sourceZoneId)
local zoneId = tonumber(resolved.zoneId or sourceZoneId)
if zoneId then
local worldResolved = self:ResolveZoneHierarchy(sourceZoneId)
local worldZoneId = tonumber(worldResolved.zoneId or sourceZoneId)
local pfqSourceZoneId = MTH_Map_WmaToPfq(sourceZoneId)
local minimapZoneId = tonumber(pfqSourceZoneId)
if worldZoneId then
node.sourceZoneId = sourceZoneId
node.zoneId = zoneId
node.parentZoneId = resolved.parentId
node.subzoneId = resolved.subzoneId
if not self.nodesByZone[zoneId] then self.nodesByZone[zoneId] = {} end
table.insert(self.nodesByZone[zoneId], node)
node.zoneId = worldZoneId
node.parentZoneId = worldResolved.parentId
node.subzoneId = worldResolved.subzoneId
node.minimapZoneId = minimapZoneId
if not self.nodesByZone[worldZoneId] then self.nodesByZone[worldZoneId] = {} end
table.insert(self.nodesByZone[worldZoneId], node)
if minimapZoneId then
if not self.minimapNodesByZone[minimapZoneId] then self.minimapNodesByZone[minimapZoneId] = {} end
table.insert(self.minimapNodesByZone[minimapZoneId], node)
end
end
end
end
@@ -996,8 +1067,8 @@ function MTH_Map:UpdateMinimap()
return
end
local mapId = self:GetCurrentMapID()
local nodes = mapId and self.nodesByZone[mapId] or nil
local mapId = self:GetCurrentMinimapMapID()
local nodes = mapId and self.minimapNodesByZone[mapId] or nil
if not mapId or not nodes then
self.lastMiniReason = "no-mapid-or-nodes"
self._lastMiniState = nil
@@ -1030,20 +1101,17 @@ function MTH_Map:UpdateMinimap()
and miniState.zoom == zoom
and miniState.nodeRevision == (self.nodeRevision or 0)
then
local dx = math.abs((xPlayer or 0) - (miniState.xPlayer or 0))
local dy = math.abs((yPlayer or 0) - (miniState.yPlayer or 0))
if dx < 0.05 and dy < 0.05 and now < (miniState.nextForceAt or 0) then
self.lastMiniReason = self.verboseMiniReasons and "throttled-small-move" or "throttle"
if xPlayer == miniState.xPlayer and yPlayer == miniState.yPlayer and now < (miniState.nextForceAt or 0) then
self.lastMiniReason = self.verboseMiniReasons and "throttled-still" or "throttle"
return
end
end
local minimapSizes = MTH_DS_MinimapSizes or (pfDB and pfDB["minimap"])
local minimapSizes = MTH_Map_GetMinimapSizes()
local mapSize = minimapSizes and minimapSizes[mapId]
if not mapSize and type(MTH_MAP_WMA_TO_PFQ) == "table" then
-- Fallback: try the pfQ key (for sub-zones not yet mapped to WMA)
local pfqId = MTH_MAP_WMA_TO_PFQ[mapId]
mapSize = pfqId and minimapSizes and minimapSizes[pfqId]
if not mapSize then
local wmaId = MTH_Map_PfqToWma(mapId)
mapSize = wmaId and minimapSizes and minimapSizes[wmaId]
end
if not mapSize then
self.lastMiniReason = self.verboseMiniReasons and "missing-minimap-size" or "missing-size"
@@ -1278,6 +1346,9 @@ function MTH_Map:Init()
self.controller:SetScript("OnEvent", function(frame, eventName)
eventName = eventName or event
if eventName == "WORLD_MAP_UPDATE" and MTH_Map._syncingMapContext then
return
end
if eventName == "PLAYER_ENTERING_WORLD" or eventName == "ZONE_CHANGED" or eventName == "ZONE_CHANGED_NEW_AREA" or eventName == "MINIMAP_ZONE_CHANGED" then
if SetMapToCurrentZone and (not WorldMapFrame or not WorldMapFrame:IsShown()) then
SetMapToCurrentZone()
@@ -1320,15 +1391,15 @@ function MTH_Map:Init()
end
if MTH_Map.enabled and MTH_Map.showMinimap then
local mapId = MTH_Map:GetCurrentMapID()
local mapId = MTH_Map:GetCurrentMinimapMapID()
if not mapId and (frame._mthMapRecoverAt or 0) <= now then
frame._mthMapRecoverAt = now + 2.0
if SetMapToCurrentZone and (not WorldMapFrame or not WorldMapFrame:IsShown()) then
SetMapToCurrentZone()
mapId = MTH_Map:GetCurrentMapID()
mapId = MTH_Map:GetCurrentMinimapMapID()
end
end
local nodes = mapId and MTH_Map.nodesByZone and MTH_Map.nodesByZone[mapId] or nil
local nodes = mapId and MTH_Map.minimapNodesByZone and MTH_Map.minimapNodesByZone[mapId] or nil
local hasNodes = nodes and table.getn(nodes) > 0
frame._mthTick = now + (hasNodes and (tonumber(MTH_Map.minimapTickActive) or 0.20) or (tonumber(MTH_Map.minimapTickIdle) or 0.75))
if hasNodes then
+8 -2
View File
@@ -252,8 +252,14 @@ local function MTH_CHRON_ApplyLiveProfile(profile)
end
engine.profile = profile
if engine.SetCandyBarGroupGrowth then
engine:SetCandyBarGroupGrowth("MTHChronometer", profile.growup and true or false)
if engine.SetCandyBarGroupGrowth and engine.SetCandyBarGroupPoint and engine.anchor then
local growup = profile.growup and true or false
if growup then
engine:SetCandyBarGroupPoint("MTHChronometer", "BOTTOM", engine.anchor, "TOP", 0, 0)
else
engine:SetCandyBarGroupPoint("MTHChronometer", "TOP", engine.anchor, "BOTTOM", 0, 0)
end
engine:SetCandyBarGroupGrowth("MTHChronometer", growup)
end
if engine.SetCandyBarGroupVerticalSpacing then
engine:SetCandyBarGroupVerticalSpacing("MTHChronometer", profile.spacing or 0)
+279 -42
View File
@@ -16,10 +16,112 @@ local function MTH_EA_OPT_GetCfg()
end
local MTH_EA_OPT_built = false
local MTH_EA_OPT_STATE = { bindStatus = nil, bindButton = nil, captureFrame = nil }
-- Helper: create a pre-wired checkbox
local function MTH_EA_OPT_CB(container, name, label, yOff, isChecked, onClick)
local cb = MTH_CreateCheckbox(container, name, label, yOff, 20)
-- ── Layout constants ──────────────────────────────────────────
-- Two equal columns separated by a gutter. Values are offsets from the
-- container TOPLEFT corner (positive X right, negative Y down).
local OPT_L = 20 -- left column X
local OPT_R = 300 -- right column X
local OPT_CW = 255 -- column width (used for text wrapping and sliders)
local function MTH_EA_OPT_GetBindText()
if not GetBindingKey then return "Unbound" end
local k1, k2 = GetBindingKey("EXPAMMO ACTION")
if not k1 and not k2 then return "Unbound" end
if k1 and k2 then return k1 .. " / " .. k2 end
return k1 or k2 or "Unbound"
end
local function MTH_EA_OPT_UpdateBindStatus()
local bs = MTH_EA_OPT_STATE.bindStatus
if not bs then return end
bs:SetText("Key: " .. MTH_EA_OPT_GetBindText())
end
-- key must already be the full chord string (e.g. "ALT-F", "BUTTON3", "MOUSEWHEELUP")
local function MTH_EA_OPT_SaveBinding(key)
if not SetBinding or not SaveBindings or not GetBindingKey then return end
local old1, old2 = GetBindingKey("EXPAMMO ACTION")
if old1 then SetBinding(old1) end
if old2 then SetBinding(old2) end
if key and key ~= "" then
SetBinding(key, "EXPAMMO ACTION")
end
local bs = 1
if GetCurrentBindingSet then bs = GetCurrentBindingSet() or 1 end
SaveBindings(bs)
end
local function MTH_EA_OPT_GetPrefix()
return string.format("%s%s%s",
(IsAltKeyDown and IsAltKeyDown() and "ALT-" or ""),
(IsControlKeyDown and IsControlKeyDown() and "CTRL-" or ""),
(IsShiftKeyDown and IsShiftKeyDown() and "SHIFT-" or ""))
end
local function MTH_EA_OPT_StopCapture()
if MTH_EA_OPT_STATE.captureFrame then
MTH_EA_OPT_STATE.captureFrame:Hide()
end
local bb = MTH_EA_OPT_STATE.bindButton
if bb then bb:SetText("Set Key") end
MTH_EA_OPT_UpdateBindStatus()
end
local function MTH_EA_OPT_MakeCaptureFrame()
local f = CreateFrame("Frame", "MTH_ExpAmmoBindCapture", UIParent)
f:SetFrameStrata("FULLSCREEN_DIALOG")
f:SetAllPoints(UIParent)
f:EnableKeyboard(true)
f:EnableMouse(true)
f:EnableMouseWheel(true)
f:Hide()
local bg = f:CreateTexture(nil, "BACKGROUND")
bg:SetAllPoints(f)
bg:SetTexture(0, 0, 0, 0.5)
local lbl = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
lbl:SetPoint("CENTER", f, "CENTER", 0, 0)
lbl:SetText("|cffffff00Press a key, mouse button, or scroll wheel|r\n|cffaaaaaa(ESC to cancel)|r")
f:SetScript("OnKeyUp", function()
local key = arg1
if not key or key == "" then return end
if key == "ESCAPE" then MTH_EA_OPT_StopCapture() return end
if key == "UNKNOWN" or key == "PRINTSCREEN" then return end
if key == "ALT" or key == "CTRL" or key == "SHIFT" then return end
MTH_EA_OPT_SaveBinding(MTH_EA_OPT_GetPrefix() .. key)
MTH_EA_OPT_StopCapture()
end)
f:SetScript("OnMouseUp", function()
local btmap = { LeftButton="BUTTON1", RightButton="BUTTON2", MiddleButton="BUTTON3", Button4="BUTTON4", Button5="BUTTON5" }
local mapped = btmap[arg1]
if not mapped then return end
local prefix = MTH_EA_OPT_GetPrefix()
if prefix == "" and (mapped == "BUTTON1" or mapped == "BUTTON2") then return end
MTH_EA_OPT_SaveBinding(prefix .. mapped)
MTH_EA_OPT_StopCapture()
end)
f:SetScript("OnMouseWheel", function()
local wheelkey = (arg1 == 1 and "MOUSEWHEELUP") or (arg1 == -1 and "MOUSEWHEELDOWN") or nil
if not wheelkey then return end
MTH_EA_OPT_SaveBinding(MTH_EA_OPT_GetPrefix() .. wheelkey)
MTH_EA_OPT_StopCapture()
end)
return f
end
local function MTH_EA_OPT_StartCapture()
if not MTH_EA_OPT_STATE.captureFrame then
MTH_EA_OPT_STATE.captureFrame = MTH_EA_OPT_MakeCaptureFrame()
end
MTH_EA_OPT_STATE.captureFrame:Show()
local bb = MTH_EA_OPT_STATE.bindButton
if bb then bb:SetText("Press key...") end
end
-- Helper: checkbox anchored to a given X column
local function MTH_EA_OPT_CB(container, name, label, yOff, xOff, isChecked, onClick)
local cb = MTH_CreateCheckbox(container, name, label, yOff, xOff or OPT_L)
if cb then
cb:SetChecked(isChecked and 1 or 0)
cb:SetScript("OnClick", function()
@@ -31,10 +133,38 @@ local function MTH_EA_OPT_CB(container, name, label, yOff, isChecked, onClick)
end
-- Helper: section header label
local function MTH_EA_OPT_Header(container, text, yOff)
local function MTH_EA_OPT_Header(container, text, yOff, xOff)
local fs = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
fs:SetPoint("TOPLEFT", container, "TOPLEFT", 20, yOff)
fs:SetPoint("TOPLEFT", container, "TOPLEFT", xOff or OPT_L, yOff)
fs:SetText("|cffff9900" .. text .. "|r")
return fs
end
-- Helper: small descriptive text
local function MTH_EA_OPT_Tip(container, text, yOff, xOff, r, g, b)
local fs = container:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
fs:SetPoint("TOPLEFT", container, "TOPLEFT", xOff or OPT_L, yOff)
fs:SetWidth(OPT_CW)
fs:SetJustifyH("LEFT")
fs:SetTextColor(r or 0.6, g or 0.6, b or 0.6)
fs:SetText(text)
return fs
end
-- Enable or disable a list of widgets (supports Frames/Buttons and FontStrings)
local function MTH_EA_OPT_SetGroupEnabled(widgets, enabled)
for _, w in ipairs(widgets) do
if w then
if w.Enable and w.Disable then
-- Frame / Button / CheckButton
if enabled then w:Enable() else w:Disable() end
end
-- Dim all (including FontStrings and frames) via alpha
if w.SetAlpha then
w:SetAlpha(enabled and 1.0 or 0.35)
end
end
end
end
local function MTH_EA_OPT_BuildUI(container)
@@ -42,27 +172,21 @@ local function MTH_EA_OPT_BuildUI(container)
MTH_EA_OPT_built = true
local cfg = MTH_EA_OPT_GetCfg()
local showHint = cfg.showHint ~= false
---------------------------------------------------------------------------
-- Title
---------------------------------------------------------------------------
local title = container:CreateFontString(nil, "ARTWORK", "GameFontHighlightLarge")
title:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -16)
title:SetPoint("TOPLEFT", container, "TOPLEFT", OPT_L, -16)
title:SetText("MM Widget — Experimental Ammunition Tracker")
-- Short description
local desc = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
desc:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -46)
desc:SetWidth(540)
desc:SetJustifyH("LEFT")
desc:SetJustifyV("TOP")
desc:SetText(
"Tracks the 1.18.1 ammo cycling mechanic.\n"
.. "Aimed Shot starts a 60s cycle: "
.. "|cffff8833Explosive|r \226\134\146 |cff33ff55Poisonous|r \226\134\146 |cff6633ffEnchanted|r\n"
.. "Each state is consumed by its matching shot (Multi-Shot / Serpent Sting / Arcane Shot)."
)
---------------------------------------------------------------------------
-- LEFT COLUMN
---------------------------------------------------------------------------
-- ── Section: Module ──────────────────────────────────────
MTH_EA_OPT_Header(container, "Module", -112)
-- ── Module ──────────────────────────────────────────────
MTH_EA_OPT_Header(container, "Module", -46)
local moduleEnabled = true
if MTH and MTH.IsModuleEnabled then
@@ -70,56 +194,169 @@ local function MTH_EA_OPT_BuildUI(container)
end
MTH_EA_OPT_CB(container, "MTH_ExpAmmoModuleCB",
"Enable MM Widget module",
-132, moduleEnabled,
-66, OPT_L, moduleEnabled,
function(v)
if MTH and MTH.SetModuleEnabled then
MTH:SetModuleEnabled("expammo", v)
end
if MTH and MTH.SetModuleEnabled then MTH:SetModuleEnabled("expammo", v) end
if MTH_ExpAmmo then MTH_ExpAmmo.SetEnabled(v) end
end)
-- ── Section: Tracker Visibility ─────────────────────────
MTH_EA_OPT_Header(container, "Tracker Visibility", -166)
-- ── Tracker Visibility ───────────────────────────────────
MTH_EA_OPT_Header(container, "Tracker Visibility", -100)
MTH_EA_OPT_CB(container, "MTH_ExpAmmoShowLnLCB",
"Show Lock and Load cell (top)",
-186, cfg.showLnL ~= false,
-120, OPT_L, cfg.showLnL ~= false,
function(v) if MTH_ExpAmmo then MTH_ExpAmmo.SetShowLnL(v) end end)
MTH_EA_OPT_CB(container, "MTH_ExpAmmoShowHintCB",
-- "Show action hint cell" also gates the right column
local rightGroup = {} -- filled below; toggled when this CB changes
local showHintCB = MTH_EA_OPT_CB(container, "MTH_ExpAmmoShowHintCB",
"Show action hint cell (bottom)",
-210, cfg.showHint ~= false,
function(v) if MTH_ExpAmmo then MTH_ExpAmmo.SetShowHint(v) end end)
-144, OPT_L, showHint,
function(v)
if MTH_ExpAmmo then MTH_ExpAmmo.SetShowHint(v) end
MTH_EA_OPT_SetGroupEnabled(rightGroup, v)
end)
MTH_EA_OPT_CB(container, "MTH_ExpAmmoHideOOCCB",
"Hide widget when out of combat",
-234, cfg.hideOOC == true,
-168, OPT_L, cfg.hideOOC == true,
function(v) if MTH_ExpAmmo then MTH_ExpAmmo.SetHideOOC(v) end end)
-- ── Section: Appearance ──────────────────────────────────
MTH_EA_OPT_Header(container, "Appearance", -268)
-- ── Appearance ───────────────────────────────────────────
MTH_EA_OPT_Header(container, "Appearance", -202)
local sizeSlider = MTH_CreateSlider(container, "MTH_ExpAmmoCellSizeSlider",
"Cell Size (px)", 20, 80, 4, -288)
"Cell Size (px)", 20, 80, 4, -222)
if sizeSlider then
sizeSlider:ClearAllPoints()
sizeSlider:SetPoint("TOPLEFT", container, "TOPLEFT", OPT_L + 5, -222)
sizeSlider:SetWidth(OPT_CW - 10)
sizeSlider:SetValue(math.max(20, math.min(80, cfg.cellSize or 40)))
sizeSlider.onChange = function(val)
if MTH_ExpAmmo then MTH_ExpAmmo.SetCellSize(val) end
end
end
local gapSlider = MTH_CreateSlider(container, "MTH_ExpAmmoCellGapSlider",
"Cell Spacing (px)", 0, 20, 1, -286)
if gapSlider then
gapSlider:ClearAllPoints()
gapSlider:SetPoint("TOPLEFT", container, "TOPLEFT", OPT_L + 5, -286)
gapSlider:SetWidth(OPT_CW - 10)
gapSlider:SetValue(math.max(0, math.min(20, cfg.cellGap or 2)))
gapSlider.onChange = function(val)
if MTH_ExpAmmo then MTH_ExpAmmo.SetCellGap(val) end
end
end
MTH_EA_OPT_CB(container, "MTH_ExpAmmoBigCDCB",
"Large cooldown numbers (centered in cell)",
-352, cfg.bigCD == true,
-350, OPT_L, cfg.bigCD == true,
function(v) if MTH_ExpAmmo then MTH_ExpAmmo.SetBigCD(v) end end)
-- ── Notes ───────────────────────────────────────────────
local tip = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
tip:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -388)
tip:SetWidth(540)
tip:SetJustifyH("LEFT")
tip:SetTextColor(0.3, 0.7, 1.0)
tip:SetText("Tip: hold ALT and drag the tracker widget to reposition it.")
-- ── Position ─────────────────────────────────────────────
MTH_EA_OPT_Header(container, "Position", -390)
local anchorButton = MTH_CreateActionButton(container, "MTH_ExpAmmoToggleAnchor",
"Toggle Anchor", OPT_L, -410, 130, 22, function()
if MTH_ExpAmmo then MTH_ExpAmmo.ToggleAnchor() end
end)
if anchorButton then anchorButton:Show() end
MTH_EA_OPT_Tip(container,
"Shows 4 drag handles around the widget (top / bottom / left / right). Drag any one to reposition it.",
-436, OPT_L)
---------------------------------------------------------------------------
-- RIGHT COLUMN — Action Hint Cell (keybind + casting)
-- All widgets here are greyed out when the bottom cell is hidden.
---------------------------------------------------------------------------
-- Vertical divider line
local divider = container:CreateTexture(nil, "BACKGROUND")
divider:SetTexture(0.3, 0.3, 0.3, 0.6)
divider:SetPoint("TOPLEFT", container, "TOPLEFT", OPT_R - 10, -46)
divider:SetPoint("BOTTOMLEFT", container, "TOPLEFT", OPT_R - 10, -530)
divider:SetWidth(1)
-- Column title
local rTitle = container:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
rTitle:SetPoint("TOPLEFT", container, "TOPLEFT", OPT_R, -46)
rTitle:SetText("|cffff9900" .. "Action Hint Cell" .. "|r")
table.insert(rightGroup, rTitle)
local rSubtitle = MTH_EA_OPT_Tip(container,
"Options below apply to the bottom cell only.\n Disable \"Show action hint cell\" to hide the cell and these controls.",
-64, OPT_R, 1.00, 1.00, 1.00)
if rSubtitle then rSubtitle:SetWidth(290) end
table.insert(rightGroup, rSubtitle)
local rNamPowerTip = MTH_EA_OPT_Tip(container,
"Notes:\n - You need NAMPOWER for this to work reliably.\n - Aimed/Steady/Multi/Arcane Shots SHOULD BE on your action bars.",
-100, OPT_R, 0.35, 0.65, 1.00)
if rNamPowerTip then rNamPowerTip:SetWidth(290) end
table.insert(rightGroup, rNamPowerTip)
-- ── Keybind ──────────────────────────────────────────────
table.insert(rightGroup, MTH_EA_OPT_Header(container, "Keybind", -182, OPT_R))
local bindTip = MTH_EA_OPT_Tip(container,
"One key to rule them all.\n Aimed Shot when ready, Steady Shot while Aimed is on cooldown,\n consume-spell (Multi / Serpent / Arcane) when proc is up.",
-200, OPT_R, 0.65, 0.65, 0.65)
if bindTip then bindTip:SetWidth(290) end
table.insert(rightGroup, bindTip)
local bindStatus = container:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
bindStatus:SetPoint("TOPLEFT", container, "TOPLEFT", OPT_R, -248)
bindStatus:SetText("Key: " .. MTH_EA_OPT_GetBindText())
MTH_EA_OPT_STATE.bindStatus = bindStatus
table.insert(rightGroup, bindStatus)
local bindButton = CreateFrame("Button", "MTH_ExpAmmoBindButton", container, "UIPanelButtonTemplate")
bindButton:SetPoint("TOPLEFT", container, "TOPLEFT", OPT_R, -264)
bindButton:SetWidth(90)
bindButton:SetHeight(22)
bindButton:SetText("Set Key")
bindButton:EnableKeyboard(false)
bindButton:SetScript("OnClick", function()
if MTH_EA_OPT_STATE.captureFrame and MTH_EA_OPT_STATE.captureFrame:IsShown() then
MTH_EA_OPT_StopCapture()
else
MTH_EA_OPT_StartCapture()
end
end)
MTH_EA_OPT_STATE.bindButton = bindButton
table.insert(rightGroup, bindButton)
local clearButton = CreateFrame("Button", "MTH_ExpAmmoBindClear", container, "UIPanelButtonTemplate")
clearButton:SetPoint("LEFT", bindButton, "RIGHT", 6, 0)
clearButton:SetWidth(70)
clearButton:SetHeight(22)
clearButton:SetText("Clear")
clearButton:SetScript("OnClick", function()
MTH_EA_OPT_SaveBinding(nil)
end)
table.insert(rightGroup, clearButton)
-- ── Casting ──────────────────────────────────────────────
table.insert(rightGroup, MTH_EA_OPT_Header(container, "Casting", -304, OPT_R))
local quiverCB = MTH_EA_OPT_CB(container, "MTH_ExpAmmoQuiverNoClipCB",
"Use Quiver no-clip casting",
-324, OPT_R, cfg.useQuiverNoClip == true,
function(v) if MTH_ExpAmmo then MTH_ExpAmmo.SetUseQuiverNoClip(v) end end)
table.insert(rightGroup, quiverCB)
local quiverTip = MTH_EA_OPT_Tip(container,
"|cff4d9cffRequires the Quiver addon.|r \n Routes Aimed/Steady/Multi cast through Quiver.CastNoClip \n to avoid clipping your auto-shot swing timer",
-350, OPT_R, 1, 1, 1)
if quiverTip then quiverTip:SetWidth(290) end
table.insert(rightGroup, quiverTip)
-- Apply initial enabled state
MTH_EA_OPT_SetGroupEnabled(rightGroup, showHint)
end
function MTH_SetupExpAmmoOptions()
+64 -46
View File
@@ -10,7 +10,7 @@ local MTH_FOM_STATE = {
bindStatus = nil,
bindButton = nil,
bindClearButton = nil,
bindingCapture = false,
captureFrame = nil,
ctrl = {},
}
@@ -93,43 +93,79 @@ local function MTH_FOM_UpdateBindingStatus()
if not bindStatus or not bindStatus.SetText then
return
end
if MTH_FOM_STATE.bindingCapture then
bindStatus:SetText(MTH_FOM_L("FOM_BIND_STATUS_PROMPT", "Feed key: press a key... (ESC to clear)"))
else
bindStatus:SetText(string.format(MTH_FOM_L("FOM_BIND_STATUS_VALUE", "Feed key: %s"), MTH_FOM_GetBindingDisplayText()))
end
bindStatus:SetText(string.format(MTH_FOM_L("FOM_BIND_STATUS_VALUE", "Feed key: %s"), MTH_FOM_GetBindingDisplayText()))
end
local function MTH_FOM_StopBindingCapture()
MTH_FOM_STATE.bindingCapture = false
if MTH_FOM_STATE.captureFrame then
MTH_FOM_STATE.captureFrame:Hide()
end
if MTH_FOM_STATE.bindButton then
if MTH_FOM_STATE.bindButton.SetPropagateKeyboardInput then
MTH_FOM_STATE.bindButton:SetPropagateKeyboardInput(true)
end
MTH_FOM_STATE.bindButton:EnableKeyboard(false)
MTH_FOM_STATE.bindButton:SetText(MTH_FOM_L("FOM_BIND_SET_KEY", "Set Key"))
end
MTH_FOM_UpdateBindingStatus()
end
local function MTH_FOM_StartBindingCapture()
MTH_FOM_STATE.bindingCapture = true
if MTH_FOM_STATE.bindButton then
MTH_FOM_STATE.bindButton:SetText(MTH_FOM_L("FOM_BIND_PRESS_KEY", "Press key..."))
MTH_FOM_STATE.bindButton:EnableKeyboard(true)
if MTH_FOM_STATE.bindButton.SetPropagateKeyboardInput then
MTH_FOM_STATE.bindButton:SetPropagateKeyboardInput(false)
end
end
MTH_FOM_UpdateBindingStatus()
local function MTH_FOM_BuildBindingChord(key)
return string.format("%s%s%s%s",
(IsAltKeyDown and IsAltKeyDown() and "ALT-" or ""),
(IsControlKeyDown and IsControlKeyDown() and "CTRL-" or ""),
(IsShiftKeyDown and IsShiftKeyDown() and "SHIFT-" or ""),
key)
end
local function MTH_FOM_BuildBindingChord(key)
local chord = ""
if IsControlKeyDown and IsControlKeyDown() and key ~= "LCTRL" and key ~= "RCTRL" then chord = chord .. "CTRL-" end
if IsAltKeyDown and IsAltKeyDown() and key ~= "LALT" and key ~= "RALT" then chord = chord .. "ALT-" end
if IsShiftKeyDown and IsShiftKeyDown() and key ~= "LSHIFT" and key ~= "RSHIFT" then chord = chord .. "SHIFT-" end
return chord .. key
local function MTH_FOM_MakeCaptureFrame()
local f = CreateFrame("Frame", "MTH_FOMBindCapture", UIParent)
f:SetFrameStrata("FULLSCREEN_DIALOG")
f:SetAllPoints(UIParent)
f:EnableKeyboard(true)
f:EnableMouse(true)
f:EnableMouseWheel(true)
f:Hide()
local bg = f:CreateTexture(nil, "BACKGROUND")
bg:SetAllPoints(f)
bg:SetTexture(0, 0, 0, 0.5)
local lbl = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
lbl:SetPoint("CENTER", f, "CENTER", 0, 0)
lbl:SetText("|cffffff00Press a key, mouse button, or scroll wheel|r\n|cffaaaaaa(ESC to cancel)|r")
f:SetScript("OnKeyUp", function()
local key = arg1
if not key or key == "" then return end
if key == "ESCAPE" then MTH_FOM_StopBindingCapture() return end
if key == "UNKNOWN" or key == "PRINTSCREEN" then return end
if key == "ALT" or key == "CTRL" or key == "SHIFT" then return end
MTH_FOM_SaveBinding(MTH_FOM_BuildBindingChord(key))
MTH_FOM_StopBindingCapture()
end)
f:SetScript("OnMouseUp", function()
local btmap = { LeftButton="BUTTON1", RightButton="BUTTON2", MiddleButton="BUTTON3", Button4="BUTTON4", Button5="BUTTON5" }
local mapped = btmap[arg1]
if not mapped then return end
local prefix = string.format("%s%s%s",
(IsAltKeyDown and IsAltKeyDown() and "ALT-" or ""),
(IsControlKeyDown and IsControlKeyDown() and "CTRL-" or ""),
(IsShiftKeyDown and IsShiftKeyDown() and "SHIFT-" or ""))
if prefix == "" and (mapped == "BUTTON1" or mapped == "BUTTON2") then return end
MTH_FOM_SaveBinding(prefix .. mapped)
MTH_FOM_StopBindingCapture()
end)
f:SetScript("OnMouseWheel", function()
local wheelkey = (arg1 == 1 and "MOUSEWHEELUP") or (arg1 == -1 and "MOUSEWHEELDOWN") or nil
if not wheelkey then return end
MTH_FOM_SaveBinding(MTH_FOM_BuildBindingChord(wheelkey))
MTH_FOM_StopBindingCapture()
end)
return f
end
local function MTH_FOM_StartBindingCapture()
if not MTH_FOM_STATE.captureFrame then
MTH_FOM_STATE.captureFrame = MTH_FOM_MakeCaptureFrame()
end
MTH_FOM_STATE.captureFrame:Show()
if MTH_FOM_STATE.bindButton then
MTH_FOM_STATE.bindButton:SetText(MTH_FOM_L("FOM_BIND_PRESS_KEY", "Press key..."))
end
end
local function MTH_FOM_SaveBinding(key)
@@ -254,31 +290,13 @@ function MTH_SetupFeedOMaticOptions()
bindButton:SetWidth(90)
bindButton:SetHeight(22)
bindButton:SetText(MTH_FOM_L("FOM_BIND_SET_KEY", "Set Key"))
bindButton:EnableKeyboard(false)
bindButton:SetScript("OnClick", function()
if MTH_FOM_STATE.bindingCapture then
if MTH_FOM_STATE.captureFrame and MTH_FOM_STATE.captureFrame:IsShown() then
MTH_FOM_StopBindingCapture()
else
MTH_FOM_StartBindingCapture()
end
end)
bindButton:SetScript("OnKeyDown", function()
if not MTH_FOM_STATE.bindingCapture then
return
end
local key = arg1
if not key or key == "" then
return
end
if key == "ESCAPE" then
MTH_FOM_SaveBinding(nil)
return
end
if key == "UNKNOWN" or key == "PRINTSCREEN" then
return
end
MTH_FOM_SaveBinding(MTH_FOM_BuildBindingChord(key))
end)
MTH_FOM_STATE.bindButton = bindButton
local bindClearButton = CreateFrame("Button", "MetaHuntOptionsFOMBindClearButton", container, "UIPanelButtonTemplate")
+10
View File
@@ -133,6 +133,11 @@ function MTH_SelectOptionsTab(tabKey)
MTH_SetupMessagesOptions()
end
end
elseif tabKey == "ZBar" then
if type(MTH_SetupZBarOptions) == "function" then
MTH_SetupZBarOptions()
MTH_OPTIONS_SETUP["ZBar"] = true
end
elseif tabKey == "Pet" then
if not MTH_OPTIONS_SETUP["Pet"] then
MTH_SetupPetOptions()
@@ -182,6 +187,11 @@ function MTH_SelectOptionsTab(tabKey)
MTH_SetupToysOptions()
MTH_OPTIONS_SETUP["Toys"] = true
end
elseif tabKey == "Craft" then
if not MTH_OPTIONS_SETUP["Craft"] then
MTH_SetupCraftOptions()
MTH_OPTIONS_SETUP["Craft"] = true
end
elseif tabKey == "SmartAmmo" then
if not MTH_OPTIONS_SETUP["SmartAmmo"] then
MTH_SetupSmartAmmoOptions()
+7 -3
View File
@@ -1,4 +1,4 @@
MTH_OPTIONS_TREE_STATE = MTH_OPTIONS_TREE_STATE or { zButtons = true, chronometer = true }
MTH_OPTIONS_TREE_STATE = MTH_OPTIONS_TREE_STATE or { zButtons = false, chronometer = false }
MTH_OPTIONS_TREE_BUTTONS = MTH_OPTIONS_TREE_BUTTONS or {}
MTH_OPTIONS_STATE = MTH_OPTIONS_STATE or {}
if MTH_OPTIONS_STATE.activeTab == nil and MTH_OPTIONS_ACTIVE_TAB ~= nil then
@@ -30,7 +30,8 @@ MTH_OPTIONS_TABS = MTH_OPTIONS_TABS or {
{ key = "General", label = "General", frame = "MetaHuntOptionsGeneral" },
{ key = "Profiles", label = "Profiles", frame = "MetaHuntOptionsProfiles" },
{ key = "Messages", label = "Messages", frame = "MetaHuntOptionsMessages" },
{ key = "Pet", label = "ZPet", frame = "MetaHuntOptionsPet" },
{ key = "ZBar", label = "zBar", frame = "MetaHuntOptionsZBar" },
{ key = "Pet", label = "ZPet", frame = "MetaHuntOptionsPet" },
{ key = "Track", label = "ZTrack", frame = "MetaHuntOptionsTrack" },
{ key = "Aspect", label = "ZAspect", frame = "MetaHuntOptionsAspect" },
{ key = "Trap", label = "ZTrap", frame = "MetaHuntOptionsTrap" },
@@ -39,6 +40,7 @@ MTH_OPTIONS_TABS = MTH_OPTIONS_TABS or {
{ key = "Mounts", label = "ZMounts", frame = "MetaHuntOptionsMounts" },
{ key = "Companions", label = "ZCompanions", frame = "MetaHuntOptionsCompanions" },
{ key = "Toys", label = "ZToys", frame = "MetaHuntOptionsToys" },
{ key = "Craft", label = "ZCraft", frame = "MetaHuntOptionsCraft" },
{ key = "SmartAmmo", label = "Smart Ammo", frame = "MetaHuntOptionsSmartAmmo" },
{ key = "FeedOMatic", label = "FeedOMatic", frame = "MetaHuntOptionsFeedOMatic" },
{ key = "AutoBuy", label = "Auto Buy", frame = "MetaHuntOptionsAutoBuy" },
@@ -66,7 +68,8 @@ MTH_OPTIONS_TREE = MTH_OPTIONS_TREE or {
label = "zButtons",
node = "zButtons",
children = {
{ label = "zAmmo", key = "Ammo" },
{ label = "zBar", key = "ZBar" },
{ label = "zAmmo", key = "Ammo" },
{ label = "zPet", key = "Pet" },
{ label = "zAspect", key = "Aspect" },
{ label = "zTrack", key = "Track" },
@@ -75,6 +78,7 @@ MTH_OPTIONS_TREE = MTH_OPTIONS_TREE or {
{ label = "zMounts", key = "Mounts" },
{ label = "zCompanions", key = "Companions" },
{ label = "zToys", key = "Toys" },
{ label = "zCraft", key = "Craft" },
},
},
{
+460 -32
View File
@@ -37,7 +37,9 @@ local function MTH_RebuildSpellOrder(buttonName, buttonObj, itemList, maxButtons
for i = 1, maxButtons do
local spellIndex = ZHunterMod_Saved[buttonName]["spells"][i]
if spellIndex and visible[spellIndex] ~= false then
info[infoIndex] = itemList[spellIndex]
-- String-based lists (zCraft) store names directly; numeric lists index into itemList
local name = (type(spellIndex) == "number") and itemList[spellIndex] or spellIndex
info[infoIndex] = name
infoIndex = infoIndex + 1
end
end
@@ -108,34 +110,34 @@ local function MTH_SetButtonEnabledState(buttonName, buttonObj, enabled)
ZHunterMod_Saved[buttonName]["enabled"] = enabled and true or false
local resolvedButtonObj = buttonObj or getglobal(buttonName)
if not resolvedButtonObj then return end
if not enabled then
if resolvedButtonObj.Hide then
resolvedButtonObj:Hide()
end
if resolvedButtonObj.count and resolvedButtonObj.name then
for i = 1, resolvedButtonObj.count do
local child = getglobal(resolvedButtonObj.name .. i)
if child and child.Hide then
child:Hide()
if resolvedButtonObj then
if not enabled then
if resolvedButtonObj.Hide then resolvedButtonObj:Hide() end
if resolvedButtonObj.count and resolvedButtonObj.name then
for i = 1, resolvedButtonObj.count do
local child = getglobal(resolvedButtonObj.name .. i)
if child and child.Hide then child:Hide() end
end
end
elseif ZHunterMod_Saved[buttonName]["parent"] and ZHunterMod_Saved[buttonName]["parent"]["hide"] then
if resolvedButtonObj.Hide then resolvedButtonObj:Hide() end
else
if resolvedButtonObj.Show then resolvedButtonObj:Show() end
MTH_RefreshButtonGeometry(buttonName, resolvedButtonObj)
end
return
end
if ZHunterMod_Saved[buttonName]["parent"] and ZHunterMod_Saved[buttonName]["parent"]["hide"] then
if resolvedButtonObj.Hide then
resolvedButtonObj:Hide()
-- If zBar is active, re-apply its layout so this button is included/excluded.
-- When re-enabling a button, also un-exclude it from the bar so it appears
-- in the bar rather than as a standalone button.
if type(MTH_ZBar_GetSaved) == "function" then
local zs = MTH_ZBar_GetSaved()
if enabled and zs.visible[buttonName] == false then
zs.visible[buttonName] = true
end
if zs.enabled and type(MTH_ZBar_ApplyLayout) == "function" then
MTH_ZBar_ApplyLayout()
end
return
end
if resolvedButtonObj.Show then
resolvedButtonObj:Show()
end
MTH_RefreshButtonGeometry(buttonName, resolvedButtonObj)
end
local function MTH_GetButtonItemList(buttonName)
@@ -170,7 +172,7 @@ local function MTH_GetButtonItemList(buttonName)
if rangedList and table.getn(rangedList) > 0 then
return rangedList
end
elseif buttonName == "zButtonMounts" or buttonName == "zButtonCompanions" or buttonName == "zButtonToys" then
elseif buttonName == "zButtonMounts" or buttonName == "zButtonCompanions" or buttonName == "zButtonToys" or buttonName == "zButtonCraft" then
if ZHunterMod_Saved and ZHunterMod_Saved[buttonName] and ZHunterMod_Saved[buttonName]["spells"] then
return ZHunterMod_Saved[buttonName]["spells"]
end
@@ -212,7 +214,7 @@ local function MTH_EnsureAmmoOptionsWatcher()
end
local function MTH_ZB_GetDefaultEnabled(buttonName)
if buttonName == "zButtonMounts" or buttonName == "zButtonCompanions" or buttonName == "zButtonToys" or buttonName == "zButtonRanged" then
if buttonName == "zButtonMounts" or buttonName == "zButtonCompanions" or buttonName == "zButtonToys" or buttonName == "zButtonCraft" or buttonName == "zButtonRanged" then
return false
end
return true
@@ -269,6 +271,27 @@ local function MTH_ZB_EnsureButtonOptionDefaults(buttonName)
end
saved["parent"]["circle"] = saved["parent"]["circle"] and true or false
if buttonName == "zButtonAspect" or buttonName == "zButtonTrack" or buttonName == "zButtonPet" then
if saved["parent"]["smart"] == nil then
saved["parent"]["smart"] = true
end
saved["parent"]["smart"] = saved["parent"]["smart"] and true or false
end
if buttonName == "zButtonMounts" or buttonName == "zButtonCompanions" then
if saved["parent"]["random"] == nil then
saved["parent"]["random"] = false
end
saved["parent"]["random"] = saved["parent"]["random"] and true or false
end
if buttonName == "zButtonMounts" then
if saved["parent"]["aqfilter"] == nil then
saved["parent"]["aqfilter"] = true
end
saved["parent"]["aqfilter"] = saved["parent"]["aqfilter"] and true or false
end
if saved["firstbutton"] ~= "LEFT" and saved["firstbutton"] ~= "RIGHT" then
saved["firstbutton"] = "RIGHT"
end
@@ -350,8 +373,17 @@ local function MTH_SetupButtonOptions(containerName, buttonName, displayName, ma
end)
end
local parentSection = MTH_ZB_EnsureSection(containerName.."ParentSection", MTH_ZB_L("ZB_SECTION_PARENT_BUTTON", "Parent Button"), -52, 116)
local childrenSection = MTH_ZB_EnsureSection(containerName.."ChildrenSection", MTH_ZB_L("ZB_SECTION_CHILDREN_BUTTONS", "Children Buttons"), -184, 310)
local parentSectionHeight = 116
if buttonName == "zButtonAspect" or buttonName == "zButtonTrack" or buttonName == "zButtonPet" then
parentSectionHeight = 200
elseif buttonName == "zButtonMounts" then
parentSectionHeight = 225
elseif buttonName == "zButtonCompanions" then
parentSectionHeight = 196
end
local childrenSectionY = -52 - parentSectionHeight - 16
local parentSection = MTH_ZB_EnsureSection(containerName.."ParentSection", MTH_ZB_L("ZB_SECTION_PARENT_BUTTON", "Parent Button"), -52, parentSectionHeight)
local childrenSection = MTH_ZB_EnsureSection(containerName.."ChildrenSection", MTH_ZB_L("ZB_SECTION_CHILDREN_BUTTONS", "Children Buttons"), childrenSectionY, 290)
local parentYOffset = -18
local childrenYOffset = -18
@@ -478,7 +510,7 @@ local function MTH_SetupButtonOptions(containerName, buttonName, displayName, ma
end
end
end
childrenYOffset = childrenYOffset - 50
childrenYOffset = childrenYOffset - 40
local showTooltip = MTH_CreateCheckbox(childrenSection or container, containerName.."ShowTooltip", MTH_ZB_L("ZB_LABEL_SHOW_TOOLTIP", "Show Tooltip"), childrenYOffset)
if showTooltip then
@@ -496,7 +528,7 @@ local function MTH_SetupButtonOptions(containerName, buttonName, displayName, ma
if btnObj then btnObj.tooltip = checked end
end)
end
childrenYOffset = childrenYOffset - 25
childrenYOffset = childrenYOffset - 20
if buttonName == "zButtonAmmo" then
local showAmmoName = MTH_CreateCheckbox(childrenSection or container, containerName.."ShowAmmoName", MTH_ZB_L("ZB_LABEL_SHOW_AMMO_NAME", "Show ammo name"), childrenYOffset)
@@ -524,9 +556,9 @@ local function MTH_SetupButtonOptions(containerName, buttonName, displayName, ma
end
end)
end
childrenYOffset = childrenYOffset - 35
childrenYOffset = childrenYOffset - 25
else
childrenYOffset = childrenYOffset - 35
childrenYOffset = childrenYOffset - 25
end
local mainSize = MTH_CreateSlider(parentSection or container, containerName.."MainButtonSize", MTH_ZB_L("ZB_LABEL_BUTTON_SIZE", "Button Size"), 10, 100, 1, parentYOffset)
@@ -585,6 +617,98 @@ local function MTH_SetupButtonOptions(containerName, buttonName, displayName, ma
end)
end
-- Smart Parent: Aspect/Track/Pet
if buttonName == "zButtonAspect" or buttonName == "zButtonTrack" or buttonName == "zButtonPet" then
parentYOffset = parentYOffset - 25
local smartParent = MTH_CreateCheckbox(parentSection or container, containerName.."SmartParent", MTH_ZB_L("ZB_LABEL_SMART_PARENT", "Smart Parent"), parentYOffset)
if smartParent then
smartParent:SetChecked(saved["parent"]["smart"])
smartParent.buttonName = buttonName
smartParent.buttonObj = buttonObj
smartParent:SetScript("OnClick", function()
if not this then return end
local checked = this:GetChecked() == 1
ZHunterMod_Saved[this.buttonName]["parent"]["smart"] = checked
local btn = this.buttonObj or getglobal(this.buttonName)
if btn then
if not checked then
local firstChild = getglobal(this.buttonName .. "1")
if firstChild and firstChild.id then
btn.id = firstChild.id
ZSpellButton_UpdateButton(btn)
ZSpellButton_UpdateCooldown(btn)
end
else
local setupFunc = getglobal(this.buttonName .. "_SetupSizeAndPosition")
if type(setupFunc) == "function" then
setupFunc()
end
end
end
end)
end
local smartDescText
if buttonName == "zButtonAspect" then
smartDescText = "When ON, the parent toggles between your 1st and 2nd aspect. If the 1st is active, the parent shows the 2nd and vice-versa. Useful for quickly swapping between two aspects in combat. When OFF, the parent always shows the 1st aspect."
elseif buttonName == "zButtonTrack" then
smartDescText = "When ON, the parent toggles between your 1st and 2nd tracking. If the 1st is active, the parent shows the 2nd and vice-versa. Great for PvP to swap between Track Hidden and Track Humanoids. When OFF, the parent always shows the 1st tracking."
elseif buttonName == "zButtonPet" then
smartDescText = "When ON, the parent dynamically changes based on your pet state: Revive if dead, Mend if hurt, Feed if unhappy, or your default spell otherwise. When OFF, the parent always shows the 1st spell."
else
smartDescText = "Dynamically swap the parent icon based on context."
end
local smartDesc = (parentSection or container):CreateFontString(containerName.."SmartParentDesc", "ARTWORK", "GameFontNormalSmall")
smartDesc:SetPoint("TOPLEFT", parentSection or container, "TOPLEFT", 40, parentYOffset - 24)
smartDesc:SetWidth(leftWidth - 50)
smartDesc:SetJustifyH("LEFT")
smartDesc:SetText(smartDescText)
smartDesc:SetTextColor(0.5, 0.7, 1.0)
end
-- Random Parent: Mounts/Companions
if buttonName == "zButtonMounts" or buttonName == "zButtonCompanions" then
parentYOffset = parentYOffset - 25
local randomParent = MTH_CreateCheckbox(parentSection or container, containerName.."RandomParent", MTH_ZB_L("ZB_LABEL_RANDOM_PARENT", "Random Parent"), parentYOffset)
if randomParent then
randomParent:SetChecked(saved["parent"]["random"])
randomParent.buttonName = buttonName
randomParent.buttonObj = buttonObj
randomParent:SetScript("OnClick", function()
if not this then return end
local checked = this:GetChecked() == 1
ZHunterMod_Saved[this.buttonName]["parent"]["random"] = checked
end)
end
local randomDesc = (parentSection or container):CreateFontString(containerName.."RandomParentDesc", "ARTWORK", "GameFontNormalSmall")
randomDesc:SetPoint("TOPLEFT", parentSection or container, "TOPLEFT", 40, parentYOffset - 24)
randomDesc:SetWidth(leftWidth - 50)
randomDesc:SetJustifyH("LEFT")
randomDesc:SetText(MTH_ZB_L("ZB_DESC_RANDOM_PARENT", "Pick a random child as parent on each login and after each use."))
randomDesc:SetTextColor(0.5, 0.7, 1.0)
end
-- AQ Mount Filter: Mounts only
if buttonName == "zButtonMounts" then
parentYOffset = parentYOffset - 45
local aqFilter = MTH_CreateCheckbox(parentSection or container, containerName.."AQFilter", MTH_ZB_L("ZB_LABEL_AQ_FILTER", "AQ Mount Filter"), parentYOffset)
if aqFilter then
aqFilter:SetChecked(saved["parent"]["aqfilter"])
aqFilter.buttonName = buttonName
aqFilter.buttonObj = buttonObj
aqFilter:SetScript("OnClick", function()
if not this then return end
local checked = this:GetChecked() == 1
ZHunterMod_Saved[this.buttonName]["parent"]["aqfilter"] = checked
end)
end
local aqDesc = (parentSection or container):CreateFontString(containerName.."AQFilterDesc", "ARTWORK", "GameFontNormalSmall")
aqDesc:SetPoint("TOPLEFT", parentSection or container, "TOPLEFT", 40, parentYOffset - 24)
aqDesc:SetWidth(leftWidth - 50)
aqDesc:SetJustifyH("LEFT")
aqDesc:SetText(MTH_ZB_L("ZB_DESC_AQ_FILTER", "Hide Qiraji mounts outside AQ40. Show only Qiraji inside."))
aqDesc:SetTextColor(0.5, 0.7, 1.0)
end
MTH_SetButtonEnabledState(buttonName, buttonObj, saved["enabled"] and true or false)
local advHeader = container:CreateFontString(containerName.."AdvHeader", "ARTWORK", "GameFontHighlight")
@@ -714,6 +838,303 @@ local function MTH_SetupButtonOptions(containerName, buttonName, displayName, ma
end
end
-- ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
-- zBar options
-- ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
function MTH_SetupZBarOptions()
if not MTH_ZBUTTONS_READY then return end
local container = MTH_GetFrame("MetaHuntOptionsZBar")
if not container then return end
MTH_ClearContainer(container)
if type(MTH_ZBar_GetSaved) ~= "function" then
local err = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
err:SetPoint("CENTER", container, "CENTER", 0, 0)
err:SetText("zBar module not loaded")
err:SetTextColor(1, 0.5, 0.5)
return
end
local s = MTH_ZBar_GetSaved()
local containerWidth = container:GetWidth() or 500
local leftWidth = 260
local rightX = leftWidth + 44
local rightWidth = containerWidth - rightX - 12
-- ===== Enable checkbox =====
local enableBtn = MTH_CreateCheckbox(container, "MetaHuntOptionsZBarEnable",
"Enable zBar — group all zButtons into a single draggable bar", -8)
if enableBtn then
enableBtn:SetChecked(s.enabled and true or false)
enableBtn:SetScript("OnClick", function()
if type(MTH_ZBar_SetEnabled) == "function" then
MTH_ZBar_SetEnabled(this:GetChecked() == 1)
end
end)
end
-- ===== Direction header =====
local dirLabel = container:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
dirLabel:SetPoint("TOPLEFT", container, "TOPLEFT", 16, -46)
dirLabel:SetText("Bar Direction")
-- ===== Horizontal radio =====
local horizBtn = MTH_CreateCheckbox(container, "MetaHuntOptionsZBarHoriz",
"Horizontal (side by side)", -64)
if horizBtn then
horizBtn:SetChecked(s.direction ~= "VERTICAL")
horizBtn:SetScript("OnClick", function()
if this:GetChecked() == 1 then
local sv = MTH_ZBar_GetSaved()
sv.direction = "HORIZONTAL"
-- Auto-correct childexpand to a valid side for a horizontal bar
if sv.childexpand ~= "TOP" and sv.childexpand ~= "BOTTOM" then
sv.childexpand = "BOTTOM"
end
local v = getglobal("MetaHuntOptionsZBarVert")
if v then v:SetChecked(false) end
if sv.enabled and type(MTH_ZBar_ApplyLayout) == "function" then
MTH_ZBar_ApplyLayout()
end
MTH_ResetAndSelectOptionsTab("ZBar")
else
this:SetChecked(true)
end
end)
end
-- ===== Vertical radio =====
local vertBtn = MTH_CreateCheckbox(container, "MetaHuntOptionsZBarVert",
"Vertical (stacked)", -90)
if vertBtn then
vertBtn:SetChecked(s.direction == "VERTICAL")
vertBtn:SetScript("OnClick", function()
if this:GetChecked() == 1 then
local sv = MTH_ZBar_GetSaved()
sv.direction = "VERTICAL"
-- Auto-correct childexpand to a valid side for a vertical bar
if sv.childexpand ~= "LEFT" and sv.childexpand ~= "RIGHT" then
sv.childexpand = "RIGHT"
end
local h = getglobal("MetaHuntOptionsZBarHoriz")
if h then h:SetChecked(false) end
if sv.enabled and type(MTH_ZBar_ApplyLayout) == "function" then
MTH_ZBar_ApplyLayout()
end
MTH_ResetAndSelectOptionsTab("ZBar")
else
this:SetChecked(true)
end
end)
end
-- ===== Children expand side (contextual to bar direction) =====
local expandLbl = container:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
expandLbl:SetPoint("TOPLEFT", container, "TOPLEFT", 16, -120)
if s.direction == "VERTICAL" then
expandLbl:SetText("Children expand side (Vertical bar):")
else
expandLbl:SetText("Children expand side (Horizontal bar):")
end
local exp1Val, exp2Val, exp1Lbl, exp2Lbl
if s.direction == "VERTICAL" then
exp1Val = "LEFT" ; exp1Lbl = "Left"
exp2Val = "RIGHT" ; exp2Lbl = "Right"
else
exp1Val = "TOP" ; exp1Lbl = "Top (above)"
exp2Val = "BOTTOM" ; exp2Lbl = "Bottom (below)"
end
local exp1Btn = MTH_CreateCheckbox(container, "MetaHuntOptionsZBarExp1", exp1Lbl, -138, 30)
if exp1Btn then
exp1Btn:SetChecked(s.childexpand == exp1Val)
exp1Btn.zbarVal = exp1Val
exp1Btn:SetScript("OnClick", function()
if this:GetChecked() == 1 then
local sv = MTH_ZBar_GetSaved()
sv.childexpand = this.zbarVal
if sv.enabled and type(MTH_ZBar_ApplyLayout) == "function" then MTH_ZBar_ApplyLayout() end
MTH_ResetAndSelectOptionsTab("ZBar")
else
this:SetChecked(true)
end
end)
end
local exp2Btn = MTH_CreateCheckbox(container, "MetaHuntOptionsZBarExp2", exp2Lbl, -164, 30)
if exp2Btn then
exp2Btn:SetChecked(s.childexpand == exp2Val)
exp2Btn.zbarVal = exp2Val
exp2Btn:SetScript("OnClick", function()
if this:GetChecked() == 1 then
local sv = MTH_ZBar_GetSaved()
sv.childexpand = this.zbarVal
if sv.enabled and type(MTH_ZBar_ApplyLayout) == "function" then MTH_ZBar_ApplyLayout() end
MTH_ResetAndSelectOptionsTab("ZBar")
else
this:SetChecked(true)
end
end)
end
-- ===== Children layout (how children #2+ are arranged relative to #1) =====
local arrLbl = container:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
arrLbl:SetPoint("TOPLEFT", container, "TOPLEFT", 16, -196)
arrLbl:SetText("Children layout:")
local arrVBtn = MTH_CreateCheckbox(container, "MetaHuntOptionsZBarArrV",
"Vertical (stacked column)", -214, 30)
if arrVBtn then
arrVBtn:SetChecked((s.childarrange or "VERTICAL") == "VERTICAL")
arrVBtn:SetScript("OnClick", function()
if this:GetChecked() == 1 then
local sv = MTH_ZBar_GetSaved()
sv.childarrange = "VERTICAL"
if sv.enabled and type(MTH_ZBar_ApplyLayout) == "function" then MTH_ZBar_ApplyLayout() end
MTH_ResetAndSelectOptionsTab("ZBar")
else
this:SetChecked(true)
end
end)
end
local arrHBtn = MTH_CreateCheckbox(container, "MetaHuntOptionsZBarArrH",
"Horizontal (side-by-side row)", -240, 30)
if arrHBtn then
arrHBtn:SetChecked((s.childarrange or "VERTICAL") == "HORIZONTAL")
arrHBtn:SetScript("OnClick", function()
if this:GetChecked() == 1 then
local sv = MTH_ZBar_GetSaved()
sv.childarrange = "HORIZONTAL"
if sv.enabled and type(MTH_ZBar_ApplyLayout) == "function" then MTH_ZBar_ApplyLayout() end
MTH_ResetAndSelectOptionsTab("ZBar")
else
this:SetChecked(true)
end
end)
end
-- ===== Spacing slider =====
local spacingSlider = MTH_CreateSlider(container, "MetaHuntOptionsZBarSpacing",
"Spacing (px)", 0, 30, 1, -300)
if spacingSlider then
spacingSlider:SetWidth(leftWidth - 40)
spacingSlider:SetValue(tonumber(s.spacing) or 4)
spacingSlider.onChange = function(val)
local sv = MTH_ZBar_GetSaved()
sv.spacing = math.floor((tonumber(val) or 4) + 0.5)
if sv.enabled and type(MTH_ZBar_ApplyLayout) == "function" then
MTH_ZBar_ApplyLayout()
end
end
end
-- ===== Button size slider =====
local sizeSlider = MTH_CreateSlider(container, "MetaHuntOptionsZBarSize",
"Button Size", 10, 100, 1, -360)
if sizeSlider then
sizeSlider:SetWidth(leftWidth - 40)
sizeSlider:SetValue(tonumber(s.size) or 36)
sizeSlider.onChange = function(val)
if type(MTH_ZBar_SetSize) == "function" then
MTH_ZBar_SetSize(val)
end
end
end
-- ===== Right column: Bar Order =====
local orderHeader = container:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
orderHeader:SetPoint("TOPLEFT", container, "TOPLEFT", rightX, -10)
orderHeader:SetText("Bar Order")
local LABELS = MTH_ZBar_GetAllButtonLabels()
local numBars = table.getn(s.order)
local listY = -35
local downX = rightX + 28
local upX = downX + 22
local labelX = upX + 26
local labelWidth = rightWidth - (labelX - rightX) - 8
if labelWidth < 80 then labelWidth = 80 end
for i = 1, numBars do
local buttonName = s.order[i]
local displayName = (LABELS and LABELS[buttonName]) or buttonName
-- Include/exclude visibility toggle
local toggleName = "MetaHuntOptionsZBarToggle" .. i
local showToggle = CreateFrame("CheckButton", toggleName, container, "UICheckButtonTemplate")
if showToggle then
showToggle:ClearAllPoints()
showToggle:SetPoint("TOPLEFT", container, "TOPLEFT", rightX, listY + 2)
local tText = getglobal(toggleName .. "Text")
if tText then tText:SetText("") end
showToggle:SetChecked(s.visible[buttonName] ~= false)
showToggle.zbarName = buttonName
showToggle:SetScript("OnClick", function()
local sv = MTH_ZBar_GetSaved()
sv.visible[this.zbarName] = (this:GetChecked() == 1)
if sv.enabled and type(MTH_ZBar_ApplyLayout) == "function" then
MTH_ZBar_ApplyLayout()
end
end)
end
-- Move Down (-)
local downBtn = CreateFrame("Button", nil, container, "UIPanelButtonTemplate")
downBtn:SetPoint("TOPLEFT", container, "TOPLEFT", downX, listY)
downBtn:SetWidth(20)
downBtn:SetHeight(20)
downBtn:SetText("-")
downBtn.zbarIndex = i
downBtn:SetScript("OnClick", function()
local idx = this.zbarIndex
local sv = MTH_ZBar_GetSaved()
local tot = table.getn(sv.order)
if idx < tot then
local tmp = sv.order[idx + 1]
sv.order[idx + 1] = sv.order[idx]
sv.order[idx] = tmp
MTH_ResetAndSelectOptionsTab("ZBar")
if sv.enabled and type(MTH_ZBar_ApplyLayout) == "function" then
MTH_ZBar_ApplyLayout()
end
end
end)
-- Move Up (+)
local upBtn = CreateFrame("Button", nil, container, "UIPanelButtonTemplate")
upBtn:SetPoint("TOPLEFT", container, "TOPLEFT", upX, listY)
upBtn:SetWidth(20)
upBtn:SetHeight(20)
upBtn:SetText("+")
upBtn.zbarIndex = i
upBtn:SetScript("OnClick", function()
local idx = this.zbarIndex
local sv = MTH_ZBar_GetSaved()
if idx > 1 then
local tmp = sv.order[idx - 1]
sv.order[idx - 1] = sv.order[idx]
sv.order[idx] = tmp
MTH_ResetAndSelectOptionsTab("ZBar")
if sv.enabled and type(MTH_ZBar_ApplyLayout) == "function" then
MTH_ZBar_ApplyLayout()
end
end
end)
-- Bar name label
local lbl = container:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
lbl:SetPoint("TOPLEFT", container, "TOPLEFT", labelX, listY)
lbl:SetText(displayName)
lbl:SetWidth(labelWidth)
lbl:SetJustifyH("LEFT")
listY = listY - 25
end
end
function MTH_SetupPetOptions()
if not MTH_ZBUTTONS_READY then return end
MTH_SetupButtonOptions("MetaHuntOptionsPet", "zButtonPet", "ZPet", 10)
@@ -773,6 +1194,13 @@ function MTH_SetupToysOptions()
MTH_SetupButtonOptions("MetaHuntOptionsToys", "zButtonToys", "ZToys", maxButtons)
end
function MTH_SetupCraftOptions()
if not MTH_ZBUTTONS_READY then return end
local itemList = MTH_GetButtonItemList("zButtonCraft")
local maxButtons = itemList and table.getn(itemList) or 1
MTH_SetupButtonOptions("MetaHuntOptionsCraft", "zButtonCraft", "ZCraft", maxButtons)
end
function MTH_SetupSmartAmmoOptions()
if not MTH_ZBUTTONS_READY then return end
local container = MTH_GetFrame("MetaHuntOptionsSmartAmmo")
@@ -1095,7 +1523,7 @@ function MTH_SetupGeneralOptions()
end
end)
end
ensureHelpText(smartAmmoSection, "MetaHuntGeneralSmartAmmoReloadHelp", "Falls back to any available ammo when preferred ammo is missing.", -154)
ensureHelpText(smartAmmoSection, "MetaHuntGeneralSmartAmmoReloadHelp", "Falls back to any available ammo when equipped ammo is out of stock.", -154)
local weaponSwapEnabled = type(MTHSmartAmmo_GetWeaponSwapEnabled) == "function" and MTHSmartAmmo_GetWeaponSwapEnabled() and true or false
local weaponSwapToggle = ensureCheckbox(smartAmmoSection, "MetaHuntGeneralSmartAmmoWeaponSwapToggle", "Enable Weapon-Swap Auto Ammo", -180, weaponSwapEnabled)
+2 -1
View File
@@ -64,7 +64,7 @@
</Backdrop>
</Frame>
<!-- Content Areas -->
<Frame name="$parentZBar" hidden="true" enableMouse="true"/>
<Frame name="$parentGeneral" hidden="true" enableMouse="true"/>
<Frame name="$parentProfiles" hidden="true" enableMouse="true"/>
<Frame name="$parentMessages" hidden="true" enableMouse="true"/>
@@ -77,6 +77,7 @@
<Frame name="$parentMounts" hidden="true" enableMouse="true"/>
<Frame name="$parentCompanions" hidden="true" enableMouse="true"/>
<Frame name="$parentToys" hidden="true" enableMouse="true"/>
<Frame name="$parentCraft" hidden="true" enableMouse="true"/>
<Frame name="$parentSmartAmmo" hidden="true" enableMouse="true"/>
<Frame name="$parentFeedOMatic" hidden="true" enableMouse="true"/>
<Frame name="$parentAutoBuy" hidden="true" enableMouse="true"/>
+2 -2
View File
@@ -10,7 +10,7 @@ VC.lastPublishedAt = nil
VC.joinAt = nil
VC.notified = false
VC._invalidVersionWarned = false
VC.maxPersistedPeers = 200
VC.maxPersistedPeers = 10000
VC.seenPeers = VC.seenPeers or {}
local function VC_GetTimeNow()
@@ -141,7 +141,7 @@ end
function VC:PrunePersistedPeers(limit)
local maxCount = tonumber(limit) or tonumber(self.maxPersistedPeers) or 200
if maxCount <= 0 then
maxCount = 200
maxCount = 10000
end
if type(self.seenPeers) ~= "table" then
return