diff --git a/CHANGELOG.md b/CHANGELOG.md index 0561666..2a70cdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,88 @@ All notable changes to MetaHunt will be documented in this file. + +## [1.5.0] - 2026-03-31 + + +### Changed + +- **Beast spawn coordinates — custom-zone remap**: Reworked 63 beast coordinate sets in the datastore. The largest changes move placeholder vanilla-zone coords onto the proper Turtle WoW custom zones for **Grim Reaches**, **Northwind**, and **Balor**, and clean up prairie beasts that were incorrectly spilling into **Thunder Bluff**. + +- **Beast spawn coordinates — Stonetalon recalibration**: Recalibrated several **Stonetalon Mountains** beast spawn clusters to match the Twow updated zone bounds and improve marker placement consistency. + +- **MM Widget — ClickThrough **: The whole widget is now clickable through, and ALT-drag to reposition it is thus no longer possible + +- **MM Widget — clearer bottom-cell display**: The bottom cell now shows the cooldown of the current shot you would use, and only shows `USE` when that shot is ready. + +- **Feed-O-Matic — NO-BUFF quarantine disabled**: The mechanism that permanently blacklisted a food item per pet family after a failed feed (no buff detected within 2.5 s) has been disabled. Foods are no longer quarantined on a no-buff timeout — the feed will simply retry without banning the item. This avoids false positives caused by server lag or client timeouts. + + +### Added + +- **zCraft — new zButton for crafting professions**: A new `zCraft` button (Options → zButtons → zCraft) scans your spellbook and shows a child button for each crafting profession you know — Alchemy, Blacksmithing, Cooking, Disenchanting, Enchanting, Engineering, Fishing, First Aid, Herbalism, Jewelcrafting, Leatherworking, Mining (Smelting), Skinning, Survival, and Tailoring. Clicking a child opens the corresponding tradeskill window. Disabled by default. + +- **zBar — unified draggable toolbar for all zButtons**: A new "zBar" tab (Options → zButtons → zBar) groups all enabled zButton bars into a single draggable anchor. Alt-drag the zBar to move the whole group together. Options: Horizontal or Vertical layout; which side children pop out (Up/Down for horizontal, Left/Right for vertical); spacing between buttons; anchor icon size; per-bar include/exclude toggle and drag-to-reorder. Disabled by default — existing layouts are untouched until you enable it. + +- **zAspect / zTrack — Smart Parent**: A new "Smart Parent" checkbox in Options → zButtons → zAspect and zTrack. When enabled, the parent button automatically toggles between your 1st and 2nd spell: if the 1st is active, the parent shows the 2nd, and vice-versa. Perfect for PvP — put Track Hidden and Track Humanoids as your first two tracking spells, and the parent always shows the one you're *not* currently using. When disabled, the parent always shows the 1st spell. Enabled by default. + +- **zPet — Smart Parent**: A new "Smart Parent" checkbox in Options → zButtons → zPet. When enabled, the parent button dynamically changes based on your pet's state: Revive Pet if dead, Mend Pet if hurt, Feed Pet if unhappy, or your default spell otherwise. When disabled, the parent always shows the 1st spell. Enabled by default. + +- **zMounts / zCompanions — Random Parent**: A new "Random Parent" checkbox in Options → zButtons → zMounts and zCompanions. When enabled, a random child mount is picked as the parent icon on each login and after each use. Disabled by default. + +- **zMounts — AQ Mount Filter**: A new "AQ Mount Filter" checkbox in Options → zButtons → zMounts. When enabled, Qiraji mounts are hidden outside AQ40, and only Qiraji mounts are shown inside AQ40. Enabled by default. + +- **zAspect — NamPower support**: The Aspect button now listens to `NamPower` aura events when available, so aspect changes update faster and without the old polling delay. + +- **MM Widget — NamPower support**: The widget now uses `NamPower` aura updates when available, which makes ammo proc tracking faster and more reliable. It still falls back to the old tracking method if NamPower is not installed. + +- **MM Widget — keybind **: The MM Widget options panel now has a "Set Key" button. Press any key (with optional ALT/CTRL/SHIFT modifiers), or mouse button/wheel to bind the MM Widget action, turning it into a One-button rotation feature. + +- **MM Widget — Quiver no-clip option**: New "Use Quiver no-clip casting" checkbox in the MM Widget options. When enabled and Quiver is installed, Aimed Shot, Steady Shot, and Multi-Shot of the Action bind are routed through Quiver's no-clip system to avoid clipping your auto-shot swing timer. + +- **MM Widget — Anchor **: Since the widget is now clickable-through, a new anchor system was implemented to move the widget: toggling the anchor button in the options shows 4 drag handles (top, bottom, left, right) outside the widget edge. Any handle can be dragged to reposition the widget. + +- **Hunter Book — Families stats modifiers**: The Families page now also shows `Health`, `Damage`, and `Armor` modifiers for every family. Those modifier values can be shown as raw multipliers or as `%` with a footer checkbox, and are color-coded for easier reading. Additionaly the footer recap now also shows the total number of families, and the Named/Coord counts are plugged on the new accurate server's data instead of old pfQuest data. + +- **FOM — keybind**: The Feed-O-Matic "Set Key" button in options now also accepts modifiers and mouse. + +- **/mth food command**: Added a new slash command to inspect Feed-O-Matic state, clear one item ban, or reset all food bans and quarantines. + +- **AntiDaze — NamPower support**: Daze detection now uses NamPower's aura events when available, replacing chat-message parsing for faster and more reliable aspect cancellation. + +- **Chronometer — NamPower support**: Timer bars now start and stop instantly via NamPower aura events when available. Bar durations are read directly from the NamPower aura array instead of guessed from spell data. + +- **Feed-O-Matic — NamPower support**: Mount and buff detection (Shadowmeld, Feign Death, eating, etc.) now uses the NamPower aura array when available, replacing the old tooltip-scraping method. + + + +### Fixed + +- **Pet lifecycle — duplicate / ghost pets in Stable**: Reworked pet identity with GUID-based matching and fixed stable tracking so the same pet is no longer split into multiple rows after tame, rename, stable swap, or `/reload`. Old stale pets should also stop reappearing in the Stable tab as fake active/stabled entries. + +- **Pet lifecycle — wrong taming info shown on old pets**: Fixed several cases where MetaHunt could invent tame data for pets that were only seen later through stable swaps or normal pet refreshes. Taming info is now only recorded from a real tame event, so old pets should no longer show fake tame location / tame date data. + +- **Hunter Book — Stable tab now trusts real stable slots**: The Stable tab now shows the current pet plus the pets that are actually in your stable slots, instead of trusting stale saved slot data from older sessions. + +- **Hunter Book — pet training kept on the correct pet**: Fixed cases where a pet could keep its row but lose its Training section after lifecycle repairs or row merges. Training/spellbook data now stays attached to the right pet. + +- **Hunter Book — stable feed item icons**: Fixed broken food icons in the Stable tab feed summary, including cases where foods like `Roasted Quail` were showing as a red square or question mark. + +- **Minimap markers moving with the player since 1.4.0**: Reworked minimap marker zone resolution and calibration so pins stay anchored to world locations again instead of sliding with player movement. The minimap path now uses exact current-zone lookup in pfQuest space, while the world map keeps the WMA-aware logic introduced for 1.18.1 zone support. + +- **Chronometer — "Grow bars upward" forgotten after `/reload`**: Checking the "Grow bars upward" option and reloading the UI would revert the bars back to growing downward, even though the checkbox still appeared checked. Fixed. + +- **Chronometer — bars appear below the anchor instead of above when "Grow bars upward" is enabled**: Even with "Grow bars upward" checked, the bars were still spawning a bit below the anchor and stacking upward from there, because of an existing issue in old `CandyBar` library. Fixed — bars now correctly start at the anchor and grow upward. + +- **Chronometer — Aimed Shot tracker removed**: Chronometer no longer tracks Aimed Shot cooldowns. The MM Widget now owns that behavior, so Chronometer keeps its hunter timers focused on the remaining spell and proc bars. + +- **MM Widget — Quiver no-clip covers Multi-Shot**: The Quiver no-clip option now explicitly includes Multi-Shot alongside Aimed Shot and Steady Shot. + +- **MM Widget — stale NamPower proc state**: The widget now resyncs from the NamPower aura array after aura events, so it no longer keeps showing a proc after the debuff is gone. + +- **Quiver no-clip macro — Steady Shot castbar not showing**: If you were using Quiver and its no-clip macro for your shots, the Quiver castbar for those shots would not appear with MetaHunt enabled. Fixed — the castbar now appears correctly when using the macro. + + ## [1.4.1] - 2026-03-24 ### Added diff --git a/MetaHunt.toc b/MetaHunt.toc index 4d2de40..64dc1a1 100644 --- a/MetaHunt.toc +++ b/MetaHunt.toc @@ -1,5 +1,5 @@ ## Interface: 11200 -## Version: 1.4.1 +## Version: 1.5.0 ## Title: MetaHunt - |cff00ff00Hunter ## Author: Metasploit and his Copilot ;) ## Notes: Unified addon suite for huntards making old addons compatible with TurtleWoW, and adding an arsenal of never seen Hunter's utilities. diff --git a/README.MD b/README.MD index af23c41..536ea2a 100644 --- a/README.MD +++ b/README.MD @@ -1,9 +1,15 @@ # MetaHunt MetaHunt is a Turtle WoW Hunter toolkit. -It is a modern modular addon compiling brand new features, and improving a few old hunter add-ons, that were broken on twow. +It is a modern modular addon compiling brand new features, and improving a few old hunter add-ons that were broken on Twow. It provides a bunch of useful tools for all huntards, wheter they are still levelling, lone-wolf, or HL raider. +### Important notes +- Altough Nampower isn't mandatory for most of the features, it is a must-have for some. +- If you were using old versions of Feed-O-Matic, ICU, zHunterMod and HunterHelper, you dont need those with MetaHunt and you should disable them to avoid conflicts. +- MetaHunt is fully compatible with Quiver and is not a replacement for it. + + ## Twow Data MetaHunt ships with large Turtle WOW datastores : @@ -39,8 +45,8 @@ Old zHunterMod addon on steroids. Fully compatible with Twow, and enhanced with - zPet : Intelligent collapsible pet Bar regrouping all pet spells and providing an intelligent button that always show you the right spell to use depending on the situation - zTrack: All your trackings in one collapsible bar - zAspect: All your trackings in one collapsible bar -- zRanged : All the ranged weampons currently in your bags to swap them fast -- But also zMounts, zToys and zCompanions. +- zRanged : All the ranged weapons currently in your bags to swap them fast +- But also zBar, zCrafts, zMounts, zToys and zCompanions. ## Feed-O-Matic diff --git a/api/AntiDaze.lua b/api/AntiDaze.lua index 5d62cd1..d20ffba 100644 --- a/api/AntiDaze.lua +++ b/api/AntiDaze.lua @@ -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" diff --git a/api/AutoStrip.lua b/api/AutoStrip.lua index 2876689..762e448 100644 --- a/api/AutoStrip.lua +++ b/api/AutoStrip.lua @@ -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)) diff --git a/api/beastlore-scan.lua b/api/beastlore-scan.lua index 1d6b7d1..13e2240 100644 --- a/api/beastlore-scan.lua +++ b/api/beastlore-scan.lua @@ -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 diff --git a/api/constants.lua b/api/constants.lua index dd856fc..9cf8621 100644 --- a/api/constants.lua +++ b/api/constants.lua @@ -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", diff --git a/api/core-feed-tracking.lua b/api/core-feed-tracking.lua index c80bf09..565831e 100644 --- a/api/core-feed-tracking.lua +++ b/api/core-feed-tracking.lua @@ -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 diff --git a/api/core-framework.lua b/api/core-framework.lua index aee3d8f..9f90965 100644 --- a/api/core-framework.lua +++ b/api/core-framework.lua @@ -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 diff --git a/api/core-scan-stablemaster.lua b/api/core-scan-stablemaster.lua index e126329..196981e 100644 --- a/api/core-scan-stablemaster.lua +++ b/api/core-scan-stablemaster.lua @@ -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) diff --git a/api/core.lua b/api/core.lua index 9d5d708..4308a8e 100644 --- a/api/core.lua +++ b/api/core.lua @@ -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 |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 ") + 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 |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 diff --git a/api/hunterbook-tab-families.lua b/api/hunterbook-tab-families.lua index f72cba7..0922573 100644 --- a/api/hunterbook-tab-families.lua +++ b/api/hunterbook-tab-families.lua @@ -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 diff --git a/api/hunterbook-tab-stable.lua b/api/hunterbook-tab-stable.lua index acc1299..7ba3618 100644 --- a/api/hunterbook-tab-stable.lua +++ b/api/hunterbook-tab-stable.lua @@ -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 diff --git a/api/hunterbook.lua b/api/hunterbook.lua index 14e7f35..1e69c65 100644 --- a/api/hunterbook.lua +++ b/api/hunterbook.lua @@ -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 diff --git a/api/localization.lua b/api/localization.lua index cc156ab..970bbd3 100644 --- a/api/localization.lua +++ b/api/localization.lua @@ -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) diff --git a/api/map-markers.lua b/api/map-markers.lua index 6010eb7..965069e 100644 --- a/api/map-markers.lua +++ b/api/map-markers.lua @@ -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 diff --git a/api/options-chronometer.lua b/api/options-chronometer.lua index a4de4eb..446c3cf 100644 --- a/api/options-chronometer.lua +++ b/api/options-chronometer.lua @@ -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) diff --git a/api/options-expammo.lua b/api/options-expammo.lua index 41cc1af..780ba1a 100644 --- a/api/options-expammo.lua +++ b/api/options-expammo.lua @@ -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() diff --git a/api/options-feedomatic.lua b/api/options-feedomatic.lua index 272f64a..4d8dc3f 100644 --- a/api/options-feedomatic.lua +++ b/api/options-feedomatic.lua @@ -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") diff --git a/api/options-shell.lua b/api/options-shell.lua index 447dff1..e40931f 100644 --- a/api/options-shell.lua +++ b/api/options-shell.lua @@ -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() diff --git a/api/options-tree.lua b/api/options-tree.lua index 0edf721..1bb92b8 100644 --- a/api/options-tree.lua +++ b/api/options-tree.lua @@ -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" }, }, }, { diff --git a/api/options-zbuttons.lua b/api/options-zbuttons.lua index aa0cb48..e005654 100644 --- a/api/options-zbuttons.lua +++ b/api/options-zbuttons.lua @@ -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) diff --git a/api/options.xml b/api/options.xml index 96298a2..041aa9d 100644 --- a/api/options.xml +++ b/api/options.xml @@ -64,7 +64,7 @@ - + @@ -77,6 +77,7 @@ + diff --git a/api/version-check.lua b/api/version-check.lua index 363e0fc..8740742 100644 --- a/api/version-check.lua +++ b/api/version-check.lua @@ -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 diff --git a/data/ds-beasts-families.lua b/data/ds-beasts-families.lua index 4e9c6f0..149f0cf 100644 --- a/data/ds-beasts-families.lua +++ b/data/ds-beasts-families.lua @@ -9,6 +9,7 @@ MTH_DS_Families = { ["icon"] = "Ability_Hunter_Pet_Bat", ["named"] = 20, ["coords"] = 18, + ["stats"] = { ["health"] = 1, ["armor"] = 1, ["damage"] = 1.07 }, ["food"] = { "fruit", "fungus", @@ -25,6 +26,7 @@ MTH_DS_Families = { ["icon"] = "Ability_Hunter_Pet_Bear", ["named"] = 45, ["coords"] = 45, + ["stats"] = { ["health"] = 1.08, ["armor"] = 1.05, ["damage"] = 0.91 }, ["food"] = { "meat", "fish", @@ -47,6 +49,7 @@ MTH_DS_Families = { ["icon"] = "Ability_Hunter_Pet_Boar", ["named"] = 45, ["coords"] = 87, + ["stats"] = { ["health"] = 1.04, ["armor"] = 1.09, ["damage"] = 0.9 }, ["food"] = { "meat", "fish", @@ -69,6 +72,7 @@ MTH_DS_Families = { ["icon"] = "Ability_Hunter_Pet_Vulture", ["named"] = 36, ["coords"] = 101, + ["stats"] = { ["health"] = 1, ["armor"] = 1.05, ["damage"] = 1 }, ["food"] = { "meat", "raw meat", @@ -86,6 +90,7 @@ MTH_DS_Families = { ["icon"] = "Ability_Hunter_Pet_Cat", ["named"] = 85, ["coords"] = 88, + ["stats"] = { ["health"] = 0.98, ["armor"] = 1, ["damage"] = 1.1 }, ["food"] = { "meat", "fish", @@ -105,6 +110,7 @@ MTH_DS_Families = { ["icon"] = "Ability_Hunter_Pet_Crab", ["named"] = 32, ["coords"] = 74, + ["stats"] = { ["health"] = 0.96, ["armor"] = 1.13, ["damage"] = 0.95 }, ["food"] = { "fruit", "fish", @@ -123,6 +129,7 @@ MTH_DS_Families = { ["icon"] = "Ability_Hunter_Pet_Crocolisk", ["named"] = 27, ["coords"] = 71, + ["stats"] = { ["health"] = 0.95, ["armor"] = 1.1, ["damage"] = 1 }, ["food"] = { "meat", "fish", @@ -140,6 +147,7 @@ MTH_DS_Families = { ["icon"] = "ability_hunter_aspectofthefox", ["named"] = 9, ["coords"] = 51, + ["stats"] = { ["health"] = 1, ["armor"] = 1, ["damage"] = 1.07 }, ["food"] = { "meat", "fruit", @@ -157,6 +165,7 @@ MTH_DS_Families = { ["icon"] = "Ability_Hunter_Pet_Gorilla", ["named"] = 24, ["coords"] = 117, + ["stats"] = { ["health"] = 1.04, ["armor"] = 1, ["damage"] = 1.02 }, ["food"] = { "fruit", "fungus", @@ -172,6 +181,7 @@ MTH_DS_Families = { ["icon"] = "Ability_Hunter_Pet_Hyena", ["named"] = 26, ["coords"] = 36, + ["stats"] = { ["health"] = 1, ["armor"] = 1.05, ["damage"] = 1 }, ["food"] = { "meat", "fruit", @@ -189,6 +199,7 @@ MTH_DS_Families = { ["icon"] = "Ability_Hunter_Pet_Owl", ["named"] = 16, ["coords"] = 59, + ["stats"] = { ["health"] = 1, ["armor"] = 1, ["damage"] = 1.07 }, ["food"] = { "meat", "raw meat", @@ -205,6 +216,7 @@ MTH_DS_Families = { ["icon"] = "Ability_Hunter_Pet_Raptor", ["named"] = 51, ["coords"] = 115, + ["stats"] = { ["health"] = 0.95, ["armor"] = 1.03, ["damage"] = 1.1 }, ["food"] = { "meat", "raw meat", @@ -222,6 +234,7 @@ MTH_DS_Families = { ["icon"] = "Ability_Hunter_Pet_Scorpid", ["named"] = 38, ["coords"] = 36, + ["stats"] = { ["health"] = 1, ["armor"] = 1.1, ["damage"] = 0.94 }, ["food"] = { "meat", "raw meat", @@ -237,6 +250,7 @@ MTH_DS_Families = { ["icon"] = "Ability_Hunter_Pet_Serpent1", ["named"] = 10, ["coords"] = 13, + ["stats"] = { ["health"] = 1.03, ["armor"] = 1.03, ["damage"] = 1.135 }, ["food"] = { "meat", "fish", @@ -254,6 +268,7 @@ MTH_DS_Families = { ["icon"] = "Ability_Hunter_Pet_Spider", ["named"] = 91, ["coords"] = 213, + ["stats"] = { ["health"] = 1, ["armor"] = 1, ["damage"] = 1.07 }, ["food"] = { "meat", "raw meat", @@ -269,6 +284,7 @@ MTH_DS_Families = { ["icon"] = "Ability_Hunter_Pet_TallStrider", ["named"] = 15, ["coords"] = 112, + ["stats"] = { ["health"] = 1.05, ["armor"] = 1, ["damage"] = 1 }, ["food"] = { "fruit", "fungus", @@ -286,6 +302,7 @@ MTH_DS_Families = { ["icon"] = "Ability_Hunter_Pet_Turtle", ["named"] = 25, ["coords"] = 42, + ["stats"] = { ["health"] = 1, ["armor"] = 1.13, ["damage"] = 0.9 }, ["food"] = { "fruit", "fish", @@ -303,6 +320,7 @@ MTH_DS_Families = { ["icon"] = "Ability_Hunter_Pet_WindSerpent", ["named"] = 27, ["coords"] = 42, + ["stats"] = { ["health"] = 1, ["armor"] = 1, ["damage"] = 1.07 }, ["food"] = { "fish", "cheese", @@ -321,6 +339,7 @@ MTH_DS_Families = { ["icon"] = "Ability_Hunter_Pet_Wolf", ["named"] = 81, ["coords"] = 99, + ["stats"] = { ["health"] = 1, ["armor"] = 1.05, ["damage"] = 1 }, ["food"] = { "meat", "raw meat", diff --git a/data/ds-beasts.lua b/data/ds-beasts.lua index d39a60f..077bf19 100644 --- a/data/ds-beasts.lua +++ b/data/ds-beasts.lua @@ -240,16 +240,16 @@ MTH_DS_Beasts = { {76.2, 76.0, 30}, {77.3, 76.5, 30}, {78.0, 78.0, 30}, - {77.1, 8.3, 34}, + {80.2, 83.2, 30}, {80.3, 81.1, 30}, - {79.6, 9.3, 34}, + {82.1, 84.0, 30}, {80.1, 81.8, 30}, {80.4, 81.7, 30}, {80.8, 81.2, 30}, {80.6, 80.2, 30}, {81.3, 82.5, 30}, - {81.5, 9.4, 34}, - {80.7, 11.2, 34}, + {83.6, 84.1, 30}, + {83.0, 85.5, 30}, {85.0, 82.9, 30}, {80.6, 78.8, 30}, {80.3, 78.2, 30}, @@ -276,8 +276,8 @@ MTH_DS_Beasts = { {80.3, 78.1, 30}, {79.3, 79.7, 30}, {79.2, 82.8, 30}, - {84.0, 11.2, 34}, - {83.9, 9.1, 34}, + {85.6, 85.5, 30}, + {85.5, 83.9, 30}, {87.1, 79.4, 30}, {87.1, 77.4, 30}, {85.9, 72.3, 30}, @@ -1065,11 +1065,11 @@ MTH_DS_Beasts = { {53.2, 82.4, 30}, {53.4, 79.9, 30}, {55.5, 76.6, 30}, - {42.9, 11.1, 34}, - {41.7, 9.8, 34}, - {43.9, 8.9, 34}, - {46.6, 9.2, 34}, - {44.6, 11.7, 34}, + {53.6, 85.4, 30}, + {52.7, 84.4, 30}, + {54.4, 83.7, 30}, + {56.5, 83.9, 30}, + {54.9, 85.9, 30}, {57.2, 82.4, 30}, {57.2, 76.4, 30}, {56.4, 75.2, 30}, @@ -1103,7 +1103,7 @@ MTH_DS_Beasts = { {71.8, 75.2, 30}, {70.8, 73.7, 30}, {73.8, 78.5, 30}, - {65.8, 8.8, 34}, + {71.4, 83.6, 30}, {72.9, 82.7, 30}, {73.5, 80.8, 30}, {69.5, 75.5, 30}, @@ -2452,7 +2452,7 @@ MTH_DS_Beasts = { {49.2, 82.8, 30}, {49.2, 75.9, 30}, {54.0, 75.5, 30}, - {42.7, 10.2, 34}, + {53.4, 84.7, 30}, {56.9, 75.9, 30}, {60.8, 79.1, 30}, {63.3, 77.0, 30}, @@ -2469,8 +2469,8 @@ MTH_DS_Beasts = { {74.6, 66.8, 30}, {77.2, 76.7, 30}, {78.1, 79.2, 30}, - {81.4, 10.2, 34}, - {76.5, 8.3, 34}, + {83.6, 84.7, 30}, + {79.7, 83.2, 30}, {81.1, 77.2, 30}, {82.2, 59.2, 30}, {77.9, 61.7, 30}, @@ -2483,8 +2483,8 @@ MTH_DS_Beasts = { {87.2, 69.3, 30}, {88.9, 77.5, 30}, {87.8, 81.1, 30}, - {14.0, 0.0, 81}, - {14.1, 0.0, 81}, + {23.0, 12.0, 81}, + {23.1, 12.0, 81}, }, ["displayId"] = 1006, ["skinId"] = 822, @@ -4468,7 +4468,7 @@ MTH_DS_Beasts = { {65.6, 51.0, 35}, {62.6, 54.4, 35}, {48.7, 72.4, 35}, - {57.8, 2.3, 17}, + {55.2, 78.3, 35}, {69.7, 51.6, 35}, {71.2, 50.9, 35}, {49.0, 73.0, 35}, @@ -9856,22 +9856,22 @@ MTH_DS_Beasts = { {29.1, 38.5, 15}, {33.4, 26.9, 15}, {35.5, 25.8, 15}, - {74.6, 39.8, 21}, + {16.7, 50.8, 15}, {35.8, 13.5, 15}, {10.6, 54.5, 15}, {21.2, 45.9, 15}, {26.3, 40.5, 15}, {36.6, 20.1, 15}, - {75.3, 39.1, 21}, + {17.7, 49.7, 15}, {91.5, 46.7, 15}, {34.3, 25.9, 15}, {33.4, 11.9, 15}, {35.8, 23.3, 15}, {35.9, 21.7, 15}, - {75.2, 39.6, 21}, + {17.6, 50.5, 15}, {19.9, 46.8, 15}, {14.2, 53.0, 15}, - {68.2, 82.5, 20}, + {29.7, 7.9, 15}, {31.7, 13.2, 15}, {63.9, 39.7, 24}, {56.8, 47.2, 24}, @@ -9890,7 +9890,7 @@ MTH_DS_Beasts = { {67.8, 35.3, 24}, {68.1, 28.9, 24}, {72.5, 68.6, 15}, - {68.0, 82.6, 20}, + {29.4, 8.1, 15}, }, ["displayId"] = 1244, ["skinId"] = 1244, @@ -11720,7 +11720,7 @@ MTH_DS_Beasts = { {43.7, 43.9, 9}, {36.7, 41.2, 9}, {43.7, 51.4, 9}, - {28.3, 100.0, 362}, + {35.6, 37.2, 9}, {38.2, 63.5, 9}, {40.5, 51.4, 9}, {40.0, 56.9, 9}, @@ -11733,8 +11733,8 @@ MTH_DS_Beasts = { {41.6, 63.4, 9}, {62.6, 54.8, 9}, {42.0, 66.4, 9}, - {38.3, 90.8, 362}, - {22.4, 84.8, 362}, + {37.6, 35.3, 9}, + {34.4, 34.1, 9}, {39.6, 66.3, 9}, {38.0, 67.1, 9}, {44.9, 40.8, 9}, @@ -11744,11 +11744,11 @@ MTH_DS_Beasts = { {46.0, 71.2, 9}, {35.7, 44.2, 9}, {36.4, 53.9, 9}, - {17.1, 70.2, 362}, + {33.3, 31.1, 9}, {54.6, 42.0, 9}, {39.3, 56.1, 9}, {41.1, 69.0, 9}, - {19.0, 68.0, 362}, + {33.7, 30.7, 9}, {38.8, 54.3, 9}, {56.0, 40.9, 9}, {36.8, 65.8, 9}, @@ -11776,7 +11776,7 @@ MTH_DS_Beasts = { {35.0, 74.4, 9}, {43.5, 38.9, 9}, {34.9, 79.9, 9}, - {74.0, 95.7, 362}, + {44.8, 36.3, 9}, {35.1, 66.4, 9}, {34.2, 69.9, 9}, {59.5, 57.1, 9}, @@ -11803,11 +11803,11 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 375, ["respawnMaxSeconds"] = 375, ["coords"] = { - {64.2, 23.0, 362}, + {42.8, 21.5, 9}, {55.9, 56.4, 9}, {47.4, 41.9, 9}, {47.9, 36.5, 9}, - {17.0, 47.6, 362}, + {33.3, 26.5, 9}, {68.2, 59.6, 9}, {66.2, 60.7, 9}, {64.2, 63.5, 9}, @@ -11816,23 +11816,23 @@ MTH_DS_Beasts = { {62.2, 64.4, 9}, {53.9, 44.1, 9}, {38.1, 47.0, 9}, - {63.5, 8.5, 362}, - {47.9, 9.4, 362}, + {42.7, 18.6, 9}, + {39.5, 18.8, 9}, {43.3, 47.0, 9}, - {41.5, 10.1, 362}, + {38.2, 18.9, 9}, {55.7, 50.6, 9}, {64.2, 61.7, 9}, {46.1, 44.3, 9}, {38.2, 39.8, 9}, {37.1, 43.0, 9}, {56.8, 44.8, 9}, - {79.8, 0.9, 362}, + {46.0, 17.0, 9}, {50.6, 46.1, 9}, {47.3, 10.2, 9}, {48.6, 14.7, 9}, {41.1, 13.2, 9}, - {92.5, 0.0, 362}, - {78.8, 8.3, 362}, + {48.6, 16.8, 9}, + {45.8, 18.5, 9}, {48.1, 10.0, 9}, {50.5, 14.0, 9}, {46.1, 10.7, 9}, @@ -11840,7 +11840,7 @@ MTH_DS_Beasts = { {60.3, 24.7, 9}, {57.7, 53.9, 9}, {51.1, 17.7, 9}, - {22.4, 6.3, 362}, + {34.4, 18.1, 9}, {54.0, 28.9, 9}, {54.3, 23.0, 9}, {54.5, 24.6, 9}, @@ -11854,32 +11854,32 @@ MTH_DS_Beasts = { {56.0, 29.5, 9}, {53.6, 34.1, 9}, {54.0, 31.3, 9}, - {72.7, 57.0, 362}, + {44.6, 28.4, 9}, {60.7, 25.6, 9}, {57.9, 20.6, 9}, {51.2, 18.6, 9}, {60.5, 53.6, 9}, {53.9, 12.6, 9}, {51.5, 27.9, 9}, - {42.1, 1.2, 362}, + {38.4, 17.1, 9}, {47.1, 7.6, 9}, {37.4, 14.8, 9}, {38.5, 12.6, 9}, {51.3, 10.1, 9}, - {95.7, 43.0, 362}, - {95.6, 62.9, 362}, + {49.2, 25.6, 9}, + {49.2, 29.6, 9}, {47.6, 13.7, 9}, {52.6, 32.5, 9}, {51.0, 23.6, 9}, {43.8, 10.2, 9}, {51.2, 26.6, 9}, {46.8, 14.0, 9}, - {70.7, 5.0, 362}, - {86.2, 8.8, 362}, - {72.9, 72.2, 362}, - {98.5, 61.4, 362}, + {44.2, 17.9, 9}, + {47.3, 18.6, 9}, + {44.6, 31.5, 9}, + {49.8, 29.3, 9}, {62.0, 26.3, 9}, - {87.9, 41.4, 362}, + {47.7, 25.3, 9}, {57.0, 17.9, 9}, {50.6, 8.1, 9}, {52.5, 35.2, 9}, @@ -11892,7 +11892,7 @@ MTH_DS_Beasts = { {53.2, 14.1, 9}, {52.5, 24.9, 9}, {52.0, 39.4, 9}, - {86.3, 50.2, 362}, + {47.3, 27.0, 9}, {56.8, 32.5, 9}, {52.0, 22.1, 9}, {55.8, 33.7, 9}, @@ -12063,11 +12063,11 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 375, ["respawnMaxSeconds"] = 375, ["coords"] = { - {36.1, 93.5, 362}, - {29.1, 95.8, 362}, - {35.8, 86.1, 362}, - {28.6, 77.2, 362}, - {29.9, 81.3, 362}, + {37.1, 35.8, 9}, + {35.7, 36.3, 9}, + {37.1, 34.3, 9}, + {35.6, 32.5, 9}, + {35.9, 33.4, 9}, {57.0, 62.5, 9}, {44.1, 41.1, 9}, {41.4, 45.2, 9}, @@ -12077,14 +12077,14 @@ MTH_DS_Beasts = { {48.0, 39.2, 9}, {46.7, 45.1, 9}, {41.5, 49.2, 9}, - {73.7, 100.0, 362}, + {44.8, 37.2, 9}, {55.8, 57.9, 9}, {39.5, 40.3, 9}, {45.4, 40.0, 9}, {47.8, 46.9, 9}, {49.3, 47.1, 9}, {42.7, 47.3, 9}, - {26.4, 39.6, 362}, + {35.2, 24.9, 9}, {46.6, 48.9, 9}, {56.9, 58.6, 9}, {46.8, 47.0, 9}, @@ -12094,13 +12094,13 @@ MTH_DS_Beasts = { {40.9, 42.2, 9}, {41.7, 40.2, 9}, {61.8, 69.3, 9}, - {22.4, 53.5, 362}, + {34.4, 27.7, 9}, {51.2, 44.2, 9}, {56.4, 59.5, 9}, {46.1, 33.6, 9}, {42.8, 50.4, 9}, {41.8, 51.0, 9}, - {26.1, 41.1, 362}, + {35.1, 25.2, 9}, {55.2, 50.2, 9}, {42.1, 46.0, 9}, {58.6, 62.5, 9}, @@ -12151,7 +12151,7 @@ MTH_DS_Beasts = { {60.3, 55.4, 9}, {42.0, 41.2, 9}, {50.2, 36.9, 9}, - {19.4, 58.5, 362}, + {33.7, 28.7, 9}, {58.9, 64.6, 9}, {54.4, 42.6, 9}, {54.1, 41.3, 9}, @@ -12162,12 +12162,12 @@ MTH_DS_Beasts = { {35.4, 51.7, 9}, {48.7, 31.1, 9}, {60.9, 56.0, 9}, - {21.6, 69.9, 362}, + {34.2, 31.1, 9}, {41.7, 52.0, 9}, {42.8, 53.0, 9}, {58.8, 52.7, 9}, {55.8, 44.0, 9}, - {57.1, 100.0, 362}, + {41.4, 37.2, 9}, {57.6, 47.2, 9}, {56.0, 45.2, 9}, {35.4, 50.0, 9}, @@ -12177,9 +12177,9 @@ MTH_DS_Beasts = { {55.4, 48.1, 9}, {54.5, 37.3, 9}, {59.4, 45.5, 9}, - {38.0, 91.3, 362}, + {37.5, 35.4, 9}, {60.5, 63.3, 9}, - {24.2, 93.3, 362}, + {34.7, 35.8, 9}, {58.2, 50.8, 9}, {38.9, 39.6, 9}, {38.3, 40.0, 9}, @@ -12224,20 +12224,20 @@ MTH_DS_Beasts = { ["respawnMaxSeconds"] = 375, ["coords"] = { {41.5, 8.1, 9}, - {44.8, 4.6, 362}, + {38.9, 17.8, 9}, {66.8, 63.6, 9}, {64.2, 65.4, 9}, {65.5, 65.4, 9}, {65.3, 63.2, 9}, {62.8, 61.7, 9}, {53.1, 32.5, 9}, - {53.8, 8.5, 362}, + {40.7, 18.6, 9}, {50.5, 32.2, 9}, - {94.8, 68.1, 362}, + {49.1, 30.7, 9}, {40.2, 10.1, 9}, - {71.8, 66.3, 362}, + {44.4, 30.3, 9}, {43.4, 9.0, 9}, - {94.9, 50.9, 362}, + {49.1, 27.2, 9}, {50.5, 19.7, 9}, {40.8, 11.1, 9}, {49.9, 11.8, 9}, @@ -12263,40 +12263,40 @@ MTH_DS_Beasts = { {66.2, 56.6, 9}, {57.4, 24.9, 9}, {52.2, 23.2, 9}, - {76.3, 0.0, 362}, + {45.3, 16.8, 9}, {42.9, 14.7, 9}, {42.7, 10.1, 9}, {50.0, 14.2, 9}, {53.9, 24.1, 9}, {57.6, 22.4, 9}, {57.1, 20.8, 9}, - {93.5, 33.7, 362}, + {48.8, 23.7, 9}, {66.2, 58.8, 9}, {63.4, 56.2, 9}, {62.1, 56.2, 9}, {61.5, 61.6, 9}, {56.2, 20.9, 9}, {54.6, 21.5, 9}, - {76.2, 9.5, 362}, - {76.5, 4.4, 362}, + {45.3, 18.8, 9}, + {45.3, 17.7, 9}, {62.3, 65.6, 9}, - {32.0, 0.0, 362}, + {36.3, 16.8, 9}, {51.0, 21.9, 9}, {50.6, 12.9, 9}, {49.2, 12.3, 9}, {52.8, 20.7, 9}, {52.5, 17.4, 9}, - {76.8, 47.9, 362}, + {45.4, 26.6, 9}, {57.1, 25.6, 9}, {52.0, 19.9, 9}, {65.2, 67.7, 9}, {58.2, 27.9, 9}, - {89.8, 57.5, 362}, + {48.0, 28.5, 9}, {59.0, 29.0, 9}, {56.3, 24.6, 9}, {64.8, 67.2, 9}, - {12.4, 16.1, 362}, - {67.5, 38.8, 362}, + {32.3, 20.1, 9}, + {43.5, 24.7, 9}, {50.9, 16.6, 9}, {66.1, 65.8, 9}, {57.4, 31.3, 9}, @@ -12304,7 +12304,7 @@ MTH_DS_Beasts = { {64.7, 67.8, 9}, {62.9, 68.1, 9}, {62.2, 66.3, 9}, - {36.2, 0.0, 362}, + {37.2, 16.8, 9}, {51.1, 14.2, 9}, {56.0, 31.3, 9}, {61.7, 68.3, 9}, @@ -12312,18 +12312,18 @@ MTH_DS_Beasts = { {66.8, 66.5, 9}, {53.3, 29.4, 9}, {53.1, 13.0, 9}, - {70.3, 65.9, 362}, - {82.3, 58.9, 362}, - {96.5, 0.0, 362}, - {77.2, 51.3, 362}, + {44.1, 30.2, 9}, + {46.5, 28.8, 9}, + {49.4, 16.8, 9}, + {45.5, 27.3, 9}, {52.3, 15.3, 9}, {49.3, 14.1, 9}, - {76.9, 60.5, 362}, + {45.4, 29.1, 9}, {50.5, 29.6, 9}, {54.1, 33.9, 9}, - {79.2, 54.8, 362}, + {45.9, 28.0, 9}, {56.8, 28.4, 9}, - {80.6, 48.2, 362}, + {46.2, 26.6, 9}, {61.0, 27.8, 9}, }, ["displayId"] = 161, @@ -12569,7 +12569,7 @@ MTH_DS_Beasts = { {51.8, 40.3, 9}, {56.7, 46.1, 9}, {35.2, 49.5, 9}, - {73.4, 88.8, 362}, + {44.7, 34.9, 9}, {47.4, 36.5, 9}, {52.1, 37.2, 9}, {60.2, 54.9, 9}, @@ -12598,7 +12598,7 @@ MTH_DS_Beasts = { {52.2, 30.3, 9}, {53.2, 23.7, 9}, {42.2, 10.0, 9}, - {38.4, 2.3, 362}, + {37.6, 17.3, 9}, {64.9, 59.6, 9}, {53.4, 18.9, 9}, {46.6, 13.4, 9}, @@ -12638,12 +12638,12 @@ MTH_DS_Beasts = { {38.1, 39.9, 9}, {42.8, 43.2, 9}, {50.5, 39.3, 9}, - {21.4, 47.2, 362}, + {34.2, 26.4, 9}, {47.3, 40.2, 9}, {43.3, 45.4, 9}, {54.4, 47.0, 9}, {46.3, 42.4, 9}, - {18.9, 43.1, 362}, + {33.6, 25.6, 9}, {44.2, 48.8, 9}, {45.2, 47.4, 9}, {39.0, 41.2, 9}, @@ -12684,18 +12684,18 @@ MTH_DS_Beasts = { {45.6, 38.1, 9}, {36.9, 48.6, 9}, {57.6, 49.5, 9}, - {60.7, 100.0, 362}, + {42.1, 37.2, 9}, {57.1, 62.8, 9}, {35.7, 42.2, 9}, - {54.4, 100.0, 362}, + {40.9, 37.2, 9}, {36.9, 44.0, 9}, {62.7, 51.6, 9}, {36.5, 48.6, 9}, - {24.7, 71.3, 362}, - {22.9, 78.1, 362}, + {34.8, 31.3, 9}, + {34.5, 32.7, 9}, {36.6, 43.0, 9}, {48.0, 43.1, 9}, - {25.7, 81.9, 362}, + {35.0, 33.5, 9}, {53.1, 40.2, 9}, {59.7, 64.8, 9}, {53.4, 40.7, 9}, @@ -12722,8 +12722,8 @@ MTH_DS_Beasts = { {47.3, 35.0, 9}, {52.0, 37.3, 9}, {53.2, 34.9, 9}, - {79.6, 67.5, 362}, - {73.1, 72.4, 362}, + {46.0, 30.6, 9}, + {44.7, 31.6, 9}, }, ["displayId"] = 1059, ["skinId"] = 1056, @@ -13035,7 +13035,7 @@ MTH_DS_Beasts = { {56.1, 17.0, 4}, {38.5, 48.9, 4}, {54.0, 18.9, 4}, - {57.3, 97.6, 321}, + {47.7, 12.7, 4}, {35.7, 31.0, 4}, {38.3, 28.9, 4}, {57.1, 19.3, 4}, @@ -13242,7 +13242,7 @@ MTH_DS_Beasts = { {61.5, 87.4, 4}, {61.0, 86.8, 4}, {70.5, 80.1, 4}, - {78.3, 48.2, 11}, + {63.3, 97.9, 4}, {66.1, 70.0, 4}, {65.9, 91.5, 4}, {62.1, 76.6, 4}, @@ -13315,17 +13315,17 @@ MTH_DS_Beasts = { {58.4, 15.7, 4}, {56.9, 10.2, 4}, {57.9, 15.2, 4}, - {75.4, 47.5, 11}, - {75.3, 47.0, 11}, - {76.0, 49.6, 11}, - {75.5, 50.7, 11}, - {74.9, 46.8, 11}, - {74.6, 46.6, 11}, - {74.2, 46.7, 11}, - {74.3, 46.5, 11}, - {73.5, 50.0, 11}, - {75.0, 50.8, 11}, - {75.6, 50.2, 11}, + {57.8, 96.6, 4}, + {57.6, 95.6, 4}, + {58.9, 100.6, 4}, + {58.0, 102.7, 4}, + {56.8, 95.3, 4}, + {56.2, 94.9, 4}, + {55.5, 95.1, 4}, + {55.7, 94.7, 4}, + {54.1, 101.4, 4}, + {57.0, 102.9, 4}, + {58.2, 101.8, 4}, }, ["displayId"] = 999, ["skinId"] = 981, @@ -14679,7 +14679,7 @@ MTH_DS_Beasts = { {58.4, 44.1, 11}, {58.3, 12.3, 11}, {58.9, 14.2, 11}, - {35.1, 17.1, 4}, + {63.6, 6.0, 11}, {52.1, 35.9, 11}, {50.0, 39.0, 11}, {44.5, 40.7, 11}, @@ -14910,7 +14910,7 @@ MTH_DS_Beasts = { {60.3, 50.4, 11}, {37.2, 26.8, 11}, {37.6, 27.5, 11}, - {60.2, 1.7, 9}, + {36.2, 28.8, 11}, {39.3, 25.6, 11}, {57.7, 6.2, 11}, {59.9, 7.5, 11}, @@ -15119,7 +15119,7 @@ MTH_DS_Beasts = { {40.9, 21.8, 11}, {40.9, 26.3, 11}, {42.7, 33.2, 11}, - {35.3, 17.6, 4}, + {63.7, 6.3, 11}, {53.3, 15.2, 11}, {59.7, 34.4, 11}, {53.5, 15.9, 11}, @@ -15163,16 +15163,16 @@ MTH_DS_Beasts = { {55.6, 14.6, 11}, {42.6, 32.7, 11}, {41.0, 21.0, 11}, - {34.9, 16.9, 4}, - {34.2, 58.3, 4}, - {35.0, 15.8, 4}, + {63.5, 5.9, 11}, + {63.1, 27.5, 11}, + {63.5, 5.3, 11}, {55.8, 16.1, 11}, {54.0, 15.5, 11}, {53.7, 15.7, 11}, {42.0, 23.5, 11}, {41.3, 24.3, 11}, {43.6, 35.1, 11}, - {34.5, 19.5, 4}, + {63.3, 7.3, 11}, {53.2, 15.5, 11}, {55.7, 15.9, 11}, {54.5, 14.5, 11}, @@ -15352,13 +15352,13 @@ MTH_DS_Beasts = { {51.9, 12.0, 9}, {53.4, 21.7, 9}, {44.4, 12.4, 9}, - {89.4, 0.3, 362}, + {48.0, 16.9, 9}, {42.6, 11.1, 9}, - {93.2, 8.5, 362}, + {48.7, 18.6, 9}, {55.9, 23.8, 9}, - {60.9, 14.3, 362}, + {42.2, 19.8, 9}, {37.5, 12.0, 9}, - {63.2, 11.7, 362}, + {42.6, 19.2, 9}, {57.0, 24.7, 9}, {44.5, 8.7, 9}, {52.0, 17.6, 9}, @@ -15366,45 +15366,45 @@ MTH_DS_Beasts = { {53.8, 21.7, 9}, {44.3, 11.2, 9}, {53.3, 25.5, 9}, - {88.5, 58.9, 362}, + {47.8, 28.8, 9}, {52.8, 23.9, 9}, {55.3, 18.4, 9}, - {71.1, 10.4, 362}, - {37.4, 0.4, 362}, + {44.3, 19.0, 9}, + {37.4, 16.9, 9}, {62.8, 58.4, 9}, {53.7, 20.6, 9}, {53.9, 19.0, 9}, {61.6, 24.8, 9}, {47.9, 15.3, 9}, - {70.2, 0.0, 362}, + {44.1, 16.8, 9}, {45.4, 12.0, 9}, - {91.8, 16.8, 362}, + {48.5, 20.3, 9}, {50.7, 22.5, 9}, {41.6, 9.8, 9}, {56.9, 17.9, 9}, {54.3, 19.5, 9}, {45.1, 13.7, 9}, - {98.3, 5.9, 362}, + {49.8, 18.0, 9}, {39.8, 11.1, 9}, {42.5, 7.3, 9}, - {60.1, 2.1, 362}, + {42.0, 17.3, 9}, {44.6, 15.1, 9}, - {27.3, 0.0, 362}, + {35.4, 16.8, 9}, {48.8, 8.9, 9}, - {66.4, 8.6, 362}, + {43.3, 18.6, 9}, {54.4, 16.9, 9}, {46.5, 11.8, 9}, {48.9, 11.5, 9}, {49.1, 10.1, 9}, {47.4, 8.6, 9}, {38.8, 9.2, 9}, - {75.0, 0.0, 362}, - {38.8, 5.8, 362}, + {45.0, 16.8, 9}, + {37.7, 18.0, 9}, {43.8, 11.3, 9}, - {91.1, 43.4, 362}, + {48.3, 25.7, 9}, {48.9, 12.8, 9}, {48.5, 13.8, 9}, - {12.2, 10.5, 362}, + {32.3, 19.0, 9}, {51.1, 12.1, 9}, {47.6, 13.0, 9}, {44.7, 13.9, 9}, @@ -15412,24 +15412,24 @@ MTH_DS_Beasts = { {56.4, 26.3, 9}, {62.3, 23.5, 9}, {50.3, 12.9, 9}, - {73.5, 0.0, 362}, - {66.4, 7.6, 362}, + {44.7, 16.8, 9}, + {43.3, 18.4, 9}, {56.4, 18.7, 9}, {57.1, 19.1, 9}, {57.8, 18.9, 9}, - {83.2, 15.2, 362}, + {46.7, 19.9, 9}, {62.1, 24.4, 9}, - {70.9, 45.5, 362}, - {81.1, 3.2, 362}, + {44.2, 26.1, 9}, + {46.3, 17.5, 9}, {53.4, 16.0, 9}, - {86.3, 40.8, 362}, + {47.3, 25.1, 9}, {44.1, 11.0, 9}, {50.6, 15.8, 9}, - {40.8, 0.0, 362}, + {38.1, 16.8, 9}, {51.6, 20.4, 9}, - {67.1, 2.8, 362}, + {43.4, 17.4, 9}, {56.4, 34.4, 9}, - {64.2, 37.0, 362}, + {42.8, 24.4, 9}, {57.2, 29.6, 9}, {54.5, 24.8, 9}, {55.0, 32.5, 9}, @@ -15441,12 +15441,12 @@ MTH_DS_Beasts = { {55.8, 21.3, 9}, {55.7, 24.7, 9}, {55.3, 23.1, 9}, - {81.3, 53.7, 362}, - {80.2, 52.4, 362}, + {46.3, 27.8, 9}, + {46.1, 27.5, 9}, {58.6, 29.8, 9}, {55.3, 34.2, 9}, {51.9, 25.2, 9}, - {74.8, 81.5, 362}, + {45.0, 33.4, 9}, {52.5, 25.8, 9}, {52.5, 12.0, 9}, {51.2, 28.4, 9}, @@ -15760,7 +15760,7 @@ MTH_DS_Beasts = { {67.0, 76.8, 43}, {66.1, 86.5, 43}, {67.9, 85.5, 43}, - {47.1, 4.6, 11}, + {66.8, 87.7, 43}, {42.1, 64.5, 43}, {50.4, 56.1, 43}, {66.5, 78.7, 43}, @@ -16325,7 +16325,7 @@ MTH_DS_Beasts = { {66.1, 86.0, 43}, {72.2, 81.7, 43}, {66.8, 85.6, 43}, - {46.4, 4.3, 11}, + {65.5, 87.2, 43}, {67.2, 85.9, 43}, {66.3, 84.7, 43}, {33.2, 59.6, 43}, @@ -16631,29 +16631,29 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 300, ["respawnMaxSeconds"] = 300, ["coords"] = { - {68.8, 88.6, 81}, - {67.3, 87.7, 81}, - {60.1, 75.1, 81}, - {60.5, 76.5, 81}, - {59.2, 78.1, 81}, - {58.9, 75.7, 81}, - {61.6, 81.8, 81}, - {61.4, 81.6, 81}, - {57.6, 76.3, 81}, - {56.2, 76.4, 81}, - {54.9, 75.9, 81}, - {55.9, 76.2, 81}, - {53.6, 76.2, 81}, - {53.4, 75.8, 81}, - {54.6, 75.2, 81}, - {54.7, 74.8, 81}, - {53.8, 75.9, 81}, - {64.3, 93.4, 81}, - {65.4, 92.3, 81}, - {60.8, 71.9, 81}, - {59.1, 72.7, 81}, - {64.9, 83.5, 81}, - {59.0, 73.8, 81}, + {67.2, 85.1, 81}, + {66.0, 84.4, 81}, + {60.2, 74.0, 81}, + {60.5, 75.1, 81}, + {59.4, 76.4, 81}, + {59.2, 74.4, 81}, + {61.4, 79.5, 81}, + {61.2, 79.3, 81}, + {58.2, 74.9, 81}, + {57.1, 75.0, 81}, + {56.0, 74.6, 81}, + {56.8, 74.9, 81}, + {54.9, 74.8, 81}, + {54.8, 74.5, 81}, + {55.7, 74.0, 81}, + {55.8, 73.7, 81}, + {55.1, 74.6, 81}, + {63.5, 89.1, 81}, + {64.4, 88.1, 81}, + {60.8, 71.3, 81}, + {59.4, 72.0, 81}, + {64.1, 80.8, 81}, + {59.3, 72.9, 81}, }, ["displayId"] = 760, ["skinId"] = 336, @@ -16674,51 +16674,51 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 300, ["respawnMaxSeconds"] = 300, ["coords"] = { - {60.7, 47.1, 81}, - {70.2, 46.1, 81}, - {69.6, 45.0, 81}, - {68.1, 45.1, 81}, - {69.2, 41.0, 81}, - {53.4, 71.7, 81}, - {53.0, 71.7, 81}, - {53.6, 71.2, 81}, - {54.1, 71.3, 81}, - {54.5, 71.9, 81}, - {75.7, 46.0, 81}, - {76.5, 46.0, 81}, - {77.4, 45.0, 81}, - {76.5, 45.1, 81}, - {76.2, 43.1, 81}, - {76.1, 53.1, 81}, - {61.3, 46.0, 81}, - {73.0, 45.3, 81}, - {75.7, 47.7, 81}, - {74.9, 46.9, 81}, - {74.8, 51.4, 81}, - {74.1, 44.5, 81}, - {74.2, 42.7, 81}, - {77.6, 50.9, 81}, - {60.8, 48.7, 81}, - {61.7, 48.5, 81}, - {59.8, 50.0, 81}, - {59.4, 49.3, 81}, - {73.6, 42.6, 81}, - {76.1, 50.6, 81}, - {75.7, 52.2, 81}, - {74.2, 43.7, 81}, - {60.1, 48.5, 81}, - {75.5, 44.5, 81}, - {74.3, 45.9, 81}, - {78.6, 52.9, 81}, - {76.4, 50.9, 81}, - {76.9, 52.5, 81}, - {76.8, 54.1, 81}, - {77.3, 50.3, 81}, - {77.5, 53.4, 81}, - {75.1, 43.2, 81}, - {74.6, 46.0, 81}, - {77.1, 44.3, 81}, - {79.3, 51.5, 81}, + {60.7, 50.9, 81}, + {68.3, 50.0, 81}, + {67.9, 49.2, 81}, + {66.6, 49.2, 81}, + {67.5, 45.8, 81}, + {54.8, 71.2, 81}, + {54.4, 71.2, 81}, + {54.9, 70.7, 81}, + {55.4, 70.8, 81}, + {55.6, 71.3, 81}, + {72.8, 49.9, 81}, + {73.4, 50.0, 81}, + {74.1, 49.1, 81}, + {73.4, 49.2, 81}, + {73.2, 47.6, 81}, + {73.1, 55.8, 81}, + {61.1, 49.9, 81}, + {70.6, 49.4, 81}, + {72.8, 51.3, 81}, + {72.1, 50.6, 81}, + {72.0, 54.4, 81}, + {71.5, 48.7, 81}, + {71.5, 47.2, 81}, + {74.3, 54.0, 81}, + {60.7, 52.2, 81}, + {61.5, 52.0, 81}, + {59.9, 53.2, 81}, + {59.6, 52.7, 81}, + {71.1, 47.2, 81}, + {73.1, 53.7, 81}, + {72.8, 55.0, 81}, + {71.5, 48.0, 81}, + {60.2, 52.0, 81}, + {72.6, 48.7, 81}, + {71.6, 49.9, 81}, + {75.1, 55.6, 81}, + {73.3, 54.0, 81}, + {73.7, 55.3, 81}, + {73.6, 56.7, 81}, + {74.0, 53.5, 81}, + {74.2, 56.0, 81}, + {72.3, 47.6, 81}, + {71.9, 49.9, 81}, + {73.9, 48.5, 81}, + {75.7, 54.5, 81}, }, ["displayId"] = 1989, ["skinId"] = 336, @@ -16739,61 +16739,61 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 300, ["respawnMaxSeconds"] = 300, ["coords"] = { - {60.0, 61.6, 81}, - {52.5, 61.1, 81}, - {62.7, 60.3, 81}, - {64.8, 60.9, 81}, - {63.1, 61.4, 81}, - {62.4, 63.4, 81}, - {62.1, 62.8, 81}, - {61.6, 63.7, 81}, - {62.1, 63.8, 81}, - {61.6, 66.4, 81}, - {61.5, 64.9, 81}, - {61.0, 66.4, 81}, - {52.0, 54.2, 81}, - {50.9, 39.6, 81}, - {51.2, 42.4, 81}, - {53.2, 55.4, 81}, - {54.0, 61.5, 81}, - {53.4, 44.9, 81}, - {61.7, 58.1, 81}, - {61.2, 57.2, 81}, - {59.0, 60.7, 81}, - {53.2, 73.1, 81}, - {52.9, 72.9, 81}, - {52.9, 72.3, 81}, - {53.7, 74.1, 81}, - {52.5, 74.0, 81}, - {52.5, 74.8, 81}, - {52.2, 75.6, 81}, - {53.4, 74.9, 81}, - {53.0, 74.4, 81}, - {52.8, 75.6, 81}, - {52.7, 73.5, 81}, - {54.8, 73.6, 81}, - {48.3, 44.1, 81}, - {54.7, 72.8, 81}, - {52.6, 62.2, 81}, - {60.9, 63.8, 81}, - {60.5, 63.0, 81}, - {52.3, 58.4, 81}, - {61.8, 61.0, 81}, - {61.1, 60.4, 81}, - {61.1, 59.4, 81}, - {62.2, 59.4, 81}, - {60.6, 59.7, 81}, - {51.8, 47.3, 81}, - {51.8, 56.6, 81}, - {53.8, 72.7, 81}, - {54.0, 73.9, 81}, - {48.5, 48.6, 81}, - {52.4, 41.8, 81}, - {47.2, 47.4, 81}, - {53.2, 43.0, 81}, - {46.8, 46.2, 81}, - {49.1, 47.3, 81}, - {48.9, 45.4, 81}, + {60.1, 62.8, 81}, + {54.0, 62.4, 81}, + {62.3, 61.8, 81}, + {64.0, 62.3, 81}, + {62.6, 62.6, 81}, + {62.1, 64.3, 81}, + {61.8, 63.8, 81}, + {61.4, 64.6, 81}, + {61.8, 64.6, 81}, + {61.4, 66.7, 81}, + {61.3, 65.6, 81}, + {60.9, 66.8, 81}, + {53.7, 56.7, 81}, + {52.8, 44.7, 81}, + {53.0, 46.9, 81}, + {54.6, 57.7, 81}, + {55.2, 62.7, 81}, + {54.8, 49.0, 81}, + {61.4, 59.9, 81}, + {61.0, 59.2, 81}, + {59.3, 62.0, 81}, + {54.6, 72.3, 81}, + {54.3, 72.1, 81}, + {54.3, 71.7, 81}, + {55.0, 73.1, 81}, + {54.1, 73.0, 81}, + {54.0, 73.7, 81}, + {53.8, 74.3, 81}, + {54.8, 73.8, 81}, + {54.4, 73.4, 81}, + {54.3, 74.3, 81}, + {54.2, 72.7, 81}, + {55.9, 72.7, 81}, + {50.7, 48.4, 81}, + {55.8, 72.0, 81}, + {54.2, 63.3, 81}, + {60.8, 64.6, 81}, + {60.5, 63.9, 81}, + {53.9, 60.2, 81}, + {61.6, 62.3, 81}, + {61.0, 61.8, 81}, + {61.0, 61.0, 81}, + {61.9, 61.0, 81}, + {60.5, 61.3, 81}, + {53.5, 51.0, 81}, + {53.5, 58.7, 81}, + {55.1, 72.0, 81}, + {55.3, 73.0, 81}, + {50.9, 52.1, 81}, + {54.0, 46.5, 81}, + {49.7, 51.1, 81}, + {54.6, 47.5, 81}, + {49.5, 50.1, 81}, + {51.3, 51.0, 81}, + {51.1, 49.4, 81}, }, ["displayId"] = 759, ["skinId"] = 336, @@ -16851,21 +16851,21 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 300, ["respawnMaxSeconds"] = 300, ["coords"] = { - {32.1, 11.5, 81}, - {32.7, 11.1, 81}, - {33.3, 12.3, 81}, - {34.5, 10.1, 81}, - {34.3, 8.0, 81}, - {34.5, 9.1, 81}, - {32.2, 8.9, 81}, - {33.3, 9.9, 81}, - {31.0, 11.5, 81}, - {33.0, 8.0, 81}, - {31.6, 12.7, 81}, - {32.2, 10.4, 81}, - {39.6, 17.7, 81}, - {39.0, 12.1, 81}, - {30.9, 9.6, 81}, + {37.6, 21.5, 81}, + {38.1, 21.2, 81}, + {38.6, 22.1, 81}, + {39.6, 20.3, 81}, + {39.4, 18.6, 81}, + {39.5, 19.5, 81}, + {37.7, 19.3, 81}, + {38.6, 20.2, 81}, + {36.7, 21.5, 81}, + {38.4, 18.6, 81}, + {37.2, 22.5, 81}, + {37.7, 20.5, 81}, + {43.6, 26.6, 81}, + {43.1, 22.0, 81}, + {36.7, 19.9, 81}, }, ["displayId"] = 11453, ["skinId"] = 11448, @@ -16888,7 +16888,7 @@ MTH_DS_Beasts = { ["coords"] = { {40.5, 50.2, 61}, {40.9, 58.3, 61}, - {45.4, 94.6, 11}, + {35.1, 27.7, 61}, {37.1, 41.9, 61}, {37.4, 55.1, 61}, }, @@ -17040,21 +17040,21 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 300, ["respawnMaxSeconds"] = 300, ["coords"] = { - {44.7, 93.4, 11}, + {33.5, 24.9, 61}, {41.7, 52.4, 61}, {41.4, 58.7, 61}, {44.2, 59.2, 61}, {45.7, 58.7, 61}, {36.8, 41.0, 61}, {40.4, 51.7, 61}, - {45.4, 94.1, 11}, + {35.1, 26.5, 61}, {33.0, 34.1, 61}, {33.1, 31.4, 61}, {33.8, 30.2, 61}, {51.1, 48.5, 61}, {49.3, 46.8, 61}, {50.3, 47.1, 61}, - {45.2, 94.8, 11}, + {34.6, 28.2, 61}, {40.1, 56.1, 61}, {47.8, 59.2, 61}, {46.5, 59.6, 61}, @@ -17155,7 +17155,7 @@ MTH_DS_Beasts = { {54.4, 11.8, 11}, {56.3, 15.5, 11}, {54.7, 12.7, 11}, - {36.0, 14.4, 4}, + {64.0, 4.6, 11}, {62.5, 28.9, 11}, {60.4, 35.5, 11}, {48.1, 42.4, 11}, @@ -17173,14 +17173,14 @@ MTH_DS_Beasts = { {62.7, 28.1, 11}, {54.7, 10.2, 11}, {56.2, 17.5, 11}, - {33.6, 57.5, 4}, + {62.8, 27.1, 11}, {61.5, 8.4, 11}, {43.3, 40.5, 11}, {54.7, 12.2, 11}, {59.2, 18.1, 11}, {43.9, 20.7, 11}, {42.5, 19.7, 11}, - {34.9, 20.1, 4}, + {63.5, 7.6, 11}, {54.2, 45.0, 11}, {46.6, 42.0, 11}, {58.3, 18.3, 11}, @@ -19769,7 +19769,7 @@ MTH_DS_Beasts = { {64.4, 22.4, 101}, {66.9, 25.8, 101}, {76.0, 34.7, 101}, - {26.0, 81.3, 81}, + {32.7, 79.1, 81}, {68.6, 34.8, 101}, {58.4, 11.7, 101}, {65.5, 35.6, 101}, @@ -26781,7 +26781,7 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 300, ["respawnMaxSeconds"] = 300, ["coords"] = { - {52.0, 73.8, 81}, + {53.6, 72.9, 81}, }, ["displayId"] = 11348, ["skinId"] = 366, @@ -28414,18 +28414,18 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 300, ["respawnMaxSeconds"] = 300, ["coords"] = { - {78.4, 78.4, 81}, - {79.3, 80.4, 81}, - {76.3, 81.2, 81}, - {75.5, 78.9, 81}, - {77.9, 74.3, 81}, - {80.1, 77.1, 81}, - {82.1, 75.7, 81}, - {82.2, 77.1, 81}, - {82.5, 80.1, 81}, - {85.6, 80.7, 81}, - {87.0, 81.3, 81}, - {84.6, 79.8, 81}, + {74.9, 76.6, 81}, + {75.7, 78.3, 81}, + {73.2, 79.0, 81}, + {72.6, 77.1, 81}, + {74.5, 73.3, 81}, + {76.3, 75.6, 81}, + {77.9, 74.5, 81}, + {78.0, 75.6, 81}, + {78.2, 78.1, 81}, + {80.7, 78.6, 81}, + {81.8, 79.0, 81}, + {79.9, 77.8, 81}, }, ["displayId"] = 3027, ["skinId"] = 3026, @@ -28446,15 +28446,15 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 120, ["respawnMaxSeconds"] = 120, ["coords"] = { - {80.0, 61.1, 81}, - {79.6, 60.5, 81}, - {79.1, 60.1, 81}, - {78.4, 60.4, 81}, - {78.0, 59.4, 81}, - {78.7, 59.7, 81}, - {79.4, 59.2, 81}, - {80.8, 60.6, 81}, - {80.4, 58.5, 81}, + {76.2, 62.4, 81}, + {75.9, 61.9, 81}, + {75.5, 61.6, 81}, + {75.0, 61.8, 81}, + {74.6, 61.0, 81}, + {75.2, 61.3, 81}, + {75.7, 60.9, 81}, + {76.8, 62.0, 81}, + {76.5, 60.2, 81}, }, ["displayId"] = 10825, ["skinId"] = 410, @@ -31084,27 +31084,27 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 300, ["respawnMaxSeconds"] = 300, ["coords"] = { - {43.6, 27.9, 81}, - {43.1, 28.1, 81}, - {42.8, 29.4, 81}, - {42.3, 29.5, 81}, - {42.1, 30.3, 81}, - {41.6, 30.6, 81}, - {41.4, 29.7, 81}, - {41.2, 28.5, 81}, - {40.7, 27.9, 81}, - {41.7, 28.4, 81}, - {42.1, 28.6, 81}, - {42.5, 28.3, 81}, - {42.8, 27.6, 81}, - {42.0, 27.4, 81}, - {41.7, 26.9, 81}, - {41.2, 26.7, 81}, - {41.1, 26.0, 81}, - {41.1, 25.7, 81}, - {41.4, 25.8, 81}, - {41.8, 29.4, 81}, - {44.1, 27.9, 81}, + {46.9, 35.0, 81}, + {46.5, 35.1, 81}, + {46.2, 36.2, 81}, + {45.8, 36.3, 81}, + {45.6, 37.0, 81}, + {45.3, 37.2, 81}, + {45.1, 36.5, 81}, + {45.0, 35.5, 81}, + {44.6, 35.1, 81}, + {45.3, 35.4, 81}, + {45.7, 35.6, 81}, + {46.0, 35.3, 81}, + {46.2, 34.7, 81}, + {45.6, 34.6, 81}, + {45.3, 34.2, 81}, + {44.9, 34.0, 81}, + {44.9, 33.5, 81}, + {44.8, 33.2, 81}, + {45.1, 33.3, 81}, + {45.4, 36.3, 81}, + {47.3, 35.0, 81}, }, ["displayId"] = 759, ["skinId"] = 336, @@ -31374,47 +31374,47 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 120, ["respawnMaxSeconds"] = 120, ["coords"] = { - {43.1, 62.7, 14}, - {43.0, 62.6, 14}, - {43.0, 62.8, 14}, - {43.1, 63.0, 14}, - {43.0, 62.9, 14}, - {43.0, 63.0, 14}, - {42.9, 63.2, 14}, - {42.8, 63.2, 14}, - {43.4, 64.7, 14}, - {43.4, 64.8, 14}, - {43.4, 64.9, 14}, - {43.1, 64.9, 14}, - {43.4, 65.2, 14}, - {43.2, 64.8, 14}, - {42.8, 65.4, 14}, - {42.6, 65.4, 14}, - {42.9, 65.3, 14}, - {42.6, 65.0, 14}, - {43.9, 64.1, 14}, - {43.7, 63.6, 14}, - {43.7, 64.0, 14}, - {44.1, 64.7, 14}, - {43.9, 64.6, 14}, - {43.6, 64.8, 14}, - {43.7, 64.5, 14}, - {44.6, 65.0, 14}, - {44.5, 65.0, 14}, - {44.2, 65.0, 14}, - {44.1, 65.1, 14}, - {43.8, 65.3, 14}, - {43.7, 65.3, 14}, - {43.7, 65.0, 14}, - {44.1, 64.4, 14}, - {43.9, 64.5, 14}, - {44.7, 64.9, 14}, - {44.8, 65.0, 14}, - {12.7, 99.2, 27}, - {42.3, 62.7, 14}, - {42.1, 62.8, 14}, - {42.2, 63.2, 14}, - {41.4, 62.4, 14}, + {44.1, 42.6, 686}, + {43.0, 41.6, 686}, + {43.0, 43.7, 686}, + {44.1, 45.9, 686}, + {43.0, 44.8, 686}, + {43.0, 45.9, 686}, + {41.9, 48.1, 686}, + {40.8, 48.1, 686}, + {47.4, 64.4, 686}, + {47.4, 65.5, 686}, + {47.4, 66.6, 686}, + {44.1, 66.6, 686}, + {47.4, 69.8, 686}, + {45.2, 65.5, 686}, + {40.8, 72.0, 686}, + {38.7, 72.0, 686}, + {41.9, 70.9, 686}, + {38.7, 67.7, 686}, + {52.8, 57.9, 686}, + {50.6, 52.4, 686}, + {50.6, 56.8, 686}, + {55.0, 64.4, 686}, + {52.8, 63.3, 686}, + {49.5, 65.5, 686}, + {50.6, 62.2, 686}, + {60.4, 67.7, 686}, + {59.3, 67.7, 686}, + {56.0, 67.7, 686}, + {55.0, 68.8, 686}, + {51.7, 70.9, 686}, + {50.6, 70.9, 686}, + {50.6, 67.7, 686}, + {55.0, 61.1, 686}, + {52.8, 62.2, 686}, + {61.5, 66.6, 686}, + {62.6, 67.7, 686}, + {33.4, 37.4, 686}, + {35.4, 42.6, 686}, + {33.2, 43.7, 686}, + {34.3, 48.1, 686}, + {25.6, 39.4, 686}, }, ["displayId"] = 20504, ["skinId"] = 20504, @@ -31435,83 +31435,83 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 120, ["respawnMaxSeconds"] = 300, ["coords"] = { - {42.2, 65.1, 14}, - {42.1, 65.0, 14}, - {42.1, 65.2, 14}, - {41.9, 65.0, 14}, - {41.7, 64.8, 14}, - {42.5, 64.6, 14}, - {42.3, 64.6, 14}, - {23.1, 94.6, 27}, - {22.1, 95.4, 27}, - {20.6, 95.4, 27}, - {20.8, 93.2, 27}, - {19.9, 93.7, 27}, - {22.0, 93.5, 27}, - {23.9, 93.4, 27}, - {25.9, 97.7, 27}, - {27.2, 98.7, 27}, - {28.5, 97.2, 27}, - {44.4, 62.4, 14}, - {30.4, 99.0, 27}, - {43.7, 62.4, 14}, - {43.6, 62.5, 14}, - {43.6, 62.9, 14}, - {43.4, 62.8, 14}, - {30.9, 96.8, 27}, - {32.3, 97.5, 27}, - {36.1, 92.8, 27}, - {33.1, 99.5, 27}, - {45.1, 62.4, 14}, - {35.8, 98.1, 27}, - {37.3, 95.2, 27}, - {37.6, 91.8, 27}, - {45.6, 62.7, 14}, - {45.7, 62.9, 14}, - {45.6, 63.4, 14}, - {45.3, 63.5, 14}, - {45.2, 64.0, 14}, - {45.1, 63.2, 14}, - {45.0, 63.4, 14}, - {44.7, 63.7, 14}, - {44.3, 63.2, 14}, - {44.1, 63.8, 14}, - {44.6, 64.1, 14}, - {44.7, 64.3, 14}, - {44.9, 64.5, 14}, - {32.9, 0.7, 30}, - {45.4, 64.3, 14}, - {45.6, 63.9, 14}, - {45.8, 64.0, 14}, - {45.9, 64.1, 14}, - {45.6, 64.7, 14}, - {45.5, 64.7, 14}, - {45.9, 64.5, 14}, - {46.0, 64.5, 14}, - {46.1, 65.0, 14}, - {45.8, 65.2, 14}, - {45.8, 65.3, 14}, - {45.9, 65.5, 14}, - {45.9, 65.5, 14}, - {45.7, 65.4, 14}, - {45.8, 64.8, 14}, - {46.1, 64.4, 14}, - {46.1, 63.2, 14}, - {0.8, 12.5, 29}, - {46.1, 62.9, 14}, - {46.0, 62.9, 14}, - {45.9, 62.7, 14}, - {46.0, 62.6, 14}, - {41.6, 99.1, 27}, - {0.4, 4.5, 29}, - {38.7, 98.3, 27}, - {45.8, 62.4, 14}, - {22.4, 97.3, 27}, - {43.3, 62.5, 14}, - {46.0, 63.5, 14}, - {46.1, 63.5, 14}, - {1.6, 19.0, 29}, - {2.1, 85.0, 28}, + {34.3, 68.8, 686}, + {33.2, 67.7, 686}, + {33.2, 69.8, 686}, + {31.1, 67.7, 686}, + {28.9, 65.5, 686}, + {37.6, 63.3, 686}, + {35.4, 63.3, 686}, + {49.2, 30.4, 686}, + {47.7, 31.6, 686}, + {45.4, 31.6, 686}, + {45.7, 28.3, 686}, + {44.3, 29.0, 686}, + {47.5, 28.7, 686}, + {50.4, 28.6, 686}, + {53.4, 35.1, 686}, + {55.4, 36.7, 686}, + {57.4, 34.4, 686}, + {58.2, 39.4, 686}, + {60.3, 37.1, 686}, + {50.6, 39.4, 686}, + {49.5, 40.5, 686}, + {49.5, 44.8, 686}, + {47.4, 43.7, 686}, + {61.0, 33.8, 686}, + {63.2, 34.8, 686}, + {68.9, 27.7, 686}, + {64.4, 37.9, 686}, + {65.8, 39.4, 686}, + {68.5, 35.7, 686}, + {70.7, 31.3, 686}, + {71.2, 26.2, 686}, + {71.2, 42.6, 686}, + {72.3, 44.8, 686}, + {71.2, 50.3, 686}, + {68.0, 51.4, 686}, + {66.9, 56.8, 686}, + {65.8, 48.1, 686}, + {64.7, 50.3, 686}, + {61.5, 53.5, 686}, + {57.1, 48.1, 686}, + {55.0, 54.6, 686}, + {60.4, 57.9, 686}, + {61.5, 60.1, 686}, + {63.6, 62.2, 686}, + {57.5, 75.5, 686}, + {69.1, 60.1, 686}, + {71.2, 55.7, 686}, + {73.4, 56.8, 686}, + {74.5, 57.9, 686}, + {71.2, 64.4, 686}, + {70.2, 64.4, 686}, + {74.5, 62.2, 686}, + {75.6, 62.2, 686}, + {76.7, 67.7, 686}, + {73.4, 69.8, 686}, + {73.4, 70.9, 686}, + {74.5, 73.1, 686}, + {74.5, 73.1, 686}, + {72.3, 72.0, 686}, + {73.4, 65.5, 686}, + {76.7, 61.1, 686}, + {76.7, 48.1, 686}, + {78.6, 44.0, 686}, + {76.7, 44.8, 686}, + {75.6, 44.8, 686}, + {74.5, 42.6, 686}, + {75.6, 41.6, 686}, + {77.3, 37.3, 686}, + {78.3, 36.7, 686}, + {72.9, 36.1, 686}, + {73.4, 39.4, 686}, + {48.1, 34.5, 686}, + {46.3, 40.5, 686}, + {75.6, 51.4, 686}, + {76.7, 51.4, 686}, + {79.3, 49.8, 686}, + {81.1, 48.1, 686}, }, ["displayId"] = 20504, ["skinId"] = 20504, @@ -31532,26 +31532,26 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 120, ["respawnMaxSeconds"] = 120, ["coords"] = { - {43.2, 65.2, 14}, - {43.0, 65.3, 14}, - {42.9, 65.4, 14}, - {42.6, 65.1, 14}, - {42.4, 65.1, 14}, - {44.0, 64.2, 14}, - {44.0, 64.6, 14}, - {44.0, 64.9, 14}, - {44.9, 64.9, 14}, - {44.7, 64.7, 14}, - {44.6, 65.2, 14}, - {44.4, 65.0, 14}, - {44.1, 65.2, 14}, - {44.2, 65.4, 14}, - {42.0, 62.4, 14}, - {42.1, 62.5, 14}, - {42.3, 62.6, 14}, - {42.0, 63.2, 14}, - {41.7, 63.2, 14}, - {43.9, 65.1, 14}, + {45.2, 69.8, 686}, + {43.0, 70.9, 686}, + {41.9, 72.0, 686}, + {38.7, 68.8, 686}, + {36.5, 68.8, 686}, + {53.9, 59.0, 686}, + {53.9, 63.3, 686}, + {53.9, 66.6, 686}, + {63.6, 66.6, 686}, + {61.5, 64.4, 686}, + {60.4, 69.8, 686}, + {58.2, 67.7, 686}, + {55.0, 69.8, 686}, + {56.0, 72.0, 686}, + {32.1, 39.4, 686}, + {33.2, 40.5, 686}, + {35.4, 41.6, 686}, + {32.1, 48.1, 686}, + {28.9, 48.1, 686}, + {52.8, 68.8, 686}, }, ["displayId"] = 1006, ["skinId"] = 822, @@ -31572,88 +31572,88 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 120, ["respawnMaxSeconds"] = 300, ["coords"] = { - {10.0, 96.1, 27}, - {9.0, 95.3, 27}, - {9.7, 97.8, 27}, - {11.0, 96.7, 27}, - {11.5, 97.3, 27}, - {12.0, 97.0, 27}, - {12.8, 97.0, 27}, - {13.2, 96.0, 27}, - {12.9, 94.8, 27}, - {13.8, 95.5, 27}, - {42.0, 65.1, 14}, - {41.8, 65.0, 14}, - {41.8, 64.8, 14}, - {41.6, 64.9, 14}, - {29.4, 97.8, 27}, - {28.2, 99.7, 27}, - {30.0, 96.2, 27}, - {28.6, 95.2, 27}, - {45.5, 62.4, 14}, - {45.6, 62.4, 14}, - {37.1, 98.8, 27}, - {45.2, 62.7, 14}, - {44.9, 62.6, 14}, - {44.7, 62.6, 14}, - {44.6, 62.7, 14}, - {44.8, 63.0, 14}, - {44.7, 63.1, 14}, - {44.5, 62.9, 14}, - {44.3, 62.6, 14}, - {44.1, 62.5, 14}, - {43.9, 62.4, 14}, - {43.5, 62.7, 14}, - {43.4, 62.6, 14}, - {32.7, 96.2, 27}, - {36.1, 91.2, 27}, - {36.2, 88.2, 27}, - {37.6, 87.9, 27}, - {36.0, 94.8, 27}, - {34.9, 99.6, 27}, - {37.5, 93.2, 27}, - {38.5, 90.2, 27}, - {45.6, 63.1, 14}, - {45.5, 63.0, 14}, - {45.4, 63.5, 14}, - {45.1, 64.2, 14}, - {45.2, 63.4, 14}, - {44.8, 63.5, 14}, - {44.5, 63.3, 14}, - {44.4, 63.4, 14}, - {44.2, 63.1, 14}, - {43.9, 63.7, 14}, - {44.4, 63.8, 14}, - {44.7, 64.1, 14}, - {44.8, 64.5, 14}, - {33.7, 0.0, 30}, - {31.5, 1.1, 30}, - {44.0, 65.5, 14}, - {44.1, 63.9, 14}, - {45.5, 64.1, 14}, - {45.5, 64.0, 14}, - {45.7, 64.2, 14}, - {45.9, 64.3, 14}, - {45.6, 64.3, 14}, - {45.7, 64.5, 14}, - {45.6, 65.0, 14}, - {45.8, 64.9, 14}, - {45.9, 64.7, 14}, - {46.0, 65.0, 14}, - {45.9, 63.2, 14}, - {1.4, 14.2, 29}, - {1.8, 12.3, 29}, - {46.2, 63.0, 14}, - {46.0, 62.7, 14}, - {46.0, 62.4, 14}, - {41.4, 100.0, 27}, - {1.1, 3.6, 29}, - {45.7, 62.7, 14}, - {45.7, 63.3, 14}, - {22.4, 99.1, 27}, - {21.5, 99.9, 27}, - {46.0, 63.8, 14}, - {46.1, 63.6, 14}, + {29.3, 32.7, 686}, + {27.7, 31.5, 686}, + {28.8, 35.3, 686}, + {30.8, 33.6, 686}, + {31.5, 34.5, 686}, + {32.3, 34.1, 686}, + {33.5, 34.1, 686}, + {34.1, 32.5, 686}, + {33.7, 30.7, 686}, + {35.0, 31.8, 686}, + {32.1, 68.8, 686}, + {30.0, 67.7, 686}, + {30.0, 65.5, 686}, + {27.8, 66.6, 686}, + {58.7, 35.3, 686}, + {56.9, 38.2, 686}, + {59.7, 32.9, 686}, + {57.5, 31.3, 686}, + {70.2, 39.4, 686}, + {71.2, 39.4, 686}, + {70.4, 36.8, 686}, + {66.9, 42.6, 686}, + {63.6, 41.6, 686}, + {61.5, 41.6, 686}, + {60.4, 42.6, 686}, + {62.6, 45.9, 686}, + {61.5, 47.0, 686}, + {59.3, 44.8, 686}, + {57.1, 41.6, 686}, + {55.0, 40.5, 686}, + {52.8, 39.4, 686}, + {48.4, 42.6, 686}, + {47.4, 41.6, 686}, + {63.8, 32.9, 686}, + {68.9, 25.2, 686}, + {69.1, 20.7, 686}, + {71.2, 20.2, 686}, + {68.8, 30.7, 686}, + {67.1, 38.0, 686}, + {71.1, 28.3, 686}, + {72.6, 23.7, 686}, + {71.2, 47.0, 686}, + {70.2, 45.9, 686}, + {69.1, 51.4, 686}, + {65.8, 59.0, 686}, + {66.9, 50.3, 686}, + {62.6, 51.4, 686}, + {59.3, 49.2, 686}, + {58.2, 50.3, 686}, + {56.0, 47.0, 686}, + {52.8, 53.5, 686}, + {58.2, 54.6, 686}, + {61.5, 57.9, 686}, + {62.6, 62.2, 686}, + {58.4, 74.8, 686}, + {56.0, 75.9, 686}, + {53.9, 73.1, 686}, + {55.0, 55.7, 686}, + {70.2, 57.9, 686}, + {70.2, 56.8, 686}, + {72.3, 59.0, 686}, + {74.5, 60.1, 686}, + {71.2, 60.1, 686}, + {72.3, 62.2, 686}, + {71.2, 67.7, 686}, + {73.4, 66.6, 686}, + {74.5, 64.4, 686}, + {75.6, 67.7, 686}, + {74.5, 48.1, 686}, + {79.2, 45.5, 686}, + {79.5, 43.8, 686}, + {77.8, 45.9, 686}, + {75.6, 42.6, 686}, + {75.6, 39.4, 686}, + {77.0, 38.6, 686}, + {78.9, 35.9, 686}, + {72.3, 42.6, 686}, + {72.3, 49.2, 686}, + {48.1, 37.3, 686}, + {46.7, 38.5, 686}, + {75.6, 54.6, 686}, + {76.7, 52.4, 686}, }, ["displayId"] = 1006, ["skinId"] = 822, @@ -31674,38 +31674,38 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 120, ["respawnMaxSeconds"] = 120, ["coords"] = { - {15.4, 95.7, 27}, - {15.8, 95.2, 27}, - {16.1, 96.0, 27}, - {16.5, 95.4, 27}, - {16.6, 94.9, 27}, - {17.1, 95.4, 27}, - {17.0, 96.3, 27}, - {17.5, 96.3, 27}, - {17.6, 97.2, 27}, - {18.0, 97.4, 27}, - {42.5, 64.9, 14}, - {42.4, 64.9, 14}, - {42.3, 65.0, 14}, - {43.9, 64.3, 14}, - {43.7, 64.8, 14}, - {44.7, 65.1, 14}, - {44.4, 65.1, 14}, - {44.3, 64.9, 14}, - {44.5, 64.7, 14}, - {44.0, 65.3, 14}, - {44.4, 65.4, 14}, - {43.9, 64.7, 14}, - {44.0, 64.4, 14}, - {19.1, 98.1, 27}, - {18.4, 98.1, 27}, - {11.8, 99.0, 27}, - {42.3, 62.4, 14}, - {42.1, 62.6, 14}, - {42.2, 63.4, 14}, - {41.9, 63.3, 14}, - {41.7, 63.3, 14}, - {44.0, 65.2, 14}, + {37.5, 32.1, 686}, + {38.1, 31.3, 686}, + {38.5, 32.5, 686}, + {39.1, 31.6, 686}, + {39.3, 30.9, 686}, + {40.1, 31.6, 686}, + {39.9, 33.0, 686}, + {40.7, 33.0, 686}, + {40.8, 34.4, 686}, + {41.4, 34.7, 686}, + {37.6, 66.6, 686}, + {36.5, 66.6, 686}, + {35.4, 67.7, 686}, + {52.8, 60.1, 686}, + {50.6, 65.5, 686}, + {61.5, 68.8, 686}, + {58.2, 68.8, 686}, + {57.1, 66.6, 686}, + {59.3, 64.4, 686}, + {53.9, 70.9, 686}, + {58.2, 72.0, 686}, + {52.8, 64.4, 686}, + {53.9, 61.1, 686}, + {43.1, 35.7, 686}, + {42.0, 35.7, 686}, + {32.0, 37.1, 686}, + {35.4, 39.4, 686}, + {33.2, 41.6, 686}, + {34.3, 50.3, 686}, + {31.1, 49.2, 686}, + {28.9, 49.2, 686}, + {53.9, 69.8, 686}, }, ["displayId"] = 11420, ["skinId"] = 9369, @@ -31726,67 +31726,67 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 120, ["respawnMaxSeconds"] = 300, ["coords"] = { - {15.7, 95.1, 27}, - {16.2, 94.9, 27}, - {16.7, 94.9, 27}, - {42.0, 65.2, 14}, - {41.8, 65.2, 14}, - {41.7, 64.7, 14}, - {42.6, 64.7, 14}, - {44.1, 62.7, 14}, - {44.3, 62.9, 14}, - {44.5, 62.6, 14}, - {44.6, 63.0, 14}, - {44.7, 62.8, 14}, - {44.8, 62.5, 14}, - {44.9, 62.8, 14}, - {45.0, 62.6, 14}, - {45.4, 62.7, 14}, - {45.4, 62.4, 14}, - {38.1, 99.3, 27}, - {38.0, 97.8, 27}, - {34.8, 95.6, 27}, - {35.2, 93.6, 27}, - {36.1, 89.4, 27}, - {35.3, 89.0, 27}, - {33.6, 94.7, 27}, - {36.8, 96.6, 27}, - {45.3, 63.2, 14}, - {45.5, 63.4, 14}, - {45.2, 63.8, 14}, - {44.9, 63.5, 14}, - {44.8, 63.4, 14}, - {44.4, 63.2, 14}, - {44.2, 63.3, 14}, - {44.0, 64.0, 14}, - {44.2, 63.6, 14}, - {44.5, 63.9, 14}, - {44.6, 64.5, 14}, - {45.0, 64.7, 14}, - {34.2, 1.5, 30}, - {30.4, 1.3, 30}, - {45.6, 63.8, 14}, - {45.7, 63.9, 14}, - {45.6, 64.1, 14}, - {45.8, 64.5, 14}, - {45.8, 64.3, 14}, - {45.6, 64.9, 14}, - {45.8, 64.7, 14}, - {45.7, 65.0, 14}, - {45.9, 63.1, 14}, - {46.0, 63.1, 14}, - {1.1, 15.8, 29}, - {46.0, 62.6, 14}, - {45.9, 62.5, 14}, - {0.6, 68.4, 28}, - {40.6, 99.1, 27}, - {45.7, 63.1, 14}, - {43.1, 62.3, 14}, - {0.9, 20.4, 29}, - {3.8, 85.2, 28}, - {46.2, 63.9, 14}, - {1.4, 27.2, 29}, - {1.4, 29.0, 29}, + {37.9, 31.2, 686}, + {38.7, 30.9, 686}, + {39.4, 30.9, 686}, + {32.1, 69.8, 686}, + {30.0, 69.8, 686}, + {28.9, 64.4, 686}, + {38.7, 64.4, 686}, + {55.0, 42.6, 686}, + {57.1, 44.8, 686}, + {59.3, 41.6, 686}, + {60.4, 45.9, 686}, + {61.5, 43.7, 686}, + {62.6, 40.5, 686}, + {63.6, 43.7, 686}, + {64.7, 41.6, 686}, + {69.1, 42.6, 686}, + {69.1, 39.4, 686}, + {72.0, 37.6, 686}, + {71.8, 35.3, 686}, + {66.9, 31.9, 686}, + {67.6, 28.9, 686}, + {68.9, 22.5, 686}, + {67.7, 21.9, 686}, + {65.1, 30.6, 686}, + {70.0, 33.5, 686}, + {68.0, 48.1, 686}, + {70.2, 50.3, 686}, + {66.9, 54.6, 686}, + {63.6, 51.4, 686}, + {62.6, 50.3, 686}, + {58.2, 48.1, 686}, + {56.0, 49.2, 686}, + {53.9, 56.8, 686}, + {56.0, 52.4, 686}, + {59.3, 55.7, 686}, + {60.4, 62.2, 686}, + {64.7, 64.4, 686}, + {58.9, 76.4, 686}, + {54.9, 76.2, 686}, + {71.2, 54.6, 686}, + {72.3, 55.7, 686}, + {71.2, 57.9, 686}, + {73.4, 62.2, 686}, + {73.4, 60.1, 686}, + {71.2, 66.6, 686}, + {73.4, 64.4, 686}, + {72.3, 67.7, 686}, + {74.5, 47.0, 686}, + {75.6, 47.0, 686}, + {78.9, 46.9, 686}, + {75.6, 41.6, 686}, + {74.5, 40.5, 686}, + {80.0, 36.6, 686}, + {75.8, 37.3, 686}, + {72.3, 47.0, 686}, + {44.1, 38.3, 686}, + {78.7, 51.1, 686}, + {82.2, 48.2, 686}, + {77.8, 55.7, 686}, + {79.2, 57.3, 686}, + {79.2, 58.9, 686}, }, ["displayId"] = 11420, ["skinId"] = 9369, @@ -31808,7 +31808,7 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 50400, ["respawnMaxSeconds"] = 86400, ["coords"] = { - {9.1, 93.7, 27}, + {27.9, 29.0, 686}, }, ["displayId"] = 707, ["skinId"] = 706, @@ -31830,7 +31830,7 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 50400, ["respawnMaxSeconds"] = 86400, ["coords"] = { - {46.0, 65.5, 14}, + {75.6, 73.1, 686}, }, ["displayId"] = 703, ["skinId"] = 377, @@ -31851,34 +31851,34 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 300, ["respawnMaxSeconds"] = 300, ["coords"] = { - {99.3, 21.4, 35}, - {97.9, 26.6, 35}, - {98.9, 29.6, 35}, - {98.8, 30.9, 35}, - {98.2, 29.9, 35}, - {97.3, 27.3, 35}, - {97.0, 30.7, 35}, - {97.0, 31.0, 35}, - {99.6, 33.5, 35}, - {99.1, 36.0, 35}, - {99.2, 36.1, 35}, - {98.1, 35.3, 35}, - {95.7, 35.5, 35}, - {96.8, 40.5, 35}, - {96.6, 38.6, 35}, - {98.1, 38.2, 35}, - {95.7, 40.3, 35}, - {94.8, 39.6, 35}, - {99.1, 44.2, 35}, - {94.5, 22.6, 35}, - {93.9, 29.4, 35}, - {93.9, 29.2, 35}, - {94.6, 28.5, 35}, - {59.4, 52.2, 14}, - {60.2, 52.7, 14}, - {97.5, 45.4, 35}, - {59.0, 54.5, 14}, - {99.3, 41.6, 35}, + {49.4, 55.2, 684}, + {48.7, 57.8, 684}, + {49.2, 59.4, 684}, + {49.1, 60.0, 684}, + {48.8, 59.5, 684}, + {48.4, 58.2, 684}, + {48.2, 59.9, 684}, + {48.2, 60.1, 684}, + {49.5, 61.4, 684}, + {49.3, 62.7, 684}, + {49.3, 62.7, 684}, + {48.8, 62.3, 684}, + {47.5, 62.4, 684}, + {48.1, 65.0, 684}, + {48.0, 64.0, 684}, + {48.8, 63.8, 684}, + {47.5, 64.9, 684}, + {47.1, 64.5, 684}, + {49.3, 66.9, 684}, + {46.9, 55.8, 684}, + {46.6, 59.3, 684}, + {46.6, 59.2, 684}, + {47.0, 58.8, 684}, + {52.7, 52.4, 684}, + {57.9, 55.7, 684}, + {48.5, 67.5, 684}, + {50.0, 67.5, 684}, + {49.4, 65.5, 684}, }, ["displayId"] = 677, ["skinId"] = 675, @@ -31899,20 +31899,20 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 300, ["respawnMaxSeconds"] = 300, ["coords"] = { - {98.9, 29.4, 35}, - {96.8, 28.7, 35}, - {97.9, 32.0, 35}, - {59.0, 53.8, 14}, - {97.0, 34.6, 35}, - {98.3, 39.7, 35}, - {98.3, 39.5, 35}, - {96.0, 42.8, 35}, - {95.9, 42.5, 35}, - {96.4, 38.6, 35}, - {95.2, 36.6, 35}, - {99.3, 44.3, 35}, - {93.9, 27.1, 35}, - {99.4, 41.5, 35}, + {49.2, 59.3, 684}, + {48.1, 58.9, 684}, + {48.7, 60.6, 684}, + {50.0, 62.9, 684}, + {48.2, 61.9, 684}, + {48.9, 64.6, 684}, + {48.9, 64.5, 684}, + {47.7, 66.2, 684}, + {47.7, 66.0, 684}, + {47.9, 64.0, 684}, + {47.3, 63.0, 684}, + {49.4, 66.9, 684}, + {46.6, 58.1, 684}, + {49.4, 65.5, 684}, }, ["displayId"] = 677, ["skinId"] = 675, @@ -31933,33 +31933,33 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 300, ["respawnMaxSeconds"] = 300, ["coords"] = { - {59.8, 49.1, 14}, - {59.8, 48.8, 14}, - {60.0, 49.0, 14}, - {59.8, 49.6, 14}, - {59.8, 49.6, 14}, - {59.6, 49.9, 14}, - {59.6, 49.2, 14}, - {59.5, 49.1, 14}, - {59.6, 48.8, 14}, - {59.8, 48.7, 14}, - {59.8, 48.7, 14}, - {59.9, 47.8, 14}, - {60.1, 48.1, 14}, - {59.5, 47.8, 14}, - {58.8, 48.3, 14}, - {59.3, 48.2, 14}, - {59.1, 48.3, 14}, - {58.9, 48.2, 14}, - {58.9, 48.2, 14}, - {59.3, 48.9, 14}, - {59.4, 48.7, 14}, - {59.4, 49.3, 14}, - {59.4, 49.2, 14}, - {59.3, 49.4, 14}, - {59.2, 49.2, 14}, - {59.8, 50.0, 14}, - {59.9, 49.6, 14}, + {55.3, 32.1, 684}, + {55.3, 30.2, 684}, + {56.6, 31.5, 684}, + {55.3, 35.4, 684}, + {55.3, 35.4, 684}, + {54.0, 37.4, 684}, + {54.0, 32.8, 684}, + {53.3, 32.1, 684}, + {54.0, 30.2, 684}, + {55.3, 29.5, 684}, + {55.3, 29.5, 684}, + {55.9, 23.6, 684}, + {57.2, 25.6, 684}, + {53.3, 23.6, 684}, + {48.7, 26.9, 684}, + {52.0, 26.2, 684}, + {50.7, 26.9, 684}, + {49.4, 26.2, 684}, + {49.4, 26.2, 684}, + {52.0, 30.8, 684}, + {52.7, 29.5, 684}, + {52.7, 33.4, 684}, + {52.7, 32.8, 684}, + {52.0, 34.1, 684}, + {51.4, 32.8, 684}, + {55.3, 38.0, 684}, + {55.9, 35.4, 684}, }, ["displayId"] = 1337, ["skinId"] = 1337, @@ -31980,84 +31980,84 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 300, ["respawnMaxSeconds"] = 300, ["coords"] = { - {96.7, 20.2, 35}, - {97.0, 16.4, 35}, - {99.1, 15.0, 35}, - {95.9, 9.3, 35}, - {93.8, 11.4, 35}, - {91.5, 12.0, 35}, - {59.1, 52.0, 14}, - {59.3, 52.4, 14}, - {59.6, 52.6, 14}, - {59.1, 52.6, 14}, - {59.8, 52.1, 14}, - {60.0, 52.2, 14}, - {60.0, 52.6, 14}, - {60.3, 52.5, 14}, - {60.1, 53.3, 14}, - {60.2, 52.9, 14}, - {59.9, 53.6, 14}, - {59.7, 53.5, 14}, - {60.0, 54.1, 14}, - {60.2, 54.1, 14}, - {59.4, 54.0, 14}, - {59.1, 53.6, 14}, - {59.1, 53.4, 14}, - {59.0, 53.0, 14}, - {93.8, 18.4, 35}, - {94.9, 14.8, 35}, - {91.1, 24.6, 35}, - {92.0, 29.0, 35}, - {92.3, 21.7, 35}, - {58.8, 50.8, 14}, - {59.0, 50.9, 14}, - {98.0, 1.3, 35}, - {58.6, 50.8, 14}, - {58.9, 50.3, 14}, - {58.7, 50.2, 14}, - {58.9, 49.8, 14}, - {59.0, 49.9, 14}, - {59.3, 50.0, 14}, - {59.5, 50.3, 14}, - {59.5, 50.8, 14}, - {59.7, 50.8, 14}, - {59.8, 50.5, 14}, - {59.7, 50.3, 14}, - {59.9, 50.6, 14}, - {60.2, 50.8, 14}, - {60.3, 51.0, 14}, - {60.4, 51.2, 14}, - {60.3, 51.4, 14}, - {60.4, 51.5, 14}, - {60.7, 51.8, 14}, - {60.8, 51.7, 14}, - {60.9, 51.5, 14}, - {61.0, 51.7, 14}, - {60.7, 52.0, 14}, - {61.2, 51.0, 14}, - {59.1, 50.0, 14}, - {58.4, 50.1, 14}, - {98.5, 78.8, 40}, - {98.2, 74.9, 40}, - {98.7, 72.1, 40}, - {97.9, 70.9, 40}, - {97.4, 72.5, 40}, - {96.9, 67.2, 40}, - {95.9, 65.9, 40}, - {95.4, 68.5, 40}, - {97.2, 63.7, 40}, - {98.9, 63.4, 40}, - {58.4, 48.5, 14}, - {58.5, 48.8, 14}, - {58.5, 49.3, 14}, - {58.6, 49.8, 14}, - {58.3, 49.7, 14}, - {98.7, 61.2, 40}, - {95.2, 56.2, 40}, - {99.8, 51.3, 40}, - {58.5, 48.0, 14}, - {58.6, 47.6, 14}, - {60.0, 50.3, 14}, + {48.1, 54.6, 684}, + {48.2, 52.6, 684}, + {49.3, 51.9, 684}, + {47.7, 49.0, 684}, + {46.6, 50.0, 684}, + {45.4, 50.3, 684}, + {50.7, 51.1, 684}, + {52.0, 53.7, 684}, + {54.0, 55.0, 684}, + {50.7, 55.0, 684}, + {55.3, 51.8, 684}, + {56.6, 52.4, 684}, + {56.6, 55.0, 684}, + {58.5, 54.4, 684}, + {57.2, 59.6, 684}, + {57.9, 57.0, 684}, + {55.9, 61.6, 684}, + {54.6, 60.9, 684}, + {56.6, 64.9, 684}, + {57.9, 64.9, 684}, + {52.7, 64.2, 684}, + {50.7, 61.6, 684}, + {50.7, 60.3, 684}, + {50.0, 57.7, 684}, + {46.6, 53.6, 684}, + {47.1, 51.8, 684}, + {45.2, 56.8, 684}, + {45.7, 59.1, 684}, + {45.8, 55.3, 684}, + {48.7, 43.3, 684}, + {50.0, 43.9, 684}, + {48.7, 44.8, 684}, + {47.4, 43.3, 684}, + {49.4, 40.0, 684}, + {48.1, 39.3, 684}, + {49.4, 36.7, 684}, + {50.0, 37.4, 684}, + {52.0, 38.0, 684}, + {53.3, 40.0, 684}, + {53.3, 43.3, 684}, + {54.6, 43.3, 684}, + {55.3, 41.3, 684}, + {54.6, 40.0, 684}, + {55.9, 42.0, 684}, + {57.9, 43.3, 684}, + {58.5, 44.6, 684}, + {59.2, 45.9, 684}, + {58.5, 47.2, 684}, + {59.2, 47.8, 684}, + {61.2, 49.8, 684}, + {61.8, 49.2, 684}, + {62.5, 47.8, 684}, + {63.1, 49.2, 684}, + {61.2, 51.1, 684}, + {64.4, 44.6, 684}, + {50.7, 38.0, 684}, + {46.1, 38.7, 684}, + {44.4, 39.5, 684}, + {44.2, 36.5, 684}, + {44.5, 34.4, 684}, + {43.9, 33.4, 684}, + {43.5, 34.7, 684}, + {43.2, 30.6, 684}, + {42.4, 29.6, 684}, + {42.0, 31.6, 684}, + {43.4, 27.9, 684}, + {44.7, 27.7, 684}, + {46.1, 28.2, 684}, + {46.8, 30.2, 684}, + {46.8, 33.4, 684}, + {47.4, 36.7, 684}, + {45.5, 36.1, 684}, + {44.5, 26.0, 684}, + {41.9, 22.1, 684}, + {45.4, 18.4, 684}, + {46.8, 24.9, 684}, + {47.4, 22.3, 684}, + {56.6, 40.0, 684}, }, ["displayId"] = 389, ["skinId"] = 193, @@ -32078,82 +32078,82 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 300, ["respawnMaxSeconds"] = 300, ["coords"] = { - {60.7, 47.8, 14}, - {60.4, 47.3, 14}, - {60.0, 47.0, 14}, - {60.4, 49.5, 14}, - {59.1, 48.9, 14}, - {60.1, 55.3, 14}, - {60.1, 55.0, 14}, - {61.3, 50.5, 14}, - {61.2, 50.0, 14}, - {61.1, 49.8, 14}, - {61.0, 49.4, 14}, - {60.9, 49.3, 14}, - {60.5, 49.1, 14}, - {60.4, 49.0, 14}, - {60.2, 48.6, 14}, - {60.3, 48.5, 14}, - {60.4, 48.3, 14}, - {60.5, 48.4, 14}, - {60.8, 48.5, 14}, - {60.8, 48.3, 14}, - {61.0, 49.0, 14}, - {60.7, 48.1, 14}, - {60.7, 47.5, 14}, - {60.7, 47.4, 14}, - {60.3, 46.9, 14}, - {60.2, 46.7, 14}, - {60.2, 46.4, 14}, - {60.3, 46.1, 14}, - {60.5, 46.0, 14}, - {59.9, 47.2, 14}, - {60.8, 49.0, 14}, - {60.8, 49.9, 14}, - {60.6, 50.0, 14}, - {60.6, 49.7, 14}, - {60.3, 50.2, 14}, - {60.3, 49.9, 14}, - {60.1, 49.9, 14}, - {59.9, 49.3, 14}, - {59.9, 49.8, 14}, - {59.7, 50.0, 14}, - {59.5, 49.5, 14}, - {59.6, 49.4, 14}, - {59.5, 48.9, 14}, - {60.0, 48.7, 14}, - {60.0, 48.4, 14}, - {59.8, 48.5, 14}, - {59.8, 48.2, 14}, - {60.1, 47.8, 14}, - {59.4, 47.9, 14}, - {59.2, 48.4, 14}, - {59.0, 48.3, 14}, - {59.0, 48.7, 14}, - {59.0, 49.0, 14}, - {58.9, 48.9, 14}, - {60.0, 55.1, 14}, - {59.8, 55.1, 14}, - {59.6, 55.4, 14}, - {59.6, 55.2, 14}, - {59.4, 55.1, 14}, - {59.1, 54.9, 14}, - {59.3, 54.8, 14}, - {59.4, 54.8, 14}, - {59.2, 54.5, 14}, - {59.0, 54.6, 14}, - {98.3, 47.1, 35}, - {59.1, 54.4, 14}, - {59.2, 54.1, 14}, - {59.4, 54.3, 14}, - {59.7, 54.5, 14}, - {59.6, 54.7, 14}, - {60.4, 55.5, 14}, - {60.6, 55.5, 14}, - {60.7, 55.3, 14}, - {60.8, 55.7, 14}, - {60.9, 55.9, 14}, - {61.0, 56.0, 14}, + {61.2, 23.6, 684}, + {59.2, 20.3, 684}, + {56.6, 18.4, 684}, + {59.2, 34.7, 684}, + {50.7, 30.8, 684}, + {57.2, 72.7, 684}, + {57.2, 70.8, 684}, + {65.1, 41.3, 684}, + {64.4, 38.0, 684}, + {63.8, 36.7, 684}, + {63.1, 34.1, 684}, + {62.5, 33.4, 684}, + {59.8, 32.1, 684}, + {59.2, 31.5, 684}, + {57.9, 28.9, 684}, + {58.5, 28.2, 684}, + {59.2, 26.9, 684}, + {59.8, 27.5, 684}, + {61.8, 28.2, 684}, + {61.8, 26.9, 684}, + {63.1, 31.5, 684}, + {61.2, 25.6, 684}, + {61.2, 21.7, 684}, + {61.2, 21.0, 684}, + {58.5, 17.7, 684}, + {57.9, 16.4, 684}, + {57.9, 14.5, 684}, + {58.5, 12.5, 684}, + {59.8, 11.8, 684}, + {55.9, 19.7, 684}, + {61.8, 31.5, 684}, + {61.8, 37.4, 684}, + {60.5, 38.0, 684}, + {60.5, 36.1, 684}, + {58.5, 39.3, 684}, + {58.5, 37.4, 684}, + {57.2, 37.4, 684}, + {55.9, 33.4, 684}, + {55.9, 36.7, 684}, + {54.6, 38.0, 684}, + {53.3, 34.7, 684}, + {54.0, 34.1, 684}, + {53.3, 30.8, 684}, + {56.6, 29.5, 684}, + {56.6, 27.5, 684}, + {55.3, 28.2, 684}, + {55.3, 26.2, 684}, + {57.2, 23.6, 684}, + {52.7, 24.3, 684}, + {51.4, 27.5, 684}, + {50.0, 26.9, 684}, + {50.0, 29.5, 684}, + {50.0, 31.5, 684}, + {49.4, 30.8, 684}, + {56.6, 71.4, 684}, + {55.3, 71.4, 684}, + {54.0, 73.4, 684}, + {54.0, 72.1, 684}, + {52.7, 71.4, 684}, + {50.7, 70.1, 684}, + {52.0, 69.5, 684}, + {52.7, 69.5, 684}, + {51.4, 67.5, 684}, + {50.0, 68.1, 684}, + {48.9, 68.4, 684}, + {50.7, 66.8, 684}, + {51.4, 64.9, 684}, + {52.7, 66.2, 684}, + {54.6, 67.5, 684}, + {54.0, 68.8, 684}, + {59.2, 74.0, 684}, + {60.5, 74.0, 684}, + {61.2, 72.7, 684}, + {61.8, 75.3, 684}, + {62.5, 76.7, 684}, + {63.1, 77.3, 684}, }, ["displayId"] = 389, ["skinId"] = 193, @@ -32174,39 +32174,39 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 120, ["respawnMaxSeconds"] = 300, ["coords"] = { - {97.7, 16.3, 35}, - {91.0, 14.4, 35}, - {60.0, 52.0, 14}, - {60.4, 52.9, 14}, - {60.0, 53.2, 14}, - {59.7, 54.3, 14}, - {59.2, 53.9, 14}, - {99.2, 24.3, 35}, - {59.6, 52.2, 14}, - {90.7, 25.6, 35}, - {58.8, 50.5, 14}, - {58.8, 49.6, 14}, - {59.1, 49.7, 14}, - {59.5, 50.0, 14}, - {59.7, 50.6, 14}, - {60.1, 51.0, 14}, - {60.3, 50.9, 14}, - {60.6, 51.6, 14}, - {61.1, 51.9, 14}, - {61.2, 51.3, 14}, - {61.0, 49.6, 14}, - {60.6, 49.1, 14}, - {60.3, 48.8, 14}, - {60.7, 48.7, 14}, - {60.6, 47.5, 14}, - {60.3, 45.9, 14}, - {60.2, 47.3, 14}, - {60.5, 50.1, 14}, - {60.2, 49.5, 14}, - {60.2, 50.5, 14}, - {61.0, 49.0, 14}, - {60.6, 49.5, 14}, - {60.8, 49.7, 14}, + {48.6, 52.5, 684}, + {45.1, 51.6, 684}, + {56.6, 51.1, 684}, + {59.2, 57.0, 684}, + {56.6, 59.0, 684}, + {54.6, 66.2, 684}, + {51.4, 63.6, 684}, + {49.3, 56.7, 684}, + {54.0, 52.4, 684}, + {45.0, 57.3, 684}, + {48.7, 41.3, 684}, + {48.7, 35.4, 684}, + {50.7, 36.1, 684}, + {53.3, 38.0, 684}, + {54.6, 42.0, 684}, + {57.2, 44.6, 684}, + {58.5, 43.9, 684}, + {60.5, 48.5, 684}, + {63.8, 50.5, 684}, + {64.4, 46.5, 684}, + {63.1, 35.4, 684}, + {60.5, 32.1, 684}, + {58.5, 30.2, 684}, + {61.2, 29.5, 684}, + {60.5, 21.7, 684}, + {58.5, 11.2, 684}, + {57.9, 20.3, 684}, + {59.8, 38.7, 684}, + {57.9, 34.7, 684}, + {57.9, 41.3, 684}, + {63.1, 31.5, 684}, + {60.5, 34.7, 684}, + {61.8, 36.1, 684}, }, ["displayId"] = 827, ["skinId"] = 520, @@ -32227,48 +32227,48 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 120, ["respawnMaxSeconds"] = 120, ["coords"] = { - {59.7, 55.2, 14}, - {59.8, 55.5, 14}, - {59.2, 55.0, 14}, - {59.1, 54.8, 14}, - {59.3, 54.6, 14}, - {99.9, 48.6, 35}, - {59.2, 54.3, 14}, - {59.6, 54.4, 14}, - {60.3, 55.2, 14}, - {60.8, 56.3, 14}, - {60.6, 56.5, 14}, - {60.4, 55.9, 14}, - {59.9, 56.1, 14}, - {60.0, 56.2, 14}, - {60.0, 56.5, 14}, - {59.9, 56.5, 14}, - {59.6, 55.9, 14}, - {60.7, 56.9, 14}, - {61.0, 56.4, 14}, - {60.4, 57.4, 14}, - {99.4, 82.1, 35}, - {97.5, 82.1, 35}, - {59.0, 57.5, 14}, - {99.8, 8.9, 17}, - {94.8, 88.1, 35}, - {99.7, 13.9, 17}, - {99.0, 20.8, 17}, - {94.5, 94.3, 35}, - {93.8, 96.8, 35}, - {97.2, 90.2, 35}, - {97.0, 95.0, 35}, - {98.6, 88.9, 35}, - {59.0, 58.0, 14}, - {59.2, 57.7, 14}, - {59.4, 57.6, 14}, - {59.5, 57.4, 14}, - {60.1, 57.9, 14}, - {60.1, 57.6, 14}, - {60.3, 57.7, 14}, - {60.2, 57.9, 14}, - {59.7, 57.0, 14}, - {59.0, 57.1, 14}, + {54.6, 72.1, 684}, + {55.3, 74.0, 684}, + {51.4, 70.8, 684}, + {50.7, 69.5, 684}, + {52.0, 68.1, 684}, + {49.7, 69.1, 684}, + {51.4, 66.2, 684}, + {54.0, 66.8, 684}, + {58.5, 72.1, 684}, + {61.8, 79.3, 684}, + {60.5, 80.6, 684}, + {59.2, 76.7, 684}, + {55.9, 78.0, 684}, + {56.6, 78.6, 684}, + {56.6, 80.6, 684}, + {55.9, 80.6, 684}, + {54.0, 76.7, 684}, + {61.2, 83.2, 684}, + {63.1, 79.9, 684}, + {59.2, 86.5, 684}, + {49.4, 86.3, 684}, + {48.5, 86.3, 684}, + {50.0, 87.1, 684}, + {46.2, 87.4, 684}, + {47.1, 89.4, 684}, + {46.2, 89.7, 684}, + {45.8, 92.9, 684}, + {46.9, 92.6, 684}, + {46.6, 93.9, 684}, + {48.3, 90.5, 684}, + {48.2, 92.9, 684}, + {49.0, 89.8, 684}, + {50.0, 90.4, 684}, + {51.4, 88.4, 684}, + {52.7, 87.8, 684}, + {53.3, 86.5, 684}, + {57.2, 89.7, 684}, + {57.2, 87.8, 684}, + {58.5, 88.4, 684}, + {57.9, 89.7, 684}, + {54.6, 83.9, 684}, + {50.0, 84.5, 684}, }, ["displayId"] = 827, ["skinId"] = 520, @@ -32310,34 +32310,34 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 120, ["respawnMaxSeconds"] = 300, ["coords"] = { - {60.2, 52.0, 14}, - {59.9, 51.9, 14}, - {59.7, 52.1, 14}, - {59.5, 52.1, 14}, - {59.3, 52.0, 14}, - {59.3, 51.7, 14}, - {59.3, 51.4, 14}, - {59.4, 51.2, 14}, - {59.5, 51.4, 14}, - {59.7, 51.2, 14}, - {59.9, 51.2, 14}, - {59.8, 51.5, 14}, - {60.0, 51.8, 14}, - {59.8, 51.7, 14}, - {59.5, 51.8, 14}, - {60.2, 51.7, 14}, - {60.3, 52.0, 14}, - {60.3, 52.3, 14}, - {60.4, 51.9, 14}, - {90.2, 5.0, 35}, - {89.9, 6.5, 35}, - {92.4, 2.1, 35}, - {93.1, 0.0, 35}, - {94.3, 2.2, 35}, - {96.2, 5.8, 35}, - {97.8, 4.3, 35}, - {100.0, 4.9, 35}, - {91.5, 5.4, 35}, + {57.9, 51.1, 684}, + {55.9, 50.5, 684}, + {54.6, 51.8, 684}, + {53.3, 51.8, 684}, + {52.0, 51.1, 684}, + {52.0, 49.2, 684}, + {52.0, 47.2, 684}, + {52.7, 45.9, 684}, + {53.3, 47.2, 684}, + {54.6, 45.9, 684}, + {55.9, 45.9, 684}, + {55.3, 47.8, 684}, + {56.6, 49.8, 684}, + {55.3, 49.2, 684}, + {53.3, 49.8, 684}, + {57.9, 49.2, 684}, + {58.5, 51.1, 684}, + {58.5, 53.1, 684}, + {59.2, 50.5, 684}, + {44.7, 46.7, 684}, + {44.6, 47.5, 684}, + {45.9, 45.3, 684}, + {46.2, 44.2, 684}, + {46.8, 45.3, 684}, + {47.8, 47.2, 684}, + {48.6, 46.4, 684}, + {49.8, 46.7, 684}, + {45.4, 47.0, 684}, }, ["displayId"] = 1244, ["skinId"] = 1244, @@ -32358,38 +32358,38 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 120, ["respawnMaxSeconds"] = 120, ["coords"] = { - {61.0, 56.2, 14}, - {60.8, 55.9, 14}, - {60.5, 56.0, 14}, - {60.3, 55.7, 14}, - {60.4, 55.6, 14}, - {60.1, 55.6, 14}, - {60.4, 56.2, 14}, - {60.1, 56.0, 14}, - {59.9, 55.9, 14}, - {59.7, 55.7, 14}, - {59.4, 55.5, 14}, - {59.6, 56.3, 14}, - {59.7, 56.1, 14}, - {59.5, 56.4, 14}, - {59.5, 56.9, 14}, - {60.0, 56.7, 14}, - {60.3, 56.4, 14}, - {60.7, 56.6, 14}, - {60.9, 57.0, 14}, - {61.0, 56.6, 14}, - {97.7, 80.4, 35}, - {59.0, 57.2, 14}, - {59.2, 57.4, 14}, - {99.1, 85.5, 35}, - {96.8, 85.9, 35}, - {94.8, 83.2, 35}, - {99.8, 18.2, 17}, - {60.1, 57.4, 14}, - {60.2, 57.5, 14}, - {60.3, 57.1, 14}, - {60.2, 56.8, 14}, - {59.8, 57.0, 14}, + {63.1, 78.6, 684}, + {61.8, 76.7, 684}, + {59.8, 77.3, 684}, + {58.5, 75.3, 684}, + {59.2, 74.7, 684}, + {57.2, 74.7, 684}, + {59.2, 78.6, 684}, + {57.2, 77.3, 684}, + {55.9, 76.7, 684}, + {54.6, 75.3, 684}, + {52.7, 74.0, 684}, + {54.0, 79.3, 684}, + {54.6, 78.0, 684}, + {53.3, 79.9, 684}, + {53.3, 83.2, 684}, + {56.6, 81.9, 684}, + {58.5, 79.9, 684}, + {61.2, 81.2, 684}, + {62.5, 83.9, 684}, + {63.1, 81.2, 684}, + {48.6, 85.4, 684}, + {50.0, 85.2, 684}, + {51.4, 86.5, 684}, + {49.3, 88.1, 684}, + {48.1, 88.3, 684}, + {47.1, 86.9, 684}, + {46.2, 91.7, 684}, + {57.2, 86.5, 684}, + {57.9, 87.1, 684}, + {58.5, 84.5, 684}, + {57.9, 82.5, 684}, + {55.3, 83.9, 684}, }, ["displayId"] = 4829, ["skinId"] = 4829, @@ -32410,21 +32410,21 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 120, ["respawnMaxSeconds"] = 300, ["coords"] = { - {59.4, 52.0, 14}, - {59.4, 51.7, 14}, - {59.4, 51.5, 14}, - {59.5, 51.1, 14}, - {59.8, 51.2, 14}, - {59.9, 51.6, 14}, - {59.7, 52.0, 14}, - {59.7, 51.7, 14}, - {60.1, 51.9, 14}, - {60.3, 51.8, 14}, - {60.4, 52.0, 14}, - {91.2, 2.6, 35}, - {92.8, 3.1, 35}, - {95.8, 2.8, 35}, - {59.0, 51.5, 14}, + {52.7, 51.1, 684}, + {52.7, 49.2, 684}, + {52.7, 47.8, 684}, + {53.3, 45.2, 684}, + {55.3, 45.9, 684}, + {55.9, 48.5, 684}, + {54.6, 51.1, 684}, + {54.6, 49.2, 684}, + {57.2, 50.5, 684}, + {58.5, 49.8, 684}, + {59.2, 51.1, 684}, + {45.2, 45.5, 684}, + {46.1, 45.8, 684}, + {47.6, 45.6, 684}, + {50.0, 47.8, 684}, }, ["displayId"] = 1034, ["skinId"] = 807, @@ -32445,24 +32445,24 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 120, ["respawnMaxSeconds"] = 120, ["coords"] = { - {60.7, 55.8, 14}, - {60.8, 56.0, 14}, - {60.5, 55.8, 14}, - {60.2, 55.5, 14}, - {60.1, 55.8, 14}, - {60.2, 56.1, 14}, - {60.0, 55.9, 14}, - {59.8, 56.0, 14}, - {59.6, 55.5, 14}, - {59.5, 56.6, 14}, - {59.7, 56.9, 14}, - {60.1, 56.6, 14}, - {60.5, 56.5, 14}, - {94.8, 80.4, 35}, - {59.1, 57.6, 14}, - {59.6, 57.4, 14}, - {60.3, 56.8, 14}, - {59.5, 57.1, 14}, + {61.2, 76.0, 684}, + {61.8, 77.3, 684}, + {59.8, 76.0, 684}, + {57.9, 74.0, 684}, + {57.2, 76.0, 684}, + {57.9, 78.0, 684}, + {56.6, 76.7, 684}, + {55.3, 77.3, 684}, + {54.0, 74.0, 684}, + {53.3, 81.2, 684}, + {54.6, 83.2, 684}, + {57.2, 81.2, 684}, + {59.8, 80.6, 684}, + {47.1, 85.4, 684}, + {50.7, 87.8, 684}, + {54.0, 86.5, 684}, + {58.5, 82.5, 684}, + {53.3, 84.5, 684}, }, ["displayId"] = 1034, ["skinId"] = 807, @@ -32631,28 +32631,28 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 300, ["respawnMaxSeconds"] = 300, ["coords"] = { - {36.0, 67.9, 14}, - {35.9, 68.1, 14}, - {36.0, 68.0, 14}, - {35.9, 68.0, 14}, - {35.0, 71.1, 14}, - {36.0, 68.2, 14}, - {36.1, 68.2, 14}, - {36.1, 67.8, 14}, - {36.4, 68.2, 14}, - {36.3, 68.1, 14}, - {36.3, 68.0, 14}, - {36.2, 68.1, 14}, - {36.2, 67.9, 14}, - {36.3, 67.9, 14}, - {36.2, 68.2, 14}, - {36.2, 68.3, 14}, - {36.1, 68.4, 14}, - {34.9, 70.9, 14}, - {34.8, 70.8, 14}, - {34.7, 70.5, 14}, - {34.8, 70.4, 14}, - {34.7, 70.3, 14}, + {45.2, 43.0, 685}, + {44.1, 45.3, 685}, + {45.2, 44.2, 685}, + {44.1, 44.2, 685}, + {33.8, 79.4, 685}, + {45.2, 46.5, 685}, + {46.3, 46.5, 685}, + {46.3, 41.9, 685}, + {49.7, 46.5, 685}, + {48.6, 45.3, 685}, + {48.6, 44.2, 685}, + {47.5, 45.3, 685}, + {47.5, 43.0, 685}, + {48.6, 43.0, 685}, + {47.5, 46.5, 685}, + {47.5, 47.6, 685}, + {46.3, 48.7, 685}, + {32.7, 77.1, 685}, + {31.6, 76.0, 685}, + {30.4, 72.6, 685}, + {31.6, 71.4, 685}, + {30.4, 70.3, 685}, }, ["displayId"] = 18390, ["skinId"] = 18390, @@ -32673,14 +32673,14 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 300, ["respawnMaxSeconds"] = 300, ["coords"] = { - {36.1, 67.9, 14}, - {36.2, 67.9, 14}, - {36.3, 68.2, 14}, - {34.6, 69.5, 14}, - {34.6, 69.3, 14}, - {34.6, 69.2, 14}, - {34.5, 68.8, 14}, - {34.5, 68.7, 14}, + {46.3, 43.0, 685}, + {47.5, 43.0, 685}, + {48.6, 46.5, 685}, + {29.3, 61.2, 685}, + {29.3, 58.9, 685}, + {29.3, 57.8, 685}, + {28.1, 53.3, 685}, + {28.1, 52.1, 685}, }, ["displayId"] = 18390, ["skinId"] = 18390, @@ -32701,22 +32701,22 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 300, ["respawnMaxSeconds"] = 300, ["coords"] = { - {36.5, 71.6, 14}, - {36.6, 71.6, 14}, - {36.7, 71.7, 14}, - {36.5, 71.8, 14}, - {36.3, 71.8, 14}, - {36.2, 71.8, 14}, - {36.2, 71.7, 14}, - {36.0, 71.8, 14}, - {36.0, 71.8, 14}, - {36.1, 72.1, 14}, - {36.2, 72.2, 14}, - {36.5, 72.1, 14}, - {36.7, 72.1, 14}, - {36.8, 72.0, 14}, - {36.9, 71.7, 14}, - {36.8, 71.6, 14}, + {50.9, 85.0, 685}, + {52.0, 85.0, 685}, + {53.1, 86.2, 685}, + {50.9, 87.3, 685}, + {48.6, 87.3, 685}, + {47.5, 87.3, 685}, + {47.5, 86.2, 685}, + {45.2, 87.3, 685}, + {45.2, 87.3, 685}, + {46.3, 90.7, 685}, + {47.5, 91.8, 685}, + {50.9, 90.7, 685}, + {53.1, 90.7, 685}, + {54.3, 89.6, 685}, + {55.4, 86.2, 685}, + {54.3, 85.0, 685}, }, ["displayId"] = 903, ["skinId"] = 165, @@ -32737,26 +32737,26 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 120, ["respawnMaxSeconds"] = 300, ["coords"] = { - {36.4, 69.7, 14}, - {36.3, 69.8, 14}, - {36.6, 69.6, 14}, - {36.7, 69.5, 14}, - {36.7, 69.5, 14}, - {36.8, 69.4, 14}, - {36.9, 69.3, 14}, - {36.5, 71.8, 14}, - {36.4, 71.8, 14}, - {36.2, 71.8, 14}, - {36.1, 71.6, 14}, - {36.2, 71.9, 14}, - {36.1, 72.0, 14}, - {36.0, 72.0, 14}, - {36.0, 71.9, 14}, - {36.2, 72.0, 14}, - {36.1, 72.1, 14}, - {36.4, 72.0, 14}, - {0.7, 1.8, 39}, - {37.0, 71.8, 14}, + {49.7, 63.5, 685}, + {48.6, 64.6, 685}, + {52.0, 62.3, 685}, + {53.1, 61.2, 685}, + {53.1, 61.2, 685}, + {54.3, 60.1, 685}, + {55.4, 58.9, 685}, + {50.9, 87.3, 685}, + {49.7, 87.3, 685}, + {47.5, 87.3, 685}, + {46.3, 85.0, 685}, + {47.5, 88.4, 685}, + {46.3, 89.6, 685}, + {45.2, 89.6, 685}, + {45.2, 88.4, 685}, + {47.5, 89.6, 685}, + {46.3, 90.7, 685}, + {49.7, 89.6, 685}, + {56.0, 90.2, 685}, + {56.6, 87.3, 685}, }, ["displayId"] = 903, ["skinId"] = 165, @@ -32777,34 +32777,34 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 300, ["respawnMaxSeconds"] = 300, ["coords"] = { - {35.0, 67.1, 14}, - {35.0, 67.2, 14}, - {35.0, 67.2, 14}, - {35.0, 67.4, 14}, - {35.0, 67.5, 14}, - {35.0, 67.6, 14}, - {35.1, 67.8, 14}, - {35.2, 67.8, 14}, - {35.1, 68.0, 14}, - {35.0, 68.0, 14}, - {35.0, 68.1, 14}, - {35.0, 68.2, 14}, - {35.0, 68.5, 14}, - {34.9, 68.8, 14}, - {34.9, 68.4, 14}, - {34.7, 68.4, 14}, - {34.7, 68.5, 14}, - {34.8, 68.7, 14}, - {34.8, 69.0, 14}, - {34.8, 69.0, 14}, - {34.8, 68.8, 14}, - {34.6, 68.8, 14}, - {34.6, 68.7, 14}, - {34.6, 68.5, 14}, - {35.2, 67.9, 14}, - {35.4, 68.0, 14}, - {35.2, 68.1, 14}, - {35.3, 67.9, 14}, + {33.8, 34.0, 685}, + {33.8, 35.1, 685}, + {33.8, 35.1, 685}, + {33.8, 37.4, 685}, + {33.8, 38.5, 685}, + {33.8, 39.6, 685}, + {35.0, 41.9, 685}, + {36.1, 41.9, 685}, + {35.0, 44.2, 685}, + {33.8, 44.2, 685}, + {33.8, 45.3, 685}, + {33.8, 46.5, 685}, + {33.8, 49.9, 685}, + {32.7, 53.3, 685}, + {32.7, 48.7, 685}, + {30.4, 48.7, 685}, + {30.4, 49.9, 685}, + {31.6, 52.1, 685}, + {31.6, 55.5, 685}, + {31.6, 55.5, 685}, + {31.6, 53.3, 685}, + {29.3, 53.3, 685}, + {29.3, 52.1, 685}, + {29.3, 49.9, 685}, + {36.1, 43.0, 685}, + {38.4, 44.2, 685}, + {36.1, 45.3, 685}, + {37.2, 43.0, 685}, }, ["displayId"] = 545, ["skinId"] = 283, @@ -32825,19 +32825,19 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 300, ["respawnMaxSeconds"] = 300, ["coords"] = { - {35.1, 67.6, 14}, - {35.0, 68.3, 14}, - {35.0, 68.4, 14}, - {35.0, 68.7, 14}, - {35.0, 68.7, 14}, - {34.8, 68.3, 14}, - {34.8, 68.4, 14}, - {34.8, 68.6, 14}, - {34.8, 68.8, 14}, - {34.9, 68.9, 14}, - {34.7, 68.9, 14}, - {34.7, 68.8, 14}, - {34.7, 68.6, 14}, + {35.0, 39.6, 685}, + {33.8, 47.6, 685}, + {33.8, 48.7, 685}, + {33.8, 52.1, 685}, + {33.8, 52.1, 685}, + {31.6, 47.6, 685}, + {31.6, 48.7, 685}, + {31.6, 51.0, 685}, + {31.6, 53.3, 685}, + {32.7, 54.4, 685}, + {30.4, 54.4, 685}, + {30.4, 53.3, 685}, + {30.4, 51.0, 685}, }, ["displayId"] = 545, ["skinId"] = 283, @@ -32858,7 +32858,7 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 300, ["respawnMaxSeconds"] = 300, ["coords"] = { - {34.8, 68.8, 14}, + {31.6, 53.3, 685}, }, ["displayId"] = 709, ["skinId"] = 336, @@ -32879,51 +32879,51 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 300, ["respawnMaxSeconds"] = 300, ["coords"] = { - {35.7, 66.1, 14}, - {35.6, 66.1, 14}, - {35.6, 66.2, 14}, - {35.7, 66.0, 14}, - {35.7, 66.3, 14}, - {35.4, 68.2, 14}, - {35.5, 68.2, 14}, - {35.6, 68.3, 14}, - {35.3, 68.3, 14}, - {35.4, 68.3, 14}, - {35.5, 68.5, 14}, - {35.6, 68.4, 14}, - {35.6, 68.4, 14}, - {35.7, 68.3, 14}, - {35.7, 68.3, 14}, - {35.8, 68.4, 14}, - {35.7, 68.5, 14}, - {35.8, 68.4, 14}, - {35.8, 68.5, 14}, - {35.8, 68.6, 14}, - {35.8, 68.7, 14}, - {35.7, 68.7, 14}, - {35.6, 68.5, 14}, - {35.7, 68.6, 14}, - {35.7, 68.8, 14}, - {35.7, 69.0, 14}, - {35.7, 69.0, 14}, - {35.6, 69.2, 14}, - {35.5, 69.2, 14}, - {35.5, 69.1, 14}, - {35.5, 69.1, 14}, - {35.5, 69.1, 14}, - {35.6, 69.1, 14}, - {35.3, 69.1, 14}, - {35.3, 69.2, 14}, - {35.5, 69.2, 14}, - {35.5, 69.4, 14}, - {35.6, 69.6, 14}, - {35.7, 69.7, 14}, - {35.7, 69.8, 14}, - {35.5, 68.8, 14}, - {35.4, 68.7, 14}, - {35.5, 68.6, 14}, - {35.5, 68.6, 14}, - {35.9, 68.5, 14}, + {41.8, 22.6, 685}, + {40.6, 22.6, 685}, + {40.6, 23.8, 685}, + {41.8, 21.5, 685}, + {41.8, 24.9, 685}, + {38.4, 46.5, 685}, + {39.5, 46.5, 685}, + {40.6, 47.6, 685}, + {37.2, 47.6, 685}, + {38.4, 47.6, 685}, + {39.5, 49.9, 685}, + {40.6, 48.7, 685}, + {40.6, 48.7, 685}, + {41.8, 47.6, 685}, + {41.8, 47.6, 685}, + {42.9, 48.7, 685}, + {41.8, 49.9, 685}, + {42.9, 48.7, 685}, + {42.9, 49.9, 685}, + {42.9, 51.0, 685}, + {42.9, 52.1, 685}, + {41.8, 52.1, 685}, + {40.6, 49.9, 685}, + {41.8, 51.0, 685}, + {41.8, 53.3, 685}, + {41.8, 55.5, 685}, + {41.8, 55.5, 685}, + {40.6, 57.8, 685}, + {39.5, 57.8, 685}, + {39.5, 56.7, 685}, + {39.5, 56.7, 685}, + {39.5, 56.7, 685}, + {40.6, 56.7, 685}, + {37.2, 56.7, 685}, + {37.2, 57.8, 685}, + {39.5, 57.8, 685}, + {39.5, 60.1, 685}, + {40.6, 62.3, 685}, + {41.8, 63.5, 685}, + {41.8, 64.6, 685}, + {39.5, 53.3, 685}, + {38.4, 52.1, 685}, + {39.5, 51.0, 685}, + {39.5, 51.0, 685}, + {44.1, 49.9, 685}, }, ["displayId"] = 833, ["skinId"] = 833, @@ -32987,7 +32987,7 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 50400, ["respawnMaxSeconds"] = 86400, ["coords"] = { - {98.8, 44.9, 35}, + {49.1, 67.2, 684}, }, ["displayId"] = 676, ["skinId"] = 322, @@ -33694,19 +33694,19 @@ MTH_DS_Beasts = { {60.3, 16.0, 39}, {62.1, 22.0, 39}, {63.3, 24.0, 39}, - {21.6, 88.9, 30}, - {21.3, 89.6, 30}, + {63.7, 25.6, 39}, + {63.4, 26.3, 39}, {63.0, 29.5, 39}, {62.5, 30.6, 39}, {62.4, 37.6, 39}, {63.1, 40.7, 39}, - {2.2, 35.0, 34}, - {1.9, 37.1, 34}, + {64.1, 40.6, 39}, + {63.8, 42.2, 39}, {64.7, 47.6, 39}, {66.8, 51.6, 39}, {65.6, 49.3, 39}, {63.8, 44.8, 39}, - {1.3, 36.5, 34}, + {63.4, 41.7, 39}, }, ["displayId"] = 18007, ["skinId"] = 18007, @@ -34003,13 +34003,13 @@ MTH_DS_Beasts = { ["respawnMinSeconds"] = 300, ["respawnMaxSeconds"] = 2500, ["coords"] = { - {17.8, 0.0, 81}, - {14.8, 0.0, 81}, - {14.2, 0.0, 81}, - {13.5, 0.0, 81}, - {16.9, 0.0, 81}, - {15.8, 2.7, 81}, - {30.5, 6.5, 81}, + {26.1, 12.0, 81}, + {23.7, 12.0, 81}, + {23.2, 12.0, 81}, + {22.6, 12.0, 81}, + {25.3, 12.0, 81}, + {24.5, 14.2, 81}, + {36.3, 17.3, 81}, }, ["displayId"] = 18338, ["skinId"] = 18338, diff --git a/data/ds-zones-wma.lua b/data/ds-zones-wma.lua index ba7b120..7f0ebb6 100644 --- a/data/ds-zones-wma.lua +++ b/data/ds-zones-wma.lua @@ -298,7 +298,7 @@ MTH_DS_WMAZones = { [42] = { pfqAreaId=148, mapId=1, left=2941.67, right=-3608.33, top=8333.33, bottom=3966.67, width=6550.00, height=4366.66 }, -- Darkshore [43] = { pfqAreaId=331, mapId=1, left=1700.00, right=-4066.67, top=4672.92, bottom=829.17, width=5766.67, height=3843.75 }, -- Ashenvale [61] = { pfqAreaId=400, mapId=1, left=-433.33, right=-4833.33, top=-3966.67, bottom=-6900.00, width=4400.00, height=2933.33 }, -- ThousandNeedles - [81] = { pfqAreaId=406, mapId=1, left=3949.00, right=-2069.00, top=3441.00, bottom=-569.00, width=6018.00, height=4010.00 }, -- StonetalonMountains + [81] = { pfqAreaId=406, mapId=1, left=3245.83, right=-1637.50, top=2916.67, bottom=-339.58, width=4883.33, height=3256.25 }, -- StonetalonMountains [101] = { pfqAreaId=405, mapId=1, left=4233.33, right=-262.50, top=452.08, bottom=-2545.83, width=4495.83, height=2997.91 }, -- Desolace [121] = { pfqAreaId=357, mapId=1, left=5441.67, right=-1508.33, top=-2366.67, bottom=-7000.00, width=6950.00, height=4633.33 }, -- Feralas [141] = { pfqAreaId=15, mapId=1, left=-975.00, right=-6225.00, top=-2033.33, bottom=-5533.33, width=5250.00, height=3500.00 }, -- Dustwallow diff --git a/locales/ui/enUS.lua b/locales/ui/enUS.lua index fcc41a9..31829b5 100644 --- a/locales/ui/enUS.lua +++ b/locales/ui/enUS.lua @@ -8,6 +8,8 @@ MTH_LocaleData.ui.enUS = { BINDING_HEADER_ZTrapHeader = "[MetaHunt] ZTrap Buttons", BINDING_HEADER_ZPetHeader = "[MetaHunt] ZPet Buttons", BINDING_HEADER_ZAmmoHeader = "[MetaHunt] ZAmmo Buttons", + BINDING_HEADER_ExpAmmoHeader = "[MetaHunt] MM Widget", + BINDING_NAME_EXPAMMOACTION = "MM Widget Action", FEEDOMATIC_NAME = "Feed-O-Matic", FEEDOMATIC_DESC = "Helps a Hunter keep his pets well fed", diff --git a/modules/autobuy/module.lua b/modules/autobuy/module.lua index 94b047f..aac4e02 100644 --- a/modules/autobuy/module.lua +++ b/modules/autobuy/module.lua @@ -6,7 +6,6 @@ local MTH_AutoBuy = { name = "autobuy", enabled = false, - version = "1.4.1", events = { "VARIABLES_LOADED", "MERCHANT_SHOW", diff --git a/modules/autoquest/module.lua b/modules/autoquest/module.lua index 0df6cff..cb3fed7 100644 --- a/modules/autoquest/module.lua +++ b/modules/autoquest/module.lua @@ -6,7 +6,6 @@ local MTH_AutoQuest = { name = "autoquest", enabled = false, - version = "1.4.1", events = { "GOSSIP_SHOW", "QUEST_GREETING", diff --git a/modules/chronometer/data-hunter.lua b/modules/chronometer/data-hunter.lua index 93cdf18..1f50774 100644 --- a/modules/chronometer/data-hunter.lua +++ b/modules/chronometer/data-hunter.lua @@ -28,7 +28,6 @@ function MTH_ChronometerHunterInstallData(engine) engine:AddTimer(engine.SPELL, BS["Flare"], 30, 0, 0, 0, { cr = "YELLOW", rc = true }) engine:AddTimer(engine.SPELL, BS["Hunter's Mark"], 120, 1, 0, 0, { cr = "RED", rc = true }) engine:AddTimer(engine.SPELL, BS["Immolation Trap"], 60, 0, 0, 0, { gr = 1, cr = "ORANGE", ea = { [BS["Immolation Trap Effect"]] = 60 } }) - engine:AddTimer(engine.SPELL, BS["Aimed Shot"], 20, 0, 0, 0, { cr = "RED", rc = true, d = { tn = BS["Swiftshot"], tb = -2, ts = -2 } }) -- base 20s; Swiftshot reduces by 2s/rank (max -6s at rank 3) engine:AddTimer(engine.SPELL, BS["Kill Command"], 12, 0, 0, 0, { cr = "RED", rc = true }) engine:AddTimer(engine.SPELL, BS["Rapid Fire"], 15, 0, 1, 1, { cr = "RED" }) engine:AddTimer(engine.SPELL, BS["Scare Beast"], 10, 1, 0, 0, { cr = "BLUE", rc = true, d = { rs = 5 } }) @@ -49,7 +48,7 @@ function MTH_ChronometerHunterInstallData(engine) engine:AddTimer(engine.EVENT, BS["Immolation Trap Effect"], 15, 1, 0, 1, { gr = 1, cr = "ORANGE", xn = BS["Immolation Trap"] }) engine:AddTimer(engine.EVENT, BS["Improved Concussive Shot"], 3, 1, 0, 0, { a = 1, cr = "BLUE", xn = BS["Concussive Shot"] }) engine:AddTimer(engine.EVENT, BS["Improved Wing Clip"], 5, 1, 0, 0, { a = 1, cr = "MAROON", xn = BS["Wing Clip"] }) - engine:AddTimer(engine.EVENT, BS["Lock and Load"], 10, 0, 1, 1, { a = 1, cr = "MAROON", tx = "Interface\\Icons\\ability_hunter_lockandload" }) -- 10s (tooltip confirmed; expires on Aimed Shot cast) + engine:AddTimer(engine.EVENT, BS["Lock and Load"], 10, 0, 1, 1, { a = 1, cr = "MAROON", tx = "Interface\\Icons\\ability_hunter_lockandload" }) -- 10s (tooltip confirmed; expires when consumed) engine:AddTimer(engine.EVENT, BS["Scent of Blood"], 8, 0, 1, 1, { a = 1, cr = "RED" }) engine:AddTimer(engine.EVENT, BS["Piercing Shot"], 8, 1, 0, 1, { a = 1, cr = "PURPLE", tx = "Interface\\Icons\\Ability_Hunter_DisarmingShot" }) engine:AddTimer(engine.EVENT, BS["Entrapment"], 5, 1, 0, 1, { a = 1, cr = "MAROON", tx = "Interface\\Icons\\Spell_Nature_StrangleVines" }) diff --git a/modules/chronometer/engine.lua b/modules/chronometer/engine.lua index 561a394..b4c9075 100644 --- a/modules/chronometer/engine.lua +++ b/modules/chronometer/engine.lua @@ -58,6 +58,18 @@ Chronometer.dataSetup = {} local latins = { I = 1, II = 2, III = 3, IV = 4, V = 5, VI = 6, VII = 7, VIII = 8, IX = 9, X = 10, XI = 11, XII = 12, XIII = 13, XIV = 14 } local BAR_GROUP = "MTHChronometer" + +-- When growing up the group attachment point must flip so bars stack above the +-- anchor rather than below it. CandyBar uses the stored group point as-is when +-- positioning each bar, so SetCandyBarGroupGrowth alone is not enough. +local function applyGrowup(self, growup) + if growup then + self:SetCandyBarGroupPoint(BAR_GROUP, "BOTTOM", self.anchor, "TOP", 0, 0) + else + self:SetCandyBarGroupPoint(BAR_GROUP, "TOP", self.anchor, "BOTTOM", 0, 0) + end + self:SetCandyBarGroupGrowth(BAR_GROUP, growup) +end local PARSER_OWNER = "MTHChronometerHunter" @@ -556,7 +568,7 @@ function Chronometer:OnEnable() self._mth_enabled = true self.profile = ensureProfile() - self:SetCandyBarGroupGrowth(BAR_GROUP, self.profile.growup and true or false) + applyGrowup(self, self.profile.growup and true or false) self:SetCandyBarGroupVerticalSpacing(BAR_GROUP, self.profile.spacing or 0) if self.anchor then end @@ -650,15 +662,34 @@ function Chronometer:OnEnable() self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_SELF_DAMAGE", function(event, info) self:SELF_DAMAGE(event, info) end) self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_DAMAGESHIELDS_ON_SELF", function(event, info) self:SELF_DAMAGE(event, info) end) self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_FAILED_LOCALPLAYER", function(event, info) self:SPELL_FAILED(event, info) end) + + -- NamPower: register authoritative self-aura events for instant bar + -- kill and duration resync. The ParserLib chat paths remain active as + -- they handle creature/target debuffs that NamPower cannot see. + if MTH and MTH.nampower then + self._nampowerSpellIdCache = {} + self:RegisterEvent("BUFF_ADDED_SELF") + self:RegisterEvent("BUFF_REMOVED_SELF") + self:RegisterEvent("DEBUFF_ADDED_SELF") + self:RegisterEvent("DEBUFF_REMOVED_SELF") + end end function Chronometer:VARIABLES_LOADED() self.profile = ensureProfile() + if self._mth_enabled then + applyGrowup(self, self.profile.growup and true or false) + self:SetCandyBarGroupVerticalSpacing(BAR_GROUP, self.profile.spacing or 0) + end MTH_CHRON_RestoreAnchorFromProfile(self, "VARIABLES_LOADED") end function Chronometer:PLAYER_ENTERING_WORLD() self.profile = ensureProfile() + if self._mth_enabled then + applyGrowup(self, self.profile.growup and true or false) + self:SetCandyBarGroupVerticalSpacing(BAR_GROUP, self.profile.spacing or 0) + end MTH_CHRON_RestoreAnchorFromProfile(self, "PLAYER_ENTERING_WORLD") end @@ -1249,6 +1280,85 @@ function Chronometer:CreateAnchor(text, r, g, b) return frame end +-- ──────────────────────────────────────────────────────────── +-- NamPower self-aura handlers +-- ──────────────────────────────────────────────────────────── + +-- Helper: resolve spellId → spell name, with cache +local function NP_ResolveName(cache, spellId) + if not spellId or spellId == 0 then return nil end + local name = cache[spellId] + if name then return name end + name = GetSpellRecField(spellId, "name") + if name and name ~= "" then + cache[spellId] = name + return name + end + return nil +end + +-- BUFF / DEBUFF removed from the player → kill matching self-bar instantly +function Chronometer:BUFF_REMOVED_SELF() + if not self.profile.fadeonfade then return end + local spellId = arg3 + local name = NP_ResolveName(self._nampowerSpellIdCache, spellId) + if not name then return end + self:KillBar(name) +end + +Chronometer.DEBUFF_REMOVED_SELF = Chronometer.BUFF_REMOVED_SELF + +-- BUFF / DEBUFF added to the player → resync running bar duration +function Chronometer:BUFF_ADDED_SELF() + local spellId = arg3 + local name = NP_ResolveName(self._nampowerSpellIdCache, spellId) + if not name then return end + + -- Find the matching self-bar + local bar = nil + for i = 1, 20 do + if self.bars[i].id and self.bars[i].name == name then + local tgt = self.bars[i].target + if tgt == "none" or tgt == UnitName("player") then + bar = self.bars[i] + break + end + end + end + if not bar then return end + + -- Get authoritative remaining duration from NamPower aura array + local remaining = nil + local auras = GetUnitField("player", "aura") + if auras then + for slot = 1, 48 do + if auras[slot] == spellId then + local _, ms = GetPlayerAuraDuration(slot - 1) + if ms and ms > 0 then + remaining = ms / 1000 + end + break + end + end + end + if not remaining then + -- Fallback: use base DBC duration + local dms = GetSpellDuration(spellId) + if dms and dms > 0 then + remaining = dms / 1000 + end + end + if not remaining then return end + + -- Resync: update total first (allows SetTimeLeft to extend), then set remaining + self:SetCandyBarTime(bar.id, remaining) + self:SetCandyBarTimeLeft(bar.id, remaining) +end + +Chronometer.DEBUFF_ADDED_SELF = Chronometer.BUFF_ADDED_SELF + +-- ──────────────────────────────────────────────────────────── + function Chronometer:COMBAT_DEATH(event, info) if not self.profile.fadeonkill then return end if info.type == "experience" then @@ -1399,7 +1509,9 @@ function Chronometer:UNIT_AURA(unit) end local hasFeedAura = false - if type(UnitBuff) == "function" then + if type(MTH_FEED_HasPetFeedBuff) == "function" then + hasFeedAura = MTH_FEED_HasPetFeedBuff() + elseif type(UnitBuff) == "function" then for i = 1, 32 do local texture = UnitBuff("pet", i) if not texture then diff --git a/modules/chronometer/module.lua b/modules/chronometer/module.lua index bbd8385..e6dca8f 100644 --- a/modules/chronometer/module.lua +++ b/modules/chronometer/module.lua @@ -1,7 +1,6 @@ local MTH_ChronometerModule = { name = "chronometer", enabled = true, - version = "1.4.1", events = {}, initialized = false, } diff --git a/modules/expammo/engine.lua b/modules/expammo/engine.lua index 41e3ae7..421ad9c 100644 --- a/modules/expammo/engine.lua +++ b/modules/expammo/engine.lua @@ -25,6 +25,7 @@ local TALENT_NAME = "Experimental Ammunition" -- ── Spell names (English) ──────────────────────────────────── local SPELL_AIMED = "Aimed Shot" +local SPELL_STEADY = "Steady Shot" local SPELL_MULTI = "Multi-Shot" local SPELL_ARCANE = "Arcane Shot" local SPELL_SERPENT = "Serpent Sting" @@ -103,6 +104,33 @@ local rt = { lastState = nil, -- ammo state that most recently ended; used for predictive display } +local MTH_EA_PendingAuraRescan = 0 + +-- Prediction verification timer. When SPELLCAST_STOP sets a predictive +-- ammo state, we record GetTime()+1.5 here. OnUpdate checks this and, +-- if the state is still the predicted one (no server confirmation arrived), +-- performs a hard aura scan. If the aura isn't present, the phantom state +-- is cleared to IDLE. Set to 0 when no verification is pending. +local MTH_EA_PredictVerifyAt = 0 + +local function MTH_EA_RequestAuraRescan(delay) + delay = tonumber(delay) or 0.05 + if delay < 0 then + delay = 0 + end + if MTH_EA_PendingAuraRescan <= 0 or delay < MTH_EA_PendingAuraRescan then + MTH_EA_PendingAuraRescan = delay + end +end + +-- ── NamPower integration ───────────────────────────────────── +-- When NamPower ≥ 2.40.0 is present, VARIABLES_LOADED unregisters +-- the 5 chat events + PLAYER_AURAS_CHANGED and registers the 4 exact +-- spellId aura events instead. Falls back to the old path otherwise. +local MTH_EA_UseNamPower = false +local MTH_EA_SpellIdToState = {} -- [spellId] → EXPLOSIVE/POISONOUS/ENCHANTED (lazy) +local MTH_EA_LnLSpellId = nil -- Lock and Load spellId (resolved on first gain) + -- ── Aura probe for state recovery and PLAYER_AURAS_CHANGED ── -- Combat log shows "You are afflicted by Explosive Ammunition" -- meaning the ammo state is implemented as a player DEBUFF, not @@ -186,7 +214,10 @@ local function MTH_EA_IconBasename(texturePath) if not texturePath then return "" end local s = string.lower(tostring(texturePath)) local _, _, base = string.find(s, "([^\\]+)$") - return base or s + base = base or s + -- strip extension (.blp, .tga, etc.) if present + base = string.gsub(base, "%.[a-z]+$", "") + return base end -- Scan player buffs+debuffs for an active ammo state via texture comparison. @@ -196,7 +227,7 @@ local function MTH_EA_FindAmmoStateByTexture() if not t then break end local base = MTH_EA_IconBasename(t) for stateKey, frag in pairs(MTH_EA_AMMO_TEX) do - if base == frag then return stateKey end + if string.find(base, frag, 1, true) then return stateKey end end end for i = 1, 16 do @@ -204,7 +235,7 @@ local function MTH_EA_FindAmmoStateByTexture() if not t then break end local base = MTH_EA_IconBasename(t) for stateKey, frag in pairs(MTH_EA_AMMO_TEX) do - if base == frag then return stateKey end + if string.find(base, frag, 1, true) then return stateKey end end end return nil @@ -225,6 +256,7 @@ end -- Scan all player auras (buffs then debuffs) for an active ammo state. -- Returns the matching STATE_ORDER key, or nil if none found. -- (Delegates to the fast texture path; tooltip path kept for debug use only.) +local MTH_EA_SetState local function MTH_EA_FindAmmoAura() return MTH_EA_FindAmmoStateByTexture() end @@ -236,6 +268,9 @@ local function MTH_EA_RecoverStateFromBuffs() rt.state = stateKey rt.expiresAt = GetTime() + CYCLE_DURATION rt.consumed = false + MTH_EA_PredictVerifyAt = 0 -- server confirmed; cancel pending verify + elseif rt.state ~= IDLE then + MTH_EA_SetState(IDLE) end end @@ -265,8 +300,10 @@ local function MTH_EA_EnsureConfigDefaults() if cfg.showLnL == nil then cfg.showLnL = true end if cfg.showHint == nil then cfg.showHint = true end if cfg.cellSize == nil then cfg.cellSize = 40 end - if cfg.bigCD == nil then cfg.bigCD = false end - if cfg.hideOOC == nil then cfg.hideOOC = false end + if cfg.cellGap == nil then cfg.cellGap = 2 end + if cfg.bigCD == nil then cfg.bigCD = false end + if cfg.hideOOC == nil then cfg.hideOOC = false end + if cfg.useQuiverNoClip == nil then cfg.useQuiverNoClip = false end end -- Combat state flag (avoids needing InCombatLockdown, which doesn't exist in 1.12) @@ -278,15 +315,17 @@ local MTH_EA_AurasChangedLastTime = 0 -- UI frame handles — declared here so MTH_EA_RefreshVisibility (and every -- function defined below) all close over the SAME locals. -local strip = nil -- root drag frame (sized to 3×1 row) +local strip = nil -- root positioning frame (sized to cross bounding box) local cells = {} -- [1..3] ammo state cells local cdTexts = {} -- [1..3] countdown fontstrings in ammo cells local cellTop = nil -- Lock and Load cell local cellTopCD = nil -- L&L countdown fontstring -local cellBot = nil -- action button cell +local cellBot = nil -- action hint cell (display only, click-through) local cellBotIcon = nil -- action button icon texture local cellBotCD = nil -- action button CD / "READY" text local cellBotBlink = 0 -- blink phase accumulator +local anchorFrames = {} -- [1..4] = BOTTOM, TOP, LEFT, RIGHT drag handles +local MTH_EA_SnapAnchor -- forward declaration (defined after ApplyLayout) -- Show/hide the strip according to enabled + hideOOC + inCombat local function MTH_EA_RefreshVisibility() @@ -303,7 +342,7 @@ local MTH_EA_UpdateUI local MTH_EA_ApplyLayout -- ── State machine ───────────────────────────────────────────── -local function MTH_EA_SetState(newState) +MTH_EA_SetState = function(newState) -- Track which state just ended so the predictive display knows what comes next. if newState == IDLE and rt.state ~= IDLE then rt.lastState = rt.state @@ -311,6 +350,9 @@ local function MTH_EA_SetState(newState) rt.state = newState rt.consumed = false rt.expiresAt = (newState ~= IDLE) and (GetTime() + CYCLE_DURATION) or 0 + -- Any authoritative state transition cancels a pending prediction verify, + -- because the state is now confirmed (or corrected) by the server. + MTH_EA_PredictVerifyAt = 0 MTH_EA_UpdateUI() end @@ -341,6 +383,7 @@ local lnl = { -- ── Confirmed icon paths (captured in-game 2026-03-21) ──────── -- Action-button spells (spellbook scan) local ICON_AIMED = "Interface\\Icons\\INV_Spear_07" +local ICON_STEADY = "Interface\\Icons\\Ability_Hunter_SteadyShot" local ICON_MULTI = "Interface\\Icons\\Ability_UpgradeMoonGlaive" local ICON_SERPENT = "Interface\\Icons\\Ability_Hunter_QuickShot" local ICON_ARCANE = "Interface\\Icons\\Ability_ImpalingBolt" @@ -370,23 +413,121 @@ local function MTH_EA_TryCaptureNewBuffIcon() lnlPendingCapture = false end --- Action button: what spell to show / cast right now +-- ── Action-bar slot cache ──────────────────────────────────── +-- Maps spell names to action bar slot numbers via icon texture comparison. +-- Much more reliable than tooltip probing in 1.12 clients. +local MTH_EA_ActionSlotCache = {} -- [spellName] = slot or false + +-- Known spell icon textures (lowercase) for action bar matching. +-- These are the SPELLBOOK icons, which match what GetActionTexture returns. +local MTH_EA_SPELL_ICONS = { + [SPELL_AIMED] = string.lower(ICON_AIMED), + [SPELL_STEADY] = string.lower(ICON_STEADY), + [SPELL_MULTI] = string.lower(ICON_MULTI), + [SPELL_ARCANE] = string.lower(ICON_ARCANE), + [SPELL_SERPENT] = string.lower(ICON_SERPENT), +} + +-- Invalidate the slot cache (call on login and action bar changes). +local function MTH_EA_ClearActionSlotCache() + MTH_EA_ActionSlotCache = {} +end + +-- Find the action bar slot for a spell by comparing action icon textures. +-- Returns slot number or nil. Result is cached until invalidated. +local function MTH_EA_FindActionSlot(spellName) + if not spellName then return nil end + local cached = MTH_EA_ActionSlotCache[spellName] + if cached ~= nil then + return cached or nil -- false → nil (scanned, not found) + end + local wantIcon = MTH_EA_SPELL_ICONS[spellName] + if not wantIcon then + MTH_EA_ActionSlotCache[spellName] = false + return nil + end + for slot = 1, 120 do + if HasAction(slot) and not GetActionText(slot) then + local tex = GetActionTexture(slot) + if tex and string.lower(tex) == wantIcon then + MTH_EA_ActionSlotCache[spellName] = slot + return slot + end + end + end + MTH_EA_ActionSlotCache[spellName] = false + return nil +end + +-- Cooldown via action bar slot (secondary path). +local function MTH_EA_ActionSpellCD(spellName) + local slot = MTH_EA_FindActionSlot(spellName) + if not slot then return 0 end + if type(GetActionCooldown) ~= "function" then return 0 end + local start, duration = GetActionCooldown(slot) + if not start or not duration or duration <= 1.5 then return 0 end + return math.max(0, (start + duration) - GetTime()) +end + +-- Cooldown via spellbook (primary, always reliable). +-- Uses the hunter talent scanner's cached spellbook slot. +local function MTH_EA_SpellbookCD(spellName) + if type(MTH_HT_GetSpellInfo) ~= "function" then return 0 end + if type(GetSpellCooldown) ~= "function" then return 0 end + local info = MTH_HT_GetSpellInfo(spellName) + if not info or not info.slot then return 0 end + local start, duration = GetSpellCooldown(info.slot, "spell") + if not start or not duration or duration <= 1.5 then return 0 end + return math.max(0, (start + duration) - GetTime()) +end + +-- Returns remaining cooldown of a spell in seconds (0 = ready). +-- Primary: spellbook slot (always available, no action bar needed). +-- Secondary: action bar slot via icon texture matching. +local function MTH_EA_GetSpellCD(spellName) + local cd = MTH_EA_SpellbookCD(spellName) + if cd > 0 then return cd end + return MTH_EA_ActionSpellCD(spellName) +end + +-- Action button: what spell to show and what spell to cast right now. +-- Returns displaySpell, icon, r, g, b, cooldownRemaining, castSpell local function MTH_EA_GetActionSpell() if rt.state == EXPLOSIVE then - return SPELL_MULTI, ICON_MULTI, 1.00, 0.60, 0.05 + local cd = MTH_EA_GetSpellCD(SPELL_MULTI) + return SPELL_MULTI, ICON_MULTI, 1.00, 0.60, 0.05, cd, (cd > 0 and SPELL_STEADY or SPELL_MULTI) elseif rt.state == POISONOUS then - return SPELL_SERPENT, ICON_SERPENT, 0.12, 0.85, 0.18 + local cd = MTH_EA_GetSpellCD(SPELL_SERPENT) + return SPELL_SERPENT, ICON_SERPENT, 0.12, 0.85, 0.18, cd, (cd > 0 and SPELL_STEADY or SPELL_SERPENT) elseif rt.state == ENCHANTED then - return SPELL_ARCANE, ICON_ARCANE, 0.38, 0.12, 1.00 + local cd = MTH_EA_GetSpellCD(SPELL_ARCANE) + return SPELL_ARCANE, ICON_ARCANE, 0.38, 0.12, 1.00, cd, (cd > 0 and SPELL_STEADY or SPELL_ARCANE) else - return SPELL_AIMED, ICON_AIMED, 1.00, 0.85, 0.00 + -- IDLE: cast Steady Shot while Aimed is on CD, Aimed when ready + local cd = MTH_EA_GetSpellCD(SPELL_AIMED) + return SPELL_AIMED, ICON_AIMED, 1.00, 0.85, 0.00, cd, (cd > 0 and SPELL_STEADY or SPELL_AIMED) end end -- Called from the keybind and from clicking the action button function MTH_EA_CastActionSpell() - local spellName = MTH_EA_GetActionSpell() - if spellName then CastSpellByName(spellName) end + local _, _, _, _, _, _, spellName = MTH_EA_GetActionSpell() + if not spellName then return end + local cfg = MTH_EA_GetConfig() + if cfg.useQuiverNoClip + and type(Quiver) == "table" + and type(Quiver.GetSecondsRemainingShoot) == "function" + and type(Quiver.CastNoClip) == "function" + then + local a, b = Quiver.GetSecondsRemainingShoot() + if a and b < -0.25 then + CastSpellByName(spellName) + else + Quiver.CastNoClip(spellName) + end + else + CastSpellByName(spellName) + end end -- Build a styled cell frame parented to `parent` @@ -396,12 +537,11 @@ local function MTH_EA_MakeCell(name, parent, w, h) c:SetHeight(h or CELL_SIZE) c:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", - edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", - edgeSize = 10, - insets = { left = 2, right = 2, top = 2, bottom = 2 }, + edgeSize = 0, + insets = { left = 0, right = 0, top = 0, bottom = 0 }, }) c:SetBackdropColor(0, 0, 0, 0.65) - c:SetBackdropBorderColor(0.3, 0.3, 0.3, 0.5) + c:EnableMouse(false) local icon = c:CreateTexture(nil, "ARTWORK") icon:SetPoint("TOPLEFT", c, "TOPLEFT", 3, -3) icon:SetPoint("BOTTOMRIGHT", c, "BOTTOMRIGHT", -3, 3) @@ -425,20 +565,7 @@ local function MTH_EA_CreateUI() strip:SetWidth(totalW) strip:SetHeight(totalH) strip:SetMovable(true) - strip:EnableMouse(true) - strip:RegisterForDrag("LeftButton") - strip:SetScript("OnDragStart", function() - if IsAltKeyDown() then this:StartMoving() end - end) - strip:SetScript("OnDragStop", function() - this:StopMovingOrSizing() - local cfg = MTH_EA_GetConfig() - local point, _, relPoint, x, y = this:GetPoint() - if point and x and y then - cfg.pos = { point = point, relPoint = relPoint, - x = math.floor(x + 0.5), y = math.floor(y + 0.5) } - end - end) + strip:EnableMouse(false) strip:SetFrameStrata("MEDIUM") local cfg = MTH_EA_GetConfig() @@ -472,28 +599,14 @@ local function MTH_EA_CreateUI() cellTop.icon:SetVertexColor(0.30, 0.30, 0.30, 0.40) cellTopCD = cellTop.cd - -- ── BOTTOM cell: action button ─────────────────────────── + -- ── BOTTOM cell: action hint (display only, click-through) ─ cellBot = MTH_EA_MakeCell("MTH_ExpAmmoBot", strip) cellBot:SetPoint("TOP", cells[2], "BOTTOM", 0, -CELL_GAP) - cellBot:EnableMouse(true) - cellBot:SetScript("OnMouseUp", function() - if arg1 == "LeftButton" then MTH_EA_CastActionSpell() end - end) cellBotIcon = cellBot.icon cellBotCD = cellBot.cd cellBotIcon:SetTexture(ICON_AIMED) cellBotIcon:SetVertexColor(1, 0.85, 0, 0.6) - -- Tooltip on the action button - cellBot:SetScript("OnEnter", function() - local spellName = MTH_EA_GetActionSpell() - GameTooltip:SetOwner(this, "ANCHOR_RIGHT") - GameTooltip:SetText(spellName or "") - GameTooltip:AddLine("Left-click or use keybind", 0.6, 0.6, 0.6) - GameTooltip:Show() - end) - cellBot:SetScript("OnLeave", function() GameTooltip:Hide() end) - MTH_EA_ApplyLayout() end @@ -560,25 +673,14 @@ MTH_EA_UpdateUI = function() -- ── BOTTOM cell: action button ──────────────────────────── if cellBot then - local spellName, icon, r, g, b = MTH_EA_GetActionSpell() - cellBotIcon:SetTexture(icon) + local displaySpell, icon, r, g, b, cdRemain = MTH_EA_GetActionSpell() if rt.state == IDLE then - -- Show Aimed Shot with CD and blink when ready - local cdRemain = 0 - if type(GetSpellCooldown) == "function" and MTH_HT_GetSpellInfo then - local info = MTH_HT_GetSpellInfo(SPELL_AIMED) - if info and info.slot then - local start, duration = GetSpellCooldown(info.slot, BOOKTYPE_SPELL) - -- Ignore GCD-length cooldowns (≤1.5 s) triggered by other casts. - -- Aimed Shot's real cooldown is always longer than the GCD. - if start and duration and duration > 1.5 then - cdRemain = math.max(0, (start + duration) - now) - end - end - end + -- Always display Aimed Shot icon + its CD. + -- The action (keybind/click) casts Steady Shot while Aimed is on CD. + cellBotIcon:SetTexture(ICON_AIMED) if cdRemain > 0 then - -- On cooldown: dim, show seconds + -- On CD: dim icon, show seconds countdown cellBotBlink = 0 cellBotIcon:SetVertexColor(r * 0.45, g * 0.45, b * 0.45, 0.70) cellBot:SetBackdropBorderColor(r * 0.35, g * 0.35, b * 0.35, 0.55) @@ -588,14 +690,20 @@ MTH_EA_UpdateUI = function() local pulse = math.abs(math.sin(cellBotBlink * 3)) cellBotIcon:SetVertexColor(r, g, b, 0.75 + pulse * 0.25) cellBot:SetBackdropBorderColor(r, g * pulse, b * 0.2, 0.7 + pulse * 0.3) - cellBotCD:SetText("|cff00ff00READY|r") + cellBotCD:SetText("|cffffe066USE|r") end else - -- Ammo active: show consume spell, fully lit + -- Ammo active: show consume spell icon, fully lit cellBotBlink = 0 + cellBotIcon:SetTexture(icon) cellBotIcon:SetVertexColor(r, g, b, 1) - cellBot:SetBackdropBorderColor(r, g, b, 1) - cellBotCD:SetText("|cffffe066USE|r") + if cdRemain > 0 then + cellBot:SetBackdropBorderColor(r * 0.35, g * 0.35, b * 0.35, 0.55) + cellBotCD:SetText("|cffaaaaaa" .. math.floor(cdRemain + 0.5) .. "|r") + else + cellBot:SetBackdropBorderColor(r, g, b, 1) + cellBotCD:SetText("|cffffe066USE|r") + end end end end @@ -606,17 +714,29 @@ MTH_EA_ApplyLayout = function() if not strip then return end local cfg = MTH_EA_GetConfig() local sz = math.max(20, math.min(80, cfg.cellSize or 40)) + local gap = math.max(0, math.min(20, cfg.cellGap or 2)) local bigCD = cfg.bigCD == true local font = CELL_FONT - strip:SetWidth(sz * 3 + CELL_GAP * 2) - strip:SetHeight(sz * 3 + CELL_GAP * 2) + strip:SetWidth(sz * 3 + gap * 2) + strip:SetHeight(sz * 3 + gap * 2) + + local iconPad = math.min(gap, 3) for i = 1, 3 do local c = cells[i] local fs = cdTexts[i] c:SetWidth(sz) c:SetHeight(sz) + c:ClearAllPoints() + if i == 1 then + c:SetPoint("LEFT", strip, "LEFT", 0, 0) + else + c:SetPoint("LEFT", cells[i-1], "RIGHT", gap, 0) + end + c.icon:ClearAllPoints() + c.icon:SetPoint("TOPLEFT", c, "TOPLEFT", iconPad, -iconPad) + c.icon:SetPoint("BOTTOMRIGHT", c, "BOTTOMRIGHT", -iconPad, iconPad) fs:ClearAllPoints() if bigCD then fs:SetPoint("CENTER", c, "CENTER", 0, 0) @@ -630,7 +750,12 @@ MTH_EA_ApplyLayout = function() if cellTop then cellTop:SetWidth(sz) cellTop:SetHeight(sz) + cellTop:ClearAllPoints() + cellTop:SetPoint("BOTTOM", cells[2], "TOP", 0, gap) if cfg.showLnL ~= false then cellTop:Show() else cellTop:Hide() end + cellTop.icon:ClearAllPoints() + cellTop.icon:SetPoint("TOPLEFT", cellTop, "TOPLEFT", iconPad, -iconPad) + cellTop.icon:SetPoint("BOTTOMRIGHT", cellTop, "BOTTOMRIGHT", -iconPad, iconPad) if cellTopCD then cellTopCD:ClearAllPoints() if bigCD then @@ -646,7 +771,12 @@ MTH_EA_ApplyLayout = function() if cellBot then cellBot:SetWidth(sz) cellBot:SetHeight(sz) + cellBot:ClearAllPoints() + cellBot:SetPoint("TOP", cells[2], "BOTTOM", 0, -gap) if cfg.showHint ~= false then cellBot:Show() else cellBot:Hide() end + cellBot.icon:ClearAllPoints() + cellBot.icon:SetPoint("TOPLEFT", cellBot, "TOPLEFT", iconPad, -iconPad) + cellBot.icon:SetPoint("BOTTOMRIGHT", cellBot, "BOTTOMRIGHT", -iconPad, iconPad) if cellBotCD then cellBotCD:ClearAllPoints() if bigCD then @@ -660,6 +790,105 @@ MTH_EA_ApplyLayout = function() end MTH_EA_UpdateUI() + MTH_EA_SnapAnchor() +end + +-- ── Anchor frames for repositioning ──────────────────────────────────────── +-- 4 handles placed outside each edge of the cross so the widget can be dragged +-- from any side and positioned flush against any screen edge. + +local ANCHOR_SIDES = { + { name = "Bot", attachPoint = "TOP", stripPoint = "BOTTOM", ox = 0, oy = -1, isWide = true }, + { name = "Top", attachPoint = "BOTTOM", stripPoint = "TOP", ox = 0, oy = 1, isWide = true }, + { name = "Lft", attachPoint = "RIGHT", stripPoint = "LEFT", ox = -1, oy = 0, isWide = false }, + { name = "Rgt", attachPoint = "LEFT", stripPoint = "RIGHT", ox = 1, oy = 0, isWide = false }, +} + +MTH_EA_SnapAnchor = function() + if not strip then return end + local cfg = MTH_EA_GetConfig() + local gap = math.max(0, math.min(20, cfg.cellGap or 2)) + local sz = math.max(20, math.min(80, cfg.cellSize or 40)) + local thin = math.max(8, math.floor(sz * 0.35 + 0.5)) + for i, side in ipairs(ANCHOR_SIDES) do + local f = anchorFrames[i] + if f then + if side.isWide then + f:SetWidth(sz * 3 + gap * 2) + f:SetHeight(thin) + else + f:SetWidth(thin) + f:SetHeight(sz * 3 + gap * 2) + end + f:ClearAllPoints() + f:SetPoint(side.attachPoint, strip, side.stripPoint, + side.ox * gap, side.oy * gap) + end + end +end + +local function MTH_EA_MakeAnchorFrame(idx, side) + local f = CreateFrame("Button", "MTH_ExpAmmoAnchor"..side.name, UIParent) + f:SetFrameStrata("HIGH") + f:EnableMouse(true) + f:RegisterForDrag("LeftButton") + f:SetBackdrop({ + bgFile = "Interface\\Buttons\\WHITE8X8", + edgeSize = 0, + insets = { left = 0, right = 0, top = 0, bottom = 0 }, + }) + f:SetBackdropColor(0.15, 0.45, 0.85, 0.75) + local label = f:CreateFontString(nil, "OVERLAY") + label:SetAllPoints() + label:SetFont(CELL_FONT or "Fonts\\FRIZQT__.TTF", 8, "OUTLINE") + label:SetJustifyH("CENTER") + label:SetText("|cffffffffMOVE|r") + f:SetScript("OnDragStart", function() + strip:StartMoving() + end) + f:SetScript("OnDragStop", function() + strip:StopMovingOrSizing() + local cfg = MTH_EA_GetConfig() + local point, _, relPoint, x, y = strip:GetPoint() + if point and x and y then + cfg.pos = { point = point, relPoint = relPoint, + x = math.floor(x + 0.5), y = math.floor(y + 0.5) } + end + MTH_EA_SnapAnchor() + end) + f:SetScript("OnEnter", function() + GameTooltip:SetOwner(this, "ANCHOR_RIGHT") + GameTooltip:SetText("|cffffff00MM Widget — Move Anchor|r") + GameTooltip:AddLine("Drag to reposition the widget", 0.6, 0.6, 0.6) + GameTooltip:Show() + end) + f:SetScript("OnLeave", function() GameTooltip:Hide() end) + return f +end + +function MTH_EA_ToggleAnchor() + if not strip then return end + local showing = false + -- Show if none exist or all are hidden + for i = 1, 4 do + if anchorFrames[i] and anchorFrames[i]:IsShown() then + showing = true + break + end + end + if showing then + for i = 1, 4 do + if anchorFrames[i] then anchorFrames[i]:Hide() end + end + else + for i, side in ipairs(ANCHOR_SIDES) do + if not anchorFrames[i] then + anchorFrames[i] = MTH_EA_MakeAnchorFrame(i, side) + end + anchorFrames[i]:Show() + end + MTH_EA_SnapAnchor() + end end -- ── Spell-name capture hook ─────────────────────────────────── @@ -673,30 +902,25 @@ local MTH_EA_LastSpell = nil local MTH_EA_InHookCSBN = false local MTH_EA_InHookCS = false local MTH_EA_InHookUA = false -local MTH_EA_OrigCSBN = CastSpellByName -local MTH_EA_OrigCS = CastSpell -local MTH_EA_OrigUA = UseAction local function MTH_EA_HookInstallCSBN() + -- Capture the full current chain at install time (VARIABLES_LOADED fires after all + -- addons — including Quiver — have loaded and installed their own hooks). Using a + -- file-parse-time snapshot here would skip every hook installed after this file was + -- parsed (e.g. Quiver), causing their CastSpellByName hooks to be silently bypassed. + local upstream = CastSpellByName CastSpellByName = function(spellText, onSelf) if MTH_EA_InHookCSBN then - local f = MTH_EA_OrigCSBN - if f and f ~= CastSpellByName then return f(spellText, onSelf) end + if upstream then return upstream(spellText, onSelf) end return end MTH_EA_InHookCSBN = true local _, _, name = string.find(tostring(spellText or ""), "([%w%'%s%-]+)") MTH_EA_LastSpell = name and (string.gsub(name, "^%s*(.-)%s*$", "%1")) or nil - local upstream = MTH_EA_OrigCSBN - -- Always call the current global so Quiver/SmartAmmo's chain is respected - local cur = CastSpellByName MTH_EA_InHookCSBN = false - if upstream and upstream ~= cur then + if upstream then return upstream(spellText, onSelf) - elseif cur ~= MTH_EA_HookInstallCSBN then - -- another addon replaced us; call them end - -- fallback: nothing more to call (we ARE the last in chain) end end @@ -719,6 +943,16 @@ local function MTH_EA_HookInstallCS() end end +-- Own tooltip probe for UseAction hook (created on first use). +-- Uses GameTooltipTemplate for reliable SetAction text population. +local MTH_EA_UAProbe = nil +local function MTH_EA_EnsureUAProbe() + if MTH_EA_UAProbe then return MTH_EA_UAProbe end + MTH_EA_UAProbe = CreateFrame("GameTooltip", "MTH_EA_UAProbe", UIParent, "GameTooltipTemplate") + MTH_EA_UAProbe:SetOwner(UIParent, "ANCHOR_NONE") + return MTH_EA_UAProbe +end + local function MTH_EA_HookInstallUA() local origUA = UseAction UseAction = function(slot, checkCursor, onSelf) @@ -727,13 +961,28 @@ local function MTH_EA_HookInstallUA() return end MTH_EA_InHookUA = true - if type(GetActionText) == "function" and type(GetSpellName) == "function" then - -- For spell-type actions, resolve name via tooltip probe - local probe = MTH_SA_AmmoProbe -- reuse SmartAmmo's tooltip probe if available - if probe then + -- For spell-type actions (not macros), resolve spell name via icon texture + -- or tooltip probe. Texture match is tried first (zero cost). + if not GetActionText(slot) then + local tex = GetActionTexture(slot) + local matched = false + if tex then + local lower = string.lower(tex) + for name, icon in pairs(MTH_EA_SPELL_ICONS) do + if lower == icon then + MTH_EA_LastSpell = name + matched = true + break + end + end + end + if not matched then + local probe = MTH_EA_EnsureUAProbe() probe:SetOwner(UIParent, "ANCHOR_NONE") + probe:ClearLines() probe:SetAction(slot) - local txt = MTH_SA_AmmoProbeTextLeft1 and MTH_SA_AmmoProbeTextLeft1:GetText() + local L1 = getglobal("MTH_EA_UAProbeTextLeft1") + local txt = L1 and L1:GetText() or nil if txt and txt ~= "" then MTH_EA_LastSpell = txt end end end @@ -744,38 +993,153 @@ local function MTH_EA_HookInstallUA() end end --- ── Debug mode ──────────────────────────────────────────────── --- /script MTH_EA_DebugMode = true → prints every Ammunition chat event --- /script MTH_EA_DebugMode = false → silent -MTH_EA_DebugMode = false - --- ── Broad debug sniffer ─────────────────────────────────────── --- A separate frame that registers EVERY CHAT_MSG_SPELL_* event and --- prints any message containing "Ammunition", so we can find exactly --- which event the server uses for the gain messages. -local MTH_EA_Sniffer = CreateFrame("Frame", "MTH_EA_SnifferFrame") -local MTH_EA_SNIFFER_EVENTS = { - "CHAT_MSG_SPELL_SELF_BUFF", - "CHAT_MSG_SPELL_AURA_GONE_SELF", - "CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS", - "CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE", - "CHAT_MSG_SPELL_CREATURE_VS_SELF_BUFF", - "CHAT_MSG_SPELL_FRIENDLYPLAYER_BUFF", - "CHAT_MSG_SPELL_HOSTILEPLAYER_BUFF", - "CHAT_MSG_SPELL_AURA_GONE_PARTY", - "CHAT_MSG_SPELL_AURA_GONE_OTHER", -} -for _, ev in ipairs(MTH_EA_SNIFFER_EVENTS) do - MTH_EA_Sniffer:RegisterEvent(ev) +-- ── NamPower helper functions ──────────────────────────────── +local function MTH_EA_CheckNamPower() + if type(GetNampowerVersion) ~= "function" then return false end + local maj, min = GetNampowerVersion() + maj = tonumber(maj) or 0 + min = tonumber(min) or 0 + return maj > 2 or (maj == 2 and min >= 40) end -MTH_EA_Sniffer:SetScript("OnEvent", function() - if not MTH_EA_DebugMode then return end - local msg = arg1 or "" - if string.find(msg, "Ammunition") then - end -end) --- ── Event frame ─────────────────────────────────────────────── +-- Resolve spellId → state key via name lookup on first encounter; cache result. +local function MTH_EA_CacheSpellId(spellId) + if MTH_EA_SpellIdToState[spellId] or spellId == MTH_EA_LnLSpellId then return end + if type(GetSpellRecField) ~= "function" then return end + local name = GetSpellRecField(spellId, "name") + if not name or name == "" then return end + local stateKey = BUFF_TO_STATE[name] + if stateKey then + MTH_EA_SpellIdToState[spellId] = stateKey + return + end + if string.find(name, "Lock and Load", 1, true) then + MTH_EA_LnLSpellId = spellId + end +end + +-- Query the authoritative remaining LnL duration from NamPower. +-- Returns seconds (float) or nil if LnL isn't active / API unavailable. +local function MTH_EA_GetLnLDurationNP() + if type(GetPlayerAuraDuration) ~= "function" then return nil end + if type(GetUnitField) ~= "function" then return nil end + local auras = GetUnitField("player", "aura") + if type(auras) ~= "table" then return nil end + -- Buffs occupy slots 1-32 (1-based in the table, 0-based for the API). + for i = 1, 32 do + local sid = tonumber(auras[i]) + if sid and sid == MTH_EA_LnLSpellId then + local _, remainMs = GetPlayerAuraDuration(i - 1) + remainMs = tonumber(remainMs) + if remainMs and remainMs > 0 then + return remainMs / 1000 + end + return nil -- slot found but no usable duration + end + end + return nil -- LnL not in aura array +end + +-- Recovery via NamPower's GetUnitField aura array (replaces texture scan). +local function MTH_EA_RecoverStateFromAuraArray() + if type(GetUnitField) ~= "function" then + MTH_EA_RecoverStateFromBuffs() + return + end + local auras = GetUnitField("player", "aura") + if type(auras) ~= "table" then + MTH_EA_RecoverStateFromBuffs() + return + end + local foundState = nil + local foundLnL = false + for _, spellId in pairs(auras) do + local sid = tonumber(spellId) + if sid and sid > 0 then + MTH_EA_CacheSpellId(sid) + local stateKey = MTH_EA_SpellIdToState[sid] + if stateKey and not foundState then + foundState = stateKey + rt.state = stateKey + rt.expiresAt = GetTime() + CYCLE_DURATION + rt.consumed = false + MTH_EA_PredictVerifyAt = 0 -- server confirmed; cancel pending verify + end + if sid == MTH_EA_LnLSpellId then + foundLnL = true + -- Always resync from authoritative NamPower duration. + local rem = MTH_EA_GetLnLDurationNP() + if rem then + lnl.active = true + lnl.expiresAt = GetTime() + rem + elseif not lnl.active then + lnl.active = true + lnl.expiresAt = GetTime() + lnl.DURATION + end + end + end + end + -- Authoritative LnL clear: if the buff is no longer in the aura array, + -- it was consumed (Aimed Shot) or expired. Clear immediately instead + -- of waiting for the hardcoded 10 s OnUpdate timer. + if not foundLnL and lnl.active then + lnl.active = false + end + if not foundState and rt.state ~= IDLE then + MTH_EA_SetState(IDLE) + end +end +-- ── Chat message handler (module-level to stay within Lua 5.0's +-- 32-upvalue-per-closure limit; OnEvent closes over too many locals +-- to host this as a nested function). +local function MTH_EA_TryHandleChatMsg(msg) + if not MTH_EA_IsEnabled() then return false end + msg = msg or "" + -- ── State GAIN patterns ───────────────────────────────── + local _, _, gained = string.find(msg, "^You are afflicted by (.+)%.$") + if not gained then + _, _, gained = string.find(msg, "^You gain (.+)%.$") + end + if not gained then + _, _, gained = string.find(msg, "^You gain the effect of (.+)%.$") + end + if gained then + local stateKey = BUFF_TO_STATE[gained] + if stateKey then + MTH_EA_SetState(stateKey) + return true + end + if string.find(gained, "Lock and Load") then + local rem = MTH_EA_GetLnLRemaining() + lnl.active = true + lnl.expiresAt = GetTime() + (rem or lnl.DURATION) + return true + end + end + -- ── State LOSS patterns ──────────────────────────────────── + local _, _, lost = string.find(msg, "^(.+) fades from you%.$") + if not lost then + _, _, lost = string.find(msg, "^(.+) fades from you$") + end + if lost then + local stateKey = BUFF_TO_STATE[lost] + if stateKey then + if rt.state == stateKey then + MTH_EA_SetState(IDLE) + end + return true + end + if string.find(lost, "Lock and Load") then + lnl.active = false + lnlPendingCapture = false + MTH_EA_SnapshotBuffs() + return true + end + end + return false +end + +-- ── Event frame ─────────────────────────────────────────────────── local frame = CreateFrame("Frame", "MTH_ExpAmmoFrame") frame:Hide() frame._elapsed = 0 @@ -793,6 +1157,7 @@ frame:RegisterEvent("CHAT_MSG_SPELL_CREATURE_VS_SELF_BUFF") frame:RegisterEvent("SPELLCAST_STOP") frame:RegisterEvent("SPELLCAST_FAILED") frame:RegisterEvent("SPELLCAST_INTERRUPTED") +frame:RegisterEvent("ACTIONBAR_SLOT_CHANGED") -- invalidate Aimed Shot slot cache frame:SetScript("OnUpdate", function() if not MTH_EA_IsEnabled() then return end @@ -800,62 +1165,142 @@ frame:SetScript("OnUpdate", function() this._elapsed = (this._elapsed or 0) + dt cellBotBlink = cellBotBlink + dt -- accumulate for blink pulse + if MTH_EA_PendingAuraRescan > 0 then + MTH_EA_PendingAuraRescan = MTH_EA_PendingAuraRescan - dt + if MTH_EA_PendingAuraRescan <= 0 then + MTH_EA_PendingAuraRescan = 0 + if MTH_EA_UseNamPower then + MTH_EA_RecoverStateFromAuraArray() + else + MTH_EA_RecoverStateFromBuffs() + end + end + end + -- Natural state expiry: the game doesn't auto-advance on expiry, -- so we go to IDLE. If the player consumed it, CHAT_MSG_AURA_GONE_SELF -- already handled it; this path only fires on genuine timeout. if rt.state ~= IDLE and GetTime() >= rt.expiresAt then MTH_EA_SetState(IDLE) + MTH_EA_PredictVerifyAt = 0 cellBotBlink = 0 end - -- LnL natural expiry (belt-and-suspenders; AURA_GONE_SELF should cover it) - if lnl.active and GetTime() >= lnl.expiresAt then + -- Prediction verification: after SPELLCAST_STOP sets a predictive state, + -- confirm the debuff actually arrived via a hard aura scan. If not found, + -- clear the phantom state. This catches stale spell-name captures, server + -- packet loss, and any other desync between the predicted and actual state. + if MTH_EA_PredictVerifyAt > 0 and rt.state ~= IDLE and GetTime() >= MTH_EA_PredictVerifyAt then + MTH_EA_PredictVerifyAt = 0 + local actual = MTH_EA_FindAmmoStateByTexture() + if not actual then + -- Predicted debuff never showed up — phantom state; clear it. + MTH_EA_SetState(IDLE) + elseif actual ~= rt.state then + -- Server sent a different state than predicted; correct it. + rt.state = actual + rt.expiresAt = GetTime() + CYCLE_DURATION + rt.consumed = false + end + -- If actual == rt.state, prediction was correct — no action needed. + end + + -- LnL: authoritative resync from NamPower every frame. + if lnl.active and MTH_EA_UseNamPower then + local rem = MTH_EA_GetLnLDurationNP() + if rem then + lnl.expiresAt = GetTime() + rem + else + -- Buff gone from aura array → expired or consumed. + lnl.active = false + end + elseif lnl.active and GetTime() >= lnl.expiresAt then + -- Legacy path: no NamPower, use hardcoded 10 s timer. lnl.active = false end if this._elapsed < 0.25 then return end this._elapsed = 0 + MTH_EA_UpdateUI() end) +-- ── Per-event helpers (module-level to keep OnEvent under Lua 5.0's +-- 32-upvalue-per-closure limit). They close over only the locals +-- each one actually needs, not the full module scope. +local function MTH_EA_HandleVarsLoaded() + MTH_EA_HookInstallCSBN() + MTH_EA_HookInstallCS() + MTH_EA_HookInstallUA() + if MTH and MTH.nampower then + MTH_EA_UseNamPower = true + frame:UnregisterEvent("PLAYER_AURAS_CHANGED") + frame:UnregisterEvent("CHAT_MSG_SPELL_SELF_BUFF") + frame:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE") + frame:UnregisterEvent("CHAT_MSG_SPELL_AURA_GONE_SELF") + frame:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS") + frame:UnregisterEvent("CHAT_MSG_SPELL_CREATURE_VS_SELF_BUFF") + frame:RegisterEvent("BUFF_ADDED_SELF") + frame:RegisterEvent("BUFF_REMOVED_SELF") + frame:RegisterEvent("DEBUFF_ADDED_SELF") + frame:RegisterEvent("DEBUFF_REMOVED_SELF") + end +end + +local function MTH_EA_HandlePlayerEnteringWorld() + MTH_EA_EnsureConfigDefaults() + + -- Class gate: only hunters may use Experimental Ammunition. + if MTH and MTH.IsClassGateBlocked and MTH:IsClassGateBlocked() then + if strip then strip:Hide() end + return + end + + rt.state = IDLE + rt.expiresAt = 0 + rt.consumed = false + rt.lastState = nil + MTH_EA_LastSpell = nil + lnl.active = false + lnlPendingCapture = false + MTH_EA_ClearActionSlotCache() + MTH_EA_SnapshotBuffs() + + -- Update talent flag from whatever the scanner has so far. + -- We do NOT gate strip visibility on this: GetTalentInfo can return + -- stale data briefly on login before the client finishes loading + -- talent data. Show the strip if enabled; MTH_HT_OnScanComplete + -- will hide it later if the talent is truly absent after a proper scan. + MTH_EA_HasTalent = MTH_HT_HasTalent and MTH_HT_HasTalent(TALENT_NAME) or false + + if not MTH_EA_IsEnabled() then return end + + MTH_EA_CreateUI() + -- Recover any active ammo aura (survives /reload in same session) + if MTH_EA_UseNamPower then + MTH_EA_RecoverStateFromAuraArray() + else + MTH_EA_RecoverStateFromBuffs() + end + inCombat = UnitAffectingCombat and UnitAffectingCombat("player") and true or false + frame:Show() + MTH_EA_RefreshVisibility() + MTH_EA_UpdateUI() +end + frame:SetScript("OnEvent", function() if event == "VARIABLES_LOADED" then - -- Install hooks once SavedVariables are available - MTH_EA_HookInstallCSBN() - MTH_EA_HookInstallCS() - MTH_EA_HookInstallUA() + MTH_EA_HandleVarsLoaded() return end if event == "PLAYER_ENTERING_WORLD" then - MTH_EA_EnsureConfigDefaults() - rt.state = IDLE - rt.expiresAt = 0 - rt.consumed = false - rt.lastState = nil - MTH_EA_LastSpell = nil - lnl.active = false - lnlPendingCapture = false - MTH_EA_SnapshotBuffs() + MTH_EA_HandlePlayerEnteringWorld() + return + end - -- Update talent flag from whatever the scanner has so far. - -- We do NOT gate strip visibility on this: GetTalentInfo can return - -- stale data briefly on login before the client finishes loading - -- talent data. Show the strip if enabled; MTH_HT_OnScanComplete - -- will hide it later if the talent is truly absent after a proper scan. - MTH_EA_HasTalent = MTH_HT_HasTalent and MTH_HT_HasTalent(TALENT_NAME) or false - - MTH_EA_CreateUI() - if MTH_EA_IsEnabled() then - -- Recover any active ammo aura (survives /reload in same session) - MTH_EA_RecoverStateFromBuffs() - inCombat = UnitAffectingCombat and UnitAffectingCombat("player") and true or false - frame:Show() - MTH_EA_RefreshVisibility() - else - strip:Hide() - end - MTH_EA_UpdateUI() + if event == "ACTIONBAR_SLOT_CHANGED" then + MTH_EA_ClearActionSlotCache() return end @@ -904,90 +1349,47 @@ frame:SetScript("OnEvent", function() end -- ── Recovery: resync ammo state after /reload ───────────────────── - if rt.state == IDLE then - local stateKey = MTH_EA_FindAmmoStateByTexture() - if stateKey then - rt.state = stateKey - rt.expiresAt = now + CYCLE_DURATION - rt.consumed = false - end - MTH_EA_UpdateUI() + if MTH_EA_UseNamPower then + MTH_EA_RecoverStateFromAuraArray() + else + MTH_EA_RecoverStateFromBuffs() end + MTH_EA_UpdateUI() + return + end + + -- ── NamPower aura events ────────────────────────────────────────── + if event == "BUFF_ADDED_SELF" or event == "DEBUFF_ADDED_SELF" then + local spellId = tonumber(arg3) + if spellId and spellId > 0 then + MTH_EA_CacheSpellId(spellId) + -- LnL proc / re-proc: get authoritative remaining time. + if spellId == MTH_EA_LnLSpellId then + lnl.active = true + local rem = MTH_EA_GetLnLDurationNP() + lnl.expiresAt = GetTime() + (rem or lnl.DURATION) + end + end + MTH_EA_RequestAuraRescan(0.05) + return + end + + if event == "BUFF_REMOVED_SELF" or event == "DEBUFF_REMOVED_SELF" then + local spellId = tonumber(arg3) + if spellId and spellId > 0 then + MTH_EA_CacheSpellId(spellId) + -- LnL consumed or expired: clear immediately, don't wait for rescan. + if spellId == MTH_EA_LnLSpellId then + lnl.active = false + end + end + MTH_EA_RequestAuraRescan(0.05) return end -- ── Primary state detection via combat log messages ─────────────── - -- On TWoW/MaNGOS the event routing for debuffs may differ from - -- vanilla. We therefore check BOTH gain and loss patterns in - -- every chat_msg_spell event we receive, and match regardless of - -- which specific event name the server chose to use. - local function MTH_EA_TryHandleChatMsg(msg) - if not MTH_EA_IsEnabled() then return false end - msg = msg or "" - -- ── State GAIN patterns ───────────────────────────────────── - local _, _, gained = string.find(msg, "^You are afflicted by (.+)%.$") - if not gained then - _, _, gained = string.find(msg, "^You gain (.+)%.$") - end - if not gained then - _, _, gained = string.find(msg, "^You gain the effect of (.+)%.$") - end - if gained then - -- Ammo state? - local stateKey = BUFF_TO_STATE[gained] - if stateKey then - if MTH_EA_DebugMode then - end - MTH_EA_SetState(stateKey) - return true - end - -- Lock and Load? Chat message is a late-arriving fallback; - -- PLAYER_AURAS_CHANGED + tooltip scan is the primary path. - if string.find(gained, "Lock and Load") then - if not lnl.active then - -- Fallback: AURAS_CHANGED missed it; sync from tooltip now. - local rem = MTH_EA_GetLnLRemaining() - lnl.active = true - lnl.expiresAt = GetTime() + (rem or lnl.DURATION) - end - if MTH_EA_DebugMode then - end - return true - end - end - -- ── State LOSS patterns ────────────────────────────────────── - local _, _, lost = string.find(msg, "^(.+) fades from you%.$") - if not lost then - _, _, lost = string.find(msg, "^(.+) fades from you$") -- no trailing period - end - if lost then - -- Ammo state? - local stateKey = BUFF_TO_STATE[lost] - if stateKey then - if MTH_EA_DebugMode then - end - if rt.state == stateKey then - MTH_EA_SetState(IDLE) - end - return true - end - -- Lock and Load? - if string.find(lost, "Lock and Load") then - if MTH_EA_DebugMode then - end - lnl.active = false - lnlPendingCapture = false - MTH_EA_SnapshotBuffs() -- reset baseline; LnL no longer on player - return true - end - end - -- Debug: print any Ammunition message even if unmatched - if MTH_EA_DebugMode then - if string.find(msg, "Ammunition") then - end - end - return false - end + -- (MTH_EA_TryHandleChatMsg is defined at module level to stay under + -- Lua 5.0's 32-upvalue-per-closure limit.) if event == "CHAT_MSG_SPELL_SELF_BUFF" or event == "CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE" @@ -1002,15 +1404,13 @@ frame:SetScript("OnEvent", function() if not MTH_EA_IsEnabled() then return end - -- We rely on MTH_EA_LastSpell captured in our CastSpellByName hook. - -- Also check MTH_SA_LastSpell as a fallback (SmartAmmo's capture). - local spellName = MTH_EA_LastSpell - or (type(MTH_SA_LastSpell) == "string" and MTH_SA_LastSpell) - or "" + -- We rely on MTH_EA_LastSpell captured in our CastSpellByName / CastSpell / + -- UseAction hooks. Do NOT fall back to MTH_SA_LastSpell — SmartAmmo clears + -- its capture on SPELLCAST_STOP in undefined order relative to us, making it + -- either stale (previous cast's name) or nil depending on frame event order. + local spellName = MTH_EA_LastSpell or "" if event == "SPELLCAST_STOP" then - if MTH_EA_DebugMode then - end -- Predictive: show the expected next ammo state immediately when Aimed Shot -- fires from IDLE, before the server debuff message arrives (~1s later). -- We predict based on lastState so we show the correct one (not always EXPLOSIVE): @@ -1019,12 +1419,24 @@ frame:SetScript("OnEvent", function() -- after POISONOUS → ENCHANTED if spellName == SPELL_AIMED and rt.state == IDLE then local predicted = PREDICT_NEXT[rt.lastState] or EXPLOSIVE - if MTH_EA_DebugMode then - end MTH_EA_SetState(predicted) + -- Schedule a verification rescan: if the predicted debuff never arrives + -- from the server within 1.5 s, the hard aura check will catch it and + -- clear back to IDLE. This prevents phantom states from persisting for + -- up to 60 s when the spell name capture was stale or the server did not + -- apply the expected debuff. + MTH_EA_PredictVerifyAt = GetTime() + 1.5 + end + -- Consume-spell advancement (NamPower path): + -- Do not advance immediately here. The aura can still be present for a + -- short moment after the cast completes, so we defer to a live aura + -- rescan and only update the state when the player aura actually changes. + if MTH_EA_UseNamPower and rt.state ~= IDLE then + local consumeSpell = CONSUME_SPELL[rt.state] + if consumeSpell and spellName == consumeSpell then + MTH_EA_PendingAuraRescan = 0.20 + end end - -- Consume-spell detection removed: CHAT_MSG_SPELL_AURA_GONE_SELF handles - -- state loss authoritatively. No AdvanceState here. MTH_EA_LastSpell = nil elseif event == "SPELLCAST_FAILED" or event == "SPELLCAST_INTERRUPTED" then @@ -1080,12 +1492,12 @@ MTH_ExpAmmo = { SetEnabled = function(enabled) local cfg = MTH_EA_GetConfig() cfg.enabled = enabled and true or false - if strip then - if cfg.enabled then - MTH_EA_RefreshVisibility() - else - strip:Hide() - end + if cfg.enabled then + MTH_EA_CreateUI() + frame:Show() + MTH_EA_RefreshVisibility() + elseif strip then + strip:Hide() end end, IsEnabled = MTH_EA_IsEnabled, @@ -1106,6 +1518,11 @@ MTH_ExpAmmo = { cfg.cellSize = math.max(20, math.min(80, tonumber(v) or 40)) MTH_EA_ApplyLayout() end, + SetCellGap = function(v) + local cfg = MTH_EA_GetConfig() + cfg.cellGap = math.max(0, math.min(20, tonumber(v) or 2)) + MTH_EA_ApplyLayout() + end, SetBigCD = function(v) local cfg = MTH_EA_GetConfig() cfg.bigCD = v and true or false @@ -1116,4 +1533,9 @@ MTH_ExpAmmo = { cfg.hideOOC = v and true or false MTH_EA_RefreshVisibility() end, + SetUseQuiverNoClip = function(v) + local cfg = MTH_EA_GetConfig() + cfg.useQuiverNoClip = v and true or false + end, + ToggleAnchor = MTH_EA_ToggleAnchor, } diff --git a/modules/feedomatic/FeedOMatic.lua b/modules/feedomatic/FeedOMatic.lua index d9d2223..495209d 100644 --- a/modules/feedomatic/FeedOMatic.lua +++ b/modules/feedomatic/FeedOMatic.lua @@ -125,6 +125,11 @@ FOM_FamilyToLegacyMap = { +-- [DISABLED] NO-BUFF quarantine: set to true to re-enable. +-- When false, failed feeds are still logged but items are never +-- blacklisted and the quarantine store is never consulted. +local FOM_QUARANTINE_ENABLED = false; + local function FOM_GetServerProfile() return FOM_ServerProfiles[FOM_SERVER_PROFILE] or {}; end @@ -186,6 +191,7 @@ local function FOM_GetFamilyKeyFromInfo(petInfo) end local function FOM_RegisterNoBuffQuarantine(itemID, petInfo) + if (not FOM_QUARANTINE_ENABLED) then return; end local numericItem = tonumber(itemID); if (numericItem == nil) then return; @@ -217,6 +223,7 @@ local function FOM_RegisterNoBuffQuarantine(itemID, petInfo) end local function FOM_IsQuarantined(itemID, petLevel, familyKey) + if (not FOM_QUARANTINE_ENABLED) then return false; end local numericItem = tonumber(itemID); local numericPet = tonumber(petLevel); if (numericItem == nil or numericPet == nil or numericPet <= 0) then @@ -1187,7 +1194,7 @@ function FOM_OnUpdate(elapsed) FOM_CoreFinalizeAttempt(FOM_LastFeedAttempt.coreAttemptId, "rejected", "no-buff"); local currentPetInfo = MTH_FOM_GetCorePetInfo(); FOM_RegisterNoBuffQuarantine(FOM_LastFeedAttempt.itemId, currentPetInfo); - if (type(MTH_FEED_BlockItemForPetLevel) == "function") then + if (FOM_QUARANTINE_ENABLED and type(MTH_FEED_BlockItemForPetLevel) == "function") then pcall(MTH_FEED_BlockItemForPetLevel, FOM_LastFeedAttempt.itemId, tonumber(currentPetInfo and currentPetInfo.level) or nil, @@ -1274,6 +1281,43 @@ function FOM_CanFeed() end + -- NamPower path: scan aura array by spellId → name, no tooltip needed + if MTH and MTH.nampower and type(GetUnitField) == "function" and type(GetSpellRecField) == "function" then + local auras = GetUnitField("player", "aura") + if type(auras) == "table" then + local dontFeedNames = { + ["Shadowmeld"] = true, + ["Feign Death"] = true, + ["Food"] = true, + ["Drink"] = true, + ["Refreshment"] = true, + } + for i = 1, 32 do + local sid = tonumber(auras[i]) + if sid and sid > 0 then + local name = GetSpellRecField(sid, "name") + if name then + if dontFeedNames[name] then + FOM_DebugLog("Can't feed; buff detected: " .. name) + return false + end + if UnitLevel("player") >= 40 then + local lname = string.lower(name) + for _, mountSub in FOM_MOUNT_NAME_SUBSTRINGS do + if string.find(lname, mountSub) then + FOM_DebugLog("Can't feed; mounted (" .. name .. ").") + return false + end + end + end + end + end + end + end + return true + end + + -- Legacy path: texture + tooltip scanning local buff, buffIndex; local dontFeedBuffTextures = { "Interface\\Icons\\Ability_Ambush", -- NE Shadowmeld (maybe not unique buff icon?) @@ -1936,7 +1980,7 @@ function FOM_RemoveFood(diet, food) end -- Clear quarantine, core exception blocks, and RemovedFoods ban for a specific item (called when user explicitly re-adds via /fom add) -local function FOM_ClearItemBans(itemID) +function FOM_ClearItemBans(itemID) local numericItem = tonumber(itemID); if (numericItem == nil) then return; end -- Clear from RemovedFoods across all diet lists @@ -2076,6 +2120,9 @@ end -- Check Feed Effect function FOM_HasFeedEffect() + if type(MTH_FEED_HasPetFeedBuff) == "function" then + return MTH_FEED_HasPetFeedBuff() + end local i = 1; local buff; diff --git a/modules/feedomatic/module.lua b/modules/feedomatic/module.lua index 80726ba..8dad1d8 100644 --- a/modules/feedomatic/module.lua +++ b/modules/feedomatic/module.lua @@ -6,7 +6,6 @@ local MTH_FeedOMatic = { name = "feedomatic", enabled = false, - version = "1.4.1", events = { "VARIABLES_LOADED", "MERCHANT_SHOW", diff --git a/modules/icu/module.lua b/modules/icu/module.lua index e8d713a..d126146 100644 --- a/modules/icu/module.lua +++ b/modules/icu/module.lua @@ -6,7 +6,6 @@ local MTH_ICU = { name = "icu", enabled = false, - version = "1.4.1", events = { "VARIABLES_LOADED", }, diff --git a/modules/smartammo/module.lua b/modules/smartammo/module.lua index e73e6a7..ccb4c11 100644 --- a/modules/smartammo/module.lua +++ b/modules/smartammo/module.lua @@ -7,7 +7,6 @@ MTH_SA_MANAGED_HOOKS = true local MTH_SmartAmmo = { name = "smartammo", enabled = true, - version = "1.4.1", events = { "VARIABLES_LOADED", -- PLAYER_ENTERING_WORLD handled by engine's own frame diff --git a/modules/tooltips/module.lua b/modules/tooltips/module.lua index 8fcb1cb..f50becb 100644 --- a/modules/tooltips/module.lua +++ b/modules/tooltips/module.lua @@ -6,7 +6,6 @@ local MTH_Tooltips = { name = "tooltips", enabled = true, - version = "1.4.1", events = { "UPDATE_MOUSEOVER_UNIT", "UNIT_NAME_UPDATE", @@ -1486,11 +1485,8 @@ local function MTH_TT_AddTooltip(unit) local ammoVendorTooltipsEnabled = MTH_TT_IsAmmoVendorTooltipsEnabled() local vendorInfo = ammoVendorTooltipsEnabled and MTH_TT_FindVendorInfo(name) or nil local row = beastTooltipsEnabled and MTH_TT_FindBeastRow(name) or nil - local savedEntry = beastTooltipsEnabled - and (type(MTH_BLS_FindSavedBeastByName) == "function") - and MTH_BLS_FindSavedBeastByName(name) or nil local scorpokAdded = MTH_TT_AddScorpokTargetsTooltip(unit, name) - if not row and not vendorInfo and not scorpokAdded and not savedEntry then + if not row and not vendorInfo and not scorpokAdded then state.lookupMiss = (state.lookupMiss or 0) + 1 MTH_TT_StateSet("lookup-miss", name, normalized) MTH_TT_Log("lookup miss: unit='" .. tostring(name) .. "' normalized='" .. tostring(normalized) .. "'") @@ -1514,13 +1510,7 @@ local function MTH_TT_AddTooltip(unit) MTH_TT_Log("vendor tooltip added: unit='" .. tostring(name) .. "'") end - -- Determine which abilities source to display. - local abilitiesSource = nil - if row then - abilitiesSource = row.abilities - elseif savedEntry and savedEntry.abilities and savedEntry.abilities ~= "" then - abilitiesSource = savedEntry.abilities - end + local abilitiesSource = row and row.abilities or nil if abilitiesSource then local abilities = MTH_TT_ParseAbilities(abilitiesSource) @@ -1546,11 +1536,6 @@ local function MTH_TT_AddTooltip(unit) end end - if savedEntry then - GameTooltip:AddLine("Beast recorded by you", 1.0, 0.85, 0.0) - addedAnything = 1 - end - if addedAnything then GameTooltip:Show() state.added = (state.added or 0) + 1 diff --git a/modules/zhunter/DeferredInit.lua b/modules/zhunter/DeferredInit.lua index 9436786..21d640a 100644 --- a/modules/zhunter/DeferredInit.lua +++ b/modules/zhunter/DeferredInit.lua @@ -67,6 +67,8 @@ local function ZHunterMod_EnsureButtonFrame(buttonName) created:SetPoint("TOP", UIParent, "TOP", 0, -464) elseif buttonName == "zButtonToys" then created:SetPoint("TOP", UIParent, "TOP", 0, -518) + elseif buttonName == "zButtonCraft" then + created:SetPoint("TOP", UIParent, "TOP", 0, -572) else created:SetPoint("CENTER", UIParent, "CENTER", 0, 0) end @@ -85,7 +87,7 @@ function ZHunterMod_DeferredInit() local mounts = ZHunterMod_EnsureButtonFrame("zButtonMounts") local companions = ZHunterMod_EnsureButtonFrame("zButtonCompanions") local toys = ZHunterMod_EnsureButtonFrame("zButtonToys") - + local craft = ZHunterMod_EnsureButtonFrame("zButtonCraft") -- Continue with all frames that exist; do not hard-abort when one optional frame is missing. local missing = {} @@ -98,12 +100,13 @@ function ZHunterMod_DeferredInit() if not mounts then table.insert(missing, "Mounts") end if not companions then table.insert(missing, "Companions") end if not toys then table.insert(missing, "Toys") end + if not craft then table.insert(missing, "Craft") end if table.getn(missing) > 0 then MTH_ZH_Print("[ZHUNTER] Warning: missing frames: " .. table.concat(missing, ", ") .. ". Continuing with available buttons.", "error") end - if not (pet or aspect or track or trap or ranged or ammo or mounts or companions or toys) then + if not (pet or aspect or track or trap or ranged or ammo or mounts or companions or toys or craft) then MTH_ZH_Print("zhunter buttons not created - check for addon conflicts!", "error") return end @@ -178,6 +181,12 @@ function ZHunterMod_DeferredInit() end end + if craft and zButtonCraft_OnEvent then + if ZHunterMod_SafeRunButtonInit(craft, zButtonCraft_OnEvent, "zButtonCraft") then + initialized = initialized + 1 + end + end + if initialized <= 0 then MTH_ZH_Print("zhunter buttons not created - check for addon conflicts!", "error") return diff --git a/modules/zhunter/ZSpellButtonTemplate.lua b/modules/zhunter/ZSpellButtonTemplate.lua index 9d8a9cc..f804208 100644 --- a/modules/zhunter/ZSpellButtonTemplate.lua +++ b/modules/zhunter/ZSpellButtonTemplate.lua @@ -138,7 +138,6 @@ local function ZHunterMod_UpdateButtonCount(parent, found) end function ZSpellButton_SetExpandDirection(parent, direction) - local button = getglobal(parent.name.."1") local offset = parent:GetWidth() / 36 local circleVisible = true if type(MTH_ZH_GetSavedTable) == "function" and parent and parent.name then @@ -154,11 +153,14 @@ function ZSpellButton_SetExpandDirection(parent, direction) parent.circle:Hide() end end + -- Children have not been created yet; circle state is already updated above. + if not (parent and parent.name) then return end if circleVisible then offset = offset * 5 else offset = offset * 3 end + local button = getglobal(parent.name.."1") button:ClearAllPoints() if direction == "TOP" then button:SetPoint("BOTTOM", parent, "TOP", 0, offset) @@ -683,28 +685,53 @@ function ZSpellButtonParent_OnLoad() if this.SetScript then this:SetScript("OnDragStart", function() if IsAltKeyDown() then - this:StartMoving() - this.isMoving = true + if type(MTH_ZBar_IsActive) == "function" and MTH_ZBar_IsActive() then + -- Alt+drag any bar button to move the whole zBar via its invisible anchor + local anchor = type(MTH_ZBar_GetAnchor) == "function" and MTH_ZBar_GetAnchor() + if anchor then + anchor:StartMoving() + anchor.isMoving = true + end + else + this:StartMoving() + this.isMoving = true + end end end) this:SetScript("OnDragStop", function() - if not this then - return + if not this then return end + if type(MTH_ZBar_IsActive) == "function" and MTH_ZBar_IsActive() then + local anchor = type(MTH_ZBar_GetAnchor) == "function" and MTH_ZBar_GetAnchor() + if anchor and anchor.isMoving then + if anchor.StopMovingOrSizing then anchor:StopMovingOrSizing() end + anchor.isMoving = false + if type(MTH_ZBar_SavePosition) == "function" then MTH_ZBar_SavePosition() end + if type(MTH_ZBar_ApplyLayout) == "function" then MTH_ZBar_ApplyLayout() end + end + else + if this.StopMovingOrSizing then this:StopMovingOrSizing() end + ZSpellButton_SaveParentPosition(this) + this.isMoving = false end - if this.StopMovingOrSizing then - this:StopMovingOrSizing() - end - ZSpellButton_SaveParentPosition(this) - this.isMoving = false end) this:SetScript("OnMouseUp", function() if this and this.isMoving then - if this.StopMovingOrSizing then - this:StopMovingOrSizing() + if this.StopMovingOrSizing then this:StopMovingOrSizing() end + if not (type(MTH_ZBar_IsActive) == "function" and MTH_ZBar_IsActive()) then + ZSpellButton_SaveParentPosition(this) end - ZSpellButton_SaveParentPosition(this) this.isMoving = false end + -- Fallback: finish anchor movement started from OnDragStart + if type(MTH_ZBar_IsActive) == "function" and MTH_ZBar_IsActive() then + local anchor = type(MTH_ZBar_GetAnchor) == "function" and MTH_ZBar_GetAnchor() + if anchor and anchor.isMoving then + if anchor.StopMovingOrSizing then anchor:StopMovingOrSizing() end + anchor.isMoving = false + if type(MTH_ZBar_SavePosition) == "function" then MTH_ZBar_SavePosition() end + if type(MTH_ZBar_ApplyLayout) == "function" then MTH_ZBar_ApplyLayout() end + end + end end) end end diff --git a/modules/zhunter/loader.xml b/modules/zhunter/loader.xml index 20b8092..2a1b17d 100644 --- a/modules/zhunter/loader.xml +++ b/modules/zhunter/loader.xml @@ -22,6 +22,7 @@ Loads all zhunter addon code and wraps it in the MTH module system + @@ -47,6 +48,11 @@ Loads all zhunter addon code and wraps it in the MTH module system + + + + + diff --git a/modules/zhunter/module.lua b/modules/zhunter/module.lua index 9f91764..2c2b438 100644 --- a/modules/zhunter/module.lua +++ b/modules/zhunter/module.lua @@ -8,7 +8,6 @@ MTH_ZH_MANAGED_HOOKS = true local MTH_ZHunter = { name = "zhunter", enabled = true, - version = "1.4.1", events = { "VARIABLES_LOADED", -- PLAYER_ENTERING_WORLD handled by bootstrap frame + adjustment frames @@ -59,6 +58,7 @@ local function MTH_ZH_ApplyDefaultWidgetSpawnLayoutOnce() "zButtonMounts", "zButtonCompanions", "zButtonToys", + "zButtonCraft", } local startY = -140 @@ -191,6 +191,7 @@ MTH_ZH_SetButtonsVisible = function(visible) "zButtonMounts", "zButtonCompanions", "zButtonToys", + "zButtonCraft", } local function MTH_ZH_SetChildButtonsVisible(parentButton, parentName, show) @@ -303,6 +304,7 @@ local function MTH_ZH_SetAdjustmentHandlersActive(active) { frame = "MTH_ZH_MountsAdjust", handler = "MTH_ZH_MountsAdjust_OnEvent", getter = "zButtonMounts_GetSaved" }, { frame = "MTH_ZH_CompanionsAdjust", handler = "MTH_ZH_CompanionsAdjust_OnEvent", getter = "zButtonCompanions_GetSaved" }, { frame = "MTH_ZH_ToysAdjust", handler = "MTH_ZH_ToysAdjust_OnEvent", getter = "zButtonToys_GetSaved" }, + { frame = "MTH_ZH_CraftAdjust", handler = "MTH_ZH_CraftAdjust_OnEvent", getter = "zButtonCraft_GetSaved" }, } local function isFeatureEnabled(getterName) @@ -348,7 +350,9 @@ local function MTH_ZH_ClearRuntimeFeatureFlags() end MTH_ZH_RestoreRuntimeFeatureFlags = function() - return + if type(MTH_ZBar_Init) == "function" then + MTH_ZBar_Init() + end end function MTH_ZHunter:init() @@ -372,6 +376,9 @@ function MTH_ZHunter:setEnabled(enabled) MTH_ZH_SetAdjustmentHandlersActive(false) MTH_ZH_SetButtonsVisible(false) MTH_ZH_SetAuxFramesVisible(false) + -- Hide the zBar anchor so it doesn't float while the module is off + local zbarAnchor = getglobal("MTH_ZBar_Anchor") + if zbarAnchor then zbarAnchor:Hide() end end end diff --git a/modules/zhunter/zBar.lua b/modules/zhunter/zBar.lua new file mode 100644 index 0000000..2579d7a --- /dev/null +++ b/modules/zhunter/zBar.lua @@ -0,0 +1,333 @@ +-- zBar: Unified draggable anchor bar for all zButton parent buttons. +-- When enabled, all included zButton parents are anchored to the zBar anchor +-- in a configurable row (horizontal) or column (vertical). +-- Individual Alt+drag on each zButton is suppressed while zBar is active. + +local ZBAR_FRAME_NAME = "MTH_ZBar_Anchor" + +local ZBAR_ALL_BUTTONS = { + "zButtonAspect", + "zButtonAmmo", + "zButtonTrack", + "zButtonTrap", + "zButtonPet", + "zButtonRanged", + "zButtonMounts", + "zButtonCompanions", + "zButtonToys", + "zButtonCraft", +} + +local ZBAR_BUTTON_LABELS = { + zButtonAspect = "zAspect", + zButtonAmmo = "zAmmo", + zButtonTrack = "zTrack", + zButtonTrap = "zTrap", + zButtonPet = "zPet", + zButtonRanged = "zRanged", + zButtonMounts = "zMounts", + zButtonCompanions = "zCompanions", + zButtonToys = "zToys", + zButtonCraft = "zCraft", +} + +function MTH_ZBar_GetAllButtons() + return ZBAR_ALL_BUTTONS +end + +function MTH_ZBar_GetAllButtonLabels() + return ZBAR_BUTTON_LABELS +end + +-- Returns (and initialises on first access) the _zbar saved-variable table. +function MTH_ZBar_GetSaved() + local root = MTH_ZH_GetSavedRoot() + if type(root["_zbar"]) ~= "table" then + root["_zbar"] = {} + end + local s = root["_zbar"] + + if s.enabled == nil then s.enabled = false end + if s.direction == nil then s.direction = "HORIZONTAL" end + if type(s.spacing) ~= "number" then s.spacing = 4 end + if type(s.size) ~= "number" then s.size = 36 end + -- childexpand: which side children appear on (TOP/BOTTOM for H bar, LEFT/RIGHT for V bar) + if s.childexpand == nil then s.childexpand = "BOTTOM" end + -- childarrange: how children #2+ are laid out (VERTICAL=column, HORIZONTAL=row) + if s.childarrange == nil then s.childarrange = "VERTICAL" end + if type(s.order) ~= "table" then + s.order = {} + for i = 1, table.getn(ZBAR_ALL_BUTTONS) do + s.order[i] = ZBAR_ALL_BUTTONS[i] + end + end + + if type(s.visible) ~= "table" then + s.visible = {} + end + + -- Migration: ensure every known button is present in the order list and + -- has an explicit visible entry. + local present = {} + for _, v in ipairs(s.order) do + present[v] = true + end + for _, v in ipairs(ZBAR_ALL_BUTTONS) do + if not present[v] then + table.insert(s.order, v) + end + if s.visible[v] == nil then + s.visible[v] = true + end + end + + return s +end + +-- Returns true when the zBar feature is both enabled AND the anchor frame +-- has been created in memory. +function MTH_ZBar_IsActive() + local s = MTH_ZBar_GetSaved() + return (s.enabled == true) and (getglobal(ZBAR_FRAME_NAME) ~= nil) +end + +-- ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― +-- Position persistence +-- ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― + +function MTH_ZBar_SavePosition() + local bar = getglobal(ZBAR_FRAME_NAME) + if not (bar and bar.GetPoint) then return end + local s = MTH_ZBar_GetSaved() + local point, _, relPoint, x, y = bar:GetPoint() + if not point then return end + s.point = tostring(point) + s.relPoint = tostring(relPoint or point) + s.x = math.floor((tonumber(x) or 0) + 0.5) + s.y = math.floor((tonumber(y) or 0) + 0.5) +end + +-- ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― +-- Anchor frame creation +-- ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― + +-- Returns the invisible anchor frame (used internally and by button drag code). +function MTH_ZBar_GetAnchor() + return getglobal(ZBAR_FRAME_NAME) +end + +function MTH_ZBar_EnsureAnchor() + local existing = getglobal(ZBAR_FRAME_NAME) + if existing then + existing:Show() + return existing + end + + local s = MTH_ZBar_GetSaved() + + -- Invisible 1x1 movable frame — used only as a position anchor for the bar. + -- Users interact via the zButton buttons themselves (Alt+drag any of them). + local bar = CreateFrame("Frame", ZBAR_FRAME_NAME, UIParent) + if not bar then return nil end + + bar:SetWidth(1) + bar:SetHeight(1) + bar:SetFrameStrata("MEDIUM") + bar:SetFrameLevel(5) + bar:SetMovable(true) + bar:SetClampedToScreen(true) + + -- Restore saved position (or fall back to a sensible default) + bar:ClearAllPoints() + if s.point and s.x and s.y then + bar:SetPoint(s.point, UIParent, s.relPoint or s.point, s.x, s.y) + else + bar:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 400, -140) + end + + bar:Show() + return bar +end + +-- ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― +-- Layout engine +-- ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― + +-- Anchors every included+enabled zButton parent relative to the zBar anchor +-- (or the previous button in the chain). +function MTH_ZBar_ApplyLayout() + local s = MTH_ZBar_GetSaved() + if not s.enabled then return end + + local bar = getglobal(ZBAR_FRAME_NAME) + if not bar then return end + + local direction = s.direction or "HORIZONTAL" + local spacing = tonumber(s.spacing) or 4 + + local prevFrame = bar + local count = 0 + local placed = {} + + for i = 1, table.getn(s.order) do + local buttonName = s.order[i] + if type(buttonName) == "string" then + local btn = getglobal(buttonName) + + local globalEnabled = ZHunterMod_Saved + and type(ZHunterMod_Saved[buttonName]) == "table" + and ZHunterMod_Saved[buttonName]["enabled"] ~= false + and ZHunterMod_Saved[buttonName]["enabled"] ~= 0 + local zbarVisible = s.visible[buttonName] ~= false + + if btn and globalEnabled and zbarVisible then + btn:ClearAllPoints() + if direction == "HORIZONTAL" then + btn:SetPoint("LEFT", prevFrame, "RIGHT", spacing, 0) + else + btn:SetPoint("TOP", prevFrame, "BOTTOM", 0, -spacing) + end + -- Size, expand direction and child arrangement are handled by each + -- bar's own _SetupSizeAndPosition, which now calls MTH_ZBar_ApplyToButton. + local setupFunc = getglobal(buttonName .. "_SetupSizeAndPosition") + if type(setupFunc) == "function" then + setupFunc() + end + prevFrame = btn + count = count + 1 + placed[buttonName] = true + end + end + end + + -- For every button NOT placed in the bar, call its own setup function so that: + -- * disabled buttons get Hide() called on them immediately + -- * excluded buttons get their individual size/expand/position applied + for _, buttonName in ipairs(ZBAR_ALL_BUTTONS) do + if not placed[buttonName] then + local setupFunc = getglobal(buttonName .. "_SetupSizeAndPosition") + if type(setupFunc) == "function" then + setupFunc() + end + end + end +end + +-- ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― +-- Per-button zBar override (called from each zButton's _SetupSizeAndPosition) +-- ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― + +-- When zBar is active this is the MASTER for size, expand direction and child +-- layout. Each zButton's _SetupSizeAndPosition calls this first; if it returns +-- true the bar must skip its own saved settings entirely. +function MTH_ZBar_ApplyToButton(btn, displayCount) + if not (type(MTH_ZBar_IsActive) == "function" and MTH_ZBar_IsActive()) then + return false + end + local s = MTH_ZBar_GetSaved() + local btnKey = (btn and btn.name) or (btn and btn.GetName and btn:GetName()) or "" + -- Only take master control if this button is actually placed in the active bar. + local globalEnabled = ZHunterMod_Saved + and type(ZHunterMod_Saved[btnKey]) == "table" + and ZHunterMod_Saved[btnKey]["enabled"] ~= false + and ZHunterMod_Saved[btnKey]["enabled"] ~= 0 + if not globalEnabled or s.visible[btnKey] == false then + return false + end + local btnSize = tonumber(s.size) or 36 + local expandDir = s.childexpand or "BOTTOM" + local arrange = s.childarrange or "VERTICAL" + local arHoriz = (expandDir == "LEFT") + local arVert = (expandDir == "TOP") + ZSpellButton_SetSize(btn, btnSize) + ZSpellButton_SetExpandDirection(btn, expandDir) + if displayCount and displayCount > 1 then + local rows = (arrange == "VERTICAL") and displayCount or 1 + ZSpellButton_ArrangeChildren(btn, rows, displayCount, arHoriz, arVert) + end + if btn.circle then btn.circle:Hide() end + return true +end + +-- ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― +-- Enable / disable +-- ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― + +-- Restores each bar to its individually saved position and hides the anchor. +function MTH_ZBar_Release() + local bar = getglobal(ZBAR_FRAME_NAME) + if bar then bar:Hide() end + + for _, buttonName in ipairs(ZBAR_ALL_BUTTONS) do + local btn = getglobal(buttonName) + if btn and ZHunterMod_Saved and type(ZHunterMod_Saved[buttonName]) == "table" then + -- Restore individually saved position + local parentSaved = ZHunterMod_Saved[buttonName]["parent"] + if type(parentSaved) == "table" then + local px = tonumber(parentSaved["x"]) + local py = tonumber(parentSaved["y"]) + local point = parentSaved["point"] + local rPoint = parentSaved["relativePoint"] or point + if px and py and type(point) == "string" and point ~= "" then + btn:ClearAllPoints() + btn:SetPoint(point, UIParent, rPoint, px, py) + end + end + -- Restore size, circle, expand direction and child layout from the bar's own saved settings + local setupFunc = getglobal(buttonName .. "_SetupSizeAndPosition") + if type(setupFunc) == "function" then + setupFunc() + end + end + end +end + +-- Called when the user toggles the Enable checkbox in the options. +function MTH_ZBar_SetEnabled(enabled) + local s = MTH_ZBar_GetSaved() + s.enabled = enabled and true or false + if enabled then + MTH_ZBar_EnsureAnchor() + MTH_ZBar_ApplyLayout() + else + MTH_ZBar_Release() + end +end + +-- Resize all bar buttons (and persist the value). +function MTH_ZBar_SetSize(size) + local s = MTH_ZBar_GetSaved() + s.size = tonumber(size) or 36 + if s.enabled and type(MTH_ZBar_ApplyLayout) == "function" then + MTH_ZBar_ApplyLayout() + end +end + +-- Called from DeferredInit / RestoreRuntimeFeatureFlags after saved vars are +-- fully loaded; creates the anchor and applies layout only when enabled. +function MTH_ZBar_Init() + local s = MTH_ZBar_GetSaved() + + -- One-time migration: fix firstbutton values corrupted to "BOTTOM" by an + -- early version of zBar that incorrectly wrote per-bar saved variables. + if not s._firstbutton_fixed then + for _, buttonName in ipairs(ZBAR_ALL_BUTTONS) do + if ZHunterMod_Saved and type(ZHunterMod_Saved[buttonName]) == "table" then + if ZHunterMod_Saved[buttonName]["firstbutton"] == "BOTTOM" then + ZHunterMod_Saved[buttonName]["firstbutton"] = "RIGHT" + end + end + end + s._firstbutton_fixed = true + -- Re-run each bar's own setup now so the fix takes immediate visual effect + for _, buttonName in ipairs(ZBAR_ALL_BUTTONS) do + local setupFunc = getglobal(buttonName .. "_SetupSizeAndPosition") + if type(setupFunc) == "function" then setupFunc() end + end + end + + if s.enabled then + MTH_ZBar_EnsureAnchor() + MTH_ZBar_ApplyLayout() + end +end diff --git a/modules/zhunter/zButtonAmmo.lua b/modules/zhunter/zButtonAmmo.lua index ff107c0..2629c9c 100644 --- a/modules/zhunter/zButtonAmmo.lua +++ b/modules/zhunter/zButtonAmmo.lua @@ -898,12 +898,14 @@ function zButtonAmmo_SetupSizeAndPosition() if arrangeCount < 0 then arrangeCount = 0 end - ZSpellButton_SetSize(zButtonAmmo, saved["parent"]["size"]) - ZSpellButton_SetSize(zButtonAmmo, saved["children"]["size"], 1) - ZSpellButton_SetExpandDirection(zButtonAmmo, saved["firstbutton"]) - ZSpellButton_ArrangeChildren(zButtonAmmo, saved["rows"], - arrangeCount, saved["horizontal"], - saved["vertical"]) + if not (type(MTH_ZBar_ApplyToButton) == "function" and MTH_ZBar_ApplyToButton(zButtonAmmo, arrangeCount)) then + ZSpellButton_SetSize(zButtonAmmo, saved["parent"]["size"]) + ZSpellButton_SetSize(zButtonAmmo, saved["children"]["size"], 1) + ZSpellButton_SetExpandDirection(zButtonAmmo, saved["firstbutton"]) + ZSpellButton_ArrangeChildren(zButtonAmmo, saved["rows"], + arrangeCount, saved["horizontal"], + saved["vertical"]) + end end function zButtonAmmo_Reset() diff --git a/modules/zhunter/zButtonAspect.lua b/modules/zhunter/zButtonAspect.lua index c5392e5..6919e4d 100644 --- a/modules/zhunter/zButtonAspect.lua +++ b/modules/zhunter/zButtonAspect.lua @@ -39,6 +39,7 @@ local zButtonAspect_MinAuraIntervalCombat = 0.60 local zButtonAspect_LastAuraTexture = nil local zButtonAspect_LastParentId = nil local zButtonAspect_AuraProbeTextLeft1 = nil +local MTH_ZH_UseNamPower = false local function zButtonAspect_GetSaved() local currentRoot = zButtonAspect_GetRoot() @@ -72,6 +73,9 @@ local function zButtonAspect_EnsureConfig() if saved["parent"]["circle"] == nil then saved["parent"]["circle"] = 1 end + if saved["parent"]["smart"] == nil then + saved["parent"]["smart"] = true + end if not saved["children"] then saved["children"] = {} end @@ -167,6 +171,12 @@ function zButtonAspect_OnEvent() MTH_ZH_AspectAdjust:RegisterEvent("CHARACTER_POINTS_CHANGED") MTH_ZH_AspectAdjust:RegisterEvent("LEARNED_SPELL_IN_TAB") MTH_ZH_AspectAdjust:SetScript("OnEvent", MTH_ZH_AspectAdjust_OnEvent) + if MTH and MTH.nampower then + MTH_ZH_UseNamPower = true + MTH_ZH_AspectAdjust:UnregisterEvent("PLAYER_AURAS_CHANGED") + MTH_ZH_AspectAdjust:RegisterEvent("BUFF_ADDED_SELF") + MTH_ZH_AspectAdjust:RegisterEvent("BUFF_REMOVED_SELF") + end zButtonAspect_Tooltip = CreateFrame("GameTooltip", "MTH_ZH_AspectProbe", nil, "GameTooltipTemplate") if zButtonAspect_Tooltip and zButtonAspect_Tooltip.GetName then local tooltipName = zButtonAspect_Tooltip:GetName() @@ -221,12 +231,14 @@ function zButtonAspect_SetupSizeAndPosition() if displayCount < 0 then displayCount = 0 end - ZSpellButton_SetSize(zButtonAspect, saved["parent"]["size"]) - ZSpellButton_SetSize(zButtonAspect, saved["children"]["size"], 1) - ZSpellButton_SetExpandDirection(zButtonAspect, saved["firstbutton"]) - ZSpellButton_ArrangeChildren(zButtonAspect, saved["rows"], - displayCount, saved["horizontal"], - saved["vertical"]) + if not (type(MTH_ZBar_ApplyToButton) == "function" and MTH_ZBar_ApplyToButton(zButtonAspect, displayCount)) then + ZSpellButton_SetSize(zButtonAspect, saved["parent"]["size"]) + ZSpellButton_SetSize(zButtonAspect, saved["children"]["size"], 1) + ZSpellButton_SetExpandDirection(zButtonAspect, saved["firstbutton"]) + ZSpellButton_ArrangeChildren(zButtonAspect, saved["rows"], + displayCount, saved["horizontal"], + saved["vertical"]) + end end function zButtonAspect_Reset() @@ -255,7 +267,11 @@ function MTH_ZH_AspectAdjust_OnEvent() if not zButtonAspect or not zButtonAspect.count then return end - if event == "PLAYER_AURAS_CHANGED" then + if event == "BUFF_ADDED_SELF" or event == "BUFF_REMOVED_SELF" then + -- NamPower: targeted event, no throttle needed. + -- arg3 may not be a reliable DBC ID so we skip spell-ID filtering and let + -- the existing texture/change guard below handle early-exit cheaply. + elseif event == "PLAYER_AURAS_CHANGED" then local now = GetTime and GetTime() or 0 local minInterval = zButtonAspect_MinAuraInterval if UnitAffectingCombat and UnitAffectingCombat("player") then @@ -271,7 +287,8 @@ function MTH_ZH_AspectAdjust_OnEvent() zButtonAspect_SetupSizeAndPosition() zButtonAspect_EnsureValidParentId() end - if event == "PLAYER_AURAS_CHANGED" or event == "PLAYER_ENTERING_WORLD" + if event == "PLAYER_AURAS_CHANGED" or event == "BUFF_ADDED_SELF" or event == "BUFF_REMOVED_SELF" + or event == "PLAYER_ENTERING_WORLD" or event == "SPELLS_CHANGED" or event == "CHARACTER_POINTS_CHANGED" or event == "LEARNED_SPELL_IN_TAB" then zButtonAspect_EnsureValidParentId() if not zButtonAspect1.id then @@ -304,13 +321,19 @@ function MTH_ZH_AspectAdjust_OnEvent() end end local i = 1 - local texture = GetSpellTexture(zButtonAspect1.id, "spell") + local texture1 = GetSpellTexture(zButtonAspect1.id, "spell") + local texture2 = zButtonAspect2 and zButtonAspect2.id and GetSpellTexture(zButtonAspect2.id, "spell") or nil local buff = UnitBuff("player", i) local spellname, buffname local newParentId = zButtonAspect1.id + local smartEnabled = zButtonAspect_GetSaved()["parent"]["smart"] ~= false while buff do - if texture == buff and zButtonAspect2.id then - newParentId = zButtonAspect2.id + if smartEnabled and zButtonAspect2 and zButtonAspect2.id then + if texture1 == buff then + newParentId = zButtonAspect2.id + elseif texture2 and texture2 == buff then + newParentId = zButtonAspect1.id + end end if needChildIconScan and buttontextures[buff] then if hasDuplicateChildTexture then @@ -332,10 +355,10 @@ function MTH_ZH_AspectAdjust_OnEvent() i = i + 1 buff = UnitBuff("player", i) end - if event == "PLAYER_AURAS_CHANGED" and zButtonAspect_LastAuraTexture == texture and zButtonAspect.id == newParentId and zButtonAspect_LastParentId == newParentId and not isParentTooltipOwned then + if (event == "PLAYER_AURAS_CHANGED" or event == "BUFF_ADDED_SELF" or event == "BUFF_REMOVED_SELF") and zButtonAspect_LastAuraTexture == texture1 and zButtonAspect.id == newParentId and zButtonAspect_LastParentId == newParentId and not isParentTooltipOwned then return end - zButtonAspect_LastAuraTexture = texture + zButtonAspect_LastAuraTexture = texture1 zButtonAspect_LastParentId = newParentId zButtonAspect.id = newParentId ZSpellButton_UpdateButton(zButtonAspect) diff --git a/modules/zhunter/zButtonCompanions.lua b/modules/zhunter/zButtonCompanions.lua index b1e9f6e..6834ff0 100644 --- a/modules/zhunter/zButtonCompanions.lua +++ b/modules/zhunter/zButtonCompanions.lua @@ -75,6 +75,9 @@ local function zButtonCompanions_EnsureConfig() if saved["parent"]["circle"] == nil then saved["parent"]["circle"] = 1 end + if saved["parent"]["random"] == nil then + saved["parent"]["random"] = false + end if not saved["children"]["size"] then saved["children"]["size"] = 36 end @@ -177,7 +180,14 @@ local function zButtonCompanions_ApplySpellIds(spellIds) end if found > 0 then - parent.id = spellIds[1] + local saved = zButtonCompanions_GetSaved() + if saved["parent"]["random"] and found > 1 then + local pick = math.random(1, found) + local child = getglobal("zButtonCompanions" .. pick) + parent.id = child and child.id or spellIds[1] + else + parent.id = spellIds[1] + end parent.isspell = 1 ZSpellButton_UpdateButton(parent) parent:Enable() @@ -227,6 +237,17 @@ function zButtonCompanions_OnEvent() return end zButtonCompanions_CreateButtons() + zButtonCompanions.afterclick = function(button) + local saved = zButtonCompanions_GetSaved() + if saved["parent"]["random"] and zButtonCompanions.found and zButtonCompanions.found > 1 then + local pick = math.random(1, zButtonCompanions.found) + local child = getglobal("zButtonCompanions" .. pick) + if child and child.id then + zButtonCompanions.id = child.id + ZSpellButton_UpdateButton(zButtonCompanions) + end + end + end MTH_ZH_CompanionsAdjust = CreateFrame("Frame", "MTH_ZH_CompanionsAdjust") MTH_ZH_CompanionsAdjust:RegisterEvent("SPELLS_CHANGED") MTH_ZH_CompanionsAdjust:RegisterEvent("PLAYER_ENTERING_WORLD") @@ -264,12 +285,14 @@ function zButtonCompanions_SetupSizeAndPosition() if displayCount < 0 then displayCount = 0 end - ZSpellButton_SetSize(zButtonCompanions, saved["parent"]["size"]) - ZSpellButton_SetSize(zButtonCompanions, saved["children"]["size"], 1) - ZSpellButton_SetExpandDirection(zButtonCompanions, saved["firstbutton"]) - ZSpellButton_ArrangeChildren(zButtonCompanions, saved["rows"], - displayCount, saved["horizontal"], - saved["vertical"]) + if not (type(MTH_ZBar_ApplyToButton) == "function" and MTH_ZBar_ApplyToButton(zButtonCompanions, displayCount)) then + ZSpellButton_SetSize(zButtonCompanions, saved["parent"]["size"]) + ZSpellButton_SetSize(zButtonCompanions, saved["children"]["size"], 1) + ZSpellButton_SetExpandDirection(zButtonCompanions, saved["firstbutton"]) + ZSpellButton_ArrangeChildren(zButtonCompanions, saved["rows"], + displayCount, saved["horizontal"], + saved["vertical"]) + end end function zButtonCompanions_Reset() diff --git a/modules/zhunter/zButtonCraft.lua b/modules/zhunter/zButtonCraft.lua new file mode 100644 index 0000000..4d84cb3 --- /dev/null +++ b/modules/zhunter/zButtonCraft.lua @@ -0,0 +1,401 @@ +local function zButtonCraft_GetRoot() + return MTH_ZH_GetSavedRoot() +end + +local root = zButtonCraft_GetRoot() +if not root["zButtonCraft"] then + root["zButtonCraft"] = {} + root["zButtonCraft"]["spells"] = {} + root["zButtonCraft"]["rows"] = 1 + root["zButtonCraft"]["horizontal"] = nil + root["zButtonCraft"]["vertical"] = nil + root["zButtonCraft"]["firstbutton"] = "RIGHT" + root["zButtonCraft"]["enabled"] = 0 + root["zButtonCraft"]["tooltip"] = 1 + root["zButtonCraft"]["parent"] = {} + root["zButtonCraft"]["parent"]["size"] = 36 + root["zButtonCraft"]["parent"]["hide"] = nil + root["zButtonCraft"]["parent"]["circle"] = 1 + root["zButtonCraft"]["children"] = {} + root["zButtonCraft"]["children"]["size"] = 36 + root["zButtonCraft"]["children"]["hideonclick"] = 1 +end + +local ZHUNTER_CRAFT_BUTTON_MAX = 14 + +local zButtonCraft_LastSpellSignature = nil +local zButtonCraft_LastRefreshAt = 0 +local zButtonCraft_MinRefreshInterval = 0.50 + +-- Exact spell names as they appear in the General spellbook tab. +-- spellName = the name returned by GetSpellName() to match against (case-insensitive) +-- display = label used for tooltips and options +local ZCRAFT_PROFESSIONS = { + { spellName = "Alchemy", display = "Alchemy" }, + { spellName = "Blacksmithing", display = "Blacksmithing" }, + { spellName = "Disenchanting", display = "Disenchanting" }, + { spellName = "Enchanting", display = "Enchanting" }, + { spellName = "Engineering", display = "Engineering" }, + { spellName = "Leatherworking", display = "Leatherworking" }, + { spellName = "Tailoring", display = "Tailoring" }, + { spellName = "Jewelcrafting", display = "Jewelcrafting" }, + { spellName = "Mining", display = "Smelting" }, + { spellName = "Cooking", display = "Cooking" }, + { spellName = "First Aid", display = "First Aid" }, + { spellName = "Herbalism", display = "Herbalism" }, + { spellName = "Survival", display = "Survival" }, + { spellName = "Fishing", display = "Fishing" }, + { spellName = "Skinning", display = "Skinning" }, +} + +-- Build a lookup: lowercase spell name → ZCRAFT_PROFESSIONS entry +local ZCRAFT_SPELL_LOOKUP = {} +for i = 1, table.getn(ZCRAFT_PROFESSIONS) do + ZCRAFT_SPELL_LOOKUP[string.lower(ZCRAFT_PROFESSIONS[i].spellName)] = ZCRAFT_PROFESSIONS[i] +end + +function zButtonCraft_GetSaved() + local currentRoot = zButtonCraft_GetRoot() + if not currentRoot["zButtonCraft"] then + currentRoot["zButtonCraft"] = {} + end + return currentRoot["zButtonCraft"] +end + +local function zButtonCraft_EnsureConfig() + local saved = zButtonCraft_GetSaved() + saved["count"] = nil + if saved["enabled"] == nil then + saved["enabled"] = 0 + end + if saved["tooltip"] == nil then + saved["tooltip"] = 1 + end + if not saved["children"] then + saved["children"] = {} + end + if saved["children"]["hideonclick"] == nil then + saved["children"]["hideonclick"] = 1 + end + if not saved["spells"] then + saved["spells"] = {} + end + if not saved["parent"] then + saved["parent"] = {} + end + if not saved["parent"]["size"] then + saved["parent"]["size"] = 36 + end + if saved["parent"]["circle"] == nil then + saved["parent"]["circle"] = 1 + end + if not saved["children"]["size"] then + saved["children"]["size"] = 36 + end + if not saved["rows"] or saved["rows"] < 1 then + saved["rows"] = 1 + end + if not saved["firstbutton"] then + saved["firstbutton"] = "RIGHT" + end +end + +local function zButtonCraft_ApplyRuntimeSettings() + if not zButtonCraft then return end + local saved = zButtonCraft_GetSaved() + zButtonCraft.tooltip = saved["tooltip"] and true or false + zButtonCraft.hideonclick = saved["children"] and saved["children"]["hideonclick"] and true or false + zButtonCraft.expandonhover = saved["children"] and saved["children"]["expandonhover"] and true or false + zButtonCraft.fadetimer = saved["children"] and tonumber(saved["children"]["fadetimer"]) or 0 +end + +local function zButtonCraft_GetSpellSignature(spellIds) + if type(spellIds) ~= "table" or table.getn(spellIds) == 0 then return "" end + local parts = {} + for i = 1, table.getn(spellIds) do + parts[i] = tostring(spellIds[i] or "") + end + return table.concat(parts, ",") +end + +-- Scans individual spells in the General tab and returns two parallel arrays: +-- spellIds = spellbook index of each matched craft spell +-- spellNames = display label for that profession +-- Results are ordered by ZCRAFT_PROFESSIONS priority. +local function zButtonCraft_CollectProfessions() + if type(GetNumSpellTabs) ~= "function" or type(GetSpellTabInfo) ~= "function" + or type(GetSpellName) ~= "function" then + return {}, {} + end + local bookType = BOOKTYPE_SPELL or "spell" + + -- Find the General tab (always tab 1, but look it up to be safe) + local generalOffset, generalEntries + local tabCount = GetNumSpellTabs() or 0 + for tabIndex = 1, tabCount do + local tabName, _, tabOffset, tabEntries = GetSpellTabInfo(tabIndex) + if tabName and string.lower(tabName) == "general" then + generalOffset = tabOffset + generalEntries = tabEntries + break + end + end + + if not generalOffset or not generalEntries or generalEntries <= 0 then + return {}, {} + end + + -- Scan every spell in General; collect matches keyed by profession order + local found = {} -- profession display → spellbook index (first match wins) + for i = 1, generalEntries do + local spellIndex = generalOffset + i + local spellName = GetSpellName(spellIndex, bookType) + if spellName then + local prof = ZCRAFT_SPELL_LOOKUP[string.lower(spellName)] + if prof and not found[prof.display] then + found[prof.display] = spellIndex + end + end + end + + -- Return results in ZCRAFT_PROFESSIONS order + local spellIds = {} + local spellNames = {} + for profIndex = 1, table.getn(ZCRAFT_PROFESSIONS) do + local prof = ZCRAFT_PROFESSIONS[profIndex] + local spellIndex = found[prof.display] + if spellIndex then + table.insert(spellIds, spellIndex) + table.insert(spellNames, prof.display) + end + end + + return spellIds, spellNames +end + +local function zButtonCraft_ApplySpellIds(spellIds) + if not zButtonCraft then return 0 end + local parent = zButtonCraft + local childCount = parent.count or ZHUNTER_CRAFT_BUTTON_MAX + + for i = 1, childCount do + local child = getglobal("zButtonCraft" .. i) + if child then + child:Hide() + child.id = nil + child.icon = nil + child.isspell = nil + end + end + + local found = 0 + for i = 1, table.getn(spellIds) do + if found >= childCount then break end + local spellId = spellIds[i] + if spellId then + found = found + 1 + local child = getglobal("zButtonCraft" .. found) + if child then + child.id = spellId + child.isspell = 1 + ZSpellButton_UpdateButton(child) + child:Show() + end + end + end + + if found > 0 then + parent.id = spellIds[1] + parent.isspell = 1 + ZSpellButton_UpdateButton(parent) + parent:Enable() + parent:Show() + else + parent.id = nil + parent:Hide() + end + + return found +end + +local function zButtonCraft_RefreshProfessions() + if not zButtonCraft then return 0 end + local saved = zButtonCraft_GetSaved() + local freshIds, freshNames = zButtonCraft_CollectProfessions() + + -- Build lookup: display name → spellbook index from the fresh scan + local nameToId = {} + for i = 1, table.getn(freshNames) do + nameToId[freshNames[i]] = freshIds[i] + end + + -- Honour saved user order when it exists + local existing = saved["spells"] + if existing and table.getn(existing) > 0 then + local orderedNames = {} + local orderedIds = {} + local seen = {} + -- Keep entries that still exist, in the user's order + for i = 1, table.getn(existing) do + local name = existing[i] + if nameToId[name] then + table.insert(orderedNames, name) + table.insert(orderedIds, nameToId[name]) + seen[name] = true + end + end + -- Append any newly learned professions at the end + for i = 1, table.getn(freshNames) do + if not seen[freshNames[i]] then + table.insert(orderedNames, freshNames[i]) + table.insert(orderedIds, freshIds[i]) + end + end + freshNames = orderedNames + freshIds = orderedIds + end + + saved["spellIds"] = freshIds + saved["spells"] = freshNames + zButtonCraft.spells = freshNames + zButtonCraft.found = zButtonCraft_ApplySpellIds(freshIds) + zButtonCraft_ApplyRuntimeSettings() + return zButtonCraft.found or 0 +end + +function zButtonCraft_OnLoad() + this:RegisterEvent("VARIABLES_LOADED") +end + +function zButtonCraft_OnEvent() + if event == "VARIABLES_LOADED" then + if not zButtonCraft then return end + if UnitClass("player") ~= ZHUNTER_HUNTER then + zButtonCraft:UnregisterAllEvents() + zButtonCraft:Hide() + return + end + zButtonCraft_CreateButtons() + MTH_ZH_CraftAdjust = CreateFrame("Frame", "MTH_ZH_CraftAdjust") + MTH_ZH_CraftAdjust:RegisterEvent("SPELLS_CHANGED") + MTH_ZH_CraftAdjust:RegisterEvent("PLAYER_ENTERING_WORLD") + MTH_ZH_CraftAdjust:SetScript("OnEvent", MTH_ZH_CraftAdjust_OnEvent) + zButtonCraft_SetupSizeAndPosition() + end +end + +function zButtonCraft_CreateButtons() + zButtonCraft_EnsureConfig() + if not zButtonCraft._mthChildrenCreated then + ZSpellButton_CreateChildren(zButtonCraft, "zButtonCraft", ZHUNTER_CRAFT_BUTTON_MAX) + zButtonCraft._mthChildrenCreated = 1 + end + zButtonCraft_RefreshProfessions() +end + +function zButtonCraft_SetupSizeAndPosition() + local saved = zButtonCraft_GetSaved() + if saved["enabled"] == false or saved["enabled"] == 0 then + if MTH_ZH_CraftAdjust then + MTH_ZH_CraftAdjust:UnregisterAllEvents() + MTH_ZH_CraftAdjust:SetScript("OnEvent", nil) + end + if zButtonCraft and zButtonCraft.Hide then + zButtonCraft:Hide() + end + return + end + if MTH_ZH_CraftAdjust then + MTH_ZH_CraftAdjust:RegisterEvent("SPELLS_CHANGED") + MTH_ZH_CraftAdjust:RegisterEvent("PLAYER_ENTERING_WORLD") + MTH_ZH_CraftAdjust:SetScript("OnEvent", MTH_ZH_CraftAdjust_OnEvent) + end + local displayCount = zButtonCraft.found or 0 + if displayCount < 0 then displayCount = 0 end + if not (type(MTH_ZBar_ApplyToButton) == "function" and MTH_ZBar_ApplyToButton(zButtonCraft, displayCount)) then + ZSpellButton_SetSize(zButtonCraft, saved["parent"]["size"]) + ZSpellButton_SetSize(zButtonCraft, saved["children"]["size"], 1) + ZSpellButton_SetExpandDirection(zButtonCraft, saved["firstbutton"]) + ZSpellButton_ArrangeChildren(zButtonCraft, saved["rows"], + displayCount, saved["horizontal"], saved["vertical"]) + end +end + +function zButtonCraft_Reset() + local currentRoot = zButtonCraft_GetRoot() + currentRoot["zButtonCraft"] = {} + local saved = zButtonCraft_GetSaved() + saved["spells"] = {} + saved["rows"] = 1 + saved["horizontal"] = nil + saved["vertical"] = nil + saved["firstbutton"] = "RIGHT" + saved["enabled"] = 1 + saved["tooltip"] = 1 + saved["parent"] = {} + saved["parent"]["size"] = 36 + saved["parent"]["hide"] = nil + saved["parent"]["circle"] = 1 + saved["children"] = {} + saved["children"]["size"] = 36 + saved["children"]["hideonclick"] = 1 + zButtonCraft_EnsureConfig() +end + +function MTH_ZH_CraftAdjust_OnEvent() + if not zButtonCraft then return end + if event == "SPELLS_CHANGED" then + local now = GetTime and GetTime() or 0 + if now > 0 and zButtonCraft_LastRefreshAt > 0 and (now - zButtonCraft_LastRefreshAt) < zButtonCraft_MinRefreshInterval then + return + end + local spellIds, _ = zButtonCraft_CollectProfessions() + local signature = zButtonCraft_GetSpellSignature(spellIds) + if signature == zButtonCraft_LastSpellSignature and not GameTooltip:IsOwned(zButtonCraft) then + return + end + zButtonCraft_LastSpellSignature = signature + zButtonCraft_LastRefreshAt = now + end + zButtonCraft_RefreshProfessions() + zButtonCraft_SetupSizeAndPosition() + if GameTooltip:IsOwned(zButtonCraft) then + ZSpellButtonParent_OnEnter(zButtonCraft) + end +end + +function zButtonCraft_KeyBinding(index) + if MTH_ZH_IsModuleEnabled and not MTH_ZH_IsModuleEnabled() then return end + local button + if index then + button = getglobal("zButtonCraft" .. index) + else + button = zButtonCraft + end + if button and button.id then + CastSpell(button.id, "spell") + if zButtonCraft.hideonclick then + if type(ZSpellButton_SetChildrenExpanded) == "function" then + ZSpellButton_SetChildrenExpanded(zButtonCraft, false) + elseif zButtonCraft.children then + zButtonCraft.children:Hide() + end + end + end +end + +SLASH_zButtonCraft1 = "/ZCraft" +SlashCmdList["zButtonCraft"] = function(msg) + if MTH_ZH_HandleDisabledSlash and MTH_ZH_HandleDisabledSlash("Craft button is disabled while module 'zhunter' is disabled.") then + return + end + if msg == "reset" then + zButtonCraft_Reset() + zButtonCraft:ClearAllPoints() + zButtonCraft:SetPoint("TOP", UIParent, "TOP", 0, -558) + zButtonCraft_RefreshProfessions() + zButtonCraft_SetupSizeAndPosition() + else + MTH_ZH_Print("Possible Commands: \"reset\"") + end +end diff --git a/modules/zhunter/zButtonCraft.xml b/modules/zhunter/zButtonCraft.xml new file mode 100644 index 0000000..b99eff0 --- /dev/null +++ b/modules/zhunter/zButtonCraft.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + diff --git a/modules/zhunter/zButtonMounts.lua b/modules/zhunter/zButtonMounts.lua index 83a9e7b..d54011e 100644 --- a/modules/zhunter/zButtonMounts.lua +++ b/modules/zhunter/zButtonMounts.lua @@ -75,6 +75,12 @@ local function zButtonMounts_EnsureConfig() if saved["parent"]["circle"] == nil then saved["parent"]["circle"] = 1 end + if saved["parent"]["random"] == nil then + saved["parent"]["random"] = false + end + if saved["parent"]["aqfilter"] == nil then + saved["parent"]["aqfilter"] = true + end if not saved["children"]["size"] then saved["children"]["size"] = 36 end @@ -177,7 +183,14 @@ local function zButtonMounts_ApplySpellIds(spellIds) end if found > 0 then - parent.id = spellIds[1] + local saved = zButtonMounts_GetSaved() + if saved["parent"]["random"] and found > 1 then + local pick = math.random(1, found) + local child = getglobal("zButtonMounts" .. pick) + parent.id = child and child.id or spellIds[1] + else + parent.id = spellIds[1] + end parent.isspell = 1 ZSpellButton_UpdateButton(parent) parent:Enable() @@ -190,6 +203,49 @@ local function zButtonMounts_ApplySpellIds(spellIds) return found end +local function zButtonMounts_IsQirajiSpell(spellId) + local bookType = BOOKTYPE_SPELL or "spell" + local name = GetSpellName(spellId, bookType) + if name and string.find(string.lower(name), "qiraji", 1, true) then + return true + end + return false +end + +local function zButtonMounts_IsInAQ40() + local zone = GetRealZoneText and GetRealZoneText() or "" + if zone == "Temple of Ahn'Qiraj" or zone == "Ahn'Qiraj" then + return true + end + return false +end + +local function zButtonMounts_FilterAQ(spellIds) + local saved = zButtonMounts_GetSaved() + if not saved["parent"]["aqfilter"] then + return spellIds + end + local inAQ = zButtonMounts_IsInAQ40() + local filtered = {} + for i = 1, table.getn(spellIds) do + local isQiraji = zButtonMounts_IsQirajiSpell(spellIds[i]) + if inAQ then + if isQiraji then + table.insert(filtered, spellIds[i]) + end + else + if not isQiraji then + table.insert(filtered, spellIds[i]) + end + end + end + -- Fallback: if filtering removed everything, return original list + if table.getn(filtered) == 0 then + return spellIds + end + return filtered +end + local function zButtonMounts_RefreshSpells() if not zButtonMounts then return 0 @@ -207,6 +263,7 @@ local function zButtonMounts_RefreshSpells() saved["spellIds"] = spellIds saved["spells"] = spellNames zButtonMounts.spells = spellNames + spellIds = zButtonMounts_FilterAQ(spellIds) zButtonMounts.found = zButtonMounts_ApplySpellIds(spellIds) zButtonMounts_ApplyRuntimeSettings() return zButtonMounts.found or 0 @@ -227,9 +284,21 @@ function zButtonMounts_OnEvent() return end zButtonMounts_CreateButtons() + zButtonMounts.afterclick = function(button) + local saved = zButtonMounts_GetSaved() + if saved["parent"]["random"] and zButtonMounts.found and zButtonMounts.found > 1 then + local pick = math.random(1, zButtonMounts.found) + local child = getglobal("zButtonMounts" .. pick) + if child and child.id then + zButtonMounts.id = child.id + ZSpellButton_UpdateButton(zButtonMounts) + end + end + end MTH_ZH_MountsAdjust = CreateFrame("Frame", "MTH_ZH_MountsAdjust") MTH_ZH_MountsAdjust:RegisterEvent("SPELLS_CHANGED") MTH_ZH_MountsAdjust:RegisterEvent("PLAYER_ENTERING_WORLD") + MTH_ZH_MountsAdjust:RegisterEvent("ZONE_CHANGED_NEW_AREA") MTH_ZH_MountsAdjust:SetScript("OnEvent", MTH_ZH_MountsAdjust_OnEvent) zButtonMounts_SetupSizeAndPosition() end @@ -261,18 +330,21 @@ function zButtonMounts_SetupSizeAndPosition() if MTH_ZH_MountsAdjust then MTH_ZH_MountsAdjust:RegisterEvent("SPELLS_CHANGED") MTH_ZH_MountsAdjust:RegisterEvent("PLAYER_ENTERING_WORLD") + MTH_ZH_MountsAdjust:RegisterEvent("ZONE_CHANGED_NEW_AREA") MTH_ZH_MountsAdjust:SetScript("OnEvent", MTH_ZH_MountsAdjust_OnEvent) end local displayCount = zButtonMounts.found or 0 if displayCount < 0 then displayCount = 0 end - ZSpellButton_SetSize(zButtonMounts, saved["parent"]["size"]) - ZSpellButton_SetSize(zButtonMounts, saved["children"]["size"], 1) - ZSpellButton_SetExpandDirection(zButtonMounts, saved["firstbutton"]) - ZSpellButton_ArrangeChildren(zButtonMounts, saved["rows"], - displayCount, saved["horizontal"], - saved["vertical"]) + if not (type(MTH_ZBar_ApplyToButton) == "function" and MTH_ZBar_ApplyToButton(zButtonMounts, displayCount)) then + ZSpellButton_SetSize(zButtonMounts, saved["parent"]["size"]) + ZSpellButton_SetSize(zButtonMounts, saved["children"]["size"], 1) + ZSpellButton_SetExpandDirection(zButtonMounts, saved["firstbutton"]) + ZSpellButton_ArrangeChildren(zButtonMounts, saved["rows"], + displayCount, saved["horizontal"], + saved["vertical"]) + end end function zButtonMounts_Reset() diff --git a/modules/zhunter/zButtonPet.lua b/modules/zhunter/zButtonPet.lua index 2fb2905..e70f24a 100644 --- a/modules/zhunter/zButtonPet.lua +++ b/modules/zhunter/zButtonPet.lua @@ -84,6 +84,9 @@ local function zButtonPet_EnsureConfig() if saved["parent"]["circle"] == nil then saved["parent"]["circle"] = 1 end + if saved["parent"]["smart"] == nil then + saved["parent"]["smart"] = true + end if saved["enabled"] == nil then saved["enabled"] = 1 end @@ -258,12 +261,14 @@ function zButtonPet_SetupSizeAndPosition() if displayCount < 0 then displayCount = 0 end - ZSpellButton_SetSize(zButtonPet, saved["parent"]["size"]) - ZSpellButton_SetSize(zButtonPet, saved["children"]["size"], 1) - ZSpellButton_SetExpandDirection(zButtonPet, saved["firstbutton"]) - ZSpellButton_ArrangeChildren(zButtonPet, saved["rows"], - displayCount, saved["horizontal"], - saved["vertical"]) + if not (type(MTH_ZBar_ApplyToButton) == "function" and MTH_ZBar_ApplyToButton(zButtonPet, displayCount)) then + ZSpellButton_SetSize(zButtonPet, saved["parent"]["size"]) + ZSpellButton_SetSize(zButtonPet, saved["children"]["size"], 1) + ZSpellButton_SetExpandDirection(zButtonPet, saved["firstbutton"]) + ZSpellButton_ArrangeChildren(zButtonPet, saved["rows"], + displayCount, saved["horizontal"], + saved["vertical"]) + end end function zButtonPet_Reset() @@ -502,7 +507,11 @@ local function zButtonPet_RefreshParentSpell() local id = zButtonPet.id name = nil - if hasCurrentPet == false and hasLivePet == false then + if saved["parent"]["smart"] == false then + if choiceId then + id = choiceId + end + elseif hasCurrentPet == false and hasLivePet == false then name = ZHUNTER_PET_TAMING elseif not status then name = ZHUNTER_PET_CALL diff --git a/modules/zhunter/zButtonRanged.lua b/modules/zhunter/zButtonRanged.lua index ab45bfe..5bd3587 100644 --- a/modules/zhunter/zButtonRanged.lua +++ b/modules/zhunter/zButtonRanged.lua @@ -648,10 +648,12 @@ function zButtonRanged_SetupSizeAndPosition() if displayCount < 0 then displayCount = 0 end - ZSpellButton_SetSize(zButtonRanged, saved["parent"]["size"]) - ZSpellButton_SetSize(zButtonRanged, saved["children"]["size"], 1) - ZSpellButton_SetExpandDirection(zButtonRanged, saved["firstbutton"]) - ZSpellButton_ArrangeChildren(zButtonRanged, saved["rows"], displayCount, saved["horizontal"], saved["vertical"]) + if not (type(MTH_ZBar_ApplyToButton) == "function" and MTH_ZBar_ApplyToButton(zButtonRanged, displayCount)) then + ZSpellButton_SetSize(zButtonRanged, saved["parent"]["size"]) + ZSpellButton_SetSize(zButtonRanged, saved["children"]["size"], 1) + ZSpellButton_SetExpandDirection(zButtonRanged, saved["firstbutton"]) + ZSpellButton_ArrangeChildren(zButtonRanged, saved["rows"], displayCount, saved["horizontal"], saved["vertical"]) + end zButtonRanged_UpdateButton(zButtonRanged) end diff --git a/modules/zhunter/zButtonToys.lua b/modules/zhunter/zButtonToys.lua index 684de43..4eb0a6d 100644 --- a/modules/zhunter/zButtonToys.lua +++ b/modules/zhunter/zButtonToys.lua @@ -267,12 +267,14 @@ function zButtonToys_SetupSizeAndPosition() if displayCount < 0 then displayCount = 0 end - ZSpellButton_SetSize(zButtonToys, saved["parent"]["size"]) - ZSpellButton_SetSize(zButtonToys, saved["children"]["size"], 1) - ZSpellButton_SetExpandDirection(zButtonToys, saved["firstbutton"]) - ZSpellButton_ArrangeChildren(zButtonToys, saved["rows"], - displayCount, saved["horizontal"], - saved["vertical"]) + if not (type(MTH_ZBar_ApplyToButton) == "function" and MTH_ZBar_ApplyToButton(zButtonToys, displayCount)) then + ZSpellButton_SetSize(zButtonToys, saved["parent"]["size"]) + ZSpellButton_SetSize(zButtonToys, saved["children"]["size"], 1) + ZSpellButton_SetExpandDirection(zButtonToys, saved["firstbutton"]) + ZSpellButton_ArrangeChildren(zButtonToys, saved["rows"], + displayCount, saved["horizontal"], + saved["vertical"]) + end end function zButtonToys_Reset() diff --git a/modules/zhunter/zButtonTrack.lua b/modules/zhunter/zButtonTrack.lua index 704fdeb..cc33a61 100644 --- a/modules/zhunter/zButtonTrack.lua +++ b/modules/zhunter/zButtonTrack.lua @@ -93,6 +93,32 @@ local function zButtonTrack_FindSpellIdByTrackingName(trackingName) return nil end +local function zButtonTrack_ScanBuffsForTrackingSpell() + if not zButtonTrack or not zButtonTrack.count then + return nil, nil + end + local childTextures = {} + for i = 1, zButtonTrack.count do + local child = getglobal("zButtonTrack" .. i) + if child and child.id then + local tex = GetSpellTexture and GetSpellTexture(child.id, "spell") or nil + if tex then + childTextures[tex] = child.id + end + end + end + local buffIndex = 1 + local buff = UnitBuff("player", buffIndex) + while buff do + if childTextures[buff] then + return childTextures[buff], buff + end + buffIndex = buffIndex + 1 + buff = UnitBuff("player", buffIndex) + end + return nil, nil +end + local function zButtonTrack_GetActiveTrackingSpellId() local activeName = nil local activeTexture = nil @@ -112,10 +138,26 @@ local function zButtonTrack_GetActiveTrackingSpellId() return byName, activeTexture end if activeTexture then - return zButtonTrack_FindSpellIdByTrackingTexture(activeTexture), activeTexture + local byTex = zButtonTrack_FindSpellIdByTrackingTexture(activeTexture) + if byTex then + return byTex, activeTexture + end end - local buff = GetTrackingTexture and GetTrackingTexture() or nil - return zButtonTrack_FindSpellIdByTrackingTexture(buff), buff + -- Fallback: scan player buffs for tracking spell icon textures + local buffId, buffTex = zButtonTrack_ScanBuffsForTrackingSpell() + if buffId then + return buffId, buffTex + end + return nil, nil +end + +local function zButtonTrack_SmartParentId(activeChildId) + local child1 = getglobal("zButtonTrack1") + local child2 = getglobal("zButtonTrack2") + if not child1 or not child1.id then return nil end + if not child2 or not child2.id then return child1.id end + if activeChildId == child1.id then return child2.id end + return child1.id end local function zButtonTrack_AfterClick(button) @@ -123,9 +165,19 @@ local function zButtonTrack_AfterClick(button) return end if button and button.id then - zButtonTrack.id = button.id - ZSpellButton_UpdateButton(zButtonTrack) - ZSpellButton_UpdateCooldown(zButtonTrack) + local saved = zButtonTrack_GetSaved() + local newId + if saved["parent"]["smart"] ~= false then + newId = zButtonTrack_SmartParentId(button.id) + else + local firstChild = getglobal("zButtonTrack1") + newId = firstChild and firstChild.id + end + if newId and newId ~= zButtonTrack.id then + zButtonTrack.id = newId + ZSpellButton_UpdateButton(zButtonTrack) + ZSpellButton_UpdateCooldown(zButtonTrack) + end zButtonTrack_SyncActiveChild(button.id) if GetSpellTexture then zButtonTrack_LastTrackingTexture = GetSpellTexture(button.id, "spell") or zButtonTrack_LastTrackingTexture @@ -156,34 +208,36 @@ local function zButtonTrack_RefreshTrackingState() if not zButtonTrack or not zButtonTrack.count then return end - local newId, trackingTexture = zButtonTrack_GetActiveTrackingSpellId() - local buff = trackingTexture or (GetTrackingTexture and GetTrackingTexture() or nil) - if not newId and not buff then - if zButtonTrack.id then - zButtonTrack_SyncActiveChild(zButtonTrack.id) - else - zButtonTrack_LastTrackingTexture = nil - zButtonTrack_SyncActiveChild(nil) + local saved = zButtonTrack_GetSaved() + local firstChild = getglobal("zButtonTrack1") + + -- Smart OFF: always show first child + if saved["parent"]["smart"] == false then + if firstChild and firstChild.id and zButtonTrack.id ~= firstChild.id then + zButtonTrack.id = firstChild.id + ZSpellButton_UpdateButton(zButtonTrack) + ZSpellButton_UpdateCooldown(zButtonTrack) end return end + + -- Smart ON: detect which tracking is active, show the OTHER one + local activeId = zButtonTrack_GetActiveTrackingSpellId() + local newId = zButtonTrack_SmartParentId(activeId) if not newId then - newId = zButtonTrack.id or nil - end - if not newId then - return - end - if zButtonTrack_NormalizeTexture(zButtonTrack_LastTrackingTexture) == zButtonTrack_NormalizeTexture(buff) and zButtonTrack.id == newId then - zButtonTrack_SyncActiveChild(newId) - return + newId = firstChild and firstChild.id end + if not newId then return end + + -- Sync checked state on children + zButtonTrack_SyncActiveChild(activeId) + + if zButtonTrack.id == newId then return end local isOwned = GameTooltip and GameTooltip.IsOwned and GameTooltip:IsOwned(zButtonTrack) - zButtonTrack_LastTrackingTexture = buff zButtonTrack.id = newId ZSpellButton_UpdateButton(zButtonTrack) ZSpellButton_UpdateCooldown(zButtonTrack) - zButtonTrack_SyncActiveChild(newId) if isOwned then ZSpellButtonParent_OnEnter(zButtonTrack) end @@ -221,6 +275,9 @@ local function zButtonTrack_EnsureConfig() if saved["parent"]["circle"] == nil then saved["parent"]["circle"] = 1 end + if saved["parent"]["smart"] == nil then + saved["parent"]["smart"] = true + end if not saved["children"] then saved["children"] = {} end @@ -323,6 +380,7 @@ function zButtonTrack_SetupSizeAndPosition() end if MTH_ZH_TrackAdjust then MTH_ZH_TrackAdjust:RegisterEvent("MINIMAP_UPDATE_TRACKING") + MTH_ZH_TrackAdjust:RegisterEvent("PLAYER_AURAS_CHANGED") MTH_ZH_TrackAdjust:RegisterEvent("PLAYER_ENTERING_WORLD") MTH_ZH_TrackAdjust:RegisterEvent("SPELLS_CHANGED") MTH_ZH_TrackAdjust:RegisterEvent("CHARACTER_POINTS_CHANGED") @@ -333,12 +391,14 @@ function zButtonTrack_SetupSizeAndPosition() if displayCount < 0 then displayCount = 0 end - ZSpellButton_SetSize(zButtonTrack, saved["parent"]["size"]) - ZSpellButton_SetSize(zButtonTrack, saved["children"]["size"], 1) - ZSpellButton_SetExpandDirection(zButtonTrack, saved["firstbutton"]) - ZSpellButton_ArrangeChildren(zButtonTrack, saved["rows"], - displayCount, saved["horizontal"], - saved["vertical"]) + if not (type(MTH_ZBar_ApplyToButton) == "function" and MTH_ZBar_ApplyToButton(zButtonTrack, displayCount)) then + ZSpellButton_SetSize(zButtonTrack, saved["parent"]["size"]) + ZSpellButton_SetSize(zButtonTrack, saved["children"]["size"], 1) + ZSpellButton_SetExpandDirection(zButtonTrack, saved["firstbutton"]) + ZSpellButton_ArrangeChildren(zButtonTrack, saved["rows"], + displayCount, saved["horizontal"], + saved["vertical"]) + end zButtonTrack_RefreshTrackingState() end @@ -372,7 +432,7 @@ function MTH_ZH_TrackAdjust_OnEvent() zButtonTrack_CreateButtons() zButtonTrack_SetupSizeAndPosition() end - if event == "MINIMAP_UPDATE_TRACKING" or event == "PLAYER_ENTERING_WORLD" + if event == "MINIMAP_UPDATE_TRACKING" or event == "PLAYER_AURAS_CHANGED" or event == "PLAYER_ENTERING_WORLD" or event == "SPELLS_CHANGED" or event == "CHARACTER_POINTS_CHANGED" or event == "LEARNED_SPELL_IN_TAB" then zButtonTrack_RefreshTrackingState() end diff --git a/modules/zhunter/zButtonTrap.lua b/modules/zhunter/zButtonTrap.lua index 7a829da..e8c897e 100644 --- a/modules/zhunter/zButtonTrap.lua +++ b/modules/zhunter/zButtonTrap.lua @@ -175,12 +175,14 @@ function zButtonTrap_SetupSizeAndPosition() if displayCount < 0 then displayCount = 0 end - ZSpellButton_SetSize(zButtonTrap, saved["parent"]["size"]) - ZSpellButton_SetSize(zButtonTrap, saved["children"]["size"], 1) - ZSpellButton_SetExpandDirection(zButtonTrap, saved["firstbutton"]) - ZSpellButton_ArrangeChildren(zButtonTrap, saved["rows"], - displayCount, saved["horizontal"], - saved["vertical"]) + if not (type(MTH_ZBar_ApplyToButton) == "function" and MTH_ZBar_ApplyToButton(zButtonTrap, displayCount)) then + ZSpellButton_SetSize(zButtonTrap, saved["parent"]["size"]) + ZSpellButton_SetSize(zButtonTrap, saved["children"]["size"], 1) + ZSpellButton_SetExpandDirection(zButtonTrap, saved["firstbutton"]) + ZSpellButton_ArrangeChildren(zButtonTrap, saved["rows"], + displayCount, saved["horizontal"], + saved["vertical"]) + end end function zButtonTrap_Reset()