mirror of
https://codeberg.org/Duvelcorp/MetaHunt.git
synced 2026-09-27 01:56:04 +00:00
1.5.0
This commit is contained in:
+46
-4
@@ -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"
|
||||
|
||||
@@ -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
@@ -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
@@ -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",
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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")
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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
@@ -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"/>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user