10 Commits

Author SHA1 Message Date
Duvel c81ce85950 Merge pull request #13 from DuvelCorp/V1.4.1
v1.4.1 event leak fixes, chronometer trap fix, profile memory, autost…
2026-03-24 14:24:08 +01:00
DuvelCorp 680ddcba8a v1.4.1 event leak fixes, chronometer trap fix, profile memory, autostrip alloc fix 2026-03-24 14:21:58 +01:00
Duvel 5c08656532 Merge pull request #12 from DuvelCorp/V1.4.1
V1.4.1 performance fixes
2026-03-24 14:15:25 +01:00
DuvelCorp 8d80d0663c V1.4.1 performance fixes 2026-03-24 14:14:33 +01:00
Duvel 43e23e7616 Merge pull request #10 from DuvelCorp/v1.4.0
v1.4.0 Twow 1.18.1
2026-03-23 22:28:09 +01:00
DuvelCorp 8479607a13 v1.4.0 Twow 1.18.1 2026-03-23 22:25:10 +01:00
Duvel f7b0318179 Add model viewer feature to README
Added model viewer feature for all beasts in the game.
2026-03-19 16:36:01 +01:00
Duvel 23bb69d094 Merge pull request #7 from DuvelCorp/v1.3.0
V1.3.0 - Last before Twow upgrade
2026-03-19 16:32:36 +01:00
DuvelCorp b2060e0aab V1.3.0 2026-03-19 16:27:21 +01:00
Duvel 609a6bdd83 Merge pull request #6 from DuvelCorp/v1.2.0
v1.2.0
2026-03-11 00:41:30 +01:00
91 changed files with 77172 additions and 59490 deletions
+4
View File
@@ -9,3 +9,7 @@
tools/
LOC*.md
LOCALIZATION*.md
# Dev tooling — Python generators, data processing scripts, scratch files
# Not part of the addon, never ship this
.tools/
+5
View File
@@ -118,4 +118,9 @@
<Binding name="ZPet 6" header="ZPetHeader">
zButtonPet_KeyBinding(6)
</Binding>
<!-- ExpAmmo Action Button -->
<Binding name="ExpAmmo Action" header="ExpAmmoHeader">
MTH_EA_CastActionSpell();
</Binding>
</Ui>
+91
View File
@@ -2,6 +2,97 @@
All notable changes to MetaHunt will be documented in this file.
## [1.4.1] - 2026-03-24
### Added
- **zTrack**: added `Find Trees` in tracking spells.
### Changed
- **Performance — MM Widget / ExpAmmo**: Replaced tooltip-based aura scanning (which triggered hidden tooltip rendering on every aura check) with direct texture/icon comparison via `UnitBuff`/`UnitDebuff`. Added a 150 ms throttle on `PLAYER_AURAS_CHANGED`, which fires 1050+ times per second in combat. Lock and Load detection and ammo cycle recovery after `/reload` both updated to use the new zero-cost texture path.
- **Performance — event frame leaks**: Some addon features were registering WoW game events at load time and never unregistering them when the feature was disabled from the options, causing their `OnEvent` handlers to fire even with the option turned off. Fixed for: Auto-Strip (`PLAYER_REGEN_*`), Smart Ammo (`SPELLCAST_*`, `START/STOP_AUTOREPEAT_SPELL`), and some zButton frames (Pet, Mounts, Track, Trap, Toys). Events are now properly registered on enable and unregistered on disable.
- **Chronometer — trap effects from other hunters**: Trap effect bars (`Immolation Trap Effect`, `Explosive Trap Effect`, `Freezing Trap Effect`, `Frost Trap Aura`) were incorrectly appearing when another hunter's trap triggered on a mob you were also targeting. .
- **Version check broadcast interval**: Increased update notification broadcast cooldown from 10 minutes to 1 hour to reduce channel traffic now that the user base has grown.
- **Profile snapshot memory**: The auto `BuildSnapshot` (called on every `/reload` via `PLAYER_LOGOUT`) was deep-copying `feedomatic.legacy`, a large migration table mirroring `FOM_Cooking`, `FOM_QuestFood`, `FOM_AddedFoods` etc. These are runtime/operational blobs, not user configuration settings, and should never be included in a profile. Introduced `MTH_Profile_CopyModules` which skips `legacy` and `history` keys, reducing the snapshot allocation from ~1 MB to a few KB.
## [1.4.0] - 2026-03-23
### TWoW 1.18.1 "Nightmares of Ursol" support
### Added
- **Complete beast database overhaul**: The MTH beast datastore was reconstructed from scratch, based on a full (beasts) 1.18.1 extract from live DB, gladly provided by Twow staff (thanks @Haaxor!). The beast data should now be 100% accurate, and notably all spawn coordinates, respawn times and pet abilities learnable on all beasts in the world.
- The **Beast Lore** page of the Book now allows to filter the list by Zone. Additionally, the beast inspector has been reformatted and all meaningful beast's data is displayed nicely.
- All new and missing **Beasts Models** have been (re)scanned from MPQ game files and are viewable in the Beast Lore page, and browsable in the Family Model viewer.
- Added **MM widget** module: tracks the new 3-state of Experimental ammo cycle (Fire → Nature → Arcane), the Lock and Load procs, and has a dynamic cell that shows either Aimed Shot availability or the right shot to use following the current Experimental ammo proc if any. Lets Make MM great again.
- Added new **Moth** pet family.
- Added new **Pollen Burst** pet ability.
- Added new **Aspect of the Viper** in zAspect.
- Added new **Chronometer** timer entries for 1.18.1 spells:
- Aimed Shot cooldown tracker
- Kill Command cooldown tracker
- Lock and Load proc timer
- Scent of Blood proc timer
- Added new **Chronometer** target debuff entries for the Experimental Ammo states:
- Poisonous Ammunition: 15s armor-reduction debuff (triggered when Serpent Sting consumes the Poisonous state)
- Enchanted Ammunition: 6s magic-resist debuff (triggered when Arcane Shot consumes the Enchanted state)
- Added the **5 new craftable ammo** to the Projectiles book's page, SmartAmmo and zAmmo button:
- **Bright Wood Arrows** (Survival: Journeyman, req. 30) — 10.5 DPS
- **Shade Wood Arrows** (Survival: Expert, req. 40) — 14.5 DPS
- **Smooth Ironfeather Arrows** (Survival: Artisan, req. 50) — 17.5 DPS
- **Starfeather Arrows** (Survival: Artisan, req. 55) — 19 DPS
- **Enchanted Thorium Shells** (Engineering: Artisan, req. 55) — 19 DPS
- Added new hunter-friends NPC of **Moonwhisper Coast** to the NPC Finder Book's page:
- **Hula Swiftmane** — Horde Stable Master (66.1, 39.1)
- **Ornala** — Horde Ammo Vendor (67.3, 37.1) — sells Arrows and Slugs
- **Badel Wildlance** — Alliance Stable Master (59.0, 24.9)
- **Elendon Truebough** — Alliance Ammo Vendor (58.8, 24.5) — sells Arrows
### Fixed
- Fixed **Chronometer** bars appearing from nearby players' combat events (e.g. another hunter's traps or DoTs triggering your own bars).
- Fixed the list in Families Book's page, that wasn't tall enough to display all families (wolves were hidden).
## [1.3.0] - 2026-03-19
### Added
- Added a **3D Model Viewer** to the Hunter Book Beast Lore inspector panel. Clicking a beast in the list shows its in-game model.
- Added a standalone **3D Model Viewer** that is shown when cliking the model of a beast in the Beast lore. The model is rotatable with the mouse, and you can browse all models of the same pet family and shows all other beasts that share the same skin, with one-click to jump to any of them in the Beast Lore.
- Added a **Family icon** column to the Beast Lore list, replacing the text family name with the pet family icon for a more compact view.
- Added an **Attack Speed (AS)** column to the Beast Lore list.
- Added `displayId` and `skinId` fields to **all ~1000 beasts** in the MetaHunt datastore. `displayId` was scraped from the Turtle WoW DB, and `skinId` derived from it following an extraction on MPQ game files. This is what powers the model viewer and skin-grouping.
- Added per-family icons to all 19 pet families in the family data table.
- Added missing food types (`raw meat`, `raw fish`) to all pet families that accept `meat` or `fish` respectively (sourced from MPQ data).
- Added **Profile Management** panel (Options → Profiles): save the current addon configuration as a named profile and instantly load it on any character. Profiles cover all module settings (ICU, Chronometer, AutoBuy, AutoQuest, SmartAmmo, Feed-O-Matic, zButtons layout) and per-character module enabled/disabled states.
- Added automatic per-character **config snapshots** saved on every logout (`CharName-RealmName`), visible in the "Copy from Character" section — import any character's snapshot as a loadable profile in one click.
### Fixed
- Fixed the pet feeding counter never incrementing: `MTH_FEED_Trace` was a no-op stub and `FOM_CORE_ATTEMPT_TRACKING_ENABLED` was `false`; both are now active.
- Fixed `FOM_AddFood` not clearing a food from `FOM_RemovedFoods` when the item belongs to the base diet list, which permanently banned it even after an explicit `/fom add`.
- Wired `FOM_ClearItemBans` into the `/fom add` command path: explicitly re-adding a food now clears it from `FOM_RemovedFoods`, FOM quarantine, and core exceptions in one step.
- Removed debug messages forgotten when releasing 1.2.
- Fixed SmartAmmo + **Quiver castbar conflict**: Quiver's castbar was disappearing when both addons were active. Root cause: MetaHunt SmartAmmo was re-installing its hooks every 0.5 seconds, constantly bumping Quiver off the top of the hook chain. Fixed by removing the repeated re-hook and adding a guard that only installs once (or when no other addon is on top).
## [1.2.0] - 2026-03-10
### Added
+9
View File
@@ -2,6 +2,15 @@
-- MetaHunt: Hunter's Toolkit Addon
-- A unified addon merging FeedOMatic, Tooltips, and zhunter
-- Modern modular architecture with legacy addon support
--
-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-- TARGET RUNTIME: LUA 5.0 (WoW 1.12 / Turtle-WoW)
-- - Max 32 upvalues per function
-- - No # operator (use table.getn)
-- - No string.gmatch (use string.gfind)
-- - NEVER validate with Lua 5.1+ tools (false passes)
-- - Errors: check Logs/FrameXML.log first
-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
------------------------------------------------------
-- Initialize beast database after all files loaded
+6 -3
View File
@@ -1,5 +1,5 @@
## Interface: 11200
## Version: 1.2.0
## Version: 1.4.1
## 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.
@@ -20,8 +20,6 @@ init\skins.xml
## Load database files (DB files must come before data\init.lua)
data\ds-beasts.lua
data\ds-beasts-extra.lua
data\ds-beasts-html-enrichment.lua
data\ds-vendors.lua
data\ds-vendors-extra.lua
data\ds-items.lua
@@ -31,12 +29,17 @@ data\ds-bags.lua
data\ds-items-atlas-sources.lua
data\ds-zones.lua
data\ds-zones-fallback.lua
data\ds-zones-wma.lua
data\ds-minimap-sizes.lua
data\ds-unit-coords.lua
data\ds-creature-coords.lua
data\ds-beasts-families.lua
data\ds-beasts-families-dbc.lua
data\ds-beasts-skins.lua
data\ds-pet-spells.lua
data\ds-pet-spells-trainer.lua
data\ds-hunter-spells.lua
data\ds-hunter-spells-dbc.lua
data\ds-racial.lua
data\init.lua
+11 -9
View File
@@ -1,14 +1,14 @@
# MetaHunt
MetaHunt is a Turtle WoW Hunter toolkit.
It is a modern modular addon that combines never-seen features, and improves/fixes a few old hunter add-ons.
It provides a bunch useful tools for all huntards, wheter they are still levelling, lone-wolf, or HL raider.
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.
## Twow Data
MetaHunt ships with large up-to-date datastores for all Turtle WOW data a hunter needs :
MetaHunt ships with large Turtle WOW datastores :
- All beasts, their location, and the abilities they can learn you
- 100% accurate beasts' data, their locations, and the abilities they can learn you
- All Pet families, their abilities and their diet
- All pet abilities ranks
- All Ammo (Arrows and Bullets)
@@ -21,6 +21,8 @@ MetaHunt ships with large up-to-date datastores for all Turtle WOW data a hunter
- Auto equip another ammo instantly during combat whenever you get out of your current ammo.
- Auto equip the right ammo when you swap from a box/xbow to a gun and the other way around.
## MM Widget
Lets Make MM great again. Tracks the new 3-state of Experimental ammo cycle (Fire → Nature → Arcane), the Lock&Load procs, and has a dynamic cell that shows either Aimed Shot availability or the right shot to use following the current Experimental ammo proc if any.
## Tooltips
@@ -29,7 +31,7 @@ MetaHunt ships with large up-to-date datastores for all Turtle WOW data a hunter
- Vendors : Shows if a vendor sells Arrows and/or Bullets.
- Your pet : Shows your pet's XP, loyalty, happiness and how much you fed it this session.
## zBouttons
## zButtons
Old zHunterMod addon on steroids. Fully compatible with Twow, and enhanced with new functionalities
@@ -61,18 +63,18 @@ Define your own simple rules for auto buy ammo and pet food when you open a vend
Just spam SHIFT-click on the NPC to validate quests Scorpok and Thorium arrows. Additionally you can have an enhanced tooltip that display your actual Scorpok-related items when you mouseover the mobs involved.
## Beast model Viewer
Display the model of all beasts in the world. Always know what you are going to tame exactly before running there ! You can also browse all skin models of the same pet family to find the best-looking pet according to your tastes.
## The Great Book of Huntards
The Book is the main GUI of MetaHunt, it allows to display the data constantly collected about you and your pet, and browse the addon's data to find anything a Hunter needs.
- Browse the MetaHunt datastores of NPCs (Beast, vendors, masters) and open the map to locate them like in pfQuest
- Model viewer of all beasts in the game.
- Track all info about the pet abilities that you know and don't know yet
- Track all info about your stabled pets
- Keep an history of all your pets after you abandon them or if they run away.
## What it will never do.
The goal of MetaHunt is to offer hunter's features that are not covered by existing Twow addons, and in others https://github.com/SabineWren/Quiver .
There's no point to reinvent the wheel, except is the wheel is broken. Nothing will work better than Quiver for auto-shot, castbar and tranqshot mgt. MetaHunt is build to work alongside of Quiver and is complementary to it, and it will remain like this unless Quiver turns to be not upgraded anymore.
+18 -6
View File
@@ -150,6 +150,10 @@ function AutoStrip_SetAutoStripToggle(enabled, silent)
if enabled then
saved["autostrip"] = 1
AutoStrip_On = 1
if AutoStrip_Frame then
AutoStrip_Frame:RegisterEvent("PLAYER_REGEN_DISABLED")
AutoStrip_Frame:RegisterEvent("PLAYER_REGEN_ENABLED")
end
if AutoStripDisplay and AutoStripDisplay:IsVisible() and AutoStripDisplayAutoCast then
AutoStripDisplayAutoCast:Show()
end
@@ -159,6 +163,10 @@ function AutoStrip_SetAutoStripToggle(enabled, silent)
else
saved["autostrip"] = nil
AutoStrip_On = nil
if AutoStrip_Frame then
AutoStrip_Frame:UnregisterEvent("PLAYER_REGEN_DISABLED")
AutoStrip_Frame:UnregisterEvent("PLAYER_REGEN_ENABLED")
end
if AutoStripDisplayAutoCast then
AutoStripDisplayAutoCast:Hide()
end
@@ -252,10 +260,12 @@ function AutoStrip_UnequipAll(weaponsOnly)
end
local function AutoStrip_OnEvent()
AutoStrip_Trace("OnEvent evt=" .. tostring(event or "")
.. " runtime=" .. tostring(AutoStrip_On and 1 or 0)
.. " wasInCombat=" .. tostring(AutoStrip_WasInCombat and 1 or 0)
.. " playerInCombat=" .. tostring((type(UnitAffectingCombat) == "function" and UnitAffectingCombat("player")) and 1 or 0))
if AutoStrip_TraceEnabled then
AutoStrip_Trace("OnEvent evt=" .. tostring(event or "")
.. " runtime=" .. tostring(AutoStrip_On and 1 or 0)
.. " wasInCombat=" .. tostring(AutoStrip_WasInCombat and 1 or 0)
.. " playerInCombat=" .. tostring((type(UnitAffectingCombat) == "function" and UnitAffectingCombat("player")) and 1 or 0))
end
if event == "VARIABLES_LOADED" then
local saved = AutoStrip_GetSaved()
AutoStrip_Trace("VARIABLES_LOADED saved.autostrip=" .. tostring(saved["autostrip"] and 1 or 0)
@@ -318,9 +328,11 @@ end
AutoStrip_Frame = CreateFrame("Frame", "MTH_AutoStripEvent")
if AutoStrip_Frame then
-- PLAYER_REGEN_DISABLED / PLAYER_REGEN_ENABLED are intentionally NOT registered here.
-- They are registered only when the feature is enabled via AutoStrip_SetAutoStripToggle(true),
-- and unregistered when it is disabled. This prevents any combat event overhead when the
-- feature is off.
AutoStrip_Frame:RegisterEvent("VARIABLES_LOADED")
AutoStrip_Frame:RegisterEvent("PLAYER_REGEN_DISABLED")
AutoStrip_Frame:RegisterEvent("PLAYER_REGEN_ENABLED")
AutoStrip_Frame:RegisterEvent("PLAYER_ENTERING_WORLD")
AutoStrip_Frame:RegisterEvent("PLAYER_LOGOUT")
AutoStrip_Frame:SetScript("OnEvent", AutoStrip_OnEvent)
+543
View File
@@ -0,0 +1,543 @@
------------------------------------------------------
-- MetaHunt: Beast Lore Scanner
-- TWoW 1.18.1 — "Nightmares of Ursol"
------------------------------------------------------
-- 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.
------------------------------------------------------
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
+16 -4
View File
@@ -1,5 +1,5 @@
MTH_CONST = MTH_CONST or {}
MTH_CONST.version = MTH_CONST.version or "1.2.0"
MTH_CONST.version = MTH_CONST.version or "1.4.1"
MTH_CONST.WEAPON_TYPES = {
BOWS = "Bows",
@@ -24,6 +24,7 @@ MTH_CONST.AMMO = {
ICETHREADED = "Ice Threaded Bullet",
THORIUM = "Thorium Shells",
ROCKSHARD = "Rockshard Pellets",
CRAFTEDTHORIUM = "Enchanted Thorium Shells",
CANNONBALL = "Miniature Cannon Balls",
},
ARROWS = {
@@ -32,16 +33,19 @@ MTH_CONST.AMMO = {
RAZOR = "Razor Arrow",
FEATHER = "Feathered Arrow",
PRECISION = "Precision Arrow",
BRIGHT = "Bright Wood Arrows",
JAGGED = "Jagged Arrow",
SHADE = "Shade Wood Arrows",
ICETHREADED = "Ice Threaded Arrow",
SMOOTH = "Smooth Ironfeather Arrows",
THORIUM = "Thorium Headed Arrow",
STAR = "Starfeather Arrows",
DOOMSHOT = "Doomshot",
},
JUNKSHOTS = {
SERPENT = "Serpent Sting",
SCORPID = "Scorpid Sting",
VIPER = "Viper Sting",
ARCANE = "Arcane Shot",
CONCUSSIVE = "Concussive Shot",
DISTRACTING = "Distracting Shot",
BAITED = "Baited Shot",
@@ -69,6 +73,7 @@ BULLETS_ICETHREADED = MTH_CONST.AMMO.BULLETS.ICETHREADED
BULLETS_THORIUM = MTH_CONST.AMMO.BULLETS.THORIUM
BULLETS_ROCKSHARD = MTH_CONST.AMMO.BULLETS.ROCKSHARD
BULLETS_CANNONBALL = MTH_CONST.AMMO.BULLETS.CANNONBALL
BULLETS_CRAFTEDTHORIUM = MTH_CONST.AMMO.BULLETS.CRAFTEDTHORIUM
ARROWS_ROUGH = MTH_CONST.AMMO.ARROWS.ROUGH
ARROWS_SHARP = MTH_CONST.AMMO.ARROWS.SHARP
@@ -77,13 +82,16 @@ ARROWS_FEATHER = MTH_CONST.AMMO.ARROWS.FEATHER
ARROWS_PRECISION = MTH_CONST.AMMO.ARROWS.PRECISION
ARROWS_JAGGED = MTH_CONST.AMMO.ARROWS.JAGGED
ARROWS_ICETHREADED = MTH_CONST.AMMO.ARROWS.ICETHREADED
ARROWS_BRIGHT = MTH_CONST.AMMO.ARROWS.BRIGHT
ARROWS_SHADE = MTH_CONST.AMMO.ARROWS.SHADE
ARROWS_SMOOTH = MTH_CONST.AMMO.ARROWS.SMOOTH
ARROWS_THORIUM = MTH_CONST.AMMO.ARROWS.THORIUM
ARROWS_STAR = MTH_CONST.AMMO.ARROWS.STAR
ARROWS_DOOMSHOT = MTH_CONST.AMMO.ARROWS.DOOMSHOT
JUNKSHOT_SERPENT = MTH_CONST.AMMO.JUNKSHOTS.SERPENT
JUNKSHOT_SCORPID = MTH_CONST.AMMO.JUNKSHOTS.SCORPID
JUNKSHOT_VIPER = MTH_CONST.AMMO.JUNKSHOTS.VIPER
JUNKSHOT_ARCANE = MTH_CONST.AMMO.JUNKSHOTS.ARCANE
JUNKSHOT_CONCUSSIVE = MTH_CONST.AMMO.JUNKSHOTS.CONCUSSIVE
JUNKSHOT_DISTRACTING = MTH_CONST.AMMO.JUNKSHOTS.DISTRACTING
JUNKSHOT_BAITED = MTH_CONST.AMMO.JUNKSHOTS.BAITED
@@ -105,6 +113,7 @@ MTH_AMMO_BULLETS = {
BULLETS_ICETHREADED,
BULLETS_THORIUM,
BULLETS_ROCKSHARD,
BULLETS_CRAFTEDTHORIUM,
BULLETS_CANNONBALL,
}
@@ -114,9 +123,13 @@ MTH_AMMO_ARROWS = {
ARROWS_RAZOR,
ARROWS_FEATHER,
ARROWS_PRECISION,
ARROWS_BRIGHT,
ARROWS_JAGGED,
ARROWS_SHADE,
ARROWS_ICETHREADED,
ARROWS_SMOOTH,
ARROWS_THORIUM,
ARROWS_STAR,
ARROWS_DOOMSHOT,
}
@@ -124,7 +137,6 @@ MTH_AMMO_JUNKSHOTS = {
JUNKSHOT_SERPENT,
JUNKSHOT_SCORPID,
JUNKSHOT_VIPER,
JUNKSHOT_ARCANE,
JUNKSHOT_CONCUSSIVE,
JUNKSHOT_DISTRACTING,
JUNKSHOT_BAITED,
+31 -2
View File
@@ -15,6 +15,7 @@ local MTH_FEED_TrackerFrame = nil
local MTH_FEED_ActiveCoreAttemptId = nil
local MTH_FEED_BootstrapFrame = nil
local MTH_FEED_NameToItemCache = {}
local MTH_FEED_InvalidateDietMapCache
local MTH_FEED_HARDCODED_LEVEL_RULES = {
{ minPetLevel = 1, minFoodLevel = 1 },
@@ -28,7 +29,10 @@ local MTH_FEED_HARDCODED_LEVEL_RULES = {
MTH_FEED_TRACE = false
local function MTH_FEED_Trace(message)
return
if not MTH_FEED_TRACE then return end
if type(DEFAULT_CHAT_FRAME) == "table" and DEFAULT_CHAT_FRAME.AddMessage then
DEFAULT_CHAT_FRAME:AddMessage("|cff88aaff[MTH-FeedTrack]|r " .. tostring(message))
end
end
local function MTH_FEED_Now()
@@ -636,6 +640,7 @@ end
function MTH_FEED_ReinstallCoreTracking()
local ok = MTH_FEED_InstallCoreHooks()
MTH_FEED_EnsureTrackerFrame()
return ok and true or false
end
@@ -852,7 +857,7 @@ end
local MTH_FEED_DietMapCache = nil
local function MTH_FEED_InvalidateDietMapCache()
MTH_FEED_InvalidateDietMapCache = function()
MTH_FEED_DietMapCache = nil
end
@@ -1457,6 +1462,30 @@ function MTH_FEED_GetSessionFeedCount(petId)
return tonumber(MTH_FEED_Runtime.sessionFeedsByPetId[petKey]) or 0
end
function MTH_FEED_DebugSessionFeeds()
local pet = MTH_FEED_GetCurrentPetContext()
local petId = pet and pet.petId or "(none)"
local count = MTH_FEED_GetSessionFeedCount(petId)
local hookOk = MTH_FEED_IsCoreHookInstalled()
local frameOk = MTH_FEED_TrackerFrame ~= nil
local activeId = tostring(MTH_FEED_ActiveCoreAttemptId or "nil")
if type(DEFAULT_CHAT_FRAME) == "table" and DEFAULT_CHAT_FRAME.AddMessage then
DEFAULT_CHAT_FRAME:AddMessage("|cff88aaff[MTH-FeedTrack]|r petId=" .. tostring(petId)
.. " feeds=" .. tostring(count)
.. " hookOk=" .. tostring(hookOk)
.. " frameOk=" .. tostring(frameOk)
.. " activeAttempt=" .. activeId)
local anyFeeds = false
for k, v in pairs(MTH_FEED_Runtime.sessionFeedsByPetId) do
anyFeeds = true
DEFAULT_CHAT_FRAME:AddMessage("|cff88aaff[MTH-FeedTrack]|r session petId='" .. tostring(k) .. "' count=" .. tostring(v))
end
if not anyFeeds then
DEFAULT_CHAT_FRAME:AddMessage("|cff88aaff[MTH-FeedTrack]|r (no session feeds recorded)")
end
end
end
function MTH_FEED_RecordUnknownFoodCandidate(payload)
local store = MTH_FEED_EnsureStore()
local itemId = payload and tonumber(payload.itemId) or nil
+7 -32
View File
@@ -1,5 +1,5 @@
MTH = MTH or {
version = "1.2.0",
version = "1.4.1",
name = "MetaHunt",
modules = {},
config = {},
@@ -266,15 +266,6 @@ if type(MTH_CharSavedVariables.modules) ~= "table" then
MTH_CharSavedVariables.modules = {}
end
local function MTH_ModuleStateDebug(message)
local text = "[MODULE STATE] " .. tostring(message or "")
if MTH and MTH.debug and MTH.Log then
MTH:Log(text, "debug")
elseif MTH and MTH.debug and MTH.Print then
MTH:Print(text, "debug")
end
end
local function MTH_IsPetUiFrameName(frameName)
local name = tostring(frameName or "")
if name == "" then
@@ -423,7 +414,6 @@ end
local function MTH_GetPersistedModuleEnabled(name, defaultEnabled)
if type(name) ~= "string" or name == "" then
MTH_ModuleStateDebug("read <invalid-name> fallback-default=" .. tostring(defaultEnabled and true or false))
return defaultEnabled and true or false
end
@@ -461,7 +451,6 @@ local function MTH_GetPersistedModuleEnabled(name, defaultEnabled)
local resolved = hardDefault and true or false
MTH_CharSavedVariables.moduleStates[resolvedName] = resolved
MTH_CharSavedVariables.modules[resolvedName].enabled = resolved
MTH_ModuleStateDebug("read " .. tostring(name) .. " seeded char default=" .. tostring(resolved))
return resolved
end
end
@@ -472,7 +461,6 @@ local function MTH_GetPersistedModuleEnabled(name, defaultEnabled)
local key = candidates[i]
if MTH_CharSavedVariables.moduleStates[key] ~= nil then
local resolved = MTH_CharSavedVariables.moduleStates[key] and true or false
MTH_ModuleStateDebug("read " .. tostring(name) .. " from char.moduleStates." .. tostring(key) .. "=" .. tostring(resolved))
MTH_CharSavedVariables.moduleStates[resolvedName] = resolved
return resolved
end
@@ -484,7 +472,6 @@ local function MTH_GetPersistedModuleEnabled(name, defaultEnabled)
local key = candidates[i]
if MTH_SavedVariables.moduleStates[key] ~= nil then
local resolved = MTH_SavedVariables.moduleStates[key] and true or false
MTH_ModuleStateDebug("read " .. tostring(name) .. " from moduleStates." .. tostring(key) .. "=" .. tostring(resolved))
MTH_CharSavedVariables.moduleStates[resolvedName] = resolved
if type(MTH_CharSavedVariables.modules[resolvedName]) ~= "table" then
MTH_CharSavedVariables.modules[resolvedName] = {}
@@ -500,7 +487,6 @@ local function MTH_GetPersistedModuleEnabled(name, defaultEnabled)
local key = candidates[i]
if type(MTH_SavedVariables.modules[key]) == "table" and MTH_SavedVariables.modules[key].enabled ~= nil then
local resolved = MTH_SavedVariables.modules[key].enabled and true or false
MTH_ModuleStateDebug("read " .. tostring(name) .. " from modules." .. tostring(key) .. ".enabled=" .. tostring(resolved))
MTH_CharSavedVariables.moduleStates[resolvedName] = resolved
if type(MTH_CharSavedVariables.modules[resolvedName]) ~= "table" then
MTH_CharSavedVariables.modules[resolvedName] = {}
@@ -515,7 +501,6 @@ local function MTH_GetPersistedModuleEnabled(name, defaultEnabled)
local key = candidates[i]
if type(MTH_SavedVariables[key]) == "table" and MTH_SavedVariables[key].enabled ~= nil then
local resolved = MTH_SavedVariables[key].enabled and true or false
MTH_ModuleStateDebug("read " .. tostring(name) .. " from legacy-top-level." .. tostring(key) .. ".enabled=" .. tostring(resolved))
MTH_CharSavedVariables.moduleStates[resolvedName] = resolved
if type(MTH_CharSavedVariables.modules[resolvedName]) ~= "table" then
MTH_CharSavedVariables.modules[resolvedName] = {}
@@ -525,7 +510,6 @@ local function MTH_GetPersistedModuleEnabled(name, defaultEnabled)
end
end
MTH_ModuleStateDebug("read " .. tostring(name) .. " no-persisted-value fallback-default=" .. tostring(defaultEnabled and true or false))
return defaultEnabled and true or false
end
@@ -555,7 +539,6 @@ local function MTH_SetPersistedModuleEnabled(name, enabled)
enabled = enabled and true or false
MTH_CharSavedVariables.moduleStates[name] = enabled
MTH_CharSavedVariables.modules[name].enabled = enabled
MTH_ModuleStateDebug("write " .. tostring(name) .. " enabled=" .. tostring(enabled))
end
function MTH:InitEventRouter()
@@ -669,7 +652,6 @@ function MTH:RegisterModule(name, module)
end
if self.modules[name] then
if self.debug then print("|cFFFFAA00MetaHunt|r: Module '" .. name .. "' already registered") end
return false
end
@@ -684,8 +666,6 @@ function MTH:RegisterModule(name, module)
local initialEnabled = MTH_GetPersistedModuleEnabled(name, defaultEnabled)
module.enabled = initialEnabled and true or false
MTH_ModuleStateDebug("register " .. tostring(name) .. " default=" .. tostring(defaultEnabled) .. " initial=" .. tostring(module.enabled))
self.modules[name] = module
self:RegisterModuleEvents(name, module.events)
@@ -709,7 +689,6 @@ function MTH:RegisterModule(name, module)
self:_SetModuleEventSubscriptions(name, true)
end
if self.debug then print("|cFF00AA00MetaHunt|r: Module '" .. name .. "' registered") end
return true
end
@@ -774,7 +753,6 @@ function MTH:SetModuleEnabled(name, enabled)
if self.SetConfig then
self:SetConfig(name, "enabled", enabled)
end
MTH_ModuleStateDebug("request " .. tostring(name) .. " no-op runtime=" .. tostring(module.enabled) .. " persisted-synced")
return true
end
@@ -796,7 +774,6 @@ function MTH:SetModuleEnabled(name, enabled)
if self.SetConfig then
self:SetConfig(name, "enabled", enabled)
end
MTH_ModuleStateDebug("request " .. tostring(name) .. " applied runtime=" .. tostring(module.enabled))
return true
end
@@ -1035,6 +1012,12 @@ function MTH:Log(msg, severity)
return chatText
end
function MTH:DebugPrint(msg)
if self.debug then
self:Print("[DEBUG] " .. tostring(msg), "debug")
end
end
function MTH:Print(msg, severity)
return self:Log(msg, severity)
end
@@ -1083,12 +1066,6 @@ function MTH:SetMessageEnabled(key, enabled)
return true
end
function MTH:DebugPrint(msg)
if self.debug then
self:Print("[DEBUG] " .. tostring(msg), "debug")
end
end
function MTH_Log(message, severity)
if MTH and MTH.Log then
return MTH:Log(message, severity)
@@ -1270,12 +1247,10 @@ function MTH:ApplyPersistedModuleStates(source)
end
local persistedEnabled = MTH_GetPersistedModuleEnabled(name, defaultEnabled)
MTH_ModuleStateDebug("bootstrap " .. tostring(source or "") .. " " .. tostring(name) .. " persisted=" .. tostring(persistedEnabled) .. " runtime=" .. tostring(module.enabled and true or false))
if module.enabled ~= persistedEnabled then
local ok, err = self:SetModuleEnabled(name, persistedEnabled)
if not ok then
MTH_ModuleStateDebug("bootstrap apply failed " .. tostring(name) .. ": " .. tostring(err))
end
else
MTH_SetPersistedModuleEnabled(name, persistedEnabled)
-2
View File
@@ -623,13 +623,11 @@ function MTH_PT_ApplyScan(triggerReason)
local msg = "New pet data recorded (" .. tostring(added) .. " new rank(s)): " .. detailsText .. ". Known totals for this character: " .. tostring(totalBaselines) .. " ability type(s), " .. tostring(totalRanks) .. " rank(s)."
if MTH_PT_IsScanMessageEnabled() then
MTH:Print(msg)
MTH:Print("INFO: " .. msg, "debug")
end
else
local msg = "New pet data recorded (" .. tostring(added) .. " new rank(s)). Known totals for this character: " .. tostring(totalBaselines) .. " ability type(s), " .. tostring(totalRanks) .. " rank(s)."
if MTH_PT_IsScanMessageEnabled() then
MTH:Print(msg)
MTH:Print("INFO: " .. msg, "debug")
end
end
end
+172
View File
@@ -0,0 +1,172 @@
------------------------------------------------------
-- MetaHunt: Hunter Talent & Spellbook Scanner
-- Runs once on login and re-runs when spells/talents change.
--
-- Exposes:
-- MTH_HT_GetTalentRank(talentName) → currentRank (0 if unlearned)
-- MTH_HT_HasTalent(talentName) → true if rank >= 1
-- MTH_HT_GetSpellInfo(spellName) → { slot, rank, rankNum } or nil
-- MTH_HT_RescanTalents() → force re-scan of all talent tabs
-- MTH_HT_RescanSpellbook() → force re-scan of spellbook
--
-- Results cached in:
-- MTH_HT_Talents["TalentName"] = { tab, idx, rank, maxRank }
-- MTH_HT_Spells["SpellName"] = { slot, rank, rankNum }
-- (spell entry is the *highest rank* known for that name)
------------------------------------------------------
MTH_HT_Talents = {}
MTH_HT_Spells = {}
-- ---------------------------------------------------------------------------
-- Talent scanner
-- Uses GetNumTalentTabs / GetNumTalents / GetTalentInfo (all vanilla 1.12)
-- ---------------------------------------------------------------------------
function MTH_HT_RescanTalents()
MTH_HT_Talents = {}
local numTabs = GetNumTalentTabs and GetNumTalentTabs() or 0
for tabIdx = 1, numTabs do
local numTalents = GetNumTalents and GetNumTalents(tabIdx) or 0
for talIdx = 1, numTalents do
local name, _, _, _, currentRank, maxRank = GetTalentInfo(tabIdx, talIdx)
if name and name ~= "" then
local existing = MTH_HT_Talents[name]
-- If the same name appears in multiple tabs (unlikely), keep highest rank.
if not existing or (tonumber(currentRank) or 0) > (existing.rank or 0) then
MTH_HT_Talents[name] = {
tab = tabIdx,
idx = talIdx,
rank = tonumber(currentRank) or 0,
maxRank = tonumber(maxRank) or 0,
}
end
end
end
end
end
-- ---------------------------------------------------------------------------
-- Spellbook scanner
-- Iterates all player spell tabs and caches every known spell by name.
-- Stores the highest rank found (so MTH_HT_Spells["Aimed Shot"].slot is
-- always the actionable rank the player actually has).
-- ---------------------------------------------------------------------------
local function MTH_HT_ParseRankNum(rankStr)
if not rankStr or rankStr == "" then return 0 end
local _, _, n = string.find(tostring(rankStr), "(%d+)")
return tonumber(n) or 0
end
function MTH_HT_RescanSpellbook()
MTH_HT_Spells = {}
local numTabs = GetNumSpellTabs and GetNumSpellTabs() or 0
for tabIdx = 1, numTabs do
local _, _, offset, numSpells = GetSpellTabInfo(tabIdx)
for i = 1, numSpells do
local slot = offset + i
local name, rank = GetSpellName(slot, BOOKTYPE_SPELL)
if name and name ~= "" then
local rankNum = MTH_HT_ParseRankNum(rank)
local existing = MTH_HT_Spells[name]
if not existing or rankNum > (existing.rankNum or 0) then
MTH_HT_Spells[name] = {
slot = slot,
rank = rank or "",
rankNum = rankNum,
}
end
end
end
end
end
-- ---------------------------------------------------------------------------
-- Public accessors
-- ---------------------------------------------------------------------------
function MTH_HT_GetTalentRank(talentName)
local entry = MTH_HT_Talents[tostring(talentName or "")]
return entry and (entry.rank or 0) or 0
end
function MTH_HT_HasTalent(talentName)
return MTH_HT_GetTalentRank(talentName) >= 1
end
function MTH_HT_GetSpellInfo(spellName)
return MTH_HT_Spells[tostring(spellName or "")]
end
-- ---------------------------------------------------------------------------
-- Event-driven scanning
-- ---------------------------------------------------------------------------
local MTH_HT_Frame = CreateFrame("Frame", "MTH_HunterTalentScanFrame")
local MTH_HT_NeedTalentRescan = false
local MTH_HT_NeedSpellRescan = false
local MTH_HT_RescanDelay = 0.6 -- seconds after event before scanning
local MTH_HT_RescanElapsed = 0
MTH_HT_Frame:RegisterEvent("PLAYER_ENTERING_WORLD")
MTH_HT_Frame:RegisterEvent("LEARNED_SPELL_IN_TAB")
MTH_HT_Frame:RegisterEvent("SPELLS_CHANGED")
MTH_HT_Frame:RegisterEvent("CHARACTER_POINTS_CHANGED")
MTH_HT_Frame:SetScript("OnEvent", function()
if event == "PLAYER_ENTERING_WORLD" then
-- Immediate scan on login
MTH_HT_RescanTalents()
MTH_HT_RescanSpellbook()
if type(MTH_HT_OnScanComplete) == "function" then
MTH_HT_OnScanComplete()
end
-- Also schedule a deferred re-scan: GetTalentInfo can return stale
-- data for a brief window after PLAYER_ENTERING_WORLD on login.
MTH_HT_NeedTalentRescan = true
MTH_HT_NeedSpellRescan = true
MTH_HT_RescanElapsed = 0
this:Show() -- enable OnUpdate for the deferred pass
return
end
-- For other events, schedule a deferred rescan so we don't fire
-- multiple times if several events arrive in the same frame.
if event == "CHARACTER_POINTS_CHANGED" then
MTH_HT_NeedTalentRescan = true
end
if event == "LEARNED_SPELL_IN_TAB" or event == "SPELLS_CHANGED" then
MTH_HT_NeedSpellRescan = true
end
MTH_HT_RescanElapsed = 0
this:Show()
end)
MTH_HT_Frame:SetScript("OnUpdate", function()
MTH_HT_RescanElapsed = (MTH_HT_RescanElapsed or 0) + (arg1 or 0)
if MTH_HT_RescanElapsed < MTH_HT_RescanDelay then return end
-- Batch-flush pending scans
if MTH_HT_NeedTalentRescan then
MTH_HT_RescanTalents()
MTH_HT_NeedTalentRescan = false
end
if MTH_HT_NeedSpellRescan then
MTH_HT_RescanSpellbook()
MTH_HT_NeedSpellRescan = false
end
-- Notify dependents
if type(MTH_HT_OnScanComplete) == "function" then
MTH_HT_OnScanComplete()
end
this:Hide()
MTH_HT_RescanElapsed = 0
end)
MTH_HT_Frame:Hide()
+19 -11
View File
@@ -429,9 +429,7 @@ local function MTH_PETS_LogConsistency(line)
return
end
local function MTH_PETS_LogTame(line)
return
end
local function MTH_PETS_LogTame(_) end
local function MTH_PETS_FormatRowConsistency(row)
if type(row) ~= "table" then
@@ -856,11 +854,21 @@ local function MTH_PETS_ParseCreatureIdFromGuid(guid)
return nil
end
-- TBC-style string GUID: "Creature-realm-map-inst-?-ENTRY-spawn"
local _, _, creatureIdText = string.find(guidText, "^Creature%-%d+%-%d+%-%d+%-%d+%-(%d+)%-%d+$")
if creatureIdText then
return tonumber(creatureIdText)
end
-- Vanilla/TurtleWoW hex GUID: "0xF130EEEEEECCCCCC" — entry is bits 24-47 (hex chars 7-12)
if string.sub(guidText, 1, 2) == "0x" and string.len(guidText) >= 14 then
local entryHex = string.sub(guidText, 7, 12)
local parsed = tonumber(entryHex, 16)
if parsed and parsed > 0 then
return parsed
end
end
return nil
end
@@ -2553,7 +2561,6 @@ function MTH_PETS_RunSavedVarCleanup(pets)
pets._betaFreshStoreApplied = nil
if purged > 0 and MTH and MTH.Print then
MTH:Print("Cleaned up " .. tostring(purged) .. " stale pet-update records from SavedVariables.", "debug")
end
end
@@ -3400,7 +3407,6 @@ end
local function MTH_ST_DebugDumpLine(line)
if MTH and MTH.Print then
MTH:Print(tostring(line or ""), "debug")
end
end
@@ -3543,7 +3549,6 @@ function MTH_CommandPetsState()
.. " petTrainingLastScan=" .. tostring(petTraining and petTraining.lastScan or 0)
.. " hasCompletedPetScan=" .. tostring(petTraining and petTraining.hasCompletedPetScan or nil)
MTH:Print(summary)
MTH:Print("[PETSSTATE] " .. summary, "debug")
end
function MTH_CommandPetsReset()
@@ -3628,7 +3633,6 @@ function MTH_CommandPetsReset()
.. " preservedHistory=" .. tostring(historyCount)
.. " nextId=" .. tostring(store.nextId)
MTH:Print(line)
MTH:Print("[PETSRESET] " .. line, "debug")
return true
end
@@ -3784,9 +3788,8 @@ function MTH_ST_HandleSpellcastEvent(evt, eventArg1, eventArg2)
if evt == "SPELLCAST_START" or evt == "SPELLCAST_CHANNEL_START"
or evt == "UNIT_SPELLCAST_START" or evt == "UNIT_SPELLCAST_CHANNEL_START" then
if not MTH_PETS_IsTameBeastSpellName(castSpellName) then
return true
end
-- For vanilla SPELLCAST_* events arg1 is a duration (ms), not spell name.
-- Always forward to RecordTameAttempt — it has all the fallback/heuristic logic.
MTH_PETS_RecordTameAttempt(evt, castSpellName)
return true
end
@@ -4156,6 +4159,12 @@ function MTH_ST_InitService()
MTH_PetLifecycleEventFrame:RegisterEvent("UNIT_PET")
MTH_PetLifecycleEventFrame:RegisterEvent("PET_BAR_UPDATE")
MTH_PetLifecycleEventFrame:RegisterEvent("UNIT_HAPPINESS")
MTH_PetLifecycleEventFrame:RegisterEvent("SPELLCAST_CHANNEL_START")
MTH_PetLifecycleEventFrame:RegisterEvent("SPELLCAST_CHANNEL_STOP")
MTH_PetLifecycleEventFrame:RegisterEvent("SPELLCAST_START")
MTH_PetLifecycleEventFrame:RegisterEvent("SPELLCAST_STOP")
MTH_PetLifecycleEventFrame:RegisterEvent("SPELLCAST_FAILED")
MTH_PetLifecycleEventFrame:RegisterEvent("SPELLCAST_INTERRUPTED")
MTH_PetLifecycleEventFrame:RegisterEvent("CHAT_MSG_COMBAT_XP_GAIN")
MTH_PetLifecycleEventFrame:RegisterEvent("PLAYER_XP_UPDATE")
MTH_PetLifecycleEventFrame:RegisterEvent("CHAT_MSG_SYSTEM")
@@ -4172,7 +4181,6 @@ function MTH_ST_InitService()
MTH_PETS_InstallCoreRenameHook()
MTH_PETS_EmitLiveState("service:init", true)
if MTH_PETS_TRACE_CONSISTENCY and MTH and MTH.Print then
MTH:Print("[PETCONSIST] trace enabled", "debug")
end
end
+114 -50
View File
@@ -301,46 +301,6 @@ if type(MTH_CommandPetSpellScan) ~= "function" then
end
end
------------------------------------------------------
-- /mth diag — Runtime Diagnostics
------------------------------------------------------
function MTH_CommandDiag()
MTH:Print("======= MetaHunt Diagnostics =======", "debug")
-- 1. Lua version
local luaVer = _VERSION or "unknown"
MTH:Print("[DIAG] _VERSION = " .. tostring(luaVer), "debug")
MTH:Print("[DIAG] string.match = " .. tostring(type(string.match) == "function"), "debug")
MTH:Print("[DIAG] string.gfind = " .. tostring(type(string.gfind) == "function"), "debug")
-- 2. Active modules
local moduleCount = 0
local enabledModules = {}
if MTH.modules then
for name, mod in pairs(MTH.modules) do
moduleCount = moduleCount + 1
if mod.enabled then
table.insert(enabledModules, name)
end
end
end
MTH:Print("[DIAG] Registered modules: " .. tostring(moduleCount), "debug")
MTH:Print("[DIAG] Enabled: " .. table.concat(enabledModules, ", "), "debug")
-- 3. Event router state
local routerFrame = (MTH._eventRouter and MTH._eventRouter.frame) or nil
local routerEvents = (MTH._eventRouter and MTH._eventRouter.eventRefs) or {}
local eventCount = 0
for _ in pairs(routerEvents) do eventCount = eventCount + 1 end
MTH:Print("[DIAG] EventRouter frame: " .. tostring(routerFrame ~= nil) .. ", events: " .. tostring(eventCount), "debug")
-- 4. Debug frame initialized?
local dfInit = (MTH_DebugFrame and MTH_DebugFrame.initialized) and true or false
MTH:Print("[DIAG] DebugFrame initialized: " .. tostring(dfInit), "debug")
MTH:Print("======= End Diagnostics =======", "debug")
end
function SlashCmdList.MTH(msg, editbox)
if MTH and MTH.ApplyClassGate and MTH:ApplyClassGate("slash") then
return
@@ -352,24 +312,128 @@ 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 diag, /mth err")
elseif lowerMsg == "options" then
MTH_CommandOptions()
elseif lowerMsg == "book" or lowerMsg == "hunterbook" then
MTH_CommandBook()
elseif lowerMsg == "peers" or lowerMsg == "who" then
MTH_CommandPeers()
elseif lowerMsg == "diag" then
MTH_CommandDiag()
MTH:Print("Available: /mth options, /mth book")
elseif lowerMsg == "err" or lowerMsg == "errors" or lowerMsg == "debug" then
if MTH_DebugFrame and MTH_DebugFrame.Toggle then
MTH_DebugFrame:Toggle()
else
MTH:Print("Debug frame is not available.")
end
elseif lowerMsg == "options" then
MTH_CommandOptions()
elseif lowerMsg == "book" or lowerMsg == "hunterbook" then
MTH_CommandBook()
elseif lowerMsg == "peers" or lowerMsg == "who" then
MTH_CommandPeers()
elseif lowerMsg == "zoneinfo" then
-- Dumps all available zone data for the current zone, for adding to ds-zones / ds-minimap-sizes
pcall(SetMapToCurrentZone)
local zoneName = (type(GetRealZoneText) == "function" and GetRealZoneText()) or ""
local subZone = (type(GetSubZoneText) == "function" and GetSubZoneText()) or ""
local zoneText = (type(GetZoneText) == "function" and GetZoneText()) or ""
local areaId = nil
if type(GetCurrentMapAreaID) == "function" then
local ok, v = pcall(GetCurrentMapAreaID)
if ok then areaId = v end
end
local cx, cy = nil, nil
if type(GetPlayerMapPosition) == "function" then
local ok, x, y = pcall(GetPlayerMapPosition, "player")
if ok then cx, cy = x, y end
end
local contId = nil
if type(GetCurrentMapContinent) == "function" then
local ok, v = pcall(GetCurrentMapContinent)
if ok then contId = v end
end
MTH:Print("|cffffff00=== Zone Info ===|r")
MTH:Print("GetRealZoneText: |cffffaa00" .. tostring(zoneName) .. "|r")
MTH:Print("GetZoneText: |cffffaa00" .. tostring(zoneText) .. "|r")
MTH:Print("GetSubZoneText: |cffffaa00" .. tostring(subZone) .. "|r")
MTH:Print("AreaID (GetCurrentMapAreaID): |cff88ffff" .. tostring(areaId) .. "|r")
MTH:Print("ContinentID: |cff88ffff" .. tostring(contId) .. "|r")
if cx and cy then
MTH:Print(string.format("Player map pos: |cff88ffff%.4f, %.4f|r (display: %.1f, %.1f)", cx, cy, cx*100, cy*100))
end
-- Also check if zone is in our tables
local inZones = MTH_DS_Zones and MTH_DS_Zones[tonumber(areaId)] and "YES" or "not in MTH_DS_Zones"
local inSizes = MTH_DS_MinimapSizes and MTH_DS_MinimapSizes[tonumber(areaId)] and "YES" or "not in ds-minimap-sizes"
MTH:Print("MTH_DS_Zones entry: " .. inZones)
MTH:Print("MTH_DS_MinimapSizes entry: " .. inSizes)
elseif lowerMsg == "npcid" then
-- Print the creature entry ID of the current target, or arm a listener for next NPC interaction
local function MTH_ParseCreatureEntryFromAnyGuid(guid)
if type(guid) ~= "string" or guid == "" then return nil end
-- Format 1: TBC-style "Creature-realm-map-inst-?-ENTRY-spawn"
local _, _, id1 = string.find(guid, "^Creature%-%d+%-%d+%-%d+%-%d+%-(%d+)%-%d+$")
if id1 then return tonumber(id1) end
-- Format 2: vanilla hex "0xF130EEEEEECCCCCC" — entry is bits 24-47 = hex chars 7-12
if string.sub(guid, 1, 2) == "0x" and string.len(guid) >= 14 then
local entryHex = string.sub(guid, 7, 12) -- 24 bits = up to 16M
local parsed = tonumber(entryHex, 16)
if parsed and parsed > 0 then return parsed end
end
return nil
end
local function MTH_PrintNpcId(unitToken)
unitToken = unitToken or "target"
local name = (type(UnitName) == "function") and UnitName(unitToken) or "?"
local level = (type(UnitLevel) == "function") and UnitLevel(unitToken) or "?"
-- Try UnitGUID first, then GetUnitGUID (TurtleWoW nameplate API)
local guid = nil
if type(UnitGUID) == "function" then
local ok, g = pcall(UnitGUID, unitToken)
if ok and g and g ~= "" then guid = g end
end
if (not guid or guid == "") and type(GetUnitGUID) == "function" then
local ok, g = pcall(GetUnitGUID, unitToken)
if ok and g and g ~= "" then guid = g end
end
local creatureId = MTH_ParseCreatureEntryFromAnyGuid(guid)
if creatureId then
MTH:Print(string.format("|cffffff00NPC:|r %s |cffffff00ID:|r |cff88ffff%s|r lvl: %s", tostring(name), tostring(creatureId), tostring(level)))
else
MTH:Print(string.format("|cffffff00NPC:|r %s lvl: %s |cffff8800GUID: %s|r", tostring(name), tostring(level), tostring(guid or "nil")))
MTH:Print("|cffaaaaaa(Could not parse creature ID from GUID)|r")
end
end
-- Try current target first
local hasTarget = (type(UnitExists) == "function") and UnitExists("target")
if hasTarget then
MTH_PrintNpcId("target")
end
-- Arm a one-shot frame for MERCHANT_SHOW / TRAINER_LIST_UPDATE / GOSSIP_SHOW
if not _G["MTH_NpcIdListenerFrame"] then
local f = CreateFrame("Frame", "MTH_NpcIdListenerFrame")
f:RegisterEvent("MERCHANT_SHOW")
f:RegisterEvent("TRAINER_LIST_UPDATE")
f:RegisterEvent("GOSSIP_SHOW")
f:SetScript("OnEvent", function()
MTH_PrintNpcId("target")
this:UnregisterEvent("MERCHANT_SHOW")
this:UnregisterEvent("TRAINER_LIST_UPDATE")
this:UnregisterEvent("GOSSIP_SHOW")
_G["MTH_NpcIdListenerFrame"] = nil
end)
if not hasTarget then
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 = {} }
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")
end
else
MTH:Print("Unknown command: " .. tostring(msg))
MTH:Print("Available: /mth options, /mth book, /mth diag, /mth err")
MTH:Print("Available: /mth options, /mth book, /mth npcid, /mth bls reset")
end
end
+1 -4
View File
@@ -373,9 +373,6 @@ eventFrame:SetScript("OnEvent", function()
end
MTH_DF_InstallHooks()
MTH_DebugFrame:Initialize()
if MTH and MTH.Print then
local mode = MTH_DF_IsGlobalCaptureEnabled() and "legacy-global" or "safe-local"
MTH:Print("=== MetaHunt Debug Frame Initialized (" .. mode .. ") ===", "debug")
end
end
end)
+404
View File
@@ -0,0 +1,404 @@
-- hunterbook-model-viewer.lua
-- Embeds a small 3D creature skin preview at the bottom of the Beast Lore /
-- Abilities inspector. Clicking it opens a larger draggable popup with
-- < / > navigation through all skin variants of the selected family.
--
-- Uses MTH_DS_BeastSkins[familyName] generated by .tools/generators/gen_beast_skins.py
-- Skin rendering: PlayerModel:SetUnit(CreatureDisplayInfo.ID)
local MTH_MV = {
inited = false,
family = nil,
skins = {},
index = 1,
facing = 0.6,
rotating = false,
rotateLastX = 0,
keepPopup = false,
skinBeastRows = {},
skinListEmpty = nil,
-- small preview (parented to book frame, anchored below MTH_BOOK_DetailBackdrop)
previewFrame = nil,
previewModel = nil,
-- popup
popup = nil,
popupModel = nil,
popupTitle = nil,
popupSkinLabel = nil,
}
-- ---------------------------------------------------------------------------
-- Internal
-- ---------------------------------------------------------------------------
local function MTH_MV_ApplyModel(model, skinId)
if not model or not skinId then
return
end
model:SetUnit(skinId)
model:SetPosition(0, 0, 0)
model:SetModelScale(1)
local useFacing = (MTH_MV.popupModel ~= nil and model == MTH_MV.popupModel)
model:SetFacing(useFacing and MTH_MV.facing or 0.6)
end
local MTH_MV_BuildSkinBeastList -- forward declaration
local function MTH_MV_DisplayCurrent()
local skin = MTH_MV.skins[MTH_MV.index]
if not skin then return end
local n = table.getn(MTH_MV.skins)
MTH_MV_ApplyModel(MTH_MV.previewModel, skin.id)
MTH_MV_ApplyModel(MTH_MV.popupModel, skin.id)
local indexStr = tostring(MTH_MV.index) .. " / " .. tostring(n)
if MTH_MV.popupSkinLabel then
MTH_MV.popupSkinLabel:SetText(indexStr .. " · " .. tostring(skin.tex or ""))
end
MTH_MV_BuildSkinBeastList(skin.id)
end
MTH_MV_BuildSkinBeastList = function(skinId)
for i = 1, table.getn(MTH_MV.skinBeastRows) do
MTH_MV.skinBeastRows[i]:Hide()
end
if MTH_MV.skinListEmpty then MTH_MV.skinListEmpty:Hide() end
if not skinId or not MTH_DS_Beasts then return end
local matches = {}
for npcId, beast in pairs(MTH_DS_Beasts) do
if type(beast) == "table" and beast.skinId == skinId then
local nm = (MTH and MTH.GetLocalizedBeastName and MTH:GetLocalizedBeastName(npcId, beast.name)) or beast.name
table.insert(matches, { id = npcId, name = nm or "?", lvl = tostring(beast.lvl or "?") })
end
end
table.sort(matches, function(a, b) return (a.name or "") < (b.name or "") end)
local maxRows = table.getn(MTH_MV.skinBeastRows)
local shown = 0
for i = 1, table.getn(matches) do
if shown >= maxRows then break end
shown = shown + 1
local row = MTH_MV.skinBeastRows[shown]
row._npcId = matches[i].id
row.nameText:SetText(matches[i].name)
row.lvlText:SetText(matches[i].lvl)
row:Show()
end
if shown == 0 and MTH_MV.skinListEmpty then
MTH_MV.skinListEmpty:Show()
end
end
local function MTH_MV_Init()
if MTH_MV.inited then return end
local backdrop = getglobal("MTH_BOOK_DetailBackdrop")
if not backdrop then
return
end
MTH_MV.inited = true
-- ----------------------------------------------------------------
-- Model preview container (sibling of inspector, full column width)
-- Hardcoded offset from book frame TOPLEFT: x=608 (right column),
-- y = -(166 inspector_top + 178 inspector_h + 6 gap) = -350
-- ----------------------------------------------------------------
local bookFrame = backdrop:GetParent()
MTH_MV.previewFrame = CreateFrame("Frame", "MTH_BOOK_SkinPreview", bookFrame)
MTH_MV.previewFrame:SetWidth(136)
MTH_MV.previewFrame:SetHeight(136)
MTH_MV.previewFrame:SetPoint("TOPLEFT", bookFrame, "TOPLEFT", 608, -384)
MTH_MV.previewFrame:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 12,
insets = {left = 3, right = 3, top = 3, bottom = 3},
})
MTH_MV.previewFrame:SetBackdropColor(0, 0, 0, 0.7)
MTH_MV.previewFrame:SetBackdropBorderColor(0.35, 0.35, 0.35, 0.8)
MTH_MV.previewFrame:Hide()
-- PlayerModel fills the inner area
MTH_MV.previewModel = CreateFrame("PlayerModel", nil, MTH_MV.previewFrame)
MTH_MV.previewModel:SetPoint("TOPLEFT", MTH_MV.previewFrame, "TOPLEFT", 3, -3)
MTH_MV.previewModel:SetPoint("BOTTOMRIGHT", MTH_MV.previewFrame, "BOTTOMRIGHT", -3, 3)
-- Invisible click overlay → opens popup
local previewBtn = CreateFrame("Button", nil, MTH_MV.previewFrame)
previewBtn:SetAllPoints(MTH_MV.previewFrame)
previewBtn:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square")
previewBtn:SetScript("OnClick", function()
MTH_BOOK_ModelViewer_OpenPopup()
end)
previewBtn:SetScript("OnEnter", function()
GameTooltip:SetOwner(MTH_MV.previewFrame, "ANCHOR_LEFT")
GameTooltip:AddLine("Click to open skin viewer", 1, 1, 1)
GameTooltip:Show()
end)
previewBtn:SetScript("OnLeave", function()
GameTooltip:Hide()
end)
-- ----------------------------------------------------------------
-- Popup frame (wider: left side = model viewer, right = beast list)
-- ----------------------------------------------------------------
local popup = CreateFrame("Frame", "MTH_BOOK_ModelPopup", UIParent)
popup:SetWidth(490)
popup:SetHeight(320)
popup:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
popup:SetFrameStrata("TOOLTIP")
popup:SetToplevel(true)
popup:SetMovable(true)
popup:EnableMouse(true)
popup:RegisterForDrag("LeftButton")
popup:SetScript("OnDragStart", function() this:StartMoving() end)
popup:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
popup:SetScript("OnHide", function() MTH_MV.rotating = false end)
popup:Hide()
popup:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
tile = true, tileSize = 32, edgeSize = 32,
insets = {left = 11, right = 12, top = 12, bottom = 11},
})
popup:SetBackdropColor(0.05, 0.05, 0.08, 0.95)
-- Large model background (left side, x=20)
local modelBg = CreateFrame("Frame", nil, popup)
modelBg:SetWidth(224)
modelBg:SetHeight(226)
modelBg:SetPoint("TOPLEFT", popup, "TOPLEFT", 20, -42)
modelBg:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 12,
insets = {left = 3, right = 3, top = 3, bottom = 3},
})
modelBg:SetBackdropColor(0, 0, 0, 0.85)
modelBg:SetBackdropBorderColor(0.35, 0.35, 0.35, 0.9)
MTH_MV.popupModel = CreateFrame("PlayerModel", "MTH_BOOK_ModelPopupModel", modelBg)
MTH_MV.popupModel:SetPoint("TOPLEFT", modelBg, "TOPLEFT", 3, -3)
MTH_MV.popupModel:SetPoint("BOTTOMRIGHT", modelBg, "BOTTOMRIGHT", -3, 3)
-- Mouse rotation: drag on the black model area rotates; outside drags the popup
MTH_MV.popupModel:EnableMouse(true)
MTH_MV.popupModel:SetScript("OnMouseDown", function()
MTH_MV.rotating = true
MTH_MV.rotateLastX = GetCursorPosition()
end)
MTH_MV.popupModel:SetScript("OnMouseUp", function()
MTH_MV.rotating = false
end)
MTH_MV.popupModel:SetScript("OnUpdate", function()
if not MTH_MV.rotating then return end
local cx = GetCursorPosition()
local dx = cx - MTH_MV.rotateLastX
MTH_MV.rotateLastX = cx
if dx ~= 0 then
MTH_MV.facing = MTH_MV.facing - dx * 0.03
MTH_MV.popupModel:SetFacing(MTH_MV.facing)
end
end)
-- Title (family name) anchored above the model
MTH_MV.popupTitle = popup:CreateFontString(nil, "OVERLAY", "GameFontNormal")
MTH_MV.popupTitle:SetPoint("TOP", modelBg, "TOP", 0, 18)
MTH_MV.popupTitle:SetTextColor(1, 0.82, 0)
MTH_MV.popupTitle:SetText("Skin Viewer")
-- Close [X] button
local closeBtn = CreateFrame("Button", nil, popup)
closeBtn:SetWidth(22)
closeBtn:SetHeight(22)
closeBtn:SetPoint("TOPRIGHT", popup, "TOPRIGHT", -10, -10)
closeBtn:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square")
local closeLbl = closeBtn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
closeLbl:SetAllPoints(closeBtn)
closeLbl:SetText("X")
closeLbl:SetTextColor(0.9, 0.9, 0.9)
closeBtn:SetScript("OnClick", function() this:GetParent():Hide() end)
-- "Unique Appearance" badge removed (inline texture syntax unsupported in this client)
-- Nav: ◀ label ▶ (kept within the left 264px area)
local prevBtn = CreateFrame("Button", nil, popup, "UIPanelButtonTemplate")
prevBtn:SetWidth(28)
prevBtn:SetHeight(20)
prevBtn:SetPoint("BOTTOMLEFT", popup, "BOTTOMLEFT", 14, 14)
prevBtn:SetText("<")
prevBtn:SetScript("OnClick", function()
local n = table.getn(MTH_MV.skins)
if n < 1 then return end
MTH_MV.index = MTH_MV.index - 1
if MTH_MV.index < 1 then MTH_MV.index = n end
MTH_MV_DisplayCurrent()
end)
local nextBtn = CreateFrame("Button", nil, popup, "UIPanelButtonTemplate")
nextBtn:SetWidth(28)
nextBtn:SetHeight(20)
nextBtn:SetPoint("BOTTOMLEFT", popup, "BOTTOMLEFT", 222, 14)
nextBtn:SetText(">")
nextBtn:SetScript("OnClick", function()
local n = table.getn(MTH_MV.skins)
if n < 1 then return end
MTH_MV.index = MTH_MV.index + 1
if MTH_MV.index > n then MTH_MV.index = 1 end
MTH_MV_DisplayCurrent()
end)
MTH_MV.popupSkinLabel = popup:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
MTH_MV.popupSkinLabel:SetPoint("BOTTOMLEFT", prevBtn, "BOTTOMRIGHT", 4, 0)
MTH_MV.popupSkinLabel:SetPoint("BOTTOMRIGHT", nextBtn, "BOTTOMLEFT", -4, 0)
MTH_MV.popupSkinLabel:SetJustifyH("CENTER")
MTH_MV.popupSkinLabel:SetTextColor(0.9, 0.9, 0.9)
-- ---------------------------------------------------------------
-- Right panel: vertical divider + beast list
-- ---------------------------------------------------------------
local divider = popup:CreateTexture(nil, "ARTWORK")
divider:SetTexture("Interface\\Buttons\\WHITE8X8")
divider:SetVertexColor(0.35, 0.35, 0.35, 0.8)
divider:SetPoint("TOPLEFT", popup, "TOPLEFT", 270, -36)
divider:SetPoint("BOTTOMLEFT", popup, "BOTTOMLEFT", 270, 12)
divider:SetWidth(1)
local listHeader = popup:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
listHeader:SetPoint("TOPLEFT", popup, "TOPLEFT", 278, -18)
listHeader:SetTextColor(1, 0.82, 0)
listHeader:SetText("Beasts with this skin")
local listSep = popup:CreateTexture(nil, "ARTWORK")
listSep:SetTexture("Interface\\Buttons\\WHITE8X8")
listSep:SetVertexColor(0.35, 0.35, 0.35, 0.6)
listSep:SetPoint("TOPLEFT", popup, "TOPLEFT", 278, -30)
listSep:SetPoint("TOPRIGHT", popup, "TOPRIGHT", -10, -30)
listSep:SetHeight(1)
MTH_MV.skinListEmpty = popup:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
MTH_MV.skinListEmpty:SetPoint("TOPLEFT", popup, "TOPLEFT", 278, -38)
MTH_MV.skinListEmpty:SetTextColor(0.5, 0.5, 0.5)
MTH_MV.skinListEmpty:SetText("\226\128\148")
MTH_MV.skinListEmpty:Hide()
-- Beast rows (14 max)
local ROW_X = 278
local ROW_START = -36
local ROW_H = 17
local MAX_ROWS = 14
for i = 1, MAX_ROWS do
local row = CreateFrame("Button", nil, popup)
row:SetPoint("TOPLEFT", popup, "TOPLEFT", ROW_X, ROW_START - (i - 1) * ROW_H)
row:SetWidth(200)
row:SetHeight(ROW_H - 1)
row._npcId = nil
local hl = row:CreateTexture(nil, "HIGHLIGHT")
hl:SetAllPoints(row)
hl:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
hl:SetBlendMode("ADD")
hl:SetAlpha(0.35)
row.nameText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
row.nameText:SetPoint("LEFT", row, "LEFT", 0, 0)
row.nameText:SetWidth(158)
row.nameText:SetJustifyH("LEFT")
row.lvlText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
row.lvlText:SetPoint("LEFT", row, "LEFT", 160, 0)
row.lvlText:SetWidth(38)
row.lvlText:SetJustifyH("LEFT")
row.lvlText:SetTextColor(0.7, 0.7, 0.7)
row:SetScript("OnClick", function()
if this._npcId and type(MTH_BOOK_JumpToBeastById) == "function" then
MTH_MV.keepPopup = true
MTH_BOOK_JumpToBeastById(this._npcId)
MTH_MV.keepPopup = false
end
end)
row:Hide()
MTH_MV.skinBeastRows[i] = row
end
MTH_MV.popup = popup
end
-- ---------------------------------------------------------------------------
-- Public API
-- ---------------------------------------------------------------------------
function MTH_BOOK_ModelViewer_SetFamily(family, skinId)
MTH_MV_Init()
if not MTH_MV.inited then return end
local skins = MTH_DS_BeastSkins and type(MTH_DS_BeastSkins) == "table" and MTH_DS_BeastSkins[family]
if not skins or table.getn(skins) == 0 then
MTH_BOOK_ModelViewer_Clear()
return
end
-- Filter to only skins used by at least one tameable beast
local usedSkinIds = {}
if MTH_DS_Beasts then
for _, beast in pairs(MTH_DS_Beasts) do
if type(beast) == "table" and beast.skinId then
usedSkinIds[beast.skinId] = true
end
end
end
local filteredSkins = {}
for i = 1, table.getn(skins) do
if usedSkinIds[skins[i].id] then
table.insert(filteredSkins, skins[i])
end
end
if table.getn(filteredSkins) == 0 then
MTH_BOOK_ModelViewer_Clear()
return
end
MTH_MV.family = family
MTH_MV.skins = filteredSkins
MTH_MV.facing = 0.6
-- If we have a specific skinId, jump to the matching skin entry.
MTH_MV.index = 1
if skinId then
for i = 1, table.getn(filteredSkins) do
if filteredSkins[i].id == skinId then
MTH_MV.index = i
break
end
end
end
if MTH_MV.popupTitle then
MTH_MV.popupTitle:SetText(tostring(family) .. " — Skins")
end
MTH_MV_DisplayCurrent()
if MTH_MV.previewFrame then MTH_MV.previewFrame:Show() end
MTH_MV_DisplayCurrent()
end
function MTH_BOOK_ModelViewer_Clear()
if MTH_MV.previewFrame then MTH_MV.previewFrame:Hide() end
if not MTH_MV.keepPopup then
if MTH_MV.popup and MTH_MV.popup:IsShown() then MTH_MV.popup:Hide() end
end
MTH_MV.family = nil
MTH_MV.skins = {}
MTH_MV.index = 1
end
function MTH_BOOK_ModelViewer_OpenPopup()
MTH_MV_Init()
if not MTH_MV.popup then return end
if table.getn(MTH_MV.skins) == 0 then return end
MTH_MV.popup:Show()
MTH_MV.popup:Raise()
MTH_MV_DisplayCurrent()
end
+107 -16
View File
@@ -2,17 +2,18 @@ if type(MTH_HUNTERBOOK_TABS) ~= "table" then MTH_HUNTERBOOK_TABS = {} end
MTH_HUNTERBOOK_TABS.pets = {
headerLabel = "Beasts",
columnLabels = { "ID", "Lvl", "Family", "Name", "Abilities", "Zone", "R", "E", "U" },
columnLabels = { "ID", "Lvl", "Fam", "Name", "Abilities", "Zone", "R", "E", "U", "AS" },
columnLayout = {
{ x = 8, width = 28, align = "LEFT" },
{ x = 38, width = 30, align = "LEFT" },
{ x = 68, width = 74, align = "LEFT" },
{ x = 142, width = 130, align = "LEFT" },
{ x = 272, width = 180, align = "LEFT" },
{ x = 452, width = 68, align = "LEFT" },
{ x = 520, width = 10, align = "CENTER" },
{ x = 532, width = 10, align = "CENTER" },
{ x = 544, width = 10, align = "CENTER" },
{ x = 68, width = 18, align = "LEFT" },
{ x = 88, width = 130, align = "LEFT" },
{ x = 220, width = 148, align = "LEFT" },
{ x = 370, width = 80, align = "LEFT" },
{ x = 452, width = 14, align = "CENTER" },
{ x = 468, width = 14, align = "CENTER" },
{ x = 484, width = 14, align = "CENTER" },
{ x = 500, width = 54, align = "LEFT" },
},
}
@@ -28,13 +29,31 @@ function MTH_BOOK_GetBeastZoneSummary(beast)
return "-"
end
local seen = {}
local zoneNames = {}
-- Count coords per WMA zone ID to find the dominant zone.
local coordCount = {}
for i = 1, table.getn(beast.coords) do
local c = beast.coords[i]
if c and c[3] and not seen[c[3]] then
seen[c[3]] = true
table.insert(zoneNames, MTH_BOOK_GetZoneName(c[3]))
if c and c[3] then
local zid = c[3]
coordCount[zid] = (coordCount[zid] or 0) + 1
end
end
local zids = {}
for zid, _ in pairs(coordCount) do
table.insert(zids, zid)
end
-- Sort by frequency descending so the zone with most spawns is listed first.
table.sort(zids, function(a, b)
return (coordCount[a] or 0) > (coordCount[b] or 0)
end)
-- Resolve to zone names, deduplicating by name (multiple WMA IDs can resolve to the same zone).
local seenNames = {}
local zoneNames = {}
for i = 1, table.getn(zids) do
local name = MTH_BOOK_GetZoneName(zids[i])
if name and not seenNames[name] then
seenNames[name] = true
table.insert(zoneNames, name)
end
end
@@ -67,6 +86,69 @@ function MTH_BOOK_SplitAbilities(text)
return out
end
local _subzoneChildCache = nil
local function MTH_BOOK_EnsureSubzoneCache()
if _subzoneChildCache then return end
_subzoneChildCache = {}
if not MTH_DS_Zones then return end
for zid, row in pairs(MTH_DS_Zones) do
local parent = row.parent
local x1 = row.x1
local y1 = row.y1
local w = row.width
local h = row.height
if parent and x1 and y1 and w and h then
local names = row.names
local name = names and (names.enUS or names[GetLocale and GetLocale() or "enUS"] or names.deDE)
if name then
if not _subzoneChildCache[parent] then
_subzoneChildCache[parent] = {}
end
table.insert(_subzoneChildCache[parent], { x1, y1, x1 + w, y1 + h, name })
end
end
end
end
function MTH_BOOK_GetBeastSubZoneSummary(beast)
if not beast or not beast.coords or table.getn(beast.coords) == 0 then return nil end
MTH_BOOK_EnsureSubzoneCache()
if not _subzoneChildCache then return nil end
local counts = {}
for i = 1, table.getn(beast.coords) do
local c = beast.coords[i]
if c then
local cx, cy, wmaId = c[1], c[2], c[3]
if cx and cy and wmaId then
local pfqId = wmaId
if type(MTH_MAP_WMA_TO_PFQ) == "table" then
pfqId = MTH_MAP_WMA_TO_PFQ[wmaId] or wmaId
end
local children = _subzoneChildCache[pfqId]
if children then
for _, sub in ipairs(children) do
if cx >= sub[1] and cx <= sub[3] and cy >= sub[2] and cy <= sub[4] then
counts[sub[5]] = (counts[sub[5]] or 0) + 1
break
end
end
end
end
end
end
local bestName, bestCount = nil, 0
for name, count in pairs(counts) do
if count > bestCount then
bestName = name
bestCount = count
end
end
return bestName
end
function MTH_BOOK_HasUsableAbilities(beast)
if not beast then return false end
local abilities = MTH_BOOK_SplitAbilities(beast.abilities)
@@ -231,8 +313,11 @@ function MTH_BOOK_BeastMatches(beastId, beast)
end
if MTH_BOOK_STATE.petInZoneOnly then
local currentZoneId = MTH_BOOK_GetCurrentZoneId()
if currentZoneId and not MTH_BOOK_BeastHasZoneId(beast, currentZoneId) then
-- Use the zone ID cached once per BuildResults call (avoids N calls to
-- GetCurrentZoneId which is expensive). If zone ID is nil (new/unknown
-- zone like Moonwhisper Coast), no DB entry can match — exclude all.
local currentZoneId = MTH_BOOK_STATE._cachedZoneId
if not currentZoneId or not MTH_BOOK_BeastHasZoneId(beast, currentZoneId) then
return false
end
end
@@ -250,6 +335,12 @@ function MTH_BOOK_BeastMatches(beastId, beast)
end
end
if MTH_BOOK_STATE.petZoneFilter and MTH_BOOK_STATE.petZoneFilter ~= "all" then
if not MTH_BOOK_BeastHasZoneId(beast, MTH_BOOK_STATE.petZoneFilter) then
return false
end
end
return true
end
@@ -257,7 +348,7 @@ function MTH_BOOK_BeastSort(a, b)
local beasts = MTH_DS_Beasts
local ba = beasts and beasts[a]
local bb = beasts and beasts[b]
if not ba or not bb then return a < b end
if not ba or not bb then return tostring(a) < tostring(b) end
local la = MTH_BOOK_ParseLevel(ba.lvl) or 0
local lb = MTH_BOOK_ParseLevel(bb.lvl) or 0
if la ~= lb then return la < lb end
+18 -3
View File
@@ -13,7 +13,6 @@ MTH_HUNTERBOOK_TABS.families = {
}
local function MTH_BOOKTAB_FamiliesTrace(message)
return
end
local function MTH_BOOKTAB_FamiliesSafeLower(value)
@@ -83,6 +82,7 @@ function MTH_BOOKTAB_BuildFamiliesRows()
for familyName, familyRow in pairs(families) do
if type(familyRow) == "table" then
MTH_BOOKTAB_FamiliesTrace("BUILD " .. tostring(familyName) .. " icon=" .. tostring(familyRow.icon))
local abilities = {}
local seen = {}
@@ -191,6 +191,7 @@ function MTH_BOOKTAB_BuildFamiliesRows()
table.insert(results, {
family = tostring(familyName),
icon = familyRow.icon or nil,
named = tonumber(familyRow.named) or 0,
coords = tonumber(familyRow.coords) or 0,
abilities = abilities,
@@ -289,9 +290,15 @@ function MTH_BOOKTAB_EnsureFamiliesUI()
row:SetPoint("TOPRIGHT", ui.rows[i - 1], "BOTTOMRIGHT", 0, -1)
end
row.familyIcon = row:CreateTexture(nil, "ARTWORK")
row.familyIcon:SetPoint("LEFT", row, "LEFT", 4, 0)
row.familyIcon:SetWidth(16)
row.familyIcon:SetHeight(16)
row.familyIcon:Hide()
row.family = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.family:SetPoint("LEFT", row, "LEFT", 4, 0)
row.family:SetWidth(106)
row.family:SetPoint("LEFT", row, "LEFT", 24, 0)
row.family:SetWidth(88)
row.family:SetJustifyH("LEFT")
row.named = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
@@ -393,6 +400,14 @@ function MTH_BOOKTAB_RenderFamiliesList()
if row then
rowFrame:Show()
rowFrame.family:SetText(tostring(row.family or "-"))
local familyIconPath = row.icon and MTH_BOOK_ResolveIconPath and MTH_BOOK_ResolveIconPath(row.icon) or nil
MTH_BOOKTAB_FamiliesTrace("RENDER " .. tostring(row.family) .. " icon=" .. tostring(row.icon) .. " path=" .. tostring(familyIconPath))
if familyIconPath then
rowFrame.familyIcon:SetTexture(familyIconPath)
rowFrame.familyIcon:Show()
else
rowFrame.familyIcon:Hide()
end
rowFrame.named:SetText(tostring(row.named or 0))
rowFrame.coords:SetText(tostring(row.coords or 0))
rowFrame.diet:SetText(tostring(row.dietText or "-"))
+407 -95
View File
@@ -69,6 +69,8 @@ local MTH_BOOK_STATE = {
petOnlyMyLevel = true,
itemOnlyMyLevel = false,
petInZoneOnly = false,
petContinent = "all",
petZoneFilter = "all",
itemSubtype = "all",
npcFunction = "all",
npcZone = "all",
@@ -150,7 +152,7 @@ local function MTH_HB_RequireOpenDeps()
return true
end
local MTH_BOOK_MAX_COLS = 9
local MTH_BOOK_MAX_COLS = 10
local MTH_BOOK_MAX_ROWS = 20
local MTH_BOOK_PetFamilyOptions = {
@@ -222,7 +224,8 @@ local MTH_BOOK_CONTENT_LAYOUT_DEFAULT = {
detailX = 608,
detailY = -166,
detailW = 136,
detailH = 324,
detailH = 208,
detailTextH = 192, -- inspector text only; model viewer sits below in its own frame
}
local MTH_BOOK_CONTENT_LAYOUT_ITEMS = {
@@ -273,7 +276,7 @@ local MTH_BOOK_CONTENT_LAYOUT_FAMILIES = {
listX = 16,
listY = -74,
listW = 728,
listH = 376,
listH = 456,
detailX = 748,
detailY = -74,
detailW = 1,
@@ -328,7 +331,7 @@ local function MTH_BOOK_ApplyContentLayoutForMode()
detailText:ClearAllPoints()
detailText:SetPoint("TOPLEFT", detailParent, "TOPLEFT", 6, -6)
detailText:SetWidth(layout.detailW - 12)
detailText:SetHeight(layout.detailH - 12)
detailText:SetHeight(layout.detailTextH or (layout.detailH - 12))
end
local sliderHeight = layout.listH - 32
@@ -363,7 +366,7 @@ local function MTH_BOOK_UpdateCheckboxLayoutByMode()
getglobal("MTH_BOOK_HideNoAbilities"),
getglobal("MTH_BOOK_HideUnknown"),
}
local xOffsets = { 18, 86, 154, 232, 372 }
local xOffsets = { 18, 86, 154, 232, 345 }
local yOffset = -140
if MTH_BOOK_STATE.mode == "npcs" then
yOffset = -116
@@ -753,12 +756,15 @@ local function MTH_BOOK_ShowAllFilteredBeastsOnMap()
end
local nodes = {}
local firstZoneId = nil
for i = 1, table.getn(MTH_BOOK_STATE.results or {}) do
local beastId = tonumber(MTH_BOOK_STATE.results[i])
local beast = beastId and MTH_DS_Beasts and MTH_DS_Beasts[beastId]
if beast and beast.coords then
local beastDisplayName = (MTH and MTH.GetLocalizedBeastName and MTH:GetLocalizedBeastName(beastId, beast.name)) or beast.name or "Unknown"
local cr, cg, cb = 0.95, 0.82, 0.10
if MTH_Map and MTH_Map.GetFamilyColor then
cr, cg, cb = MTH_Map:GetFamilyColor(beast.family)
end
for j = 1, table.getn(beast.coords) do
local c = beast.coords[j]
if c and c[1] and c[2] and c[3] then
@@ -766,9 +772,6 @@ local function MTH_BOOK_ShowAllFilteredBeastsOnMap()
local y = tonumber(c[2])
local zoneId = tonumber(c[3])
if x and y and zoneId then
if not firstZoneId then
firstZoneId = zoneId
end
local details = string.format("Family: %s\nLevel: %s\nAbilities: %s", beast.family or "?", beast.lvl or "?", beast.abilities or "None")
table.insert(nodes, {
zoneId = zoneId,
@@ -777,7 +780,7 @@ local function MTH_BOOK_ShowAllFilteredBeastsOnMap()
title = string.format("%s (%d)", beastDisplayName, beastId),
detail = details,
beastId = beastId,
color = { 0.95, 0.82, 0.10 },
color = { cr, cg, cb },
})
end
end
@@ -793,8 +796,11 @@ local function MTH_BOOK_ShowAllFilteredBeastsOnMap()
if not MTH_Map:SetSource("focus") then
return
end
if firstZoneId and MTH_Map.OpenWorldMapForZone then
MTH_Map:OpenWorldMapForZone(firstZoneId)
-- Navigate to the player's current (filter) zone, not the first coord's zone ID
-- which may point to a different zone due to multi-zone beast spawn data.
local navZoneId = MTH_BOOK_STATE._cachedZoneId
if navZoneId and MTH_Map.OpenWorldMapForZone then
MTH_Map:OpenWorldMapForZone(navZoneId)
end
MTH_Map:UpdateWorldMap()
MTH_Map:UpdateMinimap()
@@ -1075,11 +1081,17 @@ end
MTH_BOOK_GetZoneName = function(zoneId)
local normalizedId = tonumber(zoneId) or zoneId
if not normalizedId then return "Zone " .. tostring(zoneId or "?") end
if MTH_DS_ZoneNamesFallback and MTH_DS_ZoneNamesFallback[normalizedId] then
return MTH_DS_ZoneNamesFallback[normalizedId]
-- Beast coords use WMA IDs. Always translate WMA→pfQ first so that e.g.
-- WMA 4 → pfQ 14 (Durotar) instead of hitting pfQ 4 (Blasted Lands) by accident.
local pfqId = normalizedId
if type(MTH_MAP_WMA_TO_PFQ) == "table" then
pfqId = MTH_MAP_WMA_TO_PFQ[normalizedId] or normalizedId
end
if not MTH_DS_Zones then return "Zone " .. tostring(zoneId or "?") end
local row = MTH_DS_Zones[normalizedId] or MTH_DS_Zones[tostring(normalizedId)]
if MTH_DS_ZoneNamesFallback and MTH_DS_ZoneNamesFallback[pfqId] then
return MTH_DS_ZoneNamesFallback[pfqId]
end
if not MTH_DS_Zones then return "Zone " .. tostring(normalizedId) end
local row = MTH_DS_Zones[pfqId] or MTH_DS_Zones[tostring(pfqId)]
if not row then return "Zone " .. tostring(normalizedId) end
local names = row.names
if not names then return "Zone " .. tostring(normalizedId) end
@@ -1108,7 +1120,12 @@ function MTH_BOOK_GetCurrentZoneId()
if MTH_Map and type(MTH_Map.GetMapIDByName) == "function" then
local mapId = MTH_Map:GetMapIDByName(zoneName)
if mapId then
return tonumber(mapId) or mapId
-- Translate pfQ → WMA so zone IDs match coord tuples in MTH_DS_Beasts.
local pfqId = tonumber(mapId)
if pfqId and type(MTH_MAP_PFQ_TO_WMA) == "table" then
return MTH_MAP_PFQ_TO_WMA[pfqId] or pfqId
end
return pfqId or mapId
end
end
@@ -1122,7 +1139,11 @@ function MTH_BOOK_GetCurrentZoneId()
if row and row.names then
for _, value in pairs(row.names) do
if MTH_BOOK_NormalizeZoneLookupName(value) == normalized then
return tonumber(zoneId) or zoneId
local pfqId = tonumber(zoneId) or zoneId
if type(pfqId) == "number" and type(MTH_MAP_PFQ_TO_WMA) == "table" then
return MTH_MAP_PFQ_TO_WMA[pfqId] or pfqId
end
return pfqId
end
end
end
@@ -1281,6 +1302,7 @@ local function MTH_BOOK_GetSortKey(entry, col)
if col == 7 then return traits.rare and 1 or 0 end
if col == 8 then return traits.elite and 1 or 0 end
if col == 9 then return traits.unique and 1 or 0 end
if col == 10 then return tonumber(beast.attackSpeed) or 0 end
end
if MTH_BOOK_STATE.mode == "abilities" then
@@ -1430,12 +1452,25 @@ local function MTH_BOOK_BuildResults()
local results = {}
if MTH_BOOK_STATE.mode == "pets" then
if not MTH_DS_Beasts then return results end
for beastId, beast in pairs(MTH_DS_Beasts) do
if MTH_BOOK_BeastMatches(beastId, beast) then
table.insert(results, beastId)
-- Cache zone info once for this build pass (avoids calling GetCurrentZoneId
-- once per beast, which is expensive and causes freezes on large datasets).
if MTH_BOOK_STATE.petInZoneOnly then
MTH_BOOK_STATE._cachedZoneId = MTH_BOOK_GetCurrentZoneId()
MTH_BOOK_STATE._cachedZoneName = (type(GetRealZoneText) == "function" and GetRealZoneText())
or (type(GetZoneText) == "function" and GetZoneText()) or nil
else
MTH_BOOK_STATE._cachedZoneId = nil
MTH_BOOK_STATE._cachedZoneName = nil
end
if MTH_DS_Beasts then
for beastId, beast in pairs(MTH_DS_Beasts) do
if MTH_BOOK_BeastMatches(beastId, beast) then
table.insert(results, beastId)
end
end
end
table.sort(results, MTH_BOOK_BeastSort)
MTH_BOOK_ApplyActiveSort(results)
return results
@@ -2052,6 +2087,77 @@ local function MTH_BOOK_UpdatePetLearnSourceDropdownText()
MTH_BOOK_DropdownSetText(sourceDropdown, MTH_BOOK_GetPetLearnSourceLabel(MTH_BOOK_STATE.petLearnSource))
end
local MTH_BOOK_BeastContinentOptions = {
{ value = "all", label = "" },
{ value = 1, label = "Kalimdor" },
{ value = 0, label = "E. Kingdoms" },
}
local MTH_BOOK_BeastZoneOptions = {
{ value = "all", label = "All Zones" },
}
local function MTH_BOOK_BuildBeastZoneOptions(continentMapId)
local seen = {}
local rows = {}
if MTH_DS_Beasts and MTH_DS_WMAZones then
for _, beast in pairs(MTH_DS_Beasts) do
if beast and beast.coords then
for i = 1, table.getn(beast.coords) do
local c = beast.coords[i]
if c and c[3] and not seen[c[3]] then
local wmaEntry = MTH_DS_WMAZones[c[3]]
if wmaEntry and wmaEntry.mapId == continentMapId then
seen[c[3]] = true
local zoneName = MTH_BOOK_GetZoneName and MTH_BOOK_GetZoneName(c[3])
if zoneName and string.sub(zoneName, 1, 5) ~= "Zone " then
table.insert(rows, { value = c[3], label = zoneName })
end
end
end
end
end
end
end
table.sort(rows, function(a, b)
return MTH_BOOK_SafeLower(a.label) < MTH_BOOK_SafeLower(b.label)
end)
MTH_BOOK_BeastZoneOptions = { { value = "all", label = "All Zones" } }
for i = 1, table.getn(rows) do
table.insert(MTH_BOOK_BeastZoneOptions, rows[i])
end
end
local function MTH_BOOK_GetBeastContinentLabel(value)
for i = 1, table.getn(MTH_BOOK_BeastContinentOptions) do
if MTH_BOOK_BeastContinentOptions[i].value == value then
return MTH_BOOK_BeastContinentOptions[i].label
end
end
return ""
end
local function MTH_BOOK_GetBeastZoneLabel(value)
for i = 1, table.getn(MTH_BOOK_BeastZoneOptions) do
if MTH_BOOK_BeastZoneOptions[i].value == value then
return MTH_BOOK_BeastZoneOptions[i].label
end
end
return "All Zones"
end
local function MTH_BOOK_UpdateBeastContinentDropdownText()
local dd = getglobal("MTH_BOOK_BeastContinentDropdown")
if not dd then return end
MTH_BOOK_DropdownSetText(dd, MTH_BOOK_GetBeastContinentLabel(MTH_BOOK_STATE.petContinent))
end
local function MTH_BOOK_UpdateBeastZoneDropdownText()
local dd = getglobal("MTH_BOOK_BeastZoneDropdown")
if not dd then return end
MTH_BOOK_DropdownSetText(dd, MTH_BOOK_GetBeastZoneLabel(MTH_BOOK_STATE.petZoneFilter))
end
local function MTH_BOOK_GetItemSubtypeLabelByValue(value)
local options = MTH_BOOK_GetItemSubtypeOptions()
for i = 1, table.getn(options) do
@@ -2087,7 +2193,7 @@ local function MTH_BOOK_UpdateQuickFilterControls()
if MTH_BOOK_IsItemMode() then
MTH_BOOK_STATE.pageSize = 18
elseif MTH_BOOK_STATE.mode == "families" then
MTH_BOOK_STATE.pageSize = 16
MTH_BOOK_STATE.pageSize = MTH_BOOK_MAX_ROWS
elseif MTH_BOOK_STATE.mode == "stable" then
MTH_BOOK_STATE.pageSize = 5
elseif MTH_BOOK_STATE.mode == "pethistory" then
@@ -2100,6 +2206,9 @@ local function MTH_BOOK_UpdateQuickFilterControls()
local abilityDropdown = getglobal("MTH_BOOK_AbilityDropdown")
local rankDropdown = getglobal("MTH_BOOK_RankDropdown")
local petLearnSourceDropdown = getglobal("MTH_BOOK_PetLearnSourceDropdown")
local beastContinentDropdown = getglobal("MTH_BOOK_BeastContinentDropdown")
local beastZoneDropdown = getglobal("MTH_BOOK_BeastZoneDropdown")
local beastZoneLabel = getglobal("MTH_BOOK_BeastZoneLabel")
local itemSubtypeDropdown = getglobal("MTH_BOOK_ItemSubtypeDropdown")
local npcFunctionDropdown = getglobal("MTH_BOOK_NPCFunctionDropdown")
local npcZoneDropdown = getglobal("MTH_BOOK_NPCZoneDropdown")
@@ -2220,6 +2329,19 @@ local function MTH_BOOK_UpdateQuickFilterControls()
end
end
if petLearnSourceDropdown then petLearnSourceDropdown:Hide() end
if beastZoneLabel then beastZoneLabel:Show() end
if beastContinentDropdown then
beastContinentDropdown:Show()
MTH_BOOK_UpdateBeastContinentDropdownText()
end
if beastZoneDropdown then
if MTH_BOOK_STATE.petContinent ~= "all" then
beastZoneDropdown:Show()
MTH_BOOK_UpdateBeastZoneDropdownText()
else
beastZoneDropdown:Hide()
end
end
if itemSubtypeDropdown then itemSubtypeDropdown:Hide() end
if npcFunctionDropdown then npcFunctionDropdown:Hide() end
if npcZoneDropdown then npcZoneDropdown:Hide() end
@@ -2255,6 +2377,9 @@ local function MTH_BOOK_UpdateQuickFilterControls()
petLearnSourceDropdown:Show()
MTH_BOOK_UpdatePetLearnSourceDropdownText()
end
if beastZoneLabel then beastZoneLabel:Hide() end
if beastContinentDropdown then beastContinentDropdown:Hide() end
if beastZoneDropdown then beastZoneDropdown:Hide() end
if itemSubtypeDropdown then itemSubtypeDropdown:Hide() end
if petOnlyMyLevel then
petOnlyMyLevel:ClearAllPoints()
@@ -2311,6 +2436,9 @@ local function MTH_BOOK_UpdateQuickFilterControls()
if abilityDropdown then abilityDropdown:Hide() end
if rankDropdown then rankDropdown:Hide() end
if petLearnSourceDropdown then petLearnSourceDropdown:Hide() end
if beastZoneLabel then beastZoneLabel:Hide() end
if beastContinentDropdown then beastContinentDropdown:Hide() end
if beastZoneDropdown then beastZoneDropdown:Hide() end
if itemSubtypeDropdown then itemSubtypeDropdown:Hide() end
if npcFunctionDropdown then npcFunctionDropdown:Show() end
if npcZoneDropdown then npcZoneDropdown:Hide() end
@@ -2365,6 +2493,9 @@ local function MTH_BOOK_UpdateQuickFilterControls()
if abilityDropdown then abilityDropdown:Hide() end
if rankDropdown then rankDropdown:Hide() end
if petLearnSourceDropdown then petLearnSourceDropdown:Hide() end
if beastZoneLabel then beastZoneLabel:Hide() end
if beastContinentDropdown then beastContinentDropdown:Hide() end
if beastZoneDropdown then beastZoneDropdown:Hide() end
if itemSubtypeDropdown then
itemSubtypeDropdown:ClearAllPoints()
itemSubtypeDropdown:SetPoint("TOPLEFT", itemSubtypeDropdown:GetParent(), "TOPLEFT", 238, -80)
@@ -2502,6 +2633,15 @@ local function MTH_BOOK_UpdateQuickFilterControls()
if MTH_BOOK_STATE.mode ~= "pets" and petInZoneOnly then
petInZoneOnly:Hide()
end
if MTH_BOOK_STATE.mode ~= "pets" and beastContinentDropdown then
beastContinentDropdown:Hide()
end
if MTH_BOOK_STATE.mode ~= "pets" and beastZoneDropdown then
beastZoneDropdown:Hide()
end
if MTH_BOOK_STATE.mode ~= "pets" and beastZoneLabel then
beastZoneLabel:Hide()
end
if MTH_BOOK_STATE.mode ~= "npcs" and npcInZoneOnly then
npcInZoneOnly:Hide()
end
@@ -2607,6 +2747,7 @@ local function MTH_BOOK_InitRankDropdown()
MTH_BOOK_UpdateRankDropdownText()
end
local function MTH_BOOK_InitPetLearnSourceDropdown()
local sourceDropdown = getglobal("MTH_BOOK_PetLearnSourceDropdown")
if not sourceDropdown then return end
@@ -2701,6 +2842,56 @@ local function MTH_BOOK_AddStableStyleKV(lines, label, value)
table.insert(lines, "|cff9a9a9a" .. tostring(label or "") .. ":|r |cffffffff" .. tostring(value or "-") .. "|r")
end
local function MTH_BOOK_GetOrCreateDetailTitleLabel()
local lbl = getglobal("MTH_BOOK_DetailTitleLabel")
if lbl then return lbl end
local parent = getglobal("MTH_BOOK_DetailBackdrop")
if not parent then return nil end
lbl = parent:CreateFontString("MTH_BOOK_DetailTitleLabel", "OVERLAY")
lbl:SetFontObject(GameFontNormal)
lbl:SetJustifyH("LEFT")
lbl:SetJustifyV("TOP")
lbl:SetPoint("TOPLEFT", parent, "TOPLEFT", 8, -8)
lbl:SetWidth(120)
lbl:Hide()
return lbl
end
local function MTH_BOOK_ShowDetailTitle(text)
local lbl = MTH_BOOK_GetOrCreateDetailTitleLabel()
if lbl then
lbl:SetText(tostring(text or ""))
lbl:Show()
end
local editBox = getglobal("MTH_BOOK_DetailBackdropDetailText")
local parent = getglobal("MTH_BOOK_DetailBackdrop")
if editBox and parent then
editBox:ClearAllPoints()
editBox:SetPoint("TOPLEFT", parent, "TOPLEFT", 6, -28)
end
end
local function MTH_BOOK_HideDetailTitle()
local lbl = getglobal("MTH_BOOK_DetailTitleLabel")
if lbl then lbl:Hide() end
local editBox = getglobal("MTH_BOOK_DetailBackdropDetailText")
local parent = getglobal("MTH_BOOK_DetailBackdrop")
if editBox and parent then
editBox:ClearAllPoints()
editBox:SetPoint("TOPLEFT", parent, "TOPLEFT", 6, -6)
end
end
local function MTH_BOOK_FormatBeastReact(reactA, reactH)
local function reactColor(r)
local rl = r and string.lower(r) or ""
if rl == "friendly" then return "|cFF44DD44" end
if rl == "neutral" then return "|cFFFFCC00" end
return "|cFFDD4444"
end
return reactColor(reactA) .. "A|r " .. reactColor(reactH) .. "H|r"
end
local function MTH_BOOK_FormatDateTimeValue(value)
local ts = tonumber(value)
if not ts or ts <= 0 then
@@ -2941,7 +3132,7 @@ end
function MTH_BOOK_SetDetailText(detail, text)
if not detail then return end
local wrapped = tostring(text or "")
if MTH_BOOK_STATE.mode ~= "pethistory" then
if MTH_BOOK_STATE.mode ~= "pethistory" and MTH_BOOK_STATE.mode ~= "pets" and MTH_BOOK_STATE.mode ~= "abilities" then
wrapped = MTH_BOOK_WrapDetailText(wrapped)
end
detail._mthLocking = true
@@ -2994,9 +3185,15 @@ end
function MTH_BOOK_UpdateDetail()
local detail = getglobal("MTH_BOOK_DetailBackdropDetailText")
if not detail then return end
MTH_BOOK_HideDetailTitle()
-- Hide model skin preview when not in a beast-list mode
if MTH_BOOK_STATE.mode ~= "pets" and MTH_BOOK_STATE.mode ~= "abilities" then
if MTH_BOOK_ModelViewer_Clear then MTH_BOOK_ModelViewer_Clear() end
end
local petTop = MTH_BOOK_STATE.petUI and MTH_BOOK_STATE.petUI.detailTop
local petBottom = MTH_BOOK_STATE.petUI and MTH_BOOK_STATE.petUI.detailBottom
if not MTH_BOOK_STATE.selectedEntry then
if MTH_BOOK_ModelViewer_Clear then MTH_BOOK_ModelViewer_Clear() end
if MTH_BOOK_STATE.mode == "petabilities" then
if petTop and petBottom then
MTH_BOOK_SetDetailText(petTop, "|cFFFFD100Baseline|r\n\nSelect a spell baseline from the left list.")
@@ -3067,49 +3264,52 @@ function MTH_BOOK_UpdateDetail()
return
end
local beastId = selected.beastId
local beast = MTH_DS_Beasts and MTH_DS_Beasts[beastId]
if not beast then
MTH_BOOK_SetDetailText(detail, "|cFFFFD100Inspector|r\n\nSelect an entry from the list to view identity, stats, sources and locations.")
MTH_BOOK_UpdateOpenMapButton()
return
end
local lines = {}
local beastDisplayName = (MTH and MTH.GetLocalizedBeastName and MTH:GetLocalizedBeastName(beastId, beast.name)) or beast.name
table.insert(lines, "|cFFFFFF00" .. (beastDisplayName or "Unknown") .. "|r")
MTH_BOOK_AddDetailSection(lines, "Identity")
MTH_BOOK_AddDetailKV(lines, "ID", beastId)
MTH_BOOK_AddDetailKV(lines, "Family", beast.family or "?")
MTH_BOOK_ShowDetailTitle("|cFFFFFF00" .. (beastDisplayName or "Unknown") .. "|r")
MTH_BOOK_AddDetailSection(lines, "Stats")
MTH_BOOK_AddDetailKV(lines, "Level", beast.lvl or "?")
MTH_BOOK_AddDetailKV(lines, "Attack Speed", beast.attackSpeed or "?")
local lines = {}
-- Identity block
MTH_BOOK_AddStableStyleKV(lines, "ID", beastId)
MTH_BOOK_AddStableStyleKV(lines, "Family", beast.family or "?")
local rankLabel = beast.rank
if rankLabel == "normal" or rankLabel == nil or rankLabel == "" then rankLabel = nil end
if rankLabel then MTH_BOOK_AddStableStyleKV(lines, "Rank", rankLabel) end
local spawnCount = (beast.coords and table.getn(beast.coords)) or 0
MTH_BOOK_AddStableStyleKV(lines, "Spawns", spawnCount > 0 and tostring(spawnCount) or "?")
if beast.reactA or beast.reactH then
table.insert(lines, "|cff9a9a9aReaction:|r " .. MTH_BOOK_FormatBeastReact(beast.reactA, beast.reactH))
end
-- Stats block
MTH_BOOK_AddStableStyleKV(lines, "Level", beast.lvl or "?")
MTH_BOOK_AddStableStyleKV(lines, "Atk Speed", beast.attackSpeed or "?")
local respawnWindow = MTH_BOOK_FormatRespawnWindow(beast)
if respawnWindow then
MTH_BOOK_AddDetailKV(lines, "Respawn", respawnWindow)
if respawnWindow then MTH_BOOK_AddStableStyleKV(lines, "Respawn", respawnWindow) end
if beast.health then MTH_BOOK_AddStableStyleKV(lines, "Health", beast.health) end
if beast.armor then MTH_BOOK_AddStableStyleKV(lines, "Armor", beast.armor) end
if beast.dmgMin and beast.dmgMax then
MTH_BOOK_AddStableStyleKV(lines, "Damage", string.format("%.1f - %.1f", tonumber(beast.dmgMin) or 0, tonumber(beast.dmgMax) or 0))
end
MTH_BOOK_AddDetailKV(lines, "Rare", beast.rare and "Yes" or "No")
MTH_BOOK_AddDetailKV(lines, "Abilities", beast.abilities or "None")
local fam = MTH_DS_Families and beast.family and MTH_DS_Families[beast.family]
if fam and fam.abilities and table.getn(fam.abilities) > 0 then
MTH_BOOK_AddDetailSection(lines, "Family Pool")
table.insert(lines, " " .. table.concat(fam.abilities, ", "))
end
if beast.coords and table.getn(beast.coords) > 0 then
MTH_BOOK_AddDetailSection(lines, "Locations")
for i = 1, table.getn(beast.coords) do
local c = beast.coords[i]
if c then
local x = tonumber(c[1] or 0) or 0
local y = tonumber(c[2] or 0) or 0
local zoneId = c[3]
table.insert(lines, string.format(" - %s (%.1f, %.1f)", MTH_BOOK_GetZoneName(zoneId), x, y))
end
if i >= 8 then break end
end
if beast.abilities and beast.abilities ~= "" and beast.abilities ~= "None" then
MTH_BOOK_AddStableStyleKV(lines, "Abilities", beast.abilities)
end
local beastZone = MTH_BOOK_GetBeastZoneSummary and MTH_BOOK_GetBeastZoneSummary(beast)
if beastZone and beastZone ~= "-" then
MTH_BOOK_AddStableStyleKV(lines, "Zone", beastZone)
end
local subzone = MTH_BOOK_GetBeastSubZoneSummary and MTH_BOOK_GetBeastSubZoneSummary(beast)
if subzone then
MTH_BOOK_AddStableStyleKV(lines, "Subzone", subzone)
end
if MTH_BOOK_ModelViewer_SetFamily then MTH_BOOK_ModelViewer_SetFamily(beast.family, beast.skinId) end
MTH_BOOK_SetDetailText(detail, table.concat(lines, "\n"))
MTH_BOOK_UpdateOpenMapButton()
return
@@ -3128,37 +3328,38 @@ function MTH_BOOK_UpdateDetail()
MTH_BOOK_UpdateOpenMapButton()
return
end
MTH_BOOK_ShowDetailTitle("|cFFFFFF00" .. tostring(entry.abilityToken) .. "|r")
local lines = {}
table.insert(lines, "|cFFFFFF00" .. tostring(entry.abilityToken) .. "|r")
local beastDisplayName = (MTH and MTH.GetLocalizedBeastName and MTH:GetLocalizedBeastName(entry.beastId, beast.name)) or beast.name
MTH_BOOK_AddStableStyleKV(lines, "Beast", tostring(beastDisplayName or "Unknown") .. " (#" .. tostring(entry.beastId) .. ")")
MTH_BOOK_AddStableStyleKV(lines, "Family", beast.family or "?")
local rankLabel = beast.rank
if rankLabel == "normal" or rankLabel == nil or rankLabel == "" then rankLabel = nil end
if rankLabel then MTH_BOOK_AddStableStyleKV(lines, "Rank", rankLabel) end
MTH_BOOK_AddDetailSection(lines, "Identity")
MTH_BOOK_AddDetailKV(lines, "Beast", tostring(beastDisplayName or "Unknown") .. " (ID " .. tostring(entry.beastId) .. ")")
MTH_BOOK_AddDetailKV(lines, "Family", beast.family or "?")
MTH_BOOK_AddDetailSection(lines, "Stats")
MTH_BOOK_AddDetailKV(lines, "Level", beast.lvl or "?")
MTH_BOOK_AddDetailKV(lines, "Attack Speed", beast.attackSpeed or "?")
MTH_BOOK_AddStableStyleKV(lines, "Level", beast.lvl or "?")
MTH_BOOK_AddStableStyleKV(lines, "Atk Speed", beast.attackSpeed or "?")
local respawnWindow = MTH_BOOK_FormatRespawnWindow(beast)
if respawnWindow then
MTH_BOOK_AddDetailKV(lines, "Respawn", respawnWindow)
if respawnWindow then MTH_BOOK_AddStableStyleKV(lines, "Respawn", respawnWindow) end
if beast.health then MTH_BOOK_AddStableStyleKV(lines, "Health", beast.health) end
if beast.armor then MTH_BOOK_AddStableStyleKV(lines, "Armor", beast.armor) end
if beast.dmgMin and beast.dmgMax then
MTH_BOOK_AddStableStyleKV(lines, "Damage", string.format("%.1f - %.1f", tonumber(beast.dmgMin) or 0, tonumber(beast.dmgMax) or 0))
end
MTH_BOOK_AddDetailKV(lines, "Rare", beast.rare and "Yes" or "No")
if beast.coords and table.getn(beast.coords) > 0 then
MTH_BOOK_AddDetailSection(lines, "Locations")
for i = 1, table.getn(beast.coords) do
local c = beast.coords[i]
if c then
local x = tonumber(c[1] or 0) or 0
local y = tonumber(c[2] or 0) or 0
local zoneId = c[3]
table.insert(lines, string.format(" - %s (%.1f, %.1f)", MTH_BOOK_GetZoneName(zoneId), x, y))
end
if i >= 8 then break end
end
if beast.reactA or beast.reactH then
table.insert(lines, "|cff9a9a9aReaction:|r " .. MTH_BOOK_FormatBeastReact(beast.reactA, beast.reactH))
end
local beastZone = MTH_BOOK_GetBeastZoneSummary and MTH_BOOK_GetBeastZoneSummary(beast)
if beastZone and beastZone ~= "-" then
MTH_BOOK_AddStableStyleKV(lines, "Zone", beastZone)
end
local subzone = MTH_BOOK_GetBeastSubZoneSummary and MTH_BOOK_GetBeastSubZoneSummary(beast)
if subzone then
MTH_BOOK_AddStableStyleKV(lines, "Subzone", subzone)
end
if MTH_BOOK_ModelViewer_SetFamily then MTH_BOOK_ModelViewer_SetFamily(beast.family, beast.skinId) end
MTH_BOOK_SetDetailText(detail, table.concat(lines, "\n"))
MTH_BOOK_UpdateOpenMapButton()
return
@@ -3467,7 +3668,7 @@ local function MTH_BOOK_GetColumnLabels()
return tabDef.columnLabels
end
if MTH_BOOK_STATE.mode == "pets" then
return { "ID", "Lvl", "Family", "Name", "Abilities", "Zone", "R", "E", "U" }
return { "ID", "Lvl", "Fam", "Name", "Abilities", "Zone", "R", "E", "U", "AS" }
elseif MTH_BOOK_STATE.mode == "petabilities" then
return { "Ability Name", "Ranks", "Families" }
elseif MTH_BOOK_STATE.mode == "stable" then
@@ -3493,13 +3694,14 @@ local function MTH_BOOK_GetColumnLayout()
return {
{ x = 8, width = 28, align = "LEFT" },
{ x = 38, width = 30, align = "LEFT" },
{ x = 68, width = 74, align = "LEFT" },
{ x = 142, width = 130, align = "LEFT" },
{ x = 272, width = 180, align = "LEFT" },
{ x = 452, width = 68, align = "LEFT" },
{ x = 520, width = 10, align = "CENTER" },
{ x = 532, width = 10, align = "CENTER" },
{ x = 544, width = 10, align = "CENTER" },
{ x = 68, width = 18, align = "LEFT" },
{ x = 88, width = 130, align = "LEFT" },
{ x = 220, width = 148, align = "LEFT" },
{ x = 370, width = 80, align = "LEFT" },
{ x = 452, width = 14, align = "CENTER" },
{ x = 468, width = 14, align = "CENTER" },
{ x = 484, width = 14, align = "CENTER" },
{ x = 500, width = 54, align = "LEFT" },
}
elseif MTH_BOOK_STATE.mode == "petabilities" then
return {
@@ -3660,6 +3862,19 @@ local function MTH_BOOK_ApplyColumnLayout(parent)
btn.cols[itemNameColumn]:SetPoint("LEFT", btn, "LEFT", itemNameLayout.x + 12, 0)
btn.cols[itemNameColumn]:SetWidth(itemNameLayout.width - 16)
end
elseif MTH_BOOK_STATE.mode == "families" and layout[1] then
local familyCol = layout[1]
btn.itemIcon:ClearAllPoints()
btn.itemIcon:SetPoint("LEFT", btn, "LEFT", familyCol.x, 0)
if btn.cols and btn.cols[1] then
btn.cols[1]:ClearAllPoints()
btn.cols[1]:SetPoint("LEFT", btn, "LEFT", familyCol.x + 18, 0)
btn.cols[1]:SetWidth(familyCol.width - 18)
end
elseif MTH_BOOK_STATE.mode == "pets" and layout[3] then
local famCol = layout[3]
btn.itemIcon:ClearAllPoints()
btn.itemIcon:SetPoint("LEFT", btn, "LEFT", famCol.x, 0)
else
btn.itemIcon:Hide()
end
@@ -3670,8 +3885,9 @@ end
local function MTH_BOOK_GetRowValues(entry)
if MTH_BOOK_STATE.mode == "pets" then
-- MetaHunt DB entry
local beast = MTH_DS_Beasts and MTH_DS_Beasts[entry]
if not beast then return { "", "", "", "", "", "", "", "", "" } end
if not beast then return { "", "", "", "", "", "", "", "", "", "" } end
local beastDisplayName = (MTH and MTH.GetLocalizedBeastName and MTH:GetLocalizedBeastName(entry, beast.name)) or beast.name
local traits = MTH_BOOK_ParseBeastTraits(beast)
local abilities = MTH_BOOK_GetBeastAbilitiesSummary(beast)
@@ -3679,13 +3895,14 @@ local function MTH_BOOK_GetRowValues(entry)
return {
"|cFF33CCFF" .. tostring(entry) .. "|r",
tostring(beast.lvl or "?"),
tostring(beast.family or "?"),
"", -- family icon rendered separately
tostring(beastDisplayName or "Unknown"),
abilities,
zone,
(traits.rare and "R" or ""),
(traits.elite and "E" or ""),
(traits.unique and "U" or ""),
tostring(beast.attackSpeed or ""),
}
end
@@ -4090,6 +4307,31 @@ MTH_BOOK_UpdateResults = function()
btn.itemIcon:SetTexture(nil)
btn.itemIcon:Hide()
end
elseif MTH_BOOK_STATE.mode == "families" and type(entry) == "table" and entry.icon then
local iconPath = MTH_BOOK_ResolveIconPath(entry.icon)
if iconPath then
btn.itemIcon:SetTexture(iconPath)
btn.itemIcon:Show()
else
btn.itemIcon:SetTexture(nil)
btn.itemIcon:Hide()
end
elseif MTH_BOOK_STATE.mode == "pets" then
local beast = MTH_DS_Beasts and MTH_DS_Beasts[entry]
local famData = beast and beast.family and MTH_DS_Families and MTH_DS_Families[beast.family]
if famData and famData.icon and famData.icon ~= "" then
local iconPath = MTH_BOOK_ResolveIconPath(famData.icon)
if iconPath then
btn.itemIcon:SetTexture(iconPath)
btn.itemIcon:Show()
else
btn.itemIcon:SetTexture(nil)
btn.itemIcon:Hide()
end
else
btn.itemIcon:SetTexture(nil)
btn.itemIcon:Hide()
end
else
btn.itemIcon:SetTexture(nil)
btn.itemIcon:Hide()
@@ -4492,6 +4734,8 @@ local function MTH_BOOK_GetFilterWidgets()
npcFunctionDropdown = getglobal("MTH_BOOK_NPCFunctionDropdown"),
npcZoneDropdown = getglobal("MTH_BOOK_NPCZoneDropdown"),
petLearnSourceDropdown = getglobal("MTH_BOOK_PetLearnSourceDropdown"),
beastContinentDropdown = getglobal("MTH_BOOK_BeastContinentDropdown"),
beastZoneDropdown = getglobal("MTH_BOOK_BeastZoneDropdown"),
itemSubtypeDropdown = getglobal("MTH_BOOK_ItemSubtypeDropdown"),
petOnlyMyLevel = getglobal("MTH_BOOK_PetOnlyMyLevel"),
petInZoneOnly = getglobal("MTH_BOOK_PetInZoneOnly"),
@@ -4513,6 +4757,8 @@ local function MTH_BOOK_ResetStateForMode(mode)
MTH_BOOK_STATE.petOnlyMyLevel = false
MTH_BOOK_STATE.itemOnlyMyLevel = false
MTH_BOOK_STATE.petInZoneOnly = false
MTH_BOOK_STATE.petContinent = "all"
MTH_BOOK_STATE.petZoneFilter = "all"
MTH_BOOK_STATE.itemSubtype = "all"
MTH_BOOK_STATE.npcFunction = "all"
MTH_BOOK_STATE.npcZone = "all"
@@ -4576,6 +4822,8 @@ local function MTH_BOOK_ApplyStateToWidgets(widgets)
if widgets.petInZoneOnly then
widgets.petInZoneOnly:SetChecked(MTH_BOOK_STATE.mode == "pets" and MTH_BOOK_STATE.petInZoneOnly and 1 or nil)
end
if widgets.beastContinentDropdown then MTH_BOOK_UpdateBeastContinentDropdownText() end
if widgets.beastZoneDropdown then MTH_BOOK_UpdateBeastZoneDropdownText() end
if widgets.npcInZoneOnly then
widgets.npcInZoneOnly:SetChecked(MTH_BOOK_STATE.mode == "npcs" and MTH_BOOK_STATE.npcInZoneOnly and 1 or nil)
end
@@ -4614,6 +4862,8 @@ function MTH_BOOK_JumpToBeastById(beastId)
MTH_BOOK_STATE.mode = "pets"
local widgets = MTH_BOOK_GetFilterWidgets()
MTH_BOOK_ResetStateForMode("pets")
MTH_BOOK_STATE.petHideNoAbilities = false
MTH_BOOK_STATE.petHideUnknown = false
MTH_BOOK_STATE.search = tostring(id)
MTH_BOOK_STATE.forcedBeastId = id
MTH_BOOK_ApplyStateToWidgets(widgets)
@@ -4823,6 +5073,8 @@ local function MTH_BOOK_ResetAllFiltersAndRefresh()
if widgets.abilityDropdown then MTH_BOOK_UpdateAbilityDropdownText() end
if widgets.rankDropdown then MTH_BOOK_UpdateRankDropdownText() end
if widgets.petLearnSourceDropdown then MTH_BOOK_UpdatePetLearnSourceDropdownText() end
if widgets.beastContinentDropdown then MTH_BOOK_UpdateBeastContinentDropdownText() end
if widgets.beastZoneDropdown then MTH_BOOK_UpdateBeastZoneDropdownText() end
if widgets.itemSubtypeDropdown then MTH_BOOK_UpdateItemSubtypeDropdownText() end
if widgets.npcFunctionDropdown or widgets.npcZoneDropdown then MTH_BOOK_UpdateNPCDropdownTexts() end
@@ -4830,6 +5082,71 @@ local function MTH_BOOK_ResetAllFiltersAndRefresh()
MTH_BOOK_RefreshFilter()
end
local function MTH_BOOK_WireBeastDropdowns()
local continentDD = getglobal("MTH_BOOK_BeastContinentDropdown")
if continentDD then
UIDropDownMenu_Initialize(continentDD, function()
for i = 1, table.getn(MTH_BOOK_BeastContinentOptions) do
local option = MTH_BOOK_BeastContinentOptions[i]
local info = MTH_BOOK_DropdownCreateInfo()
info.text = (option.label ~= "") and option.label or "(any)"
info.func = function()
MTH_BOOK_STATE.petContinent = option.value
MTH_BOOK_STATE.petZoneFilter = "all"
MTH_BOOK_STATE.page = 1
MTH_BOOK_STATE.selectedEntry = nil
MTH_BOOK_DropdownSetText(continentDD, info.text)
if option.value ~= "all" then
MTH_BOOK_BuildBeastZoneOptions(option.value)
local zoneDD = getglobal("MTH_BOOK_BeastZoneDropdown")
if zoneDD then
UIDropDownMenu_Initialize(zoneDD, MTH_BOOK_STATE._beastZoneInitFunc)
zoneDD:Show()
MTH_BOOK_UpdateBeastZoneDropdownText()
end
else
local zoneDD = getglobal("MTH_BOOK_BeastZoneDropdown")
if zoneDD then zoneDD:Hide() end
end
MTH_BOOK_RefreshFilter()
end
info.checked = (MTH_BOOK_STATE.petContinent == option.value)
UIDropDownMenu_AddButton(info)
end
end)
UIDropDownMenu_SetWidth(80, continentDD)
UIDropDownMenu_JustifyText("LEFT", continentDD)
MTH_BOOK_UpdateBeastContinentDropdownText()
end
local zoneInitFunc = function()
local zoneDD = getglobal("MTH_BOOK_BeastZoneDropdown")
for i = 1, table.getn(MTH_BOOK_BeastZoneOptions) do
local option = MTH_BOOK_BeastZoneOptions[i]
local info = MTH_BOOK_DropdownCreateInfo()
info.text = option.label
info.func = function()
MTH_BOOK_STATE.petZoneFilter = option.value
MTH_BOOK_STATE.page = 1
MTH_BOOK_STATE.selectedEntry = nil
if zoneDD then MTH_BOOK_DropdownSetText(zoneDD, option.label) end
MTH_BOOK_RefreshFilter()
end
info.checked = (MTH_BOOK_STATE.petZoneFilter == option.value)
UIDropDownMenu_AddButton(info)
end
end
MTH_BOOK_STATE._beastZoneInitFunc = zoneInitFunc
local zoneDD = getglobal("MTH_BOOK_BeastZoneDropdown")
if zoneDD then
UIDropDownMenu_Initialize(zoneDD, zoneInitFunc)
UIDropDownMenu_SetWidth(120, zoneDD)
UIDropDownMenu_JustifyText("LEFT", zoneDD)
MTH_BOOK_UpdateBeastZoneDropdownText()
end
end
local function MTH_BOOK_WireUI(frame)
if frame and frame.SetBackdropColor then
frame:SetBackdropColor(0.05, 0.05, 0.05, 1.00)
@@ -4851,12 +5168,6 @@ local function MTH_BOOK_WireUI(frame)
local requireVendor = getglobal("MTH_BOOK_RequireVendor")
local requireDrop = getglobal("MTH_BOOK_RequireDrop")
local requireObject = getglobal("MTH_BOOK_RequireObject")
local abilityDropdown = getglobal("MTH_BOOK_AbilityDropdown")
local rankDropdown = getglobal("MTH_BOOK_RankDropdown")
local petLearnSourceDropdown = getglobal("MTH_BOOK_PetLearnSourceDropdown")
local itemSubtypeDropdown = getglobal("MTH_BOOK_ItemSubtypeDropdown")
local npcFunctionDropdown = getglobal("MTH_BOOK_NPCFunctionDropdown")
local npcZoneDropdown = getglobal("MTH_BOOK_NPCZoneDropdown")
local petBookScanButton = getglobal("MTH_BOOK_PetBookScanButton")
local hideNoAbilities = getglobal("MTH_BOOK_HideNoAbilities")
local hideUnknown = getglobal("MTH_BOOK_HideUnknown")
@@ -4917,6 +5228,7 @@ local function MTH_BOOK_WireUI(frame)
MTH_BOOK_InitFamilyDropdown()
MTH_BOOK_InitAbilityDropdown()
MTH_BOOK_InitRankDropdown()
MTH_BOOK_WireBeastDropdowns()
MTH_BOOK_InitPetLearnSourceDropdown()
MTH_BOOK_InitItemSubtypeDropdown()
MTH_BOOK_InitNPCFunctionDropdown()
+24 -5
View File
@@ -57,6 +57,14 @@
</Anchors>
</FontString>
<FontString name="$parentBeastZoneLabel" inherits="GameFontNormalSmall" text="Zone:" hidden="true">
<Anchors>
<Anchor point="TOPLEFT">
<Offset><AbsDimension x="464" y="-142"/></Offset>
</Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
@@ -74,7 +82,7 @@
</Frame>
<Frame name="$parentDetailBackdrop">
<Size><AbsDimension x="136" y="324"/></Size>
<Size><AbsDimension x="136" y="155"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="608" y="-166"/></Offset></Anchor>
</Anchors>
@@ -85,7 +93,7 @@
</Backdrop>
<Frames>
<EditBox name="$parentDetailText" autoFocus="false" multiLine="true" text="Select an entry to view details.">
<Size><AbsDimension x="120" y="306"/></Size>
<Size><AbsDimension x="120" y="139"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="8" y="-8"/></Offset></Anchor>
</Anchors>
@@ -96,7 +104,7 @@
<Button name="$parentOpenMapButton" inherits="UIPanelButtonTemplate" text="Open Map" hidden="true">
<Size><AbsDimension x="136" y="22"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="608" y="-496"/></Offset></Anchor>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="608" y="-524"/></Offset></Anchor>
</Anchors>
</Button>
@@ -265,7 +273,7 @@
</Frame>
<CheckButton name="$parentPetInZoneOnly" inherits="UICheckButtonTemplate" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="488" y="-112"/></Offset></Anchor>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="526" y="-106"/></Offset></Anchor>
</Anchors>
</CheckButton>
<Button name="$parentShowAllOnMapButton" inherits="UIPanelButtonTemplate" text="Show all on map" hidden="true">
@@ -298,8 +306,19 @@
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="232" y="-140"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="$parentHideUnknown" inherits="UICheckButtonTemplate">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="372" y="-140"/></Offset></Anchor></Anchors>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="290" y="-140"/></Offset></Anchor></Anchors>
</CheckButton>
<Frame name="$parentBeastContinentDropdown" inherits="UIDropDownMenuTemplate" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="482" y="-136"/></Offset></Anchor>
</Anchors>
</Frame>
<Frame name="$parentBeastZoneDropdown" inherits="UIDropDownMenuTemplate" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="592" y="-136"/></Offset></Anchor>
</Anchors>
</Frame>
<Frame name="$parentListSliderBackdrop">
<Size><AbsDimension x="22" y="322"/></Size>
+46 -22
View File
@@ -12,6 +12,7 @@ MTH_Map = {
mapZoneCache = {},
focusNodes = {},
pendingZoneOpenId = nil,
pendingZoneOpenPfqId = nil,
pendingZoneOpenSourceId = nil,
pendingZoneOpenAttempts = 0,
pendingZoneOpenNextAttemptAt = 0,
@@ -60,11 +61,6 @@ local function MTH_Map_Mod(a, b)
return 0
end
local function MTH_Map_Log(msg)
if MTH and MTH.debug and MTH.Print then
MTH:Print("[MAP] " .. tostring(msg), "debug")
end
end
local function MTH_Map_NormalizeName(name)
if not name then return "" end
@@ -130,12 +126,18 @@ local function MTH_Map_GetZoneNameById(zoneId)
local normalizedId = tonumber(zoneId) or zoneId
if not normalizedId then return nil end
if MTH_DS_ZoneNamesFallback and MTH_DS_ZoneNamesFallback[normalizedId] then
return MTH_DS_ZoneNamesFallback[normalizedId]
-- 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
if MTH_DS_ZoneNamesFallback and MTH_DS_ZoneNamesFallback[pfqId] then
return MTH_DS_ZoneNamesFallback[pfqId]
end
if not MTH_DS_Zones then return nil end
local row = MTH_DS_Zones[normalizedId] or MTH_DS_Zones[tostring(normalizedId)]
local row = MTH_DS_Zones[pfqId] or MTH_DS_Zones[tostring(pfqId)]
if not row or not row.names then return nil end
return row.names.enUS or row.names[GetLocale()] or row.names.deDE or row.names.frFR
end
@@ -327,7 +329,6 @@ function MTH_Map:BuildZoneLookup()
for _ in pairs(self.zoneNameNormToId) do
count = count + 1
end
MTH_Map_Log("Zone lookup rebuilt: " .. tostring(count) .. " entries")
end
function MTH_Map:GetMapIDByName(name)
@@ -418,6 +419,15 @@ 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()
local continent = GetCurrentMapContinent and GetCurrentMapContinent() or nil
local zone = GetCurrentMapZone and GetCurrentMapZone() or nil
@@ -434,6 +444,7 @@ function MTH_Map:GetCurrentMapID()
if mapId then
local resolved = self:ResolveZoneHierarchy(mapId)
local normalized = resolved.zoneId or mapId
normalized = MTH_Map_PfqToWma(normalized)
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
@@ -449,6 +460,7 @@ function MTH_Map:GetCurrentMapID()
mapId = self:GetMapIDByName(real)
local resolved = self:ResolveZoneHierarchy(mapId)
local normalized = resolved.zoneId or mapId
normalized = MTH_Map_PfqToWma(normalized)
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
@@ -467,6 +479,7 @@ end
local function MTH_Map_ClearPendingZoneOpen()
MTH_Map.pendingZoneOpenId = nil
MTH_Map.pendingZoneOpenPfqId = nil
MTH_Map.pendingZoneOpenSourceId = nil
MTH_Map.pendingZoneOpenAttempts = 0
MTH_Map.pendingZoneOpenNextAttemptAt = 0
@@ -532,8 +545,12 @@ end
function MTH_Map:OpenWorldMapForZone(zoneId)
local zid = tonumber(zoneId)
if not zid then return false end
local resolved = self:ResolveZoneHierarchy(zid)
local targetZoneId = tonumber(resolved.zoneId or zid) or zid
-- Translate WMA ID → pfQ so game map APIs (pfMap, SetMapZoom) work correctly.
local pfqZid = (type(MTH_MAP_WMA_TO_PFQ) == "table" and MTH_MAP_WMA_TO_PFQ[zid]) or zid
local resolved = self:ResolveZoneHierarchy(pfqZid)
local targetZoneId = tonumber(resolved.zoneId or pfqZid) or pfqZid
-- WMA ID of this zone, for comparing against GetCurrentMapID() which returns WMA IDs.
local targetWmaId = (type(MTH_MAP_PFQ_TO_WMA) == "table" and MTH_MAP_PFQ_TO_WMA[targetZoneId]) or zid
if ToggleWorldMap then
if not WorldMapFrame or not WorldMapFrame:IsShown() then
@@ -546,34 +563,39 @@ function MTH_Map:OpenWorldMapForZone(zoneId)
if pfMap and pfMap.SetMapByID then
pfMap:SetMapByID(targetZoneId)
local currentAfterPF = self:GetCurrentMapID()
if tonumber(currentAfterPF) == tonumber(targetZoneId) then
if tonumber(currentAfterPF) == tonumber(targetWmaId) then
MTH_Map_ClearPendingZoneOpen()
return true
end
end
local zoomApplied = MTH_Map_ApplyWorldMapZone(targetZoneId, zid)
local zoomApplied = MTH_Map_ApplyWorldMapZone(zid, zid)
if zoomApplied then
local currentAfterZoom = self:GetCurrentMapID()
if tonumber(currentAfterZoom) == tonumber(targetZoneId) then
if tonumber(currentAfterZoom) == tonumber(targetWmaId) then
MTH_Map_ClearPendingZoneOpen()
return true
end
end
local nowMapId = self:GetCurrentMapID()
if tonumber(nowMapId) == tonumber(targetZoneId) then
if tonumber(nowMapId) == tonumber(targetWmaId) then
MTH_Map_ClearPendingZoneOpen()
return true
end
self.pendingZoneOpenId = targetZoneId
self.pendingZoneOpenSourceId = zid
self.pendingZoneOpenId = targetWmaId
self.pendingZoneOpenPfqId = targetZoneId
self.pendingZoneOpenSourceId = pfqZid
self.pendingZoneOpenAttempts = 8
self.pendingZoneOpenNextAttemptAt = (GetTime and GetTime() or 0) + 0.25
return true
end
function MTH_Map:GetFamilyColor(family)
return MTH_Map_GetFamilyColor(family)
end
function MTH_Map:FocusBeast(beastId, beast)
local id = tonumber(beastId)
if not id then return false end
@@ -890,7 +912,6 @@ function MTH_Map:RebuildNodes()
end
end
MTH_Map_Log("Rebuilt nodes for source '" .. tostring(self.activeSource) .. "'")
end
function MTH_Map:UpdateWorldMap()
@@ -1019,6 +1040,11 @@ function MTH_Map:UpdateMinimap()
local minimapSizes = MTH_DS_MinimapSizes or (pfDB and pfDB["minimap"])
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]
end
if not mapSize then
self.lastMiniReason = self.verboseMiniReasons and "missing-minimap-size" or "missing-size"
self._lastMiniState = nil
@@ -1227,7 +1253,6 @@ function MTH_Map:RegisterDefaultProviders()
end
if unresolvedCount > 0 then
MTH_Map_Log("drops source: " .. tostring(unresolvedCount) .. " drop-creature links have no loaded coordinates (sample IDs: " .. table.concat(unresolvedSamples, ", ") .. ")")
end
return nodes
@@ -1274,9 +1299,9 @@ function MTH_Map:Init()
if WorldMapFrame and WorldMapFrame:IsShown() then
if pfMap and pfMap.SetMapByID then
pfMap:SetMapByID(MTH_Map.pendingZoneOpenId)
pfMap:SetMapByID(MTH_Map.pendingZoneOpenPfqId or MTH_Map.pendingZoneOpenId)
end
MTH_Map_ApplyWorldMapZone(MTH_Map.pendingZoneOpenId, MTH_Map.pendingZoneOpenSourceId)
MTH_Map_ApplyWorldMapZone(MTH_Map.pendingZoneOpenId or MTH_Map.pendingZoneOpenPfqId, MTH_Map.pendingZoneOpenId or MTH_Map.pendingZoneOpenPfqId)
local nowMapId = MTH_Map:GetCurrentMapID()
if tonumber(nowMapId) == tonumber(MTH_Map.pendingZoneOpenId) then
MTH_Map_ClearPendingZoneOpen()
@@ -1318,5 +1343,4 @@ function MTH_Map:Init()
end)
end
MTH_Map_Log("Map system initialized")
end
+133
View File
@@ -0,0 +1,133 @@
------------------------------------------------------
-- MetaHunt: Experimental Ammunition Options Panel
-- TWoW 1.18.1 — "Nightmares of Ursol"
------------------------------------------------------
local function MTH_EA_OPT_Ready()
return type(MTH_GetFrame) == "function"
and type(MTH_CreateCheckbox) == "function"
and type(MTH_CreateSlider) == "function"
end
local function MTH_EA_OPT_GetCfg()
if type(MTH_SavedVariables) ~= "table" then return {} end
if type(MTH_SavedVariables.modules) ~= "table" then return {} end
return MTH_SavedVariables.modules["expammo"] or {}
end
local MTH_EA_OPT_built = false
-- 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)
if cb then
cb:SetChecked(isChecked and 1 or 0)
cb:SetScript("OnClick", function()
local v = this:GetChecked()
onClick(v == 1 or v == true)
end)
end
return cb
end
-- Helper: section header label
local function MTH_EA_OPT_Header(container, text, yOff)
local fs = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
fs:SetPoint("TOPLEFT", container, "TOPLEFT", 20, yOff)
fs:SetText("|cffff9900" .. text .. "|r")
end
local function MTH_EA_OPT_BuildUI(container)
if MTH_EA_OPT_built then return end
MTH_EA_OPT_built = true
local cfg = MTH_EA_OPT_GetCfg()
-- Title
local title = container:CreateFontString(nil, "ARTWORK", "GameFontHighlightLarge")
title:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -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)."
)
-- ── Section: Module ──────────────────────────────────────
MTH_EA_OPT_Header(container, "Module", -112)
local moduleEnabled = true
if MTH and MTH.IsModuleEnabled then
moduleEnabled = MTH:IsModuleEnabled("expammo", true) and true or false
end
MTH_EA_OPT_CB(container, "MTH_ExpAmmoModuleCB",
"Enable MM Widget module",
-132, moduleEnabled,
function(v)
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)
MTH_EA_OPT_CB(container, "MTH_ExpAmmoShowLnLCB",
"Show Lock and Load cell (top)",
-186, 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 (bottom)",
-210, cfg.showHint ~= false,
function(v) if MTH_ExpAmmo then MTH_ExpAmmo.SetShowHint(v) end end)
MTH_EA_OPT_CB(container, "MTH_ExpAmmoHideOOCCB",
"Hide widget when out of combat",
-234, cfg.hideOOC == true,
function(v) if MTH_ExpAmmo then MTH_ExpAmmo.SetHideOOC(v) end end)
-- ── Section: Appearance ──────────────────────────────────
MTH_EA_OPT_Header(container, "Appearance", -268)
local sizeSlider = MTH_CreateSlider(container, "MTH_ExpAmmoCellSizeSlider",
"Cell Size (px)", 20, 80, 4, -288)
if sizeSlider then
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
MTH_EA_OPT_CB(container, "MTH_ExpAmmoBigCDCB",
"Large cooldown numbers (centered in cell)",
-352, 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.")
end
function MTH_SetupExpAmmoOptions()
if not MTH_EA_OPT_Ready() then return end
local container = MTH_GetFrame("MetaHuntOptionsExpAmmo")
if not container then return end
local ok, err = pcall(MTH_EA_OPT_BuildUI, container)
if not ok and MTH and MTH.Print then
MTH:Print("[MTH ExpAmmo OPTIONS] " .. tostring(err), "error")
end
end
+403
View File
@@ -0,0 +1,403 @@
------------------------------------------------------
-- MetaHunt Profile Management Options Panel
------------------------------------------------------
local MAX_PROFILE_ROWS = 15
local MAX_CHAR_ROWS = 10
local ROW_H = 26
-- Row pools — anonymous frames created once against scroll content, reused each refresh
local gProfileRows = {} -- [i] = { frame, nameText, infoText, loadBtn, delBtn }
local gCharRows = {} -- [i] = { frame, nameText, infoText, copyBtn }
-- Static element refs created once
local gActiveLabel = nil
local gSetupDone = false
-- Pending confirmations — globals so StaticPopup OnAccept can read them
MTH_PROFILE_PENDING_LOAD = nil
MTH_PROFILE_PENDING_DELETE = nil
MTH_PROFILE_PENDING_SAVE = nil
-- ── Static popup dialogs ───────────────────────────────────────────────────
StaticPopupDialogs["MTH_PROFILE_LOAD_CONFIRM"] = {
text = "Load profile \"%s\"?\n\nCurrent settings will be replaced.\nThe UI will reload to apply all changes.",
button1 = "Load & Reload",
button2 = "Cancel",
OnAccept = function()
if MTH_PROFILE_PENDING_LOAD then
local ok, err = MTH_Profile_Load(MTH_PROFILE_PENDING_LOAD)
MTH_PROFILE_PENDING_LOAD = nil
if ok then
ReloadUI()
elseif MTH and MTH.Print then
MTH:Print("Profile load failed: " .. tostring(err), "error")
end
end
end,
timeout = 0, whileDead = 0, hideOnEscape = 1,
}
StaticPopupDialogs["MTH_PROFILE_DELETE_CONFIRM"] = {
text = "Delete profile \"%s\"?\n\nThis cannot be undone.",
button1 = "Delete",
button2 = "Cancel",
OnAccept = function()
if MTH_PROFILE_PENDING_DELETE then
MTH_Profile_Delete(MTH_PROFILE_PENDING_DELETE)
MTH_PROFILE_PENDING_DELETE = nil
MTH_SetupProfilesOptions()
end
end,
timeout = 0, whileDead = 0, hideOnEscape = 1,
}
StaticPopupDialogs["MTH_PROFILE_SAVE_CONFIRM"] = {
text = "Profile \"%s\" already exists.\nOverwrite with current settings?",
button1 = "Overwrite",
button2 = "Cancel",
OnAccept = function()
if MTH_PROFILE_PENDING_SAVE then
MTH_Profile_Save(MTH_PROFILE_PENDING_SAVE)
MTH_PROFILE_PENDING_SAVE = nil
MTH_SetupProfilesOptions()
end
end,
timeout = 0, whileDead = 0, hideOnEscape = 1,
}
-- ── internal helpers ───────────────────────────────────────────────────────
local function PROF_TrimString(s)
if type(s) ~= "string" then return "" end
return string.gsub(s, "^%s*(.-)%s*$", "%1")
end
local function PROF_DoSave(rawName)
local name = PROF_TrimString(rawName)
if string.len(name) == 0 then
if MTH and MTH.Print then MTH:Print("Enter a profile name first.", "error") end
return
end
if string.len(name) > 50 then
if MTH and MTH.Print then MTH:Print("Profile name too long (50 chars max).", "error") end
return
end
local existing = type(MTH_SavedVariables) == "table"
and type(MTH_SavedVariables.profiles) == "table"
and MTH_SavedVariables.profiles[name] ~= nil
if existing then
MTH_PROFILE_PENDING_SAVE = name
StaticPopup_Show("MTH_PROFILE_SAVE_CONFIRM", name)
else
MTH_Profile_Save(name)
if MTH and MTH.Print then MTH:Print("Profile saved: " .. name) end
MTH_SetupProfilesOptions()
end
end
-- ── scroll frames + row pool creation (idempotent) ────────────────────────
local function PROF_EnsureScrollFrames(container)
if not MTH_GetFrame("MetaHuntOptionsProfilesScroll") then
local sf = CreateFrame("ScrollFrame", "MetaHuntOptionsProfilesScroll", container, "UIPanelScrollFrameTemplate")
local sc = CreateFrame("Frame", "MetaHuntOptionsProfilesContent", sf)
sc:SetWidth(510)
sc:SetHeight(MAX_PROFILE_ROWS * ROW_H + 4)
sf:SetScrollChild(sc)
end
if not MTH_GetFrame("MetaHuntOptionsCharScroll") then
local csf = CreateFrame("ScrollFrame", "MetaHuntOptionsCharScroll", container, "UIPanelScrollFrameTemplate")
local csc = CreateFrame("Frame", "MetaHuntOptionsCharContent", csf)
csc:SetWidth(510)
csc:SetHeight(MAX_CHAR_ROWS * ROW_H + 4)
csf:SetScrollChild(csc)
end
end
local function PROF_EnsureProfileRows()
local sc = MTH_GetFrame("MetaHuntOptionsProfilesContent")
if not sc then return end
for i = 1, MAX_PROFILE_ROWS do
if not gProfileRows[i] then
local row = CreateFrame("Frame", nil, sc)
row:SetHeight(ROW_H)
row:SetWidth(510)
row:SetPoint("TOPLEFT", sc, "TOPLEFT", 0, -(i - 1) * ROW_H)
local nameText = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
nameText:SetPoint("LEFT", row, "LEFT", 4, 2)
nameText:SetWidth(210)
nameText:SetJustifyH("LEFT")
local infoText = row:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
infoText:SetPoint("LEFT", row, "LEFT", 218, 2)
infoText:SetWidth(155)
infoText:SetJustifyH("LEFT")
infoText:SetTextColor(0.55, 0.55, 0.55)
local loadBtn = CreateFrame("Button", nil, row, "UIPanelButtonTemplate")
loadBtn:SetWidth(58)
loadBtn:SetHeight(20)
loadBtn:SetPoint("RIGHT", row, "RIGHT", -62, 0)
loadBtn:SetText("Load")
local delBtn = CreateFrame("Button", nil, row, "UIPanelButtonTemplate")
delBtn:SetWidth(58)
delBtn:SetHeight(20)
delBtn:SetPoint("RIGHT", row, "RIGHT", 0, 0)
delBtn:SetText("Delete")
gProfileRows[i] = { frame = row, nameText = nameText, infoText = infoText, loadBtn = loadBtn, delBtn = delBtn }
end
end
end
local function PROF_EnsureCharRows()
local csc = MTH_GetFrame("MetaHuntOptionsCharContent")
if not csc then return end
for i = 1, MAX_CHAR_ROWS do
if not gCharRows[i] then
local row = CreateFrame("Frame", nil, csc)
row:SetHeight(ROW_H)
row:SetWidth(510)
row:SetPoint("TOPLEFT", csc, "TOPLEFT", 0, -(i - 1) * ROW_H)
local nameText = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
nameText:SetPoint("LEFT", row, "LEFT", 4, 2)
nameText:SetWidth(250)
nameText:SetJustifyH("LEFT")
nameText:SetTextColor(0.70, 0.85, 1.00)
local infoText = row:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
infoText:SetPoint("LEFT", row, "LEFT", 258, 2)
infoText:SetWidth(120)
infoText:SetJustifyH("LEFT")
infoText:SetTextColor(0.55, 0.55, 0.55)
local copyBtn = CreateFrame("Button", nil, row, "UIPanelButtonTemplate")
copyBtn:SetWidth(80)
copyBtn:SetHeight(20)
copyBtn:SetPoint("RIGHT", row, "RIGHT", 0, 0)
copyBtn:SetText("Import")
gCharRows[i] = { frame = row, nameText = nameText, infoText = infoText, copyBtn = copyBtn }
end
end
end
-- ── dynamic refresh functions ──────────────────────────────────────────────
local function PROF_RefreshActive()
if not gActiveLabel then return end
local active = type(MTH_Profile_GetActive) == "function" and MTH_Profile_GetActive()
if active then
gActiveLabel:SetText("Active: |cffffd200" .. active .. "|r")
gActiveLabel:SetTextColor(0.90, 0.90, 0.90)
else
gActiveLabel:SetText("Active: |cff888888Custom (unsaved)|r")
gActiveLabel:SetTextColor(0.90, 0.90, 0.90)
end
gActiveLabel:Show()
end
local function PROF_RefreshProfileList()
PROF_EnsureProfileRows()
local profiles = type(MTH_Profile_GetList) == "function" and MTH_Profile_GetList() or {}
local active = type(MTH_Profile_GetActive) == "function" and MTH_Profile_GetActive()
local n = table.getn(profiles)
local sc = MTH_GetFrame("MetaHuntOptionsProfilesContent")
if sc then
sc:SetHeight(math.max(n * ROW_H + 4, ROW_H))
end
for i = 1, MAX_PROFILE_ROWS do
local row = gProfileRows[i]
if not row then break end
local name = profiles[i]
if name then
-- Name (with active indicator)
if name == active then
row.nameText:SetText(name .. " |cff00cc44[active]|r")
else
row.nameText:SetText(name)
end
-- Info: date saved by who
local p = type(MTH_SavedVariables) == "table"
and type(MTH_SavedVariables.profiles) == "table"
and MTH_SavedVariables.profiles[name]
local info = ""
if type(p) == "table" then
if p.savedAt and p.savedAt ~= "" then
info = p.savedAt
end
end
row.infoText:SetText(info)
-- Wire buttons (SetScript replaces previous handler each refresh)
local rowName = name
row.loadBtn:SetScript("OnClick", function()
MTH_PROFILE_PENDING_LOAD = rowName
StaticPopup_Show("MTH_PROFILE_LOAD_CONFIRM", rowName)
end)
row.delBtn:SetScript("OnClick", function()
MTH_PROFILE_PENDING_DELETE = rowName
StaticPopup_Show("MTH_PROFILE_DELETE_CONFIRM", rowName)
end)
row.frame:Show()
row.loadBtn:Show()
row.delBtn:Show()
else
row.frame:Hide()
end
end
end
local function PROF_RefreshCharList()
PROF_EnsureCharRows()
local chars = type(MTH_Profile_GetCharList) == "function" and MTH_Profile_GetCharList() or {}
local n = table.getn(chars)
local csc = MTH_GetFrame("MetaHuntOptionsCharContent")
if csc then
csc:SetHeight(math.max(n * ROW_H + 4, ROW_H))
end
for i = 1, MAX_CHAR_ROWS do
local row = gCharRows[i]
if not row then break end
local charKey = chars[i]
if charKey then
local snap = type(MTH_SavedVariables) == "table"
and type(MTH_SavedVariables.charSnapshots) == "table"
and MTH_SavedVariables.charSnapshots[charKey]
local info = (type(snap) == "table" and snap.savedAt) and snap.savedAt or ""
row.nameText:SetText(charKey)
row.infoText:SetText(info)
local ck = charKey
row.copyBtn:SetScript("OnClick", function()
local ok, pName = MTH_Profile_CopyFromChar(ck)
if ok then
MTH_SetupProfilesOptions()
if MTH and MTH.Print then
MTH:Print('Imported "' .. ck .. '" as profile "' .. tostring(pName) .. '".')
end
end
end)
row.frame:Show()
row.copyBtn:Show()
else
row.frame:Hide()
end
end
end
-- ── main setup (static elements created once, dynamic lists refreshed every call) ──
function MTH_SetupProfilesOptions()
local container = MTH_GetFrame("MetaHuntOptionsProfiles")
if not container then return end
if not gSetupDone then
-- Title
local title = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
title:SetPoint("TOPLEFT", container, "TOPLEFT", 6, -6)
title:SetText("Profiles")
title:SetTextColor(1.00, 0.82, 0.00)
-- Active profile indicator
gActiveLabel = container:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
gActiveLabel:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -4)
gActiveLabel:SetWidth(540)
gActiveLabel:SetJustifyH("LEFT")
-- ── Save As section ───────────────────────────────────────────────
local saveHdr = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
saveHdr:SetPoint("TOPLEFT", gActiveLabel, "BOTTOMLEFT", 0, -10)
saveHdr:SetText("Save current settings as:")
saveHdr:SetTextColor(0.90, 0.90, 0.90)
local eb = CreateFrame("EditBox", "MTH_ProfileNameInput", container, "InputBoxTemplate")
eb:SetWidth(240)
eb:SetHeight(20)
eb:SetMaxLetters(50)
eb:SetAutoFocus(false)
eb:SetPoint("TOPLEFT", saveHdr, "BOTTOMLEFT", 2, -6)
eb:SetScript("OnEnterPressed", function()
PROF_DoSave(this:GetText())
this:SetText("")
this:ClearFocus()
end)
eb:SetScript("OnEscapePressed", function()
this:SetText("")
this:ClearFocus()
end)
local saveBtn = CreateFrame("Button", "MTH_ProfileSaveBtn", container, "UIPanelButtonTemplate")
saveBtn:SetWidth(70)
saveBtn:SetHeight(20)
saveBtn:SetPoint("LEFT", eb, "RIGHT", 6, 0)
saveBtn:SetText("Save")
saveBtn:SetScript("OnClick", function()
local box = MTH_GetFrame("MTH_ProfileNameInput")
if box then
PROF_DoSave(box:GetText())
box:SetText("")
box:ClearFocus()
end
end)
-- ── Saved Profiles section ────────────────────────────────────────
local profHdr = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
profHdr:SetPoint("TOPLEFT", eb, "BOTTOMLEFT", -2, -12)
profHdr:SetText("Saved Profiles")
profHdr:SetTextColor(1.00, 0.82, 0.00)
local rule1 = container:CreateTexture(nil, "ARTWORK")
rule1:SetPoint("TOPLEFT", profHdr, "BOTTOMLEFT", 0, -3)
rule1:SetWidth(540)
rule1:SetHeight(1)
rule1:SetTexture(0.30, 0.30, 0.30, 0.80)
PROF_EnsureScrollFrames(container)
local sf = MTH_GetFrame("MetaHuntOptionsProfilesScroll")
sf:SetPoint("TOPLEFT", rule1, "BOTTOMLEFT", 0, -4)
sf:SetWidth(530)
sf:SetHeight(170)
sf:Show()
-- ── Copy from Character section ───────────────────────────────────
local charHdr = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
charHdr:SetPoint("TOPLEFT", sf, "BOTTOMLEFT", 0, -10)
charHdr:SetText("Copy from Character")
charHdr:SetTextColor(1.00, 0.82, 0.00)
local charNote = container:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
charNote:SetPoint("TOPLEFT", charHdr, "BOTTOMLEFT", 0, -2)
charNote:SetWidth(540)
charNote:SetJustifyH("LEFT")
charNote:SetText("Snapshots are saved automatically on logout. Click Import to create a profile from one.")
charNote:SetTextColor(0.60, 0.60, 0.60)
local rule2 = container:CreateTexture(nil, "ARTWORK")
rule2:SetPoint("TOPLEFT", charNote, "BOTTOMLEFT", 0, -3)
rule2:SetWidth(540)
rule2:SetHeight(1)
rule2:SetTexture(0.30, 0.30, 0.30, 0.80)
local csf = MTH_GetFrame("MetaHuntOptionsCharScroll")
csf:SetPoint("TOPLEFT", rule2, "BOTTOMLEFT", 0, -4)
csf:SetWidth(530)
csf:SetHeight(140)
csf:Show()
gSetupDone = true
end
-- Refresh dynamic content every time the panel is opened
PROF_RefreshActive()
PROF_RefreshProfileList()
PROF_RefreshCharList()
end
+14 -2
View File
@@ -3,8 +3,8 @@ local MTH_OPTIONS_SETUP = {} -- Track which frames have been set up
MTH_OPTIONS_CONST = MTH_OPTIONS_CONST or {}
MTH_OPTIONS_CONST.NAV_DEFAULT_WIDTH = MTH_OPTIONS_CONST.NAV_DEFAULT_WIDTH or 120
MTH_OPTIONS_CONST.WINDOW_PADDING = MTH_OPTIONS_CONST.WINDOW_PADDING or 12
MTH_OPTIONS_CONST.CONTENT_TOP_OFFSET = MTH_OPTIONS_CONST.CONTENT_TOP_OFFSET or -72
MTH_OPTIONS_CONST.CONTENT_HEIGHT = MTH_OPTIONS_CONST.CONTENT_HEIGHT or 540
MTH_OPTIONS_CONST.CONTENT_TOP_OFFSET = MTH_OPTIONS_CONST.CONTENT_TOP_OFFSET or -58
MTH_OPTIONS_CONST.CONTENT_HEIGHT = MTH_OPTIONS_CONST.CONTENT_HEIGHT or 566
MTH_OPTIONS_CONST.CLOSE_BTN_SIZE = MTH_OPTIONS_CONST.CLOSE_BTN_SIZE or 22
MTH_OPTIONS_CONST.CLOSE_BTN_OFFSET_X = MTH_OPTIONS_CONST.CLOSE_BTN_OFFSET_X or -8
MTH_OPTIONS_CONST.CLOSE_BTN_OFFSET_Y = MTH_OPTIONS_CONST.CLOSE_BTN_OFFSET_Y or -8
@@ -117,6 +117,11 @@ function MTH_SelectOptionsTab(tabKey)
MTH_SetupGeneralOptions()
MTH_OPTIONS_SETUP["General"] = true
end
elseif tabKey == "Profiles" then
if type(MTH_SetupProfilesOptions) == "function" then
MTH_SetupProfilesOptions()
MTH_OPTIONS_SETUP["Profiles"] = true
end
elseif tabKey == "Messages" then
if not MTH_OPTIONS_SETUP["Messages"] then
if type(MTH_SetupMessagesOptions) == "function" then
@@ -200,6 +205,13 @@ function MTH_SelectOptionsTab(tabKey)
MTH_SetupAutoQuestOptions()
MTH_OPTIONS_SETUP["AutoQuest"] = true
end
elseif tabKey == "ExpAmmo" then
if not MTH_OPTIONS_SETUP["ExpAmmo"] then
if type(MTH_SetupExpAmmoOptions) == "function" then
MTH_SetupExpAmmoOptions()
MTH_OPTIONS_SETUP["ExpAmmo"] = true
end
end
elseif tabKey == "ICU" then
if type(MTH_SetupICUOptions) == "function" then
MTH_SetupICUOptions()
+6 -2
View File
@@ -27,7 +27,8 @@ MTH_OPTIONS_CONST = MTH_OPTIONS_CONST or {
}
MTH_OPTIONS_TABS = MTH_OPTIONS_TABS or {
{ key = "General", label = "General", frame = "MetaHuntOptionsGeneral" },
{ 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 = "Track", label = "ZTrack", frame = "MetaHuntOptionsTrack" },
@@ -43,6 +44,7 @@ MTH_OPTIONS_TABS = MTH_OPTIONS_TABS or {
{ key = "AutoBuy", label = "Auto Buy", frame = "MetaHuntOptionsAutoBuy" },
{ key = "AutoQuest", label = "Auto Quest", frame = "MetaHuntOptionsAutoQuest" },
{ key = "ICU", label = "ICU", frame = "MetaHuntOptionsICU" },
{ key = "ExpAmmo", label = "MM Widget", frame = "MetaHuntOptionsExpAmmo" },
{ key = "ChronometerGeneral", label = "General", frame = "MetaHuntOptionsChronometer" },
{ key = "ChronometerBar", label = "Bar", frame = "MetaHuntOptionsChronometer" },
{ key = "ChronometerClassSpells", label = "Hunter Spells", frame = "MetaHuntOptionsChronometer" },
@@ -52,12 +54,14 @@ MTH_OPTIONS_TABS = MTH_OPTIONS_TABS or {
}
MTH_OPTIONS_TREE = MTH_OPTIONS_TREE or {
{ label = "General", key = "General" },
{ label = "General", key = "General" },
{ label = "Profiles", key = "Profiles" },
{ label = "Messages", key = "Messages" },
{ label = "Auto Buy", key = "AutoBuy" },
{ label = "Auto Quest", key = "AutoQuest" },
{ label = "Feed-O-Matic", key = "FeedOMatic" },
{ label = "ICU", key = "ICU" },
{ label = "MM Widget", key = "ExpAmmo" },
{
label = "zButtons",
node = "zButtons",
+4 -2
View File
@@ -46,12 +46,12 @@
<Frames>
<Frame name="$parentNav" enableMouse="true">
<Size>
<AbsDimension x="120" y="540"/>
<AbsDimension x="120" y="566"/>
</Size>
<Anchors>
<Anchor point="TOPLEFT">
<Offset>
<AbsDimension x="12" y="-72"/>
<AbsDimension x="12" y="-58"/>
</Offset>
</Anchor>
</Anchors>
@@ -66,6 +66,7 @@
<!-- Content Areas -->
<Frame name="$parentGeneral" hidden="true" enableMouse="true"/>
<Frame name="$parentProfiles" hidden="true" enableMouse="true"/>
<Frame name="$parentMessages" hidden="true" enableMouse="true"/>
<Frame name="$parentPet" hidden="true" enableMouse="true"/>
<Frame name="$parentTrack" hidden="true" enableMouse="true"/>
@@ -81,6 +82,7 @@
<Frame name="$parentAutoBuy" hidden="true" enableMouse="true"/>
<Frame name="$parentAutoQuest" hidden="true" enableMouse="true"/>
<Frame name="$parentICU" hidden="true" enableMouse="true"/>
<Frame name="$parentExpAmmo" hidden="true" enableMouse="true"/>
<Frame name="$parentChronometer" hidden="true" enableMouse="true"/>
<Frame name="$parentCredits" hidden="true" enableMouse="true"/>
+3
View File
@@ -6,6 +6,9 @@ function MTH_ST_LifecycleService:HandleEvent(evt, eventArg1, eventArg2)
if type(MTH_ST_HandlePlayerEnteringWorld) == "function" and MTH_ST_HandlePlayerEnteringWorld(evt) then
return true, "lifecycle-enter-world"
end
if type(MTH_ST_HandleSpellcastEvent) == "function" and MTH_ST_HandleSpellcastEvent(evt, eventArg1, eventArg2) then
return true, "lifecycle-spellcast"
end
if type(MTH_ST_HandleUnitPetEvent) == "function" and MTH_ST_HandleUnitPetEvent(evt, eventArg1) then
if evt == "UNIT_PET" then
if eventArg1 == "player" then
+226
View File
@@ -0,0 +1,226 @@
------------------------------------------------------
-- MetaHunt Profile System
-- Captures:
-- MTH_SavedVariables.modules (account-wide option values, e.g. feedomatic)
-- MTH_CharSavedVariables.modules (per-char option values: ICU, Chronometer,
-- AutoBuy, AutoQuest, SmartAmmo, tooltips...)
-- MTH_CharSavedVariables.moduleStates (enabled/disabled per char)
-- ZHunterMod_Saved / MTH_CharSavedVariables.modules.zhunter (zButtons layout)
-- Auto-snapshots current character on PLAYER_LOGOUT into
-- MTH_SavedVariables.charSnapshots["CharName-RealmName"]
------------------------------------------------------
local function MTH_Profile_DeepCopy(orig)
if type(orig) ~= "table" then return orig end
local copy = {}
for k, v in orig do
copy[MTH_Profile_DeepCopy(k)] = MTH_Profile_DeepCopy(v)
end
return copy
end
-- Shallow-copy a module's saved data, skipping keys that hold large operational
-- blobs (e.g. feedomatic's "legacy" table which mirrors FOM_Cooking, FOM_QuestFood
-- etc.) — those are runtime migration artifacts, not user configuration settings.
local MTH_PROFILE_SKIP_KEYS = { legacy = true, history = true }
local function MTH_Profile_CopyModules(src)
if type(src) ~= "table" then return {} end
local out = {}
for modName, modData in src do
if type(modData) == "table" then
local modCopy = {}
for k, v in modData do
if not MTH_PROFILE_SKIP_KEYS[k] then
modCopy[MTH_Profile_DeepCopy(k)] = MTH_Profile_DeepCopy(v)
end
end
out[modName] = modCopy
else
out[modName] = modData
end
end
return out
end
local function MTH_Profile_EnsureStore()
if type(MTH_SavedVariables) ~= "table" then
MTH_SavedVariables = {}
end
if type(MTH_SavedVariables.profiles) ~= "table" then
MTH_SavedVariables.profiles = {}
end
if type(MTH_SavedVariables.charSnapshots) ~= "table" then
MTH_SavedVariables.charSnapshots = {}
end
end
local function MTH_Profile_GetCharKey()
local name = (type(UnitName) == "function") and UnitName("player") or "Unknown"
local realm = (type(GetRealmName) == "function") and GetRealmName() or "Unknown"
name = (name and name ~= "") and name or "Unknown"
realm = (realm and realm ~= "") and realm or "Unknown"
return name .. "-" .. realm
end
local function MTH_Profile_BuildSnapshot()
local snap = {}
snap.savedBy = MTH_Profile_GetCharKey()
snap.savedAt = (type(date) == "function") and date("%Y-%m-%d %H:%M") or ""
-- Account-wide module option values (e.g. feedomatic)
snap.config = (type(MTH_SavedVariables) == "table"
and type(MTH_SavedVariables.modules) == "table")
and MTH_Profile_CopyModules(MTH_SavedVariables.modules)
or {}
-- Per-character module option values (ICU, Chronometer, AutoBuy, SmartAmmo, etc.)
snap.charConfig = (type(MTH_CharSavedVariables) == "table"
and type(MTH_CharSavedVariables.modules) == "table")
and MTH_Profile_CopyModules(MTH_CharSavedVariables.modules)
or {}
-- Per-character module enabled/disabled states
snap.moduleStates = (type(MTH_CharSavedVariables) == "table"
and type(MTH_CharSavedVariables.moduleStates) == "table")
and MTH_Profile_DeepCopy(MTH_CharSavedVariables.moduleStates)
or {}
-- zButtons config — kept as dedicated key for clarity
-- (also lives in charConfig.zhunter but we keep it explicit)
local zh = nil
if type(MTH_CharSavedVariables) == "table"
and type(MTH_CharSavedVariables.modules) == "table"
and type(MTH_CharSavedVariables.modules.zhunter) == "table"
then
zh = MTH_CharSavedVariables.modules.zhunter
elseif type(ZHunterMod_Saved) == "table" then
zh = ZHunterMod_Saved
end
snap.zhunter = zh and MTH_Profile_DeepCopy(zh) or {}
return snap
end
-- Save current settings as a named profile
function MTH_Profile_Save(name)
if type(name) ~= "string" or string.len(name) == 0 then
return false, "invalid name"
end
MTH_Profile_EnsureStore()
MTH_SavedVariables.profiles[name] = MTH_Profile_BuildSnapshot()
return true
end
-- Delete a named profile
function MTH_Profile_Delete(name)
MTH_Profile_EnsureStore()
if MTH_SavedVariables.profiles[name] == nil then return false end
MTH_SavedVariables.profiles[name] = nil
if type(MTH_CharSavedVariables) == "table"
and MTH_CharSavedVariables.activeProfile == name
then
MTH_CharSavedVariables.activeProfile = nil
end
return true
end
-- Apply a profile to SavedVariables; a /reload is required for changes to take full effect
function MTH_Profile_Load(name)
MTH_Profile_EnsureStore()
local p = MTH_SavedVariables.profiles[name]
if type(p) ~= "table" then return false, "profile not found" end
if type(MTH_CharSavedVariables) ~= "table" then MTH_CharSavedVariables = {} end
if type(MTH_CharSavedVariables.modules) ~= "table" then MTH_CharSavedVariables.modules = {} end
-- Apply account-wide module option values (e.g. feedomatic)
if type(p.config) == "table" then
if type(MTH_SavedVariables.modules) ~= "table" then
MTH_SavedVariables.modules = {}
end
for modName, modData in p.config do
MTH_SavedVariables.modules[modName] = MTH_Profile_DeepCopy(modData)
end
end
-- Apply per-character module option values (ICU, Chronometer, AutoBuy, SmartAmmo, etc.)
if type(p.charConfig) == "table" then
for modName, modData in p.charConfig do
MTH_CharSavedVariables.modules[modName] = MTH_Profile_DeepCopy(modData)
end
end
-- Apply module enabled/disabled
if type(p.moduleStates) == "table" then
if type(MTH_CharSavedVariables.moduleStates) ~= "table" then
MTH_CharSavedVariables.moduleStates = {}
end
for k, v in p.moduleStates do
MTH_CharSavedVariables.moduleStates[k] = v
end
end
-- Apply zButtons (also written into charConfig.zhunter above, but keep explicit)
if type(p.zhunter) == "table" then
ZHunterMod_Saved = MTH_Profile_DeepCopy(p.zhunter)
MTH_CharSavedVariables.modules.zhunter = ZHunterMod_Saved
end
MTH_CharSavedVariables.activeProfile = name
return true
end
-- Create a named profile from another character's auto-snapshot
-- profileName defaults to charKey if not provided
function MTH_Profile_CopyFromChar(charKey, profileName)
MTH_Profile_EnsureStore()
local snap = MTH_SavedVariables.charSnapshots[charKey]
if type(snap) ~= "table" then
return false, "no snapshot for " .. tostring(charKey)
end
local name = (type(profileName) == "string" and string.len(profileName) > 0)
and profileName or charKey
MTH_SavedVariables.profiles[name] = MTH_Profile_DeepCopy(snap)
return true, name
end
-- Return sorted list of profile names
function MTH_Profile_GetList()
MTH_Profile_EnsureStore()
local list = {}
for name, _ in MTH_SavedVariables.profiles do
table.insert(list, name)
end
table.sort(list)
return list
end
-- Return sorted list of character snapshot keys
function MTH_Profile_GetCharList()
MTH_Profile_EnsureStore()
local list = {}
for charKey, _ in MTH_SavedVariables.charSnapshots do
table.insert(list, charKey)
end
table.sort(list)
return list
end
-- Return the active profile name for the current character, or nil
function MTH_Profile_GetActive()
if type(MTH_CharSavedVariables) == "table" then
return MTH_CharSavedVariables.activeProfile
end
return nil
end
-- Auto-snapshot on logout so other characters can import this config
local MTH_ProfileEventFrame = CreateFrame("Frame", "MTH_ProfileEventFrame")
MTH_ProfileEventFrame:RegisterEvent("PLAYER_LOGOUT")
MTH_ProfileEventFrame:SetScript("OnEvent", function()
if event == "PLAYER_LOGOUT" then
MTH_Profile_EnsureStore()
MTH_SavedVariables.charSnapshots[MTH_Profile_GetCharKey()] = MTH_Profile_BuildSnapshot()
end
end)
+2 -2
View File
@@ -221,8 +221,8 @@ end
function VC:ResetPublishDelay()
local now = VC_GetTimeNow()
if self.lastPublishedAt and (now - self.lastPublishedAt) < 600 then
return -- 10-minute cooldown between publishes
if self.lastPublishedAt and (now - self.lastPublishedAt) < 3600 then
return -- 1-hour cooldown between publishes
end
self.nextPublishAt = now + math.random(10, 20)
end
File diff suppressed because it is too large Load Diff
+329
View File
@@ -0,0 +1,329 @@
-- MetaHunt: Beast family DBC enrichment (scales, food validation)
-- AUTO-GENERATED by .tools — do not edit by hand.
-- Source DBC(s): CreatureFamily.dbc, ItemPetFood.dbc
-- Generated: 2026-03-20 13:57 UTC
if not MTH_DS_Families then return end
-- DBC ID=1 PetFoodMask=208 scale=[0.70-1.00]
if MTH_DS_Families["Wolves"] then
MTH_DS_Families["Wolves"]["dbcId"] = 1
MTH_DS_Families["Wolves"]["minScale"] = 0.7
MTH_DS_Families["Wolves"]["maxScale"] = 1.0
MTH_DS_Families["Wolves"]["minScaleLevel"] = 1
MTH_DS_Families["Wolves"]["maxScaleLevel"] = 60
MTH_DS_Families["Wolves"]["foodMask"] = 208
MTH_DS_Families["Wolves"]["foodDbc"] = {
"fungus",
"raw meat",
"raw fish",
}
end
-- DBC ID=2 PetFoodMask=209 scale=[0.70-1.10]
if MTH_DS_Families["Cats"] then
MTH_DS_Families["Cats"]["dbcId"] = 2
MTH_DS_Families["Cats"]["minScale"] = 0.7
MTH_DS_Families["Cats"]["maxScale"] = 1.1
MTH_DS_Families["Cats"]["minScaleLevel"] = 1
MTH_DS_Families["Cats"]["maxScaleLevel"] = 60
MTH_DS_Families["Cats"]["foodMask"] = 209
MTH_DS_Families["Cats"]["foodDbc"] = {
"meat",
"fungus",
"raw meat",
"raw fish",
}
end
-- DBC ID=3 PetFoodMask=203 scale=[0.40-0.60]
if MTH_DS_Families["Spiders"] then
MTH_DS_Families["Spiders"]["dbcId"] = 3
MTH_DS_Families["Spiders"]["minScale"] = 0.4
MTH_DS_Families["Spiders"]["maxScale"] = 0.6
MTH_DS_Families["Spiders"]["minScaleLevel"] = 1
MTH_DS_Families["Spiders"]["maxScaleLevel"] = 60
MTH_DS_Families["Spiders"]["foodMask"] = 203
MTH_DS_Families["Spiders"]["foodDbc"] = {
"meat",
"fish",
"bread",
"raw meat",
"raw fish",
}
end
-- DBC ID=4 PetFoodMask=210 scale=[0.60-1.00]
if MTH_DS_Families["Bears"] then
MTH_DS_Families["Bears"]["dbcId"] = 4
MTH_DS_Families["Bears"]["minScale"] = 0.6
MTH_DS_Families["Bears"]["maxScale"] = 1.0
MTH_DS_Families["Bears"]["minScaleLevel"] = 1
MTH_DS_Families["Bears"]["maxScaleLevel"] = 60
MTH_DS_Families["Bears"]["foodMask"] = 210
MTH_DS_Families["Bears"]["foodDbc"] = {
"bread",
"cheese",
"fish",
"fruit",
"fungus",
"meat",
"raw fish",
"raw meat",
}
end
-- DBC ID=5 PetFoodMask=211 scale=[0.60-1.00]
if MTH_DS_Families["Boars"] then
MTH_DS_Families["Boars"]["dbcId"] = 5
MTH_DS_Families["Boars"]["minScale"] = 0.6
MTH_DS_Families["Boars"]["maxScale"] = 1.0
MTH_DS_Families["Boars"]["minScaleLevel"] = 1
MTH_DS_Families["Boars"]["maxScaleLevel"] = 60
MTH_DS_Families["Boars"]["foodMask"] = 211
MTH_DS_Families["Boars"]["foodDbc"] = {
"bread",
"cheese",
"fish",
"fruit",
"fungus",
"meat",
"raw fish",
"raw meat",
}
end
-- DBC ID=6 PetFoodMask=212 scale=[0.40-0.60]
if MTH_DS_Families["Crocolisks"] then
MTH_DS_Families["Crocolisks"]["dbcId"] = 6
MTH_DS_Families["Crocolisks"]["minScale"] = 0.4
MTH_DS_Families["Crocolisks"]["maxScale"] = 0.6
MTH_DS_Families["Crocolisks"]["minScaleLevel"] = 1
MTH_DS_Families["Crocolisks"]["maxScaleLevel"] = 60
MTH_DS_Families["Crocolisks"]["foodMask"] = 212
MTH_DS_Families["Crocolisks"]["foodDbc"] = {
"cheese",
"fungus",
"raw meat",
"raw fish",
}
end
-- DBC ID=7 PetFoodMask=213 scale=[0.50-0.90]
if MTH_DS_Families["Carrion Birds"] then
MTH_DS_Families["Carrion Birds"]["dbcId"] = 7
MTH_DS_Families["Carrion Birds"]["minScale"] = 0.5
MTH_DS_Families["Carrion Birds"]["maxScale"] = 0.9
MTH_DS_Families["Carrion Birds"]["minScaleLevel"] = 1
MTH_DS_Families["Carrion Birds"]["maxScaleLevel"] = 60
MTH_DS_Families["Carrion Birds"]["foodMask"] = 213
MTH_DS_Families["Carrion Birds"]["foodDbc"] = {
"meat",
"cheese",
"fungus",
"raw meat",
"raw fish",
}
end
-- DBC ID=8 PetFoodMask=214 scale=[0.70-1.40]
if MTH_DS_Families["Crabs"] then
MTH_DS_Families["Crabs"]["dbcId"] = 8
MTH_DS_Families["Crabs"]["minScale"] = 0.7
MTH_DS_Families["Crabs"]["maxScale"] = 1.4
MTH_DS_Families["Crabs"]["minScaleLevel"] = 1
MTH_DS_Families["Crabs"]["maxScaleLevel"] = 60
MTH_DS_Families["Crabs"]["foodMask"] = 214
MTH_DS_Families["Crabs"]["foodDbc"] = {
"fish",
"cheese",
"fungus",
"raw meat",
"raw fish",
}
end
-- DBC ID=9 PetFoodMask=215 scale=[0.70-1.00]
if MTH_DS_Families["Gorillas"] then
MTH_DS_Families["Gorillas"]["dbcId"] = 9
MTH_DS_Families["Gorillas"]["minScale"] = 0.7
MTH_DS_Families["Gorillas"]["maxScale"] = 1.0
MTH_DS_Families["Gorillas"]["minScaleLevel"] = 1
MTH_DS_Families["Gorillas"]["maxScaleLevel"] = 60
MTH_DS_Families["Gorillas"]["foodMask"] = 215
MTH_DS_Families["Gorillas"]["foodDbc"] = {
"meat",
"fish",
"cheese",
"fungus",
"raw meat",
"raw fish",
}
end
-- DBC ID=11 PetFoodMask=217 scale=[0.50-0.80]
if MTH_DS_Families["Raptors"] then
MTH_DS_Families["Raptors"]["dbcId"] = 11
MTH_DS_Families["Raptors"]["minScale"] = 0.5
MTH_DS_Families["Raptors"]["maxScale"] = 0.8
MTH_DS_Families["Raptors"]["minScaleLevel"] = 1
MTH_DS_Families["Raptors"]["maxScaleLevel"] = 60
MTH_DS_Families["Raptors"]["foodMask"] = 217
MTH_DS_Families["Raptors"]["foodDbc"] = {
"meat",
"bread",
"fungus",
"raw meat",
"raw fish",
}
end
-- DBC ID=12 PetFoodMask=218 scale=[0.50-0.80]
if MTH_DS_Families["Tallstriders"] then
MTH_DS_Families["Tallstriders"]["dbcId"] = 12
MTH_DS_Families["Tallstriders"]["minScale"] = 0.5
MTH_DS_Families["Tallstriders"]["maxScale"] = 0.8
MTH_DS_Families["Tallstriders"]["minScaleLevel"] = 1
MTH_DS_Families["Tallstriders"]["maxScaleLevel"] = 60
MTH_DS_Families["Tallstriders"]["foodMask"] = 218
MTH_DS_Families["Tallstriders"]["foodDbc"] = {
"fish",
"bread",
"fungus",
"raw meat",
"raw fish",
}
end
-- DBC ID=20 PetFoodMask=236 scale=[0.70-1.00]
if MTH_DS_Families["Scorpids"] then
MTH_DS_Families["Scorpids"]["dbcId"] = 20
MTH_DS_Families["Scorpids"]["minScale"] = 0.7
MTH_DS_Families["Scorpids"]["maxScale"] = 1.0
MTH_DS_Families["Scorpids"]["minScaleLevel"] = 1
MTH_DS_Families["Scorpids"]["maxScaleLevel"] = 60
MTH_DS_Families["Scorpids"]["foodMask"] = 236
MTH_DS_Families["Scorpids"]["foodDbc"] = {
"cheese",
"bread",
"fruit",
"raw meat",
"raw fish",
}
end
-- DBC ID=21 PetFoodMask=251 scale=[0.50-0.80]
if MTH_DS_Families["Turtles"] then
MTH_DS_Families["Turtles"]["dbcId"] = 21
MTH_DS_Families["Turtles"]["minScale"] = 0.5
MTH_DS_Families["Turtles"]["maxScale"] = 0.8
MTH_DS_Families["Turtles"]["minScaleLevel"] = 1
MTH_DS_Families["Turtles"]["maxScaleLevel"] = 60
MTH_DS_Families["Turtles"]["foodMask"] = 251
MTH_DS_Families["Turtles"]["foodDbc"] = {
"meat",
"fish",
"bread",
"fungus",
"fruit",
"raw meat",
"raw fish",
}
end
-- DBC ID=24 PetFoodMask=653 scale=[0.40-0.70]
if MTH_DS_Families["Bats"] then
MTH_DS_Families["Bats"]["dbcId"] = 24
MTH_DS_Families["Bats"]["minScale"] = 0.4
MTH_DS_Families["Bats"]["maxScale"] = 0.7
MTH_DS_Families["Bats"]["minScaleLevel"] = 1
MTH_DS_Families["Bats"]["maxScaleLevel"] = 60
MTH_DS_Families["Bats"]["foodMask"] = 653
MTH_DS_Families["Bats"]["foodDbc"] = {
"meat",
"cheese",
"bread",
"raw fish",
}
end
-- DBC ID=25 PetFoodMask=654 scale=[0.70-0.90]
if MTH_DS_Families["Hyenas"] then
MTH_DS_Families["Hyenas"]["dbcId"] = 25
MTH_DS_Families["Hyenas"]["minScale"] = 0.7
MTH_DS_Families["Hyenas"]["maxScale"] = 0.9
MTH_DS_Families["Hyenas"]["minScaleLevel"] = 1
MTH_DS_Families["Hyenas"]["maxScaleLevel"] = 60
MTH_DS_Families["Hyenas"]["foodMask"] = 654
MTH_DS_Families["Hyenas"]["foodDbc"] = {
"fish",
"cheese",
"bread",
"raw fish",
}
end
-- DBC ID=26 PetFoodMask=655 scale=[0.50-0.80]
if MTH_DS_Families["Owls"] then
MTH_DS_Families["Owls"]["dbcId"] = 26
MTH_DS_Families["Owls"]["minScale"] = 0.5
MTH_DS_Families["Owls"]["maxScale"] = 0.8
MTH_DS_Families["Owls"]["minScaleLevel"] = 1
MTH_DS_Families["Owls"]["maxScaleLevel"] = 60
MTH_DS_Families["Owls"]["foodMask"] = 655
MTH_DS_Families["Owls"]["foodDbc"] = {
"meat",
"fish",
"cheese",
"bread",
"raw fish",
}
end
-- DBC ID=27 PetFoodMask=656 scale=[0.50-0.70]
if MTH_DS_Families["Wind Serpents"] then
MTH_DS_Families["Wind Serpents"]["dbcId"] = 27
MTH_DS_Families["Wind Serpents"]["minScale"] = 0.5
MTH_DS_Families["Wind Serpents"]["maxScale"] = 0.7
MTH_DS_Families["Wind Serpents"]["minScaleLevel"] = 1
MTH_DS_Families["Wind Serpents"]["maxScaleLevel"] = 60
MTH_DS_Families["Wind Serpents"]["foodMask"] = 656
MTH_DS_Families["Wind Serpents"]["foodDbc"] = {
"fungus",
"raw fish",
}
end
-- DBC ID=35 PetFoodMask=1009 scale=[0.60-0.80]
if MTH_DS_Families["Serpents"] then
MTH_DS_Families["Serpents"]["dbcId"] = 35
MTH_DS_Families["Serpents"]["minScale"] = 0.6
MTH_DS_Families["Serpents"]["maxScale"] = 0.8
MTH_DS_Families["Serpents"]["minScaleLevel"] = 1
MTH_DS_Families["Serpents"]["maxScaleLevel"] = 60
MTH_DS_Families["Serpents"]["foodMask"] = 1009
MTH_DS_Families["Serpents"]["foodDbc"] = {
"meat",
"fungus",
"fruit",
"raw meat",
"raw fish",
}
end
-- DBC ID=36 PetFoodMask=1010 scale=[0.70-0.90]
if MTH_DS_Families["Foxes"] then
MTH_DS_Families["Foxes"]["dbcId"] = 36
MTH_DS_Families["Foxes"]["minScale"] = 0.7
MTH_DS_Families["Foxes"]["maxScale"] = 0.9
MTH_DS_Families["Foxes"]["minScaleLevel"] = 1
MTH_DS_Families["Foxes"]["maxScaleLevel"] = 60
MTH_DS_Families["Foxes"]["foodMask"] = 1010
MTH_DS_Families["Foxes"]["foodDbc"] = {
"fish",
"fungus",
"fruit",
"raw meat",
"raw fish",
}
end
+58 -1
View File
@@ -6,6 +6,7 @@ if not MTH_DS then MTH_DS = {} end
MTH_DS_Families = {
["Bats"] = {
["icon"] = "Ability_Hunter_Pet_Bat",
["named"] = 20,
["coords"] = 18,
["food"] = {
@@ -21,6 +22,7 @@ MTH_DS_Families = {
},
},
["Bears"] = {
["icon"] = "Ability_Hunter_Pet_Bear",
["named"] = 45,
["coords"] = 45,
["food"] = {
@@ -30,6 +32,8 @@ MTH_DS_Families = {
"cheese",
"fruit",
"fungus",
"raw meat",
"raw fish",
},
["abilities"] = {
"Bite",
@@ -40,6 +44,7 @@ MTH_DS_Families = {
},
},
["Boars"] = {
["icon"] = "Ability_Hunter_Pet_Boar",
["named"] = 45,
["coords"] = 87,
["food"] = {
@@ -49,6 +54,8 @@ MTH_DS_Families = {
"cheese",
"fruit",
"fungus",
"raw meat",
"raw fish",
},
["abilities"] = {
"Bite",
@@ -59,10 +66,12 @@ MTH_DS_Families = {
},
},
["Carrion Birds"] = {
["icon"] = "Ability_Hunter_Pet_Vulture",
["named"] = 36,
["coords"] = 101,
["food"] = {
"meat",
"raw meat",
},
["abilities"] = {
"Bite",
@@ -74,11 +83,14 @@ MTH_DS_Families = {
},
},
["Cats"] = {
["icon"] = "Ability_Hunter_Pet_Cat",
["named"] = 85,
["coords"] = 88,
["food"] = {
"meat",
"fish",
"raw meat",
"raw fish",
},
["abilities"] = {
"Bite",
@@ -90,6 +102,7 @@ MTH_DS_Families = {
},
},
["Crabs"] = {
["icon"] = "Ability_Hunter_Pet_Crab",
["named"] = 32,
["coords"] = 74,
["food"] = {
@@ -97,6 +110,7 @@ MTH_DS_Families = {
"fish",
"fungus",
"bread",
"raw fish",
},
["abilities"] = {
"Bubble Barrier",
@@ -106,11 +120,14 @@ MTH_DS_Families = {
},
},
["Crocolisks"] = {
["icon"] = "Ability_Hunter_Pet_Crocolisk",
["named"] = 27,
["coords"] = 71,
["food"] = {
"meat",
"fish",
"raw meat",
"raw fish",
},
["abilities"] = {
"Bite",
@@ -120,11 +137,13 @@ MTH_DS_Families = {
},
},
["Foxes"] = {
["icon"] = "ability_hunter_aspectofthefox",
["named"] = 9,
["coords"] = 51,
["food"] = {
"meat",
"fruit",
"raw meat",
},
["abilities"] = {
"Bite",
@@ -135,6 +154,7 @@ MTH_DS_Families = {
},
},
["Gorillas"] = {
["icon"] = "Ability_Hunter_Pet_Gorilla",
["named"] = 24,
["coords"] = 117,
["food"] = {
@@ -149,11 +169,13 @@ MTH_DS_Families = {
},
},
["Hyenas"] = {
["icon"] = "Ability_Hunter_Pet_Hyena",
["named"] = 26,
["coords"] = 36,
["food"] = {
"meat",
"fruit",
"raw meat",
},
["abilities"] = {
"Bite",
@@ -164,10 +186,12 @@ MTH_DS_Families = {
},
},
["Owls"] = {
["icon"] = "Ability_Hunter_Pet_Owl",
["named"] = 16,
["coords"] = 59,
["food"] = {
"meat",
"raw meat",
},
["abilities"] = {
"Claw",
@@ -178,10 +202,12 @@ MTH_DS_Families = {
},
},
["Raptors"] = {
["icon"] = "Ability_Hunter_Pet_Raptor",
["named"] = 51,
["coords"] = 115,
["food"] = {
"meat",
"raw meat",
},
["abilities"] = {
"Bite",
@@ -193,10 +219,12 @@ MTH_DS_Families = {
},
},
["Scorpids"] = {
["icon"] = "Ability_Hunter_Pet_Scorpid",
["named"] = 38,
["coords"] = 36,
["food"] = {
"meat",
"raw meat",
},
["abilities"] = {
"Claw",
@@ -205,12 +233,15 @@ MTH_DS_Families = {
"Scorpid Poison",
},
},
["Serpents (Cobra)"] = {
["Serpents"] = {
["icon"] = "Ability_Hunter_Pet_Serpent1",
["named"] = 10,
["coords"] = 13,
["food"] = {
"meat",
"fish",
"raw meat",
"raw fish",
},
["abilities"] = {
"Bite",
@@ -220,10 +251,12 @@ MTH_DS_Families = {
},
},
["Spiders"] = {
["icon"] = "Ability_Hunter_Pet_Spider",
["named"] = 91,
["coords"] = 213,
["food"] = {
"meat",
"raw meat",
},
["abilities"] = {
"Bite",
@@ -233,6 +266,7 @@ MTH_DS_Families = {
},
},
["Tallstriders"] = {
["icon"] = "Ability_Hunter_Pet_TallStrider",
["named"] = 15,
["coords"] = 112,
["food"] = {
@@ -249,12 +283,14 @@ MTH_DS_Families = {
},
},
["Turtles"] = {
["icon"] = "Ability_Hunter_Pet_Turtle",
["named"] = 25,
["coords"] = 42,
["food"] = {
"fruit",
"fish",
"fungus",
"raw fish",
},
["abilities"] = {
"Bite",
@@ -264,12 +300,14 @@ MTH_DS_Families = {
},
},
["Wind Serpents"] = {
["icon"] = "Ability_Hunter_Pet_WindSerpent",
["named"] = 27,
["coords"] = 42,
["food"] = {
"fish",
"cheese",
"bread",
"raw fish",
},
["abilities"] = {
"Bite",
@@ -280,10 +318,12 @@ MTH_DS_Families = {
},
},
["Wolves"] = {
["icon"] = "Ability_Hunter_Pet_Wolf",
["named"] = 81,
["coords"] = 99,
["food"] = {
"meat",
"raw meat",
},
["abilities"] = {
"Bite",
@@ -293,4 +333,21 @@ MTH_DS_Families = {
"Growl",
},
},
-- ── Added TWoW 1.18.1 "Nightmares of Ursol" ──────────────────
["Moths"] = {
["icon"] = "Ability_Hunter_Pet_Moth", -- TODO: confirm MPQ icon name
["named"] = 0,
["coords"] = 0,
["food"] = {
"fruit",
"fungus",
},
["abilities"] = {
"Bite",
"Cower",
"Growl",
"Pollen Burst", -- family unique: 40 Focus, Instant, 30s CD
-- Heals party 308-338 (30yd), -20% atk speed targets (10yd, 8s)
},
},
}
File diff suppressed because it is too large Load Diff
+890
View File
@@ -0,0 +1,890 @@
-- MetaHunt: Beast appearance skin variants per family
-- AUTO-GENERATED by .tools — do not edit by hand.
-- Source DBC(s): CreatureDisplayInfo.dbc, CreatureModelData.dbc
-- Generated: 2026-03-23 15:09 UTC
-- Total skins: 258 across 20 families
if not MTH_DS then MTH_DS = {} end
MTH_DS_BeastSkins = {
["Bats"] = {
{id=1954, tex="BatSkin01", unique=false, modelPath="Creature\\FelBat\\FelBat.mdx"},
{id=3956, tex="BatSkinBrown01", unique=false, modelPath="Creature\\FelBat\\FelBat.mdx"},
{id=4185, tex="BatSkinViolet01", unique=false, modelPath="Creature\\FelBat\\FelBat.mdx"},
{id=4735, tex="BatSkinWhite01", unique=false, modelPath="Creature\\FelBat\\FelBat.mdx"},
{id=15191, tex="MobatSkinBlue01", unique=true, modelPath="Creature\\mobat\\Mobat.mdx"},
},
["Bears"] = {
{id=706, tex="BearSkinBlack", unique=false, modelPath="Creature\\bear\\Bear.mdx"},
{id=806, tex="BearSkinBlue", unique=false, modelPath="Creature\\bear\\Bear.mdx"},
{id=820, tex="BearSkinDrkBrown", unique=false, modelPath="Creature\\bear\\Bear.mdx"},
{id=822, tex="BearSkinBrown", unique=false, modelPath="Creature\\bear\\Bear.mdx"},
{id=865, tex="BearSkinWhite", unique=false, modelPath="Creature\\bear\\Bear.mdx"},
{id=1082, tex="BearSkinBlackDiseased", unique=true, modelPath="Creature\\bear\\Bear.mdx"},
{id=1083, tex="BearSkinDrkBrownDiseased", unique=false, modelPath="Creature\\bear\\Bear.mdx"},
{id=9492, tex="BearSkinrex2", unique=false, modelPath="Creature\\bear\\Bear.mdx"},
{id=18352, tex="PolarBearCubSkin", unique=true, modelPath="Creature\\bear\\Bear.mdx"},
{id=19113, tex="FatherBear", unique=true, modelPath="Creature\\bear\\Bear.mdx"},
{id=20635, tex="BearSkinBrownDiseased", unique=true, modelPath="Creature\\bear\\Bear.mdx"},
{id=20636, tex="BearSkinBlueDiseased", unique=true, modelPath="Creature\\bear\\Bear.mdx"},
{id=20637, tex="BearSkinWhiteDiseased", unique=true, modelPath="Creature\\bear\\Bear.mdx"},
{id=21549, tex="bearskinblue2", unique=true, modelPath="Creature\\bear\\Bear.mdx"},
{id=21550, tex="bearskinblue3", unique=true, modelPath="Creature\\bear\\Bear.mdx"},
},
["Boars"] = {
{id=193, tex="BoarSkinIvory", unique=false, modelPath="Creature\\Boar\\Boar.mdx"},
{id=377, tex="BoarSkinBrown", unique=false, modelPath="Creature\\Boar\\Boar.mdx"},
{id=381, tex="BoarSkinBlue", unique=false, modelPath="Creature\\Boar\\Boar.mdx"},
{id=2450, tex="BoarSkinIvoryArmored", unique=false, modelPath="Creature\\Boar\\Boar.mdx"},
{id=3026, tex="BoarSkinCrimson", unique=false, modelPath="Creature\\Boar\\Boar.mdx"},
{id=4713, tex="BoarSkinBrownArmored", unique=false, modelPath="Creature\\Boar\\Boar.mdx"},
{id=4714, tex="BoarSkinBlueArmored", unique=true, modelPath="Creature\\Boar\\Boar.mdx"},
{id=6121, tex="BoarSkinUndead", unique=false, modelPath="Creature\\Boar\\Boar.mdx"},
{id=8869, tex="BoarSkinYellow", unique=false, modelPath="Creature\\Boar\\Boar.mdx"},
{id=18729, tex="QuillBeastBlue", unique=true, modelPath="Creature\\Boar\\Boar.mdx"},
{id=20504, tex="BoarSkinQuel", unique=true, modelPath="Creature\\Boar\\Boar.mdx"},
},
["Carrion Birds"] = {
{id=388, tex="CarrionBirdSkin", unique=false, modelPath="Creature\\CarrionBird\\CarrionBird.mdx"},
{id=410, tex="CarrionBirdSkinBrown", unique=false, modelPath="Creature\\CarrionBird\\CarrionBird.mdx"},
{id=490, tex="CarrionBirdSkinRed", unique=false, modelPath="Creature\\CarrionBird\\CarrionBird.mdx"},
{id=507, tex="CarrionBirdSkinBlue", unique=false, modelPath="Creature\\CarrionBird\\CarrionBird.mdx"},
{id=18390, tex="CarrionBirdSkinWhite", unique=true, modelPath="Creature\\CarrionBird\\CarrionBird.mdx"},
},
["Cats"] = {
{id=320, tex="TigerSkinRed", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=321, tex="TigerSkinBlackStriped", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=599, tex="TigerSkinBlack", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=616, tex="TigerSkinWhite", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=632, tex="TigerSkinYellow", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=748, tex="TigerSkinSnow", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=892, tex="DruidCatSkinBlack", unique=true, modelPath="Creature\\DruidCat\\DruidCat.mdx"},
{id=1056, tex="TigerSkinBrown", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=1057, tex="LionSkinGold", unique=false, modelPath="Creature\\Lion\\Lion.mdx"},
{id=1933, tex="LionessSkinGold", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=1934, tex="LionSkinWhite", unique=false, modelPath="Creature\\Lion\\Lion.mdx"},
{id=3029, tex="TigerSkinDark", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=4424, tex="LionSkinBlack", unique=true, modelPath="Creature\\Lion\\Lion.mdx"},
{id=5448, tex="MaineCoon", unique=true, modelPath="Creature\\Cat\\Cat.mdx"},
{id=5554, tex="OrangeTabby", unique=true, modelPath="Creature\\Cat\\Cat.mdx"},
{id=5555, tex="SilverTabby", unique=true, modelPath="Creature\\Cat\\Cat.mdx"},
{id=5556, tex="Bombay", unique=true, modelPath="Creature\\Cat\\Cat.mdx"},
{id=5585, tex="Birman", unique=false, modelPath="Creature\\Cat\\Cat.mdx"},
{id=5586, tex="CornishRex", unique=true, modelPath="Creature\\Cat\\Cat.mdx"},
{id=9209, tex="Cringer", unique=true, modelPath="Creature\\Cat\\Cat.mdx"},
{id=9230, tex="TigerSkinGreen", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=9949, tex="TigerSkinIce", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=9954, tex="RidingTigerSkinLavender", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=9956, tex="RidingTigerSkinWhitenosaddle", unique=false, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=9958, tex="TigerSkinNostripeWhite", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=9989, tex="White", unique=false, modelPath="Creature\\Cat\\Cat.mdx"},
{id=10054, tex="RidingTigerSkinAqua", unique=false, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=11448, tex="TigerSkinBlackSpotted", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=11709, tex="Salome", unique=true, modelPath="Creature\\Cat\\Cat.mdx"},
{id=18027, tex="LynxSkinRed", unique=true, modelPath="Creature\\DruidCat\\DruidCat.mdx"},
{id=18028, tex="LynxSkinYellow", unique=true, modelPath="Creature\\DruidCat\\DruidCat.mdx"},
{id=18389, tex="Boots", unique=true, modelPath="Creature\\Cat\\Cat.mdx"},
{id=18426, tex="RidingTigerSkinYellownosaddle", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=18427, tex="RidingTigerSkinIcenosaddle", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=18428, tex="RidingTigerSkinRednosaddle", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=18429, tex="RidingTigerSkinSnownosaddle", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=18430, tex="RidingTigerSkinGoldnosaddle", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=18431, tex="RidingTigerSkinBrownnosaddle", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=18432, tex="RidingTigerSkinBlacknosaddle", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=18433, tex="RidingTigerSkinDarknosaddle", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=18434, tex="RidingTigerSkinNostripeWhitenosaddle", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=20388, tex="TIGERSKINBLACKGEM", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=20389, tex="TigerSkinDiamond", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=20390, tex="TigerSkinJade", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=20391, tex="TigerSkinRuby", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=20392, tex="TigerSkinSaphire", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=20393, tex="TigerSkinTurquoise", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=20410, tex="LionessSkinEmerald", unique=true, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=20437, tex="RidingTigerSkinDarknosaddle.blp", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
},
["Crabs"] = {
{id=342, tex="CrabSkinBronze", unique=false, modelPath="Creature\\Crab\\Crab.mdx"},
{id=979, tex="CrabSkinSaphire", unique=false, modelPath="Creature\\Crab\\Crab.mdx"},
{id=981, tex="CrabSkinIvory", unique=false, modelPath="Creature\\Crab\\Crab.mdx"},
{id=1307, tex="CrabSkinVermillian", unique=false, modelPath="Creature\\Crab\\Crab.mdx"},
{id=18757, tex="CrabSkinDarkDiamond", unique=true, modelPath="Creature\\Crab\\Crab.mdx"},
{id=18758, tex="CrabSkinTrueSilver", unique=true, modelPath="Creature\\Crab\\Crab.mdx"},
},
["Crocolisks"] = {
{id=807, tex="CrocodileSkinSwamp", unique=false, modelPath="Creature\\Crocodile\\Crocodile.mdx"},
{id=833, tex="CrocodileSkinRiver", unique=false, modelPath="Creature\\Crocodile\\Crocodile.mdx"},
{id=925, tex="CrocodileSkinMarsh", unique=false, modelPath="Creature\\Crocodile\\Crocodile.mdx"},
{id=1609, tex="CrocodileSkinAlbino", unique=false, modelPath="Creature\\Crocodile\\Crocodile.mdx"},
},
["Foxes"] = {
{id=18004, tex="MistFoxSkin", unique=true, modelPath="Creature\\Mistfox\\Mistfox.mdx"},
{id=18005, tex="MistFoxSkinArctic", unique=true, modelPath="Creature\\Mistfox\\Mistfox.mdx"},
{id=18006, tex="MistFoxSkinBlack", unique=false, modelPath="Creature\\Mistfox\\Mistfox.mdx"},
{id=18007, tex="MistFoxSkinBrown", unique=true, modelPath="Creature\\Mistfox\\Mistfox.mdx"},
{id=18008, tex="MistFoxSkinRed", unique=false, modelPath="Creature\\Mistfox\\Mistfox.mdx"},
{id=18244, tex="MistFoxSkinSpirit", unique=true, modelPath="Creature\\Mistfox\\Mistfox.mdx"},
{id=18773, tex="MistFoxSkinPink", unique=true, modelPath="Creature\\Mistfox\\Mistfox.mdx"},
{id=19105, tex="MistFoxSkinEvil", unique=true, modelPath="Creature\\Mistfox\\Mistfox.mdx"},
{id=19106, tex="MistFoxSkinGolden", unique=true, modelPath="Creature\\Mistfox\\Mistfox.mdx"},
{id=21282, tex="fox2_black", unique=true, modelPath="Creature\\fox2\\fox2.mdx"},
{id=21283, tex="fox2_blue", unique=true, modelPath="Creature\\fox2\\fox2.mdx"},
{id=21284, tex="fox2_darkred", unique=true, modelPath="Creature\\fox2\\fox2.mdx"},
{id=21285, tex="fox2_green", unique=true, modelPath="Creature\\fox2\\fox2.mdx"},
{id=21286, tex="fox2_orange", unique=true, modelPath="Creature\\fox2\\fox2.mdx"},
{id=21287, tex="fox2_purple", unique=true, modelPath="Creature\\fox2\\fox2.mdx"},
{id=21288, tex="fox2_red", unique=true, modelPath="Creature\\fox2\\fox2.mdx"},
{id=21289, tex="fox2_teal", unique=true, modelPath="Creature\\fox2\\fox2.mdx"},
{id=21290, tex="fox2_white", unique=true, modelPath="Creature\\fox2\\fox2.mdx"},
},
["Gorillas"] = {
{id=792, tex="GorillaSkinSilver", unique=false, modelPath="Creature\\Gorilla\\Gorilla.mdx"},
{id=809, tex="GorillaSkinBlack", unique=false, modelPath="Creature\\Gorilla\\Gorilla.mdx"},
{id=838, tex="GorillaSkinGrey", unique=false, modelPath="Creature\\Gorilla\\Gorilla.mdx"},
{id=840, tex="GorillaSkinWhite", unique=false, modelPath="Creature\\Gorilla\\Gorilla.mdx"},
{id=3186, tex="GorillaSkinRed", unique=false, modelPath="Creature\\Gorilla\\Gorilla.mdx"},
{id=8841, tex="GorillaSkinAmy", unique=true, modelPath="Creature\\Gorilla\\Gorilla.mdx"},
},
["Hyenas"] = {
{id=1534, tex="HyenaSkinOarnge", unique=false, modelPath="Creature\\Hyena\\Hyena.mdx"},
{id=1536, tex="HyenaSkin", unique=false, modelPath="Creature\\Hyena\\Hyena.mdx"},
{id=2709, tex="HyenaSkinRed", unique=false, modelPath="Creature\\Hyena\\Hyena.mdx"},
{id=2710, tex="HyenaSkinYellow", unique=false, modelPath="Creature\\Hyena\\Hyena.mdx"},
{id=2713, tex="HyenaSkinBlue", unique=false, modelPath="Creature\\Hyena\\Hyena.mdx"},
{id=2714, tex="HyenaSkinWhite", unique=false, modelPath="Creature\\Hyena\\Hyena.mdx"},
{id=2726, tex="HyenaSkinBlack", unique=false, modelPath="Creature\\Hyena\\Hyena.mdx"},
},
["Moths"] = {
{id=21298, tex="mothardenwealdmount_blue", unique=true, modelPath="Creature\\mothardenwealdmount\\mothardenwealdmount.mdx"},
{id=21299, tex="mothardenwealdmount_dark", unique=true, modelPath="Creature\\mothardenwealdmount\\mothardenwealdmount.mdx"},
{id=21300, tex="mothardenwealdmount_mint", unique=true, modelPath="Creature\\mothardenwealdmount\\mothardenwealdmount.mdx"},
{id=21301, tex="mothardenwealdmount_red", unique=true, modelPath="Creature\\mothardenwealdmount\\mothardenwealdmount.mdx"},
{id=21431, tex="MothSkinBeige", unique=true, modelPath="Creature\\Moth\\Moth.mdx"},
{id=21432, tex="MothSkinBlue", unique=true, modelPath="Creature\\Moth\\Moth.mdx"},
{id=21433, tex="mothskingrey", unique=true, modelPath="Creature\\Moth\\Moth.mdx"},
{id=21434, tex="MothSkinRed", unique=true, modelPath="Creature\\Moth\\Moth.mdx"},
{id=21435, tex="MothSkinWhite", unique=true, modelPath="Creature\\Moth\\Moth.mdx"},
{id=21436, tex="mothskinyellow", unique=true, modelPath="Creature\\Moth\\Moth.mdx"},
},
["Owls"] = {
{id=4566, tex="OwlBrown", unique=false, modelPath="Creature\\Owl\\Owl.mdx"},
{id=4877, tex="OwlGrey", unique=false, modelPath="Creature\\Owl\\Owl.mdx"},
{id=6212, tex="OwlWhite", unique=false, modelPath="Creature\\Owl\\Owl.mdx"},
{id=6298, tex="OwlBlue", unique=true, modelPath="Creature\\Owl\\Owl.mdx"},
{id=6299, tex="OwlBlack", unique=true, modelPath="Creature\\Owl\\Owl.mdx"},
{id=18338, tex="Eagle", unique=false, modelPath="Creature\\Eagle\\Eagle.mdx"},
{id=18339, tex="BrownEagle", unique=true, modelPath="Creature\\Eagle\\Eagle.mdx"},
{id=18368, tex="OwlSpirit", unique=true, modelPath="Creature\\Owl\\Owl.mdx"},
{id=18746, tex="EagleRaven", unique=true, modelPath="Creature\\Eagle\\Eagle.mdx"},
{id=21109, tex="OwlRuby", unique=true, modelPath="Creature\\Owl\\Owl.mdx"},
{id=21110, tex="OwlSapphire", unique=true, modelPath="Creature\\Owl\\Owl.mdx"},
},
["Raptors"] = {
{id=180, tex="RaptorSkinOrange", unique=false, modelPath="Creature\\Raptor\\Raptor.mdx"},
{id=322, tex="RaptorSkinMottledDarkGreen", unique=false, modelPath="Creature\\Raptor\\Raptor.mdx"},
{id=675, tex="RaptorSkinMottledBlueGreen", unique=false, modelPath="Creature\\Raptor\\Raptor.mdx"},
{id=787, tex="RaptorSkinYellow", unique=false, modelPath="Creature\\Raptor\\Raptor.mdx"},
{id=960, tex="RaptorSkinViolet", unique=false, modelPath="Creature\\Raptor\\Raptor.mdx"},
{id=1337, tex="RaptorSkinGrey", unique=false, modelPath="Creature\\Raptor\\Raptor.mdx"},
{id=1959, tex="RaptorSkinRed", unique=false, modelPath="Creature\\Raptor\\Raptor.mdx"},
{id=5291, tex="RidingRaptorSkinObsidian", unique=false, modelPath="Creature\\Raptor\\Raptor.mdx"},
},
["Scorpids"] = {
{id=2414, tex="ScorpionskinPink", unique=false, modelPath="Creature\\Scorpion\\Scorpion.mdx"},
{id=2485, tex="ScorpionskinBeige", unique=false, modelPath="Creature\\Scorpion\\Scorpion.mdx"},
{id=2487, tex="ScorpionskinYellow", unique=false, modelPath="Creature\\Scorpion\\Scorpion.mdx"},
{id=2488, tex="ScorpionskinBlack", unique=false, modelPath="Creature\\Scorpion\\Scorpion.mdx"},
{id=2489, tex="ScorpionskinRed", unique=false, modelPath="Creature\\Scorpion\\Scorpion.mdx"},
{id=2729, tex="ScorpionskinGolden", unique=false, modelPath="Creature\\Scorpion\\Scorpion.mdx"},
{id=2730, tex="ScorpionskinBlue", unique=false, modelPath="Creature\\Scorpion\\Scorpion.mdx"},
{id=7469, tex="SilithidScarabSkinBlack", unique=false, modelPath="Creature\\SILITHIDSCARAB\\SilithidScarab.mdx"},
{id=8971, tex="SilithidScarabSkinRed", unique=false, modelPath="Creature\\SILITHIDSCARAB\\SilithidScarab.mdx"},
{id=9028, tex="SilithidScarabSkinWhite", unique=false, modelPath="Creature\\SILITHIDSCARAB\\SilithidScarab.mdx"},
{id=9469, tex="SilithidScarabSkinYellow", unique=false, modelPath="Creature\\SILITHIDSCARAB\\SilithidScarab.mdx"},
{id=10983, tex="ScorpionskinSilver", unique=false, modelPath="Creature\\Scorpion\\Scorpion.mdx"},
{id=11093, tex="SilithidScarabSkinTeal", unique=false, modelPath="Creature\\SILITHIDSCARAB\\SilithidScarab.mdx"},
{id=11104, tex="SilithidScarabSkinGold", unique=false, modelPath="Creature\\SILITHIDSCARAB\\SilithidScarab.mdx"},
{id=11107, tex="SilithidScarabSkinPurple", unique=false, modelPath="Creature\\SILITHIDSCARAB\\SilithidScarab.mdx"},
{id=11143, tex="SilithidScarabSkinTan", unique=false, modelPath="Creature\\SILITHIDSCARAB\\SilithidScarab.mdx"},
{id=18373, tex="SilithidScarabSkinGreen", unique=true, modelPath="Creature\\SILITHIDSCARAB\\SilithidScarab.mdx"},
{id=21268, tex="ScorpionskinDarkIron", unique=true, modelPath="Creature\\Scorpion\\Scorpion.mdx"},
},
["Serpents"] = {
{id=1206, tex="SnakeSkinBlack", unique=false, modelPath="Creature\\snake\\snake.mdx"},
{id=1986, tex="SnakeSkinDarkGreenYellow", unique=false, modelPath="Creature\\snake\\snake.mdx"},
{id=1987, tex="SnakeSkinYellowBlack", unique=false, modelPath="Creature\\snake\\snake.mdx"},
{id=2954, tex="SnakeSkinRedWhiteBlack", unique=true, modelPath="Creature\\snake\\snake.mdx"},
{id=2955, tex="SnakeSkinWhite", unique=false, modelPath="Creature\\snake\\snake.mdx"},
{id=2956, tex="SnakeSkinBlue", unique=false, modelPath="Creature\\snake\\snake.mdx"},
{id=2957, tex="SnakeSkinBrown", unique=true, modelPath="Creature\\snake\\snake.mdx"},
{id=2958, tex="SnakeSkinGreen", unique=false, modelPath="Creature\\snake\\snake.mdx"},
{id=4305, tex="SerpantSkinWhite", unique=false, modelPath="Creature\\Serpent\\Serpent.mdx"},
{id=4312, tex="SerpantSkinPurple", unique=false, modelPath="Creature\\Serpent\\Serpent.mdx"},
{id=4317, tex="SerpantSkinBlue", unique=false, modelPath="Creature\\Serpent\\Serpent.mdx"},
{id=4435, tex="SerpantSkinGreen", unique=false, modelPath="Creature\\Serpent\\Serpent.mdx"},
{id=6303, tex="SnakeSkinRed", unique=true, modelPath="Creature\\snake\\snake.mdx"},
{id=11034, tex="SerpantSkinOlive", unique=false, modelPath="Creature\\Serpent\\Serpent.mdx"},
{id=15182, tex="SerpantSkinBrown", unique=true, modelPath="Creature\\Serpent\\Serpent.mdx"},
{id=20633, tex="SerpantSkinViolet", unique=true, modelPath="Creature\\Serpent\\Serpent.mdx"},
{id=20634, tex="SerpantSkinRuby", unique=true, modelPath="Creature\\Serpent\\Serpent.mdx"},
{id=21269, tex="SerpentSkinRuby", unique=true, modelPath="Creature\\Serpent\\Serpent.mdx"},
},
["Spiders"] = {
{id=30, tex="MineSpiderSkinSteel", unique=false, modelPath="Creature\\MineSpider\\MineSpider.mdx"},
{id=283, tex="MineSpiderSkinGreen", unique=false, modelPath="Creature\\MineSpider\\MineSpider.mdx"},
{id=336, tex="TarantulaSkinGreen", unique=false, modelPath="Creature\\Tarantula\\Tarantula.mdx"},
{id=366, tex="TarantulaSkinOrange", unique=false, modelPath="Creature\\Tarantula\\Tarantula.mdx"},
{id=513, tex="MineSpiderSkinOlive", unique=false, modelPath="Creature\\MineSpider\\MineSpider.mdx"},
{id=520, tex="TarantulaSkinBrown", unique=false, modelPath="Creature\\Tarantula\\Tarantula.mdx"},
{id=711, tex="MineSpiderSkinWetLands", unique=false, modelPath="Creature\\MineSpider\\MineSpider.mdx"},
{id=955, tex="MineSpiderSkinCave", unique=false, modelPath="Creature\\MineSpider\\MineSpider.mdx"},
{id=963, tex="MineSpiderSkinBlood", unique=false, modelPath="Creature\\MineSpider\\MineSpider.mdx"},
{id=1091, tex="TarantulaSkinGrey", unique=false, modelPath="Creature\\Tarantula\\Tarantula.mdx"},
{id=2536, tex="MineSpiderSkinJungle", unique=false, modelPath="Creature\\MineSpider\\MineSpider.mdx"},
{id=4456, tex="MineSpiderSkinCrystal", unique=false, modelPath="Creature\\MineSpider\\MineSpider.mdx"},
{id=4457, tex="TarantulaSkinMagma", unique=false, modelPath="Creature\\Tarantula\\Tarantula.mdx"},
{id=15937, tex="MineSpiderSkinViolet", unique=true, modelPath="Creature\\MineSpider\\MineSpider.mdx"},
},
["Tallstriders"] = {
{id=38, tex="TallStriderSkinTurquoise", unique=false, modelPath="Creature\\Tallstrider\\TallStrider.mdx"},
{id=178, tex="TallStriderSkinBrown", unique=false, modelPath="Creature\\Tallstrider\\TallStrider.mdx"},
{id=1220, tex="TallStriderSkinGray", unique=false, modelPath="Creature\\Tallstrider\\TallStrider.mdx"},
{id=1221, tex="TallStriderSkinIvory", unique=false, modelPath="Creature\\Tallstrider\\TallStrider.mdx"},
{id=1281, tex="TallStriderSkinPurple", unique=true, modelPath="Creature\\Tallstrider\\TallStrider.mdx"},
{id=1961, tex="TallStriderSkinPink", unique=true, modelPath="Creature\\Tallstrider\\TallStrider.mdx"},
{id=20509, tex="HawkstriderBlue", unique=true, modelPath="Creature\\Hawkstrider\\Hawkstrider.mdx"},
{id=20510, tex="HawkstriderGold", unique=true, modelPath="Creature\\Hawkstrider\\Hawkstrider.mdx"},
{id=20511, tex="HawkstriderGreen", unique=true, modelPath="Creature\\Hawkstrider\\Hawkstrider.mdx"},
{id=20512, tex="HawkstriderRed", unique=true, modelPath="Creature\\Hawkstrider\\Hawkstrider.mdx"},
{id=20513, tex="HawkstriderWhite", unique=true, modelPath="Creature\\Hawkstrider\\Hawkstrider.mdx"},
{id=21554, tex="TallStriderSkinBlue", unique=true, modelPath="Creature\\Tallstrider\\TallStrider.mdx"},
{id=21555, tex="TallStriderSkinViolet", unique=true, modelPath="Creature\\Tallstrider\\TallStrider.mdx"},
},
["Turtles"] = {
{id=1244, tex="SeaTurtleSkin", unique=false, modelPath="Creature\\SeaTurtle\\SeaTurtle.mdx"},
{id=2307, tex="SeaTurtleSkinWhite", unique=false, modelPath="Creature\\SeaTurtle\\SeaTurtle.mdx"},
{id=4829, tex="SeaTurtleSkinGrey", unique=false, modelPath="Creature\\SeaTurtle\\SeaTurtle.mdx"},
{id=5026, tex="SeaTurtleSkinRed", unique=false, modelPath="Creature\\SeaTurtle\\SeaTurtle.mdx"},
{id=5126, tex="SeaTurtleSkinBlue", unique=false, modelPath="Creature\\SeaTurtle\\SeaTurtle.mdx"},
{id=16259, tex="TurtleSkinGreen", unique=false, modelPath="Creature\\Turtle\\Turtle.mdx"},
{id=16360, tex="TurtleSkinBrown", unique=true, modelPath="Creature\\Turtle\\Turtle.mdx"},
{id=16361, tex="TurtleSkinBlack", unique=true, modelPath="Creature\\Turtle\\Turtle.mdx"},
},
["Wind Serpents"] = {
{id=1336, tex="WindSerpentSkin", unique=false, modelPath="Creature\\WindSerpent\\WindSerpent.mdx"},
{id=2699, tex="WindSerpentSkinRed", unique=false, modelPath="Creature\\WindSerpent\\WindSerpent.mdx"},
{id=2700, tex="WindSerpentSkinGreen", unique=false, modelPath="Creature\\WindSerpent\\WindSerpent.mdx"},
{id=2703, tex="WindSerpentSkinWhite", unique=false, modelPath="Creature\\WindSerpent\\WindSerpent.mdx"},
{id=3006, tex="WindSerpentSkinBlack", unique=false, modelPath="Creature\\WindSerpent\\WindSerpent.mdx"},
{id=18386, tex="WindSerpentSkinOarnge", unique=true, modelPath="Creature\\WindSerpent\\WindSerpent.mdx"},
{id=20514, tex="ManaWurmSkinBlack", unique=true, modelPath="Creature\\Manawurm\\manawurm.mdx"},
{id=20515, tex="MANAWURMSKINGREEN", unique=true, modelPath="Creature\\Manawurm\\manawurm.mdx"},
{id=20516, tex="ManaWurmSkinPurple", unique=true, modelPath="Creature\\Manawurm\\manawurm.mdx"},
{id=20517, tex="ManaWurmSkinQuel", unique=true, modelPath="Creature\\Manawurm\\manawurm.mdx"},
{id=20518, tex="ManaWurmSkinSilver1", unique=true, modelPath="Creature\\Manawurm\\manawurm.mdx"},
{id=20519, tex="ManaWurmSkinWhite", unique=true, modelPath="Creature\\Manawurm\\manawurm.mdx"},
},
["Wolves"] = {
{id=73, tex="Direwolfskinbrown", unique=true, modelPath="Creature\\DireWolf\\DireWolf.mdx"},
{id=161, tex="WolfSkinCoyote", unique=false, modelPath="Creature\\Wolf\\Wolf.mdx"},
{id=165, tex="WolfSkinTimber", unique=false, modelPath="Creature\\Wolf\\Wolf.mdx"},
{id=246, tex="DireWolfSkinDarkBlack", unique=false, modelPath="Creature\\DireWolf\\DireWolf.mdx"},
{id=644, tex="WolfSkinBlack", unique=false, modelPath="Creature\\Wolf\\Wolf.mdx"},
{id=720, tex="DireWolfSkinLightBlue", unique=false, modelPath="Creature\\DireWolf\\DireWolf.mdx"},
{id=776, tex="WolfSkinArctic", unique=false, modelPath="Creature\\Wolf\\Wolf.mdx"},
{id=868, tex="WolfSkinArcticAlpha", unique=true, modelPath="Creature\\Wolf\\Wolf.mdx"},
{id=1950, tex="WolfSkin_Ghost", unique=true, modelPath="Creature\\Wolf\\Wolf.mdx"},
{id=3916, tex="HoundSkinBlue", unique=false, modelPath="Creature\\DarkHound\\DarkHound.mdx"},
{id=4124, tex="WolfSkinDiseased", unique=false, modelPath="Creature\\Wolf\\Wolf.mdx"},
{id=6195, tex="HoundSkinGrey", unique=false, modelPath="Creature\\DarkHound\\DarkHound.mdx"},
{id=7890, tex="HoundSkinPurple", unique=false, modelPath="Creature\\DarkHound\\DarkHound.mdx"},
{id=8180, tex="HoundSkinRed", unique=false, modelPath="Creature\\DarkHound\\DarkHound.mdx"},
{id=9369, tex="DireWolfSkinReddishBrown", unique=false, modelPath="Creature\\DireWolf\\DireWolf.mdx"},
{id=9564, tex="DireWolfSkinLightGrey", unique=false, modelPath="Creature\\DireWolf\\DireWolf.mdx"},
{id=9567, tex="DireWolfSkinBlueBrown", unique=true, modelPath="Creature\\DireWolf\\DireWolf.mdx"},
{id=9568, tex="DireWolfSkinDarkBrown", unique=false, modelPath="Creature\\DireWolf\\DireWolf.mdx"},
{id=11414, tex="DireWolfSkinDarkGrey", unique=true, modelPath="Creature\\DireWolf\\DireWolf.mdx"},
{id=14312, tex="HoundSkinWhite", unique=false, modelPath="Creature\\DarkHound\\DarkHound.mdx"},
},
}
-- Maps any beast displayId to the canonical skin id used in MTH_DS_BeastSkins
-- Total: 577 entries
MTH_DS_DisplayToSkin = {
[368] = 30,
[372] = 165,
[380] = 165,
[382] = 366,
[389] = 193,
[418] = 30,
[447] = 165,
[471] = 320,
[498] = 366,
[499] = 193,
[503] = 193,
[538] = 513,
[539] = 30,
[545] = 283,
[557] = 161,
[566] = 520,
[598] = 320,
[604] = 165,
[607] = 381,
[613] = 599,
[614] = 320,
[615] = 322,
[633] = 599,
[641] = 342,
[643] = 161,
[647] = 165,
[648] = 322,
[649] = 180,
[651] = 366,
[659] = 513,
[666] = 165,
[670] = 180,
[671] = 180,
[672] = 180,
[673] = 180,
[674] = 322,
[676] = 322,
[677] = 675,
[695] = 322,
[698] = 320,
[699] = 342,
[703] = 377,
[704] = 377,
[707] = 706,
[709] = 336,
[719] = 706,
[741] = 246,
[744] = 381,
[749] = 748,
[755] = 675,
[759] = 336,
[760] = 336,
[762] = 706,
[768] = 283,
[780] = 644,
[781] = 644,
[782] = 644,
[783] = 165,
[784] = 644,
[785] = 776,
[788] = 180,
[801] = 776,
[802] = 776,
[814] = 807,
[815] = 807,
[821] = 520,
[827] = 520,
[831] = 807,
[834] = 833,
[835] = 807,
[837] = 792,
[839] = 809,
[841] = 792,
[842] = 838,
[843] = 838,
[844] = 792,
[845] = 809,
[855] = 776,
[869] = 776,
[902] = 822,
[903] = 165,
[909] = 520,
[910] = 520,
[913] = 865,
[917] = 632,
[918] = 632,
[945] = 180,
[948] = 180,
[949] = 675,
[954] = 748,
[957] = 283,
[958] = 955,
[959] = 955,
[961] = 787,
[982] = 822,
[996] = 979,
[997] = 981,
[998] = 979,
[999] = 981,
[1000] = 979,
[1001] = 981,
[1006] = 822,
[1007] = 806,
[1008] = 632,
[1030] = 720,
[1034] = 807,
[1035] = 925,
[1036] = 925,
[1037] = 833,
[1038] = 925,
[1039] = 833,
[1040] = 38,
[1041] = 178,
[1042] = 178,
[1043] = 632,
[1055] = 632,
[1058] = 1056,
[1059] = 1056,
[1080] = 807,
[1087] = 955,
[1088] = 513,
[1089] = 513,
[1090] = 955,
[1094] = 809,
[1095] = 599,
[1100] = 161,
[1103] = 1091,
[1104] = 1091,
[1105] = 388,
[1106] = 388,
[1157] = 955,
[1160] = 955,
[1164] = 161,
[1167] = 193,
[1192] = 507,
[1207] = 776,
[1208] = 377,
[1210] = 490,
[1219] = 178,
[1222] = 161,
[1228] = 388,
[1229] = 388,
[1250] = 833,
[1251] = 833,
[1259] = 180,
[1283] = 178,
[1284] = 1221,
[1338] = 960,
[1339] = 1337,
[1340] = 1337,
[1535] = 1534,
[1742] = 1336,
[1744] = 960,
[1746] = 1337,
[1747] = 960,
[1873] = 513,
[1938] = 1307,
[1939] = 1307,
[1955] = 1954,
[1960] = 1959,
[1962] = 1959,
[1971] = 1057,
[1972] = 1057,
[1973] = 1057,
[1974] = 1336,
[1975] = 1336,
[1977] = 1057,
[1978] = 1933,
[1989] = 336,
[1990] = 822,
[2031] = 1056,
[2239] = 979,
[2278] = 1056,
[2279] = 616,
[2300] = 1056,
[2301] = 1536,
[2305] = 410,
[2308] = 2307,
[2352] = 246,
[2378] = 822,
[2424] = 366,
[2433] = 30,
[2437] = 599,
[2453] = 2450,
[2486] = 2485,
[2491] = 2488,
[2537] = 2536,
[2538] = 2536,
[2539] = 2536,
[2540] = 283,
[2541] = 283,
[2542] = 366,
[2543] = 366,
[2544] = 366,
[2545] = 366,
[2546] = 366,
[2548] = 925,
[2549] = 925,
[2550] = 807,
[2570] = 1959,
[2571] = 1959,
[2573] = 1959,
[2574] = 1959,
[2609] = 1534,
[2702] = 2699,
[2705] = 2703,
[2706] = 2700,
[2707] = 2700,
[2708] = 2700,
[2711] = 2710,
[2712] = 2710,
[2715] = 2714,
[2716] = 2714,
[2731] = 2730,
[2732] = 2487,
[2741] = 1220,
[2765] = 2729,
[2766] = 2729,
[2850] = 1609,
[2864] = 2488,
[2902] = 1244,
[2995] = 2958,
[2996] = 1609,
[3027] = 3026,
[3030] = 3029,
[3031] = 3029,
[3035] = 3026,
[3126] = 1206,
[3187] = 3186,
[3188] = 3186,
[3200] = 806,
[3201] = 806,
[3202] = 165,
[3203] = 165,
[3204] = 2699,
[3205] = 2699,
[3247] = 2489,
[3248] = 490,
[4065] = 2699,
[4090] = 1609,
[4091] = 2700,
[4167] = 2956,
[4268] = 1206,
[4343] = 1986,
[4440] = 1987,
[4442] = 960,
[4458] = 4457,
[4472] = 748,
[4480] = 4312,
[4613] = 161,
[4615] = 4566,
[4636] = 2710,
[4689] = 2709,
[4716] = 4713,
[4732] = 3956,
[4733] = 4185,
[4734] = 3956,
[4767] = 4317,
[4768] = 4435,
[5027] = 5026,
[5030] = 5026,
[5052] = 2307,
[5066] = 1933,
[5067] = 599,
[5068] = 3029,
[5069] = 632,
[5127] = 5126,
[5242] = 960,
[5244] = 792,
[5245] = 838,
[5290] = 675,
[5292] = 960,
[5294] = 792,
[5295] = 838,
[5296] = 838,
[5510] = 822,
[5557] = 342,
[5558] = 981,
[5559] = 979,
[5560] = 1307,
[5905] = 2488,
[5966] = 4305,
[5985] = 2489,
[6068] = 2488,
[6069] = 2488,
[6070] = 2488,
[6073] = 1091,
[6076] = 1220,
[6082] = 1056,
[6122] = 6121,
[6126] = 1244,
[6196] = 6195,
[6210] = 616,
[6213] = 1091,
[6214] = 1091,
[6248] = 616,
[6300] = 4877,
[6368] = 5126,
[6468] = 5291,
[6805] = 3029,
[6807] = 3026,
[6808] = 336,
[6942] = 1056,
[6943] = 599,
[7046] = 5126,
[7114] = 5126,
[7339] = 644,
[7347] = 2489,
[7348] = 490,
[7349] = 490,
[7409] = 1206,
[7470] = 7469,
[7510] = 4457,
[7511] = 7469,
[7569] = 2703,
[7619] = 7469,
[7691] = 2703,
[7829] = 3006,
[7836] = 4829,
[7837] = 4829,
[7839] = 4829,
[7840] = 4829,
[7891] = 7890,
[7892] = 7890,
[7893] = 7890,
[7894] = 4185,
[7896] = 1954,
[7897] = 4185,
[7906] = 2699,
[8014] = 520,
[8050] = 2714,
[8129] = 840,
[8173] = 2955,
[8174] = 1987,
[8181] = 8180,
[8271] = 4456,
[8472] = 675,
[8473] = 960,
[8808] = 4185,
[8837] = 865,
[8838] = 806,
[8839] = 792,
[8840] = 806,
[8842] = 865,
[8843] = 706,
[8844] = 3186,
[8870] = 381,
[8871] = 8869,
[8969] = 2487,
[8970] = 2487,
[9019] = 8180,
[9020] = 3916,
[9021] = 3916,
[9022] = 7890,
[9029] = 9028,
[9050] = 675,
[9074] = 1954,
[9276] = 1083,
[9277] = 1083,
[9278] = 4124,
[9280] = 4124,
[9287] = 644,
[9354] = 7469,
[9370] = 9369,
[9371] = 9369,
[9372] = 9369,
[9509] = 644,
[9511] = 644,
[9535] = 3956,
[9558] = 644,
[9562] = 246,
[9563] = 246,
[9565] = 981,
[9566] = 979,
[9569] = 9568,
[9570] = 342,
[9571] = 342,
[9572] = 9568,
[9573] = 979,
[9749] = 2488,
[9750] = 4735,
[9755] = 4456,
[9756] = 4456,
[9829] = 513,
[9910] = 30,
[9913] = 2699,
[9929] = 4456,
[9934] = 336,
[9950] = 9949,
[9951] = 9949,
[9953] = 616,
[9955] = 9949,
[9957] = 9949,
[9990] = 9989,
[9992] = 9230,
[9993] = 9230,
[9997] = 4456,
[10015] = 193,
[10031] = 8971,
[10033] = 1954,
[10055] = 1934,
[10113] = 10054,
[10114] = 1934,
[10133] = 840,
[10183] = 2699,
[10209] = 246,
[10270] = 2726,
[10271] = 2726,
[10273] = 507,
[10274] = 776,
[10278] = 720,
[10618] = 865,
[10824] = 388,
[10825] = 410,
[10826] = 507,
[10827] = 490,
[10828] = 6212,
[10829] = 4877,
[10830] = 4877,
[10831] = 4877,
[10832] = 4877,
[10833] = 6212,
[10902] = 2726,
[10903] = 2713,
[10904] = 2713,
[10947] = 2307,
[10984] = 2487,
[10985] = 2487,
[10986] = 2414,
[10987] = 2414,
[10988] = 10983,
[10991] = 2700,
[11029] = 320,
[11031] = 320,
[11032] = 3956,
[11089] = 9469,
[11094] = 11093,
[11098] = 2485,
[11105] = 11104,
[11108] = 11107,
[11144] = 11143,
[11315] = 1959,
[11316] = 787,
[11317] = 960,
[11318] = 675,
[11319] = 322,
[11348] = 366,
[11411] = 246,
[11412] = 720,
[11413] = 9564,
[11415] = 9564,
[11416] = 776,
[11417] = 9564,
[11418] = 9564,
[11419] = 9564,
[11420] = 9369,
[11421] = 246,
[11422] = 720,
[11444] = 9958,
[11445] = 9958,
[11446] = 616,
[11447] = 320,
[11449] = 321,
[11450] = 321,
[11451] = 1056,
[11452] = 599,
[11453] = 11448,
[11454] = 3029,
[11455] = 320,
[11829] = 4735,
[11908] = 820,
[11909] = 820,
[11910] = 599,
[12151] = 11093,
[12156] = 11107,
[12157] = 11104,
[12159] = 9469,
[12433] = 410,
[12966] = 2710,
[13096] = 7469,
[13097] = 10983,
[13111] = 963,
[13589] = 1609,
[14256] = 925,
[14313] = 490,
[14315] = 822,
[14316] = 822,
[14318] = 11448,
[14319] = 507,
[14364] = 9956,
[14557] = 11034,
[14558] = 4435,
[14559] = 4305,
[14562] = 3956,
[14572] = 599,
[14657] = 5126,
[14658] = 4829,
[14659] = 1244,
[14660] = 5026,
[14661] = 2307,
[14780] = 1954,
[14853] = 711,
[14935] = 599,
[14950] = 955,
[14952] = 955,
[15072] = 711,
[15132] = 4317,
[15133] = 4435,
[15135] = 4312,
[15136] = 711,
[15137] = 3006,
[15138] = 320,
[15145] = 833,
[15150] = 4317,
[15151] = 320,
[15180] = 9369,
[15181] = 14312,
[15204] = 9369,
[15227] = 4735,
[15275] = 2699,
[15302] = 3186,
[15327] = 2489,
[15336] = 2485,
[15339] = 11104,
[15383] = 10983,
[15384] = 2729,
[15385] = 10983,
[15422] = 2730,
[15423] = 2730,
[15433] = 2488,
[15464] = 9028,
[15467] = 11104,
[15468] = 11143,
[15469] = 2485,
[15470] = 2729,
[15471] = 2489,
[15533] = 2414,
[15534] = 6212,
[15682] = 11107,
[15878] = 776,
[15929] = 7469,
[15938] = 513,
[15939] = 2536,
[15964] = 14312,
[15969] = 3916,
[16145] = 5126,
[16359] = 16259,
[16622] = 5585,
[18115] = 9492,
[18177] = 18008,
[18353] = 18338,
[18493] = 18006,
[20369] = 18027,
[20370] = 1933,
[20371] = 11448,
[20372] = 321,
[20373] = 9230,
[20374] = 18432,
[20375] = 18431,
[20376] = 18433,
[20377] = 18430,
[20378] = 18427,
[20379] = 18434,
[20380] = 18428,
[20381] = 18429,
[20382] = 9956,
[20383] = 18426,
[20384] = 11448,
[20385] = 321,
[20386] = 9230,
[20387] = 1933,
[20394] = 20388,
[20395] = 20389,
[20396] = 20390,
[20397] = 20391,
[20398] = 20392,
[20399] = 20393,
}
+32590 -6387
View File
File diff suppressed because it is too large Load Diff
+11602 -11602
View File
File diff suppressed because it is too large Load Diff
+844
View File
@@ -0,0 +1,844 @@
-- MetaHunt: Hunter spell data extracted from Spell.dbc (SpellFamilyName=9). Field layout: 173 fields. Indexed by spell ID: MTH_DS_HunterSpellsDBC[spellId]. Named lookup (rank 1): MTH_DS_HunterSpellsByName['SpellName'].
-- AUTO-GENERATED by .tools — do not edit by hand.
-- Source DBC(s): Spell.dbc, SpellCastTimes.dbc, SpellDuration.dbc, SpellIcon.dbc
-- Generated: 2026-03-20 14:10 UTC
if not MTH_DS then MTH_DS = {} end
MTH_DS_HunterSpellsDBC = {}
do
local t = MTH_DS_HunterSpellsDBC
t[19596] = {name="[Deprecated] Bestial Swiftness",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19574] = {name="[Deprecated] Bestial Wrath",cast=0,cd=120000,categoryCD=120000,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=0}
t[19500] = {name="[Deprecated] Hawk Eye",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19457] = {name="[Deprecated] Improved Arcane Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19458] = {name="[Deprecated] Improved Arcane Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24386] = {name="[Deprecated] Improved Aspect of the Monkey",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24387] = {name="[Deprecated] Improved Aspect of the Monkey",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19424] = {name="[Deprecated] Improved Hunter's Mark",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19425] = {name="[Deprecated] Improved Hunter's Mark",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19494] = {name="[Deprecated] Improved Scorpid Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19234] = {name="[Deprecated] Improved Wing Clip",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19235] = {name="[Deprecated] Improved Wing Clip",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19374] = {name="[Deprecated] Killer Instinct",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19375] = {name="[Deprecated] Killer Instinct",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19562] = {name="[Deprecated] Pathfinding",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19563] = {name="[Deprecated] Pathfinding",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[14275] = {name="[Deprecated] Scorpid Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=6000,manaCost=90,icon="",familyFlags=32768}
t[14276] = {name="[Deprecated] Scorpid Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=3600000,manaCost=125,icon="",familyFlags=32768}
t[14277] = {name="[Deprecated] Scorpid Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=165,icon="",familyFlags=32768}
t[19611] = {name="[Deprecated] Thick Hide",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19613] = {name="[Deprecated] Thick Hide",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51520] = {name="[Deprecated] Trueshot Aura Percent Bonus",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=0}
t[51521] = {name="[Deprecated] Trueshot Aura Percent Bonus",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=0}
t[51522] = {name="[Deprecated] Trueshot Aura Percent Bonus",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=0}
t[28752] = {name="Adrenaline Rush",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19434] = {name="Aimed Shot",cast=3000,cd=6000,categoryCD=0,recoveryCD=6000,duration=60000,manaCost=75,icon="",familyFlags=131072}
t[20900] = {name="Aimed Shot",cast=3000,cd=6000,categoryCD=0,recoveryCD=6000,duration=5000,manaCost=115,icon="",familyFlags=131072}
t[20901] = {name="Aimed Shot",cast=3000,cd=6000,categoryCD=0,recoveryCD=6000,duration=1000,manaCost=160,icon="",familyFlags=131072}
t[20902] = {name="Aimed Shot",cast=3000,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=210,icon="",familyFlags=131072}
t[20903] = {name="Aimed Shot",cast=3000,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=260,icon="",familyFlags=131072}
t[20904] = {name="Aimed Shot",cast=3000,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=310,icon="",familyFlags=131072}
t[27632] = {name="Aimed Shot",cast=3000,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=310,icon="",familyFlags=131072}
t[23721] = {name="Arcane Infused",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[3044] = {name="Arcane Shot",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=600000,manaCost=25,icon="",familyFlags=2048}
t[14281] = {name="Arcane Shot",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=30000,manaCost=35,icon="",familyFlags=2048}
t[14282] = {name="Arcane Shot",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=60000,manaCost=50,icon="",familyFlags=2048}
t[14283] = {name="Arcane Shot",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=5000,manaCost=80,icon="",familyFlags=2048}
t[14284] = {name="Arcane Shot",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=1000,manaCost=105,icon="",familyFlags=2048}
t[14285] = {name="Arcane Shot",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=135,icon="",familyFlags=2048}
t[14286] = {name="Arcane Shot",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=160,icon="",familyFlags=2048}
t[14287] = {name="Arcane Shot",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=190,icon="",familyFlags=2048}
t[23157] = {name="Arcane Shot Mana Reduction",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[13161] = {name="Aspect of the Beast",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1800000,manaCost=50,icon="",familyFlags=0}
t[5118] = {name="Aspect of the Cheetah",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=40,icon="",familyFlags=2097152}
t[45651] = {name="Aspect of the Fox",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=120000,manaCost=140,icon="",familyFlags=0}
t[13165] = {name="Aspect of the Hawk",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=20,icon="",familyFlags=1048576}
t[14318] = {name="Aspect of the Hawk",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=20000,manaCost=35,icon="",familyFlags=1048576}
t[14319] = {name="Aspect of the Hawk",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=5000,manaCost=50,icon="",familyFlags=1048576}
t[14320] = {name="Aspect of the Hawk",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=11000,manaCost=70,icon="",familyFlags=1048576}
t[14321] = {name="Aspect of the Hawk",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=90,icon="",familyFlags=1048576}
t[14322] = {name="Aspect of the Hawk",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=110,icon="",familyFlags=1048576}
t[25296] = {name="Aspect of the Hawk",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=120,icon="",familyFlags=1048576}
t[13163] = {name="Aspect of the Monkey",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=120000,manaCost=20,icon="",familyFlags=524288}
t[13159] = {name="Aspect of the Pack",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=100,icon="",familyFlags=2097152}
t[45652] = {name="Aspect of the Snake",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=120000,manaCost=20,icon="",familyFlags=0}
t[45664] = {name="Aspect of the Snake",cast=0,cd=1500,categoryCD=1500,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[45649] = {name="Aspect of the Turtle",cast=0,cd=180000,categoryCD=180000,recoveryCD=0,duration=60000,manaCost=10,icon="",familyFlags=0}
t[20043] = {name="Aspect of the Wild",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=90,icon="",familyFlags=0}
t[20190] = {name="Aspect of the Wild",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=115,icon="",familyFlags=0}
t[45650] = {name="Aspect of the Wolf",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=20,icon="",familyFlags=1048576}
t[51496] = {name="Aspect of the Wolf",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=20000,manaCost=35,icon="",familyFlags=1048576}
t[51497] = {name="Aspect of the Wolf",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=5000,manaCost=50,icon="",familyFlags=1048576}
t[51498] = {name="Aspect of the Wolf",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=11000,manaCost=70,icon="",familyFlags=1048576}
t[51499] = {name="Aspect of the Wolf",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=90,icon="",familyFlags=1048576}
t[51500] = {name="Aspect of the Wolf",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=110,icon="",familyFlags=1048576}
t[51501] = {name="Aspect of the Wolf",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=120,icon="",familyFlags=1048576}
t[75] = {name="Auto Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=0,icon="",familyFlags=1}
t[46023] = {name="Avoidance",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19767] = {name="Aynasha's Bow",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[46321] = {name="Baited Shot",cast=0,cd=8000,categoryCD=8000,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19461] = {name="Barrage",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19462] = {name="Barrage",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24691] = {name="Barrage",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[22996] = {name="Battle Standard",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[23005] = {name="Battle Standard",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[23034] = {name="Battle Standard",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[23035] = {name="Battle Standard",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[23538] = {name="Battle Standard",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[23539] = {name="Battle Standard",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[47355] = {name="Beast Mastery",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[29233] = {name="Beast of the Unseen Path",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19590] = {name="Bestial Discipline",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19592] = {name="Bestial Discipline",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19603] = {name="Bestial Fury",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19605] = {name="Bestial Fury",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19606] = {name="Bestial Fury",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19607] = {name="Bestial Fury",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19608] = {name="Bestial Fury",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51554] = {name="Bestial Precision",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51555] = {name="Bestial Precision",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24395] = {name="Bestial Wrath",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=0}
t[24396] = {name="Bestial Wrath",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=0}
t[24397] = {name="Bestial Wrath",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=0}
t[26592] = {name="Bestial Wrath",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=0}
t[17253] = {name="Bite",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=10000,manaCost=35,icon="",familyFlags=0}
t[17255] = {name="Bite",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=15000,manaCost=35,icon="",familyFlags=0}
t[17256] = {name="Bite",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=230000,manaCost=35,icon="",familyFlags=0}
t[17257] = {name="Bite",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=160000,manaCost=35,icon="",familyFlags=0}
t[17258] = {name="Bite",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=6000,manaCost=35,icon="",familyFlags=0}
t[17259] = {name="Bite",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=1200000,manaCost=35,icon="",familyFlags=0}
t[17260] = {name="Bite",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=35,icon="",familyFlags=0}
t[17261] = {name="Bite",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=35,icon="",familyFlags=0}
t[36523] = {name="Bubble Barrier",cast=0,cd=30000,categoryCD=0,recoveryCD=30000,duration=60000,manaCost=20,icon="",familyFlags=0}
t[36524] = {name="Bubble Barrier",cast=0,cd=30000,categoryCD=0,recoveryCD=30000,duration=160000,manaCost=20,icon="",familyFlags=0}
t[36525] = {name="Bubble Barrier",cast=0,cd=30000,categoryCD=0,recoveryCD=30000,duration=1200000,manaCost=20,icon="",familyFlags=0}
t[36526] = {name="Bubble Barrier",cast=0,cd=30000,categoryCD=0,recoveryCD=30000,duration=0,manaCost=20,icon="",familyFlags=0}
t[51575] = {name="Carve",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=60000,manaCost=0,icon="",familyFlags=0}
t[52415] = {name="Carve",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=11000,manaCost=0,icon="",familyFlags=0}
t[52416] = {name="Carve",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=0,manaCost=0,icon="",familyFlags=0}
t[24348] = {name="Cheetah Sprint",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[3009] = {name="Claw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=25,icon="",familyFlags=0}
t[3010] = {name="Claw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=25,icon="",familyFlags=0}
t[16827] = {name="Claw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=25,icon="",familyFlags=0}
t[16828] = {name="Claw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=15000,manaCost=25,icon="",familyFlags=0}
t[16829] = {name="Claw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=230000,manaCost=25,icon="",familyFlags=0}
t[16830] = {name="Claw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=160000,manaCost=25,icon="",familyFlags=0}
t[16831] = {name="Claw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=6000,manaCost=25,icon="",familyFlags=0}
t[16832] = {name="Claw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=25,icon="",familyFlags=0}
t[19239] = {name="Clever Traps",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19245] = {name="Clever Traps",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[20576] = {name="Command",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[21563] = {name="Command",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[5116] = {name="Concussive Shot",cast=0,cd=12000,categoryCD=12000,recoveryCD=0,duration=15000,manaCost=0,icon="",familyFlags=512}
t[23158] = {name="Concussive Shot Cooldown Reduction",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[49559] = {name="Coordinated Assault",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[28317] = {name="Copy of Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[19306] = {name="Counterattack",cast=0,cd=5000,categoryCD=0,recoveryCD=5000,duration=1800000,manaCost=45,icon="",familyFlags=0}
t[20909] = {name="Counterattack",cast=0,cd=5000,categoryCD=0,recoveryCD=5000,duration=3600000,manaCost=65,icon="",familyFlags=0}
t[20910] = {name="Counterattack",cast=0,cd=5000,categoryCD=0,recoveryCD=5000,duration=0,manaCost=85,icon="",familyFlags=0}
t[36548] = {name="Death Roll",cast=0,cd=30000,categoryCD=30000,recoveryCD=0,duration=60000,manaCost=50,icon="",familyFlags=0}
t[36549] = {name="Death Roll",cast=0,cd=30000,categoryCD=30000,recoveryCD=0,duration=60000,manaCost=50,icon="",familyFlags=0}
t[36550] = {name="Death Roll",cast=0,cd=30000,categoryCD=30000,recoveryCD=0,duration=1800000,manaCost=50,icon="",familyFlags=0}
t[36551] = {name="Death Roll",cast=0,cd=30000,categoryCD=30000,recoveryCD=0,duration=1200000,manaCost=50,icon="",familyFlags=0}
t[36552] = {name="Death Roll",cast=0,cd=30000,categoryCD=30000,recoveryCD=0,duration=0,manaCost=50,icon="",familyFlags=0}
t[36553] = {name="Death Roll",cast=0,cd=30000,categoryCD=30000,recoveryCD=0,duration=0,manaCost=50,icon="",familyFlags=0}
t[24992] = {name="Demonic Transformation",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=0}
t[23578] = {name="Detect Weakness",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19263] = {name="Deterrence",cast=0,cd=300000,categoryCD=300000,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=0}
t[781] = {name="Disengage",cast=0,cd=5000,categoryCD=0,recoveryCD=5000,duration=60000,manaCost=50,icon="",familyFlags=0}
t[14272] = {name="Disengage",cast=0,cd=5000,categoryCD=0,recoveryCD=5000,duration=0,manaCost=100,icon="",familyFlags=0}
t[14273] = {name="Disengage",cast=0,cd=5000,categoryCD=0,recoveryCD=5000,duration=0,manaCost=150,icon="",familyFlags=0}
t[14274] = {name="Distracting Shot",cast=0,cd=8000,categoryCD=0,recoveryCD=8000,duration=60000,manaCost=30,icon="",familyFlags=65536}
t[15629] = {name="Distracting Shot",cast=0,cd=8000,categoryCD=0,recoveryCD=8000,duration=1800000,manaCost=50,icon="",familyFlags=65536}
t[15630] = {name="Distracting Shot",cast=0,cd=8000,categoryCD=0,recoveryCD=8000,duration=1200000,manaCost=70,icon="",familyFlags=65536}
t[15631] = {name="Distracting Shot",cast=0,cd=8000,categoryCD=0,recoveryCD=8000,duration=0,manaCost=90,icon="",familyFlags=65536}
t[15632] = {name="Distracting Shot",cast=0,cd=8000,categoryCD=0,recoveryCD=8000,duration=0,manaCost=110,icon="",familyFlags=65536}
t[20736] = {name="Distracting Shot",cast=0,cd=8000,categoryCD=0,recoveryCD=8000,duration=30000,manaCost=20,icon="",familyFlags=65536}
t[19416] = {name="Efficiency",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19417] = {name="Efficiency",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19418] = {name="Efficiency",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19419] = {name="Efficiency",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19420] = {name="Efficiency",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51552] = {name="Elusiveness",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51553] = {name="Elusiveness",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51517] = {name="Endless Quiver",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51518] = {name="Endless Quiver",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19583] = {name="Endurance Training",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19584] = {name="Endurance Training",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19585] = {name="Endurance Training",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19586] = {name="Endurance Training",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19587] = {name="Endurance Training",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19184] = {name="Entrapment",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19387] = {name="Entrapment",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19388] = {name="Entrapment",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19389] = {name="Entrapment",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19390] = {name="Entrapment",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[13813] = {name="Explosive Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=0,manaCost=275,icon="",familyFlags=128}
t[14316] = {name="Explosive Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=0,manaCost=395,icon="",familyFlags=128}
t[14317] = {name="Explosive Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=0,manaCost=520,icon="",familyFlags=128}
t[13812] = {name="Explosive Trap Effect",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=4}
t[14314] = {name="Explosive Trap Effect",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=4}
t[14315] = {name="Explosive Trap Effect",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=4}
t[51519] = {name="Extra Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=0,icon="",familyFlags=1}
t[1002] = {name="Eyes of the Beast",cast=2000,cd=0,categoryCD=0,recoveryCD=0,duration=120000,manaCost=20,icon="",familyFlags=4194304}
t[17331] = {name="Fang of the Crystal Spider",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[52402] = {name="Faster Bleeds 5 (Hunter)",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[5384] = {name="Feign Death",cast=0,cd=30000,categoryCD=30000,recoveryCD=0,duration=1800000,manaCost=80,icon="Spell_Nature_Regenerate",familyFlags=256}
t[19598] = {name="Ferocity",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19599] = {name="Ferocity",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19600] = {name="Ferocity",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19601] = {name="Ferocity",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19602] = {name="Ferocity",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[46269] = {name="Fire Breath",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=10000,manaCost=50,icon="",familyFlags=0}
t[46270] = {name="Fire Breath",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=0,manaCost=50,icon="",familyFlags=0}
t[19781] = {name="Flame Spear",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=75000,manaCost=0,icon="",familyFlags=0}
t[1543] = {name="Flare",cast=0,cd=15000,categoryCD=15000,recoveryCD=0,duration=6000,manaCost=50,icon="Spell_Magic_PolymorphChicken",familyFlags=0}
t[1499] = {name="Freezing Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=60000,manaCost=50,icon="",familyFlags=128}
t[14310] = {name="Freezing Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=1200000,manaCost=75,icon="",familyFlags=128}
t[14311] = {name="Freezing Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=0,manaCost=100,icon="",familyFlags=128}
t[27753] = {name="Freezing Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=0,manaCost=100,icon="Spell_Nature_Regenerate",familyFlags=128}
t[21750] = {name="Freezing Trap (TEST)",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=60000,manaCost=50,icon="",familyFlags=0}
t[3355] = {name="Freezing Trap Effect",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=8}
t[14308] = {name="Freezing Trap Effect",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=8}
t[14309] = {name="Freezing Trap Effect",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=8}
t[19621] = {name="Frenzy",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19622] = {name="Frenzy",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19623] = {name="Frenzy",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19624] = {name="Frenzy",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19625] = {name="Frenzy",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[13809] = {name="Frost Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=5000,manaCost=60,icon="",familyFlags=128}
t[13810] = {name="Frost Trap Aura",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=5000,manaCost=0,icon="",familyFlags=16}
t[14207] = {name="Frost Trap Effect",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=5000,manaCost=0,icon="",familyFlags=16}
t[24597] = {name="Furious Howl",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=0,manaCost=50,icon="",familyFlags=0}
t[24603] = {name="Furious Howl",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=1200000,manaCost=50,icon="",familyFlags=0}
t[24604] = {name="Furious Howl",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=60000,manaCost=50,icon="",familyFlags=0}
t[24605] = {name="Furious Howl",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=160000,manaCost=50,icon="",familyFlags=0}
t[46296] = {name="Grace",cast=0,cd=60000,categoryCD=60000,recoveryCD=0,duration=60000,manaCost=10,icon="Spell_Nature_GuardianWard",familyFlags=0}
t[14824] = {name="Haste",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[14825] = {name="Haste",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[14826] = {name="Haste",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[14827] = {name="Haste",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[14828] = {name="Haste",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[14829] = {name="Haste",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[29413] = {name="Haste",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[29414] = {name="Haste",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[29415] = {name="Haste",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[29416] = {name="Haste",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[29417] = {name="Haste",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[29418] = {name="Haste",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19498] = {name="Hawk Eye",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19499] = {name="Hawk Eye",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[21942] = {name="Hunter Giant Stalker",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[1130] = {name="Hunter's Mark",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=600000,manaCost=15,icon="",familyFlags=1024}
t[14323] = {name="Hunter's Mark",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=45000,manaCost=30,icon="",familyFlags=1024}
t[14324] = {name="Hunter's Mark",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=45,icon="",familyFlags=1024}
t[14325] = {name="Hunter's Mark",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=60,icon="",familyFlags=1024}
t[13795] = {name="Immolation Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=230000,manaCost=50,icon="",familyFlags=128}
t[14302] = {name="Immolation Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=240000,manaCost=90,icon="",familyFlags=128}
t[14303] = {name="Immolation Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=1000,manaCost=135,icon="",familyFlags=128}
t[14304] = {name="Immolation Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=0,manaCost=190,icon="",familyFlags=128}
t[14305] = {name="Immolation Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=0,manaCost=245,icon="",familyFlags=128}
t[13797] = {name="Immolation Trap Effect",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=230000,manaCost=0,icon="",familyFlags=4}
t[14298] = {name="Immolation Trap Effect",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=240000,manaCost=0,icon="Spell_Nature_MoonGlow",familyFlags=4}
t[14299] = {name="Immolation Trap Effect",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1000,manaCost=0,icon="Spell_Nature_MoonGlow",familyFlags=4}
t[14300] = {name="Immolation Trap Effect",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="Spell_Nature_MoonGlow",familyFlags=4}
t[14301] = {name="Immolation Trap Effect",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="Spell_Nature_MoonGlow",familyFlags=4}
t[15860] = {name="Impale",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="Spell_Holy_Resurrection",familyFlags=1}
t[16001] = {name="Impale",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="Spell_Holy_Resurrection",familyFlags=1}
t[24049] = {name="Impale",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=0,icon="Spell_Holy_Resurrection",familyFlags=1}
t[26548] = {name="Impale",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=0,icon="Spell_Holy_Resurrection",familyFlags=1}
t[23559] = {name="Improved Aspect of the Hawk",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19407] = {name="Improved Concussive Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19412] = {name="Improved Concussive Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19413] = {name="Improved Concussive Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19414] = {name="Improved Concussive Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19415] = {name="Improved Concussive Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24465] = {name="Improved Concussive Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19378] = {name="Improved Explosive Trap",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19379] = {name="Improved Explosive Trap",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19557] = {name="Improved Eyes of the Beast",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19558] = {name="Improved Eyes of the Beast",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[45662] = {name="Improved Eyes of the Beast",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[45663] = {name="Improved Eyes of the Beast",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19286] = {name="Improved Feign Death",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19287] = {name="Improved Feign Death",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24432] = {name="Improved Feign Death",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19288] = {name="Improved Freezing Trap",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24298] = {name="Improved Freezing Trap",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24299] = {name="Improved Freezing Trap",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19421] = {name="Improved Hunter's Mark",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19422] = {name="Improved Hunter's Mark",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19423] = {name="Improved Hunter's Mark",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19246] = {name="Improved Immolation Trap",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19247] = {name="Improved Immolation Trap",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19248] = {name="Improved Immolation Trap",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19572] = {name="Improved Mend Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19573] = {name="Improved Mend Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[23560] = {name="Improved Mend Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24406] = {name="Improved Mend Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19549] = {name="Improved Primal Aspects",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19550] = {name="Improved Primal Aspects",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19551] = {name="Improved Primal Aspects",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19575] = {name="Improved Revive Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24443] = {name="Improved Revive Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19491] = {name="Improved Scorpid Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19493] = {name="Improved Scorpid Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24467] = {name="Improved Serpent Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51515] = {name="Improved Steady Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51516] = {name="Improved Steady Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19464] = {name="Improved Stings",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19465] = {name="Improved Stings",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19466] = {name="Improved Stings",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19467] = {name="Improved Stings",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19468] = {name="Improved Stings",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[52603] = {name="Improved Swift Aspects",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[23566] = {name="Improved Volley and Multishot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19228] = {name="Improved Wing Clip",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19232] = {name="Improved Wing Clip",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19233] = {name="Improved Wing Clip",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[22854] = {name="Increased Hunter Pet Damage",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[27205] = {name="Increased Pet Damage +3%",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[52413] = {name="Increased Pet Damage +4%",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[41827] = {name="Kill Command",cast=0,cd=12000,categoryCD=12000,recoveryCD=0,duration=0,manaCost=0,icon="Spell_Holy_PrayerOfHealing",familyFlags=0}
t[41828] = {name="Kill Command",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19162] = {name="Killer Instinct",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19163] = {name="Killer Instinct",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19165] = {name="Killer Instinct",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19370] = {name="Killer Instinct",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19371] = {name="Killer Instinct",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19373] = {name="Killer Instinct",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[20907] = {name="Lacerate",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=80,icon="INV_Gauntlets_06",familyFlags=0}
t[20908] = {name="Lacerate",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=95,icon="INV_Gauntlets_06",familyFlags=0}
t[24118] = {name="Lacerate",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=65,icon="INV_Gauntlets_06",familyFlags=0}
t[24119] = {name="Lacerate",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=80,icon="INV_Gauntlets_06",familyFlags=0}
t[24120] = {name="Lacerate",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=95,icon="INV_Gauntlets_06",familyFlags=0}
t[45653] = {name="Lacerate",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=65,icon="INV_Gauntlets_06",familyFlags=0}
t[45654] = {name="Lacerate",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=80,icon="INV_Gauntlets_06",familyFlags=0}
t[45655] = {name="Lacerate",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=95,icon="INV_Gauntlets_06",familyFlags=0}
t[45656] = {name="Lacerate",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=0}
t[45657] = {name="Lacerate",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=80,icon="INV_Gauntlets_06",familyFlags=0}
t[45658] = {name="Lacerate",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=95,icon="INV_Gauntlets_06",familyFlags=0}
t[48049] = {name="Lacerate",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[48050] = {name="Lacerate",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="INV_Gauntlets_06",familyFlags=0}
t[24844] = {name="Lightning Breath",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=50,icon="Spell_Shadow_GatherShadows",familyFlags=0}
t[25008] = {name="Lightning Breath",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=30000,manaCost=50,icon="Spell_Shadow_GatherShadows",familyFlags=0}
t[25009] = {name="Lightning Breath",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=160000,manaCost=50,icon="Spell_Shadow_GatherShadows",familyFlags=0}
t[25010] = {name="Lightning Breath",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1000,manaCost=50,icon="Spell_Shadow_GatherShadows",familyFlags=0}
t[25011] = {name="Lightning Breath",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=50,icon="Spell_Shadow_GatherShadows",familyFlags=0}
t[25012] = {name="Lightning Breath",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=50,icon="Spell_Shadow_GatherShadows",familyFlags=0}
t[19168] = {name="Lightning Reflexes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19180] = {name="Lightning Reflexes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19181] = {name="Lightning Reflexes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24296] = {name="Lightning Reflexes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24297] = {name="Lightning Reflexes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[46297] = {name="Lizard Bolt",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=30000,manaCost=35,icon="Ability_Gouge",familyFlags=0}
t[46298] = {name="Lizard Bolt",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=160000,manaCost=35,icon="Ability_Gouge",familyFlags=0}
t[46299] = {name="Lizard Bolt",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1000,manaCost=35,icon="Ability_Gouge",familyFlags=0}
t[46300] = {name="Lizard Bolt",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=35,icon="Ability_Gouge",familyFlags=0}
t[46301] = {name="Lizard Bolt",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=35,icon="Ability_Gouge",familyFlags=0}
t[136] = {name="Mend Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=30000,manaCost=50,icon="",familyFlags=8388608}
t[3111] = {name="Mend Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=90,icon="",familyFlags=8388608}
t[3661] = {name="Mend Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=5000,manaCost=155,icon="",familyFlags=8388608}
t[3662] = {name="Mend Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1000,manaCost=225,icon="",familyFlags=8388608}
t[13542] = {name="Mend Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=300,icon="",familyFlags=8388608}
t[13543] = {name="Mend Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=385,icon="",familyFlags=8388608}
t[13544] = {name="Mend Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=480,icon="",familyFlags=8388608}
t[1495] = {name="Mongoose Bite",cast=0,cd=5000,categoryCD=0,recoveryCD=5000,duration=230000,manaCost=30,icon="Trade_BrewPoison",familyFlags=2}
t[14269] = {name="Mongoose Bite",cast=0,cd=5000,categoryCD=0,recoveryCD=5000,duration=1800000,manaCost=40,icon="Trade_BrewPoison",familyFlags=2}
t[14270] = {name="Mongoose Bite",cast=0,cd=5000,categoryCD=0,recoveryCD=5000,duration=0,manaCost=50,icon="Trade_BrewPoison",familyFlags=2}
t[14271] = {name="Mongoose Bite",cast=0,cd=5000,categoryCD=0,recoveryCD=5000,duration=0,manaCost=65,icon="Trade_BrewPoison",familyFlags=2}
t[19485] = {name="Mortal Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19487] = {name="Mortal Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19488] = {name="Mortal Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19489] = {name="Mortal Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19490] = {name="Mortal Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[2643] = {name="Multi-Shot",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=20000,manaCost=100,icon="",familyFlags=4096}
t[14288] = {name="Multi-Shot",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=1800000,manaCost=140,icon="",familyFlags=4096}
t[14289] = {name="Multi-Shot",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=3600000,manaCost=175,icon="",familyFlags=4096}
t[14290] = {name="Multi-Shot",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=0,manaCost=210,icon="",familyFlags=4096}
t[25294] = {name="Multi-Shot",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=0,manaCost=230,icon="",familyFlags=4096}
t[28751] = {name="Multi-Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[28539] = {name="Multi-Shot Damage Increase",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[52602] = {name="Multishot and Carve Cooldown",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[28761] = {name="Not There",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[36532] = {name="Packleader",cast=0,cd=90000,categoryCD=90000,recoveryCD=0,duration=60000,manaCost=50,icon="INV_Potion_40",familyFlags=0}
t[36562] = {name="Packleader",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="INV_Potion_40",familyFlags=0}
t[19559] = {name="Pathfinding",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19560] = {name="Pathfinding",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[52685] = {name="Pet Focus Regen Bonus",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51512] = {name="Piercing Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51513] = {name="Piercing Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51514] = {name="Piercing Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=120000,manaCost=0,icon="",familyFlags=0}
t[46292] = {name="Pin",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=230000,manaCost=10,icon="",familyFlags=0}
t[46293] = {name="Pin",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=6000,manaCost=10,icon="",familyFlags=0}
t[46294] = {name="Pin",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=10,icon="",familyFlags=0}
t[46295] = {name="Pin",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=10,icon="",familyFlags=0}
t[51576] = {name="Planning Ahead",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51577] = {name="Planning Ahead",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51578] = {name="Planning Ahead Dummy",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[46271] = {name="Poison Spit",cast=0,cd=8000,categoryCD=8000,recoveryCD=8000,duration=300000,manaCost=35,icon="Spell_Nature_LightningShield",familyFlags=0}
t[46272] = {name="Poison Spit",cast=0,cd=8000,categoryCD=8000,recoveryCD=8000,duration=0,manaCost=35,icon="Spell_Nature_LightningShield",familyFlags=0}
t[46273] = {name="Poison Spit",cast=0,cd=8000,categoryCD=8000,recoveryCD=8000,duration=0,manaCost=35,icon="Spell_Nature_LightningShield",familyFlags=0}
t[24450] = {name="Prowl",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=1800000,manaCost=40,icon="Spell_Fire_Fireball02",familyFlags=0}
t[24452] = {name="Prowl",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=1200000,manaCost=40,icon="Spell_Fire_Fireball02",familyFlags=0}
t[24453] = {name="Prowl",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=0,manaCost=40,icon="Spell_Fire_Fireball02",familyFlags=0}
t[51547] = {name="Quick Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51548] = {name="Quick Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51549] = {name="Quick Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51550] = {name="Quick Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51551] = {name="Quick Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51542] = {name="Quick Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="Spell_Shadow_ImpPhaseShift",familyFlags=0}
t[51543] = {name="Quick Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="Spell_Shadow_ImpPhaseShift",familyFlags=0}
t[51544] = {name="Quick Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="Spell_Shadow_ImpPhaseShift",familyFlags=0}
t[51545] = {name="Quick Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="Spell_Shadow_ImpPhaseShift",familyFlags=0}
t[51546] = {name="Quick Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="Spell_Shadow_ImpPhaseShift",familyFlags=0}
t[19511] = {name="Ranged Weapon Specialization",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[3045] = {name="Rapid Fire",cast=0,cd=300000,categoryCD=0,recoveryCD=300000,duration=240000,manaCost=100,icon="",familyFlags=32}
t[28755] = {name="Rapid Fire",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[2973] = {name="Raptor Strike",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=10000,manaCost=15,icon="Spell_Nature_NatureTouchGrow",familyFlags=2}
t[14260] = {name="Raptor Strike",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=15000,manaCost=25,icon="Spell_Nature_NatureTouchGrow",familyFlags=2}
t[14261] = {name="Raptor Strike",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=230000,manaCost=35,icon="Spell_Nature_NatureTouchGrow",familyFlags=2}
t[14262] = {name="Raptor Strike",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=160000,manaCost=45,icon="Spell_Nature_NatureTouchGrow",familyFlags=2}
t[14263] = {name="Raptor Strike",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=6000,manaCost=55,icon="Spell_Nature_NatureTouchGrow",familyFlags=2}
t[14264] = {name="Raptor Strike",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=1200000,manaCost=70,icon="Spell_Nature_NatureTouchGrow",familyFlags=2}
t[14265] = {name="Raptor Strike",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=85,icon="Spell_Nature_NatureTouchGrow",familyFlags=2}
t[14266] = {name="Raptor Strike",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=100,icon="Spell_Nature_NatureTouchGrow",familyFlags=2}
t[23989] = {name="Readiness",cast=0,cd=300000,categoryCD=300000,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51570] = {name="Resourcefulness",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51571] = {name="Resourcefulness",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51572] = {name="Resourcefulness",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51573] = {name="Resourcefulness",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51574] = {name="Resourcefulness",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[982] = {name="Revive Pet",cast=10000,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=16777216}
t[23499] = {name="Revive Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=16777216}
t[36535] = {name="Roar of Fortitude",cast=0,cd=120000,categoryCD=120000,recoveryCD=0,duration=1800000,manaCost=50,icon="",familyFlags=0}
t[36536] = {name="Savage Rend",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=10000,manaCost=35,icon="INV_Gauntlets_06",familyFlags=0}
t[36537] = {name="Savage Rend",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=30000,manaCost=35,icon="INV_Gauntlets_06",familyFlags=0}
t[36538] = {name="Savage Rend",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=160000,manaCost=35,icon="INV_Gauntlets_06",familyFlags=0}
t[36539] = {name="Savage Rend",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=1000,manaCost=35,icon="INV_Gauntlets_06",familyFlags=0}
t[36540] = {name="Savage Rend",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=0,manaCost=35,icon="INV_Gauntlets_06",familyFlags=0}
t[36541] = {name="Savage Rend",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=0,manaCost=35,icon="INV_Gauntlets_06",familyFlags=0}
t[46282] = {name="Savage Rend",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=25,icon="",familyFlags=0}
t[46283] = {name="Savage Rend",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=25,icon="",familyFlags=0}
t[46284] = {name="Savage Rend",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=25,icon="",familyFlags=0}
t[46285] = {name="Savage Rend",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=25,icon="",familyFlags=0}
t[46286] = {name="Savage Rend",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=25,icon="",familyFlags=0}
t[19159] = {name="Savage Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19160] = {name="Savage Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19503] = {name="Scatter Shot",cast=0,cd=30000,categoryCD=30000,recoveryCD=0,duration=1800000,manaCost=0,icon="INV_Flask_05",familyFlags=262144}
t[23601] = {name="Scatter Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1800000,manaCost=0,icon="INV_Flask_05",familyFlags=262144}
t[24583] = {name="Scorpid Poison",cast=0,cd=3000,categoryCD=0,recoveryCD=3000,duration=160000,manaCost=25,icon="Spell_Nature_LightningShield",familyFlags=0}
t[24586] = {name="Scorpid Poison",cast=0,cd=3000,categoryCD=0,recoveryCD=3000,duration=1200000,manaCost=25,icon="Spell_Nature_LightningShield",familyFlags=0}
t[24587] = {name="Scorpid Poison",cast=0,cd=3000,categoryCD=0,recoveryCD=3000,duration=0,manaCost=25,icon="Spell_Nature_LightningShield",familyFlags=0}
t[24640] = {name="Scorpid Poison",cast=0,cd=3000,categoryCD=0,recoveryCD=3000,duration=15000,manaCost=25,icon="Spell_Nature_LightningShield",familyFlags=0}
t[45659] = {name="Scorpid Stike",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=165,icon="",familyFlags=32768}
t[3043] = {name="Scorpid Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=45000,manaCost=70,icon="",familyFlags=32768}
t[51509] = {name="Scorpid Sting Damage Reduction",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=45000,manaCost=0,icon="",familyFlags=0}
t[24423] = {name="Screech",cast=0,cd=5000,categoryCD=5000,recoveryCD=0,duration=15000,manaCost=20,icon="",familyFlags=0}
t[24577] = {name="Screech",cast=0,cd=5000,categoryCD=5000,recoveryCD=0,duration=160000,manaCost=20,icon="",familyFlags=0}
t[24578] = {name="Screech",cast=0,cd=5000,categoryCD=5000,recoveryCD=0,duration=0,manaCost=20,icon="",familyFlags=0}
t[24579] = {name="Screech",cast=0,cd=5000,categoryCD=5000,recoveryCD=0,duration=0,manaCost=20,icon="",familyFlags=0}
t[1978] = {name="Serpent Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=120000,manaCost=15,icon="",familyFlags=16384}
t[13549] = {name="Serpent Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=30,icon="",familyFlags=16384}
t[13550] = {name="Serpent Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=20000,manaCost=50,icon="",familyFlags=16384}
t[13551] = {name="Serpent Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=240000,manaCost=80,icon="",familyFlags=16384}
t[13552] = {name="Serpent Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=115,icon="",familyFlags=16384}
t[13553] = {name="Serpent Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=3600000,manaCost=150,icon="",familyFlags=16384}
t[13554] = {name="Serpent Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=190,icon="",familyFlags=16384}
t[13555] = {name="Serpent Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=230,icon="",familyFlags=16384}
t[25295] = {name="Serpent Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=250,icon="",familyFlags=16384}
t[45661] = {name="Serpent Strike",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=250,icon="",familyFlags=16384}
t[29218] = {name="Server Spell",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[26064] = {name="Shell Shield",cast=0,cd=60000,categoryCD=60000,recoveryCD=0,duration=60000,manaCost=10,icon="",familyFlags=0}
t[6660] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[15547] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[15620] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[16100] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[16496] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[17353] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[18561] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[20463] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[22121] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[22411] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[22907] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[23073] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[23337] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[2480] = {name="Shoot Bow",cast=1000,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=0,icon="",familyFlags=1}
t[7919] = {name="Shoot Crossbow",cast=1000,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=0,icon="",familyFlags=1}
t[7918] = {name="Shoot Gun",cast=1000,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=0,icon="",familyFlags=1}
t[25200] = {name="Shoot Silithus Rifleman Long Range",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[26282] = {name="Shoot Tauren Rifleman",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[28318] = {name="Slime Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[19501] = {name="Spell - Reuse 19501",cast=0,cd=30000,categoryCD=30000,recoveryCD=0,duration=1800000,manaCost=0,icon="",familyFlags=262144}
t[27919] = {name="Spit",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[28758] = {name="Stalker's Ally",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[3035] = {name="Steady Shot",cast=1000,cd=0,categoryCD=0,recoveryCD=0,duration=120000,manaCost=0,icon="",familyFlags=0}
t[3036] = {name="Steady Shot",cast=1000,cd=0,categoryCD=0,recoveryCD=0,duration=120000,manaCost=0,icon="",familyFlags=0}
t[3037] = {name="Steady Shot",cast=1000,cd=0,categoryCD=0,recoveryCD=0,duration=45000,manaCost=0,icon="",familyFlags=0}
t[3038] = {name="Steady Shot",cast=1000,cd=0,categoryCD=0,recoveryCD=0,duration=1800000,manaCost=0,icon="",familyFlags=0}
t[3668] = {name="Steady Shot",cast=1000,cd=0,categoryCD=0,recoveryCD=0,duration=11000,manaCost=0,icon="",familyFlags=0}
t[45970] = {name="Steady Shot",cast=1000,cd=0,categoryCD=0,recoveryCD=0,duration=11000,manaCost=0,icon="",familyFlags=0}
t[45972] = {name="Steady Shot",cast=1000,cd=0,categoryCD=0,recoveryCD=0,duration=11000,manaCost=0,icon="",familyFlags=0}
t[45974] = {name="Steady Shot",cast=1000,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[52684] = {name="Steady Shot and Raptor Strike Crit Bonus",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51579] = {name="Stinging Nettle",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51580] = {name="Stinging Nettle",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[36531] = {name="Strider Presence",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=50,icon="",familyFlags=0}
t[49557] = {name="Strike Together",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[49558] = {name="Strike Together",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[26173] = {name="Striker's Arcane Shot Bonus",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[26174] = {name="Striker's Rapid Fire Bonus",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24416] = {name="Superior Aspects",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24269] = {name="Superior Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24270] = {name="Superior Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24271] = {name="Superior Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24272] = {name="Superior Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24273] = {name="Superior Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19290] = {name="Surefooted",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19294] = {name="Surefooted",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24283] = {name="Surefooted",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24286] = {name="Surefooted",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24287] = {name="Surefooted",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19255] = {name="Survivalist",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19256] = {name="Survivalist",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19257] = {name="Survivalist",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19258] = {name="Survivalist",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19259] = {name="Survivalist",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19552] = {name="Swift Aspects",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19553] = {name="Swift Aspects",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19554] = {name="Swift Aspects",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19555] = {name="Swift Aspects",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19556] = {name="Swift Aspects",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19454] = {name="Swiftshot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19455] = {name="Swiftshot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19456] = {name="Swiftshot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[46287] = {name="Swipe",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=10000,manaCost=20,icon="Spell_Frost_ChillingBolt",familyFlags=0}
t[46288] = {name="Swipe",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=10000,manaCost=20,icon="Spell_Frost_ChillingBolt",familyFlags=0}
t[46289] = {name="Swipe",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=10000,manaCost=20,icon="Spell_Frost_ChillingBolt",familyFlags=0}
t[46290] = {name="Swipe",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=10000,manaCost=20,icon="Spell_Frost_ChillingBolt",familyFlags=0}
t[46291] = {name="Swipe",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=10000,manaCost=20,icon="Spell_Frost_ChillingBolt",familyFlags=0}
t[19678] = {name="Tame Adult Plainstrider",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[19683] = {name="Tame Armored Scorpid",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[44017] = {name="Tame Armored Scorpid",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[46705] = {name="Tame Ashfeather Swooper",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[46701] = {name="Tame Blackvenom Scorpid",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[50904] = {name="Tame Decrepit Darkhound",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[44007] = {name="Tame Decrepit Darkhound.",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[19681] = {name="Tame Dire Mottled Boar",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[44013] = {name="Tame Dire Mottled Boar.",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[46091] = {name="Tame Elder Crimson Lynx",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[50905] = {name="Tame Greater Dusk Bat",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[44009] = {name="Tame Greater Duskbat",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[19597] = {name="Tame Ice Claw Bear",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[44023] = {name="Tame Ice Claw Bear",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[46093] = {name="Tame Ivory Hawkstrider",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[19677] = {name="Tame Large Crag Boar",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[44019] = {name="Tame Large Crag Boar.",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[46095] = {name="Tame Lavender Mana Wyrm",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[46703] = {name="Tame Muckreef Crawler",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[19685] = {name="Tame Nightsaber Stalker",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[19679] = {name="Tame Prairie Stalker",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[44003] = {name="Tame Prowler",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[19676] = {name="Tame Snow Leopard",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[44021] = {name="Tame Snow Leopard",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[44001] = {name="Tame Stonetusk Boar",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[19686] = {name="Tame Strigid Screecher",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[19682] = {name="Tame Surf Crawler",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[44015] = {name="Tame Surf Crawler",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[19680] = {name="Tame Swoop",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[44011] = {name="Tame Tirisfal Plagued Bear",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[50906] = {name="Tame Tirisfal Plagued Bear",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[19684] = {name="Tame Webwood Lurker",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[44005] = {name="Tame Young Forest Bear",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[8875] = {name="Tamed Pet Passive (DND)",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=33554432}
t[19580] = {name="Tamed Pet Passive (DND)",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=67108864}
t[19581] = {name="Tamed Pet Passive (DND)",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=134217728}
t[19582] = {name="Tamed Pet Passive (DND)",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=268435456}
t[19589] = {name="Tamed Pet Passive (DND)",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=536870912}
t[19591] = {name="Tamed Pet Passive (DND)",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=1073741824}
t[20782] = {name="Tamed Pet Passive (DND)",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[20784] = {name="Tamed Pet Passive (DND)",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2147483648}
t[19609] = {name="Thick Hide",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19610] = {name="Thick Hide",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19612] = {name="Thick Hide",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[10277] = {name="Throw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=0}
t[15607] = {name="Throw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=0}
t[15795] = {name="Throw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=0}
t[16000] = {name="Throw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=0}
t[19785] = {name="Throw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=75000,manaCost=0,icon="",familyFlags=0}
t[22887] = {name="Throw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=0}
t[26090] = {name="Thunderstomp",cast=0,cd=40000,categoryCD=40000,recoveryCD=0,duration=1800000,manaCost=60,icon="",familyFlags=0}
t[26187] = {name="Thunderstomp",cast=0,cd=40000,categoryCD=40000,recoveryCD=0,duration=1200000,manaCost=60,icon="",familyFlags=0}
t[26188] = {name="Thunderstomp",cast=0,cd=40000,categoryCD=40000,recoveryCD=0,duration=0,manaCost=60,icon="",familyFlags=0}
t[51156] = {name="Thunderstomp",cast=0,cd=40000,categoryCD=40000,recoveryCD=0,duration=0,manaCost=60,icon="",familyFlags=0}
t[19801] = {name="Tranquilizing Shot",cast=0,cd=20000,categoryCD=20000,recoveryCD=0,duration=0,manaCost=270,icon="Ability_Warrior_Disarm",familyFlags=65536}
t[19376] = {name="Trap Mastery",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19377] = {name="Trap Mastery",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51581] = {name="Trap Mastery",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19506] = {name="Trueshot Aura",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=0}
t[20905] = {name="Trueshot Aura",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[20906] = {name="Trueshot Aura",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19616] = {name="Unleashed Fury",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19617] = {name="Unleashed Fury",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19618] = {name="Unleashed Fury",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19619] = {name="Unleashed Fury",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19620] = {name="Unleashed Fury",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[26176] = {name="Unseen Path Pet Bonus",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51586] = {name="Untamed Trapper",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51584] = {name="Vicious Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51585] = {name="Vicious Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[3034] = {name="Viper Sting",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=1000,manaCost=135,icon="",familyFlags=65536}
t[14279] = {name="Viper Sting",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=175,icon="",familyFlags=65536}
t[14280] = {name="Viper Sting",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=215,icon="",familyFlags=65536}
t[45660] = {name="Viper Strike",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=215,icon="",familyFlags=65536}
t[1510] = {name="Volley",cast=0,cd=60000,categoryCD=0,recoveryCD=60000,duration=1200000,manaCost=350,icon="",familyFlags=8192}
t[14294] = {name="Volley",cast=0,cd=60000,categoryCD=0,recoveryCD=60000,duration=0,manaCost=420,icon="",familyFlags=8192}
t[14295] = {name="Volley",cast=0,cd=60000,categoryCD=0,recoveryCD=60000,duration=0,manaCost=490,icon="",familyFlags=8192}
t[22908] = {name="Volley",cast=0,cd=60000,categoryCD=0,recoveryCD=60000,duration=0,manaCost=0,icon="",familyFlags=8192}
t[36533] = {name="Web",cast=0,cd=60000,categoryCD=0,recoveryCD=60000,duration=0,manaCost=50,icon="Spell_Holy_Heal02",familyFlags=0}
t[26226] = {name="Widow Bite",cast=0,cd=4000,categoryCD=0,recoveryCD=4000,duration=60000,manaCost=40,icon="Ability_Hunter_AimedShot",familyFlags=0}
t[2974] = {name="Wing Clip",cast=0,cd=3000,categoryCD=0,recoveryCD=3000,duration=30000,manaCost=40,icon="INV_Food_Egg_02",familyFlags=64}
t[14267] = {name="Wing Clip",cast=0,cd=3000,categoryCD=0,recoveryCD=3000,duration=11000,manaCost=60,icon="INV_Food_Egg_02",familyFlags=64}
t[14268] = {name="Wing Clip",cast=0,cd=3000,categoryCD=0,recoveryCD=3000,duration=0,manaCost=80,icon="INV_Food_Egg_02",familyFlags=64}
t[27633] = {name="Wing Clip",cast=0,cd=3000,categoryCD=0,recoveryCD=3000,duration=0,manaCost=80,icon="INV_Food_Egg_02",familyFlags=64}
t[19386] = {name="Wyvern Sting",cast=0,cd=120000,categoryCD=0,recoveryCD=120000,duration=1200000,manaCost=115,icon="",familyFlags=65536}
t[24131] = {name="Wyvern Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=65536}
t[24132] = {name="Wyvern Sting",cast=0,cd=120000,categoryCD=0,recoveryCD=120000,duration=0,manaCost=155,icon="",familyFlags=65536}
t[24133] = {name="Wyvern Sting",cast=0,cd=120000,categoryCD=0,recoveryCD=120000,duration=0,manaCost=205,icon="",familyFlags=65536}
t[24134] = {name="Wyvern Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=65536}
t[24135] = {name="Wyvern Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=65536}
t[24335] = {name="Wyvern Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=205,icon="",familyFlags=0}
t[24336] = {name="Wyvern Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=65536}
t[26180] = {name="Wyvern Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[26233] = {name="Wyvern Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=65536}
t[26748] = {name="Wyvern Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=65536}
end
MTH_DS_HunterSpellsByName = {}
do
local t = MTH_DS_HunterSpellsByName
t["[Deprecated] Bestial Swiftness"] = MTH_DS_HunterSpellsDBC[19596]
t["[Deprecated] Bestial Wrath"] = MTH_DS_HunterSpellsDBC[19574]
t["[Deprecated] Hawk Eye"] = MTH_DS_HunterSpellsDBC[19500]
t["[Deprecated] Improved Arcane Shot"] = MTH_DS_HunterSpellsDBC[19457]
t["[Deprecated] Improved Aspect of the Monkey"] = MTH_DS_HunterSpellsDBC[24386]
t["[Deprecated] Improved Hunter's Mark"] = MTH_DS_HunterSpellsDBC[19424]
t["[Deprecated] Improved Scorpid Sting"] = MTH_DS_HunterSpellsDBC[19494]
t["[Deprecated] Improved Wing Clip"] = MTH_DS_HunterSpellsDBC[19234]
t["[Deprecated] Killer Instinct"] = MTH_DS_HunterSpellsDBC[19374]
t["[Deprecated] Pathfinding"] = MTH_DS_HunterSpellsDBC[19562]
t["[Deprecated] Scorpid Sting"] = MTH_DS_HunterSpellsDBC[14275]
t["[Deprecated] Thick Hide"] = MTH_DS_HunterSpellsDBC[19611]
t["[Deprecated] Trueshot Aura Percent Bonus"] = MTH_DS_HunterSpellsDBC[51520]
t["Adrenaline Rush"] = MTH_DS_HunterSpellsDBC[28752]
t["Aimed Shot"] = MTH_DS_HunterSpellsDBC[19434]
t["Arcane Infused"] = MTH_DS_HunterSpellsDBC[23721]
t["Arcane Shot"] = MTH_DS_HunterSpellsDBC[3044]
t["Arcane Shot Mana Reduction"] = MTH_DS_HunterSpellsDBC[23157]
t["Aspect of the Beast"] = MTH_DS_HunterSpellsDBC[13161]
t["Aspect of the Cheetah"] = MTH_DS_HunterSpellsDBC[5118]
t["Aspect of the Fox"] = MTH_DS_HunterSpellsDBC[45651]
t["Aspect of the Hawk"] = MTH_DS_HunterSpellsDBC[13165]
t["Aspect of the Monkey"] = MTH_DS_HunterSpellsDBC[13163]
t["Aspect of the Pack"] = MTH_DS_HunterSpellsDBC[13159]
t["Aspect of the Snake"] = MTH_DS_HunterSpellsDBC[45652]
t["Aspect of the Turtle"] = MTH_DS_HunterSpellsDBC[45649]
t["Aspect of the Wild"] = MTH_DS_HunterSpellsDBC[20043]
t["Aspect of the Wolf"] = MTH_DS_HunterSpellsDBC[45650]
t["Auto Shot"] = MTH_DS_HunterSpellsDBC[75]
t["Avoidance"] = MTH_DS_HunterSpellsDBC[46023]
t["Aynasha's Bow"] = MTH_DS_HunterSpellsDBC[19767]
t["Baited Shot"] = MTH_DS_HunterSpellsDBC[46321]
t["Barrage"] = MTH_DS_HunterSpellsDBC[19461]
t["Battle Standard"] = MTH_DS_HunterSpellsDBC[22996]
t["Beast Mastery"] = MTH_DS_HunterSpellsDBC[47355]
t["Beast of the Unseen Path"] = MTH_DS_HunterSpellsDBC[29233]
t["Bestial Discipline"] = MTH_DS_HunterSpellsDBC[19590]
t["Bestial Fury"] = MTH_DS_HunterSpellsDBC[19603]
t["Bestial Precision"] = MTH_DS_HunterSpellsDBC[51554]
t["Bestial Wrath"] = MTH_DS_HunterSpellsDBC[24395]
t["Bite"] = MTH_DS_HunterSpellsDBC[17253]
t["Bubble Barrier"] = MTH_DS_HunterSpellsDBC[36523]
t["Carve"] = MTH_DS_HunterSpellsDBC[51575]
t["Cheetah Sprint"] = MTH_DS_HunterSpellsDBC[24348]
t["Claw"] = MTH_DS_HunterSpellsDBC[3009]
t["Clever Traps"] = MTH_DS_HunterSpellsDBC[19239]
t["Command"] = MTH_DS_HunterSpellsDBC[20576]
t["Concussive Shot"] = MTH_DS_HunterSpellsDBC[5116]
t["Concussive Shot Cooldown Reduction"] = MTH_DS_HunterSpellsDBC[23158]
t["Coordinated Assault"] = MTH_DS_HunterSpellsDBC[49559]
t["Copy of Shoot"] = MTH_DS_HunterSpellsDBC[28317]
t["Counterattack"] = MTH_DS_HunterSpellsDBC[19306]
t["Death Roll"] = MTH_DS_HunterSpellsDBC[36548]
t["Demonic Transformation"] = MTH_DS_HunterSpellsDBC[24992]
t["Detect Weakness"] = MTH_DS_HunterSpellsDBC[23578]
t["Deterrence"] = MTH_DS_HunterSpellsDBC[19263]
t["Disengage"] = MTH_DS_HunterSpellsDBC[781]
t["Distracting Shot"] = MTH_DS_HunterSpellsDBC[14274]
t["Efficiency"] = MTH_DS_HunterSpellsDBC[19416]
t["Elusiveness"] = MTH_DS_HunterSpellsDBC[51552]
t["Endless Quiver"] = MTH_DS_HunterSpellsDBC[51517]
t["Endurance Training"] = MTH_DS_HunterSpellsDBC[19583]
t["Entrapment"] = MTH_DS_HunterSpellsDBC[19184]
t["Explosive Trap"] = MTH_DS_HunterSpellsDBC[13813]
t["Explosive Trap Effect"] = MTH_DS_HunterSpellsDBC[13812]
t["Extra Shot"] = MTH_DS_HunterSpellsDBC[51519]
t["Eyes of the Beast"] = MTH_DS_HunterSpellsDBC[1002]
t["Fang of the Crystal Spider"] = MTH_DS_HunterSpellsDBC[17331]
t["Faster Bleeds 5 (Hunter)"] = MTH_DS_HunterSpellsDBC[52402]
t["Feign Death"] = MTH_DS_HunterSpellsDBC[5384]
t["Ferocity"] = MTH_DS_HunterSpellsDBC[19598]
t["Fire Breath"] = MTH_DS_HunterSpellsDBC[46269]
t["Flame Spear"] = MTH_DS_HunterSpellsDBC[19781]
t["Flare"] = MTH_DS_HunterSpellsDBC[1543]
t["Freezing Trap"] = MTH_DS_HunterSpellsDBC[1499]
t["Freezing Trap (TEST)"] = MTH_DS_HunterSpellsDBC[21750]
t["Freezing Trap Effect"] = MTH_DS_HunterSpellsDBC[3355]
t["Frenzy"] = MTH_DS_HunterSpellsDBC[19621]
t["Frost Trap"] = MTH_DS_HunterSpellsDBC[13809]
t["Frost Trap Aura"] = MTH_DS_HunterSpellsDBC[13810]
t["Frost Trap Effect"] = MTH_DS_HunterSpellsDBC[14207]
t["Furious Howl"] = MTH_DS_HunterSpellsDBC[24597]
t["Grace"] = MTH_DS_HunterSpellsDBC[46296]
t["Haste"] = MTH_DS_HunterSpellsDBC[14824]
t["Hawk Eye"] = MTH_DS_HunterSpellsDBC[19498]
t["Hunter Giant Stalker"] = MTH_DS_HunterSpellsDBC[21942]
t["Hunter's Mark"] = MTH_DS_HunterSpellsDBC[1130]
t["Immolation Trap"] = MTH_DS_HunterSpellsDBC[13795]
t["Immolation Trap Effect"] = MTH_DS_HunterSpellsDBC[13797]
t["Impale"] = MTH_DS_HunterSpellsDBC[15860]
t["Improved Aspect of the Hawk"] = MTH_DS_HunterSpellsDBC[23559]
t["Improved Concussive Shot"] = MTH_DS_HunterSpellsDBC[19407]
t["Improved Explosive Trap"] = MTH_DS_HunterSpellsDBC[19378]
t["Improved Eyes of the Beast"] = MTH_DS_HunterSpellsDBC[19557]
t["Improved Feign Death"] = MTH_DS_HunterSpellsDBC[19286]
t["Improved Freezing Trap"] = MTH_DS_HunterSpellsDBC[19288]
t["Improved Hunter's Mark"] = MTH_DS_HunterSpellsDBC[19421]
t["Improved Immolation Trap"] = MTH_DS_HunterSpellsDBC[19246]
t["Improved Mend Pet"] = MTH_DS_HunterSpellsDBC[19572]
t["Improved Primal Aspects"] = MTH_DS_HunterSpellsDBC[19549]
t["Improved Revive Pet"] = MTH_DS_HunterSpellsDBC[19575]
t["Improved Scorpid Sting"] = MTH_DS_HunterSpellsDBC[19491]
t["Improved Serpent Sting"] = MTH_DS_HunterSpellsDBC[24467]
t["Improved Steady Shot"] = MTH_DS_HunterSpellsDBC[51515]
t["Improved Stings"] = MTH_DS_HunterSpellsDBC[19464]
t["Improved Swift Aspects"] = MTH_DS_HunterSpellsDBC[52603]
t["Improved Volley and Multishot"] = MTH_DS_HunterSpellsDBC[23566]
t["Improved Wing Clip"] = MTH_DS_HunterSpellsDBC[19228]
t["Increased Hunter Pet Damage"] = MTH_DS_HunterSpellsDBC[22854]
t["Increased Pet Damage +3%"] = MTH_DS_HunterSpellsDBC[27205]
t["Increased Pet Damage +4%"] = MTH_DS_HunterSpellsDBC[52413]
t["Kill Command"] = MTH_DS_HunterSpellsDBC[41827]
t["Killer Instinct"] = MTH_DS_HunterSpellsDBC[19162]
t["Lacerate"] = MTH_DS_HunterSpellsDBC[20907]
t["Lightning Breath"] = MTH_DS_HunterSpellsDBC[24844]
t["Lightning Reflexes"] = MTH_DS_HunterSpellsDBC[19168]
t["Lizard Bolt"] = MTH_DS_HunterSpellsDBC[46297]
t["Mend Pet"] = MTH_DS_HunterSpellsDBC[136]
t["Mongoose Bite"] = MTH_DS_HunterSpellsDBC[1495]
t["Mortal Shots"] = MTH_DS_HunterSpellsDBC[19485]
t["Multi-Shot"] = MTH_DS_HunterSpellsDBC[2643]
t["Multi-Shot Damage Increase"] = MTH_DS_HunterSpellsDBC[28539]
t["Multishot and Carve Cooldown"] = MTH_DS_HunterSpellsDBC[52602]
t["Not There"] = MTH_DS_HunterSpellsDBC[28761]
t["Packleader"] = MTH_DS_HunterSpellsDBC[36532]
t["Pathfinding"] = MTH_DS_HunterSpellsDBC[19559]
t["Pet Focus Regen Bonus"] = MTH_DS_HunterSpellsDBC[52685]
t["Piercing Shots"] = MTH_DS_HunterSpellsDBC[51512]
t["Pin"] = MTH_DS_HunterSpellsDBC[46292]
t["Planning Ahead"] = MTH_DS_HunterSpellsDBC[51576]
t["Planning Ahead Dummy"] = MTH_DS_HunterSpellsDBC[51578]
t["Poison Spit"] = MTH_DS_HunterSpellsDBC[46271]
t["Prowl"] = MTH_DS_HunterSpellsDBC[24450]
t["Quick Shots"] = MTH_DS_HunterSpellsDBC[51547]
t["Quick Strikes"] = MTH_DS_HunterSpellsDBC[51542]
t["Ranged Weapon Specialization"] = MTH_DS_HunterSpellsDBC[19511]
t["Rapid Fire"] = MTH_DS_HunterSpellsDBC[3045]
t["Raptor Strike"] = MTH_DS_HunterSpellsDBC[2973]
t["Readiness"] = MTH_DS_HunterSpellsDBC[23989]
t["Resourcefulness"] = MTH_DS_HunterSpellsDBC[51570]
t["Revive Pet"] = MTH_DS_HunterSpellsDBC[982]
t["Roar of Fortitude"] = MTH_DS_HunterSpellsDBC[36535]
t["Savage Rend"] = MTH_DS_HunterSpellsDBC[36536]
t["Savage Strikes"] = MTH_DS_HunterSpellsDBC[19159]
t["Scatter Shot"] = MTH_DS_HunterSpellsDBC[19503]
t["Scorpid Poison"] = MTH_DS_HunterSpellsDBC[24583]
t["Scorpid Stike"] = MTH_DS_HunterSpellsDBC[45659]
t["Scorpid Sting"] = MTH_DS_HunterSpellsDBC[3043]
t["Scorpid Sting Damage Reduction"] = MTH_DS_HunterSpellsDBC[51509]
t["Screech"] = MTH_DS_HunterSpellsDBC[24423]
t["Serpent Sting"] = MTH_DS_HunterSpellsDBC[1978]
t["Serpent Strike"] = MTH_DS_HunterSpellsDBC[45661]
t["Server Spell"] = MTH_DS_HunterSpellsDBC[29218]
t["Shell Shield"] = MTH_DS_HunterSpellsDBC[26064]
t["Shoot"] = MTH_DS_HunterSpellsDBC[6660]
t["Shoot Bow"] = MTH_DS_HunterSpellsDBC[2480]
t["Shoot Crossbow"] = MTH_DS_HunterSpellsDBC[7919]
t["Shoot Gun"] = MTH_DS_HunterSpellsDBC[7918]
t["Shoot Silithus Rifleman Long Range"] = MTH_DS_HunterSpellsDBC[25200]
t["Shoot Tauren Rifleman"] = MTH_DS_HunterSpellsDBC[26282]
t["Slime Shot"] = MTH_DS_HunterSpellsDBC[28318]
t["Spell - Reuse 19501"] = MTH_DS_HunterSpellsDBC[19501]
t["Spit"] = MTH_DS_HunterSpellsDBC[27919]
t["Stalker's Ally"] = MTH_DS_HunterSpellsDBC[28758]
t["Steady Shot"] = MTH_DS_HunterSpellsDBC[3035]
t["Steady Shot and Raptor Strike Crit Bonus"] = MTH_DS_HunterSpellsDBC[52684]
t["Stinging Nettle"] = MTH_DS_HunterSpellsDBC[51579]
t["Strider Presence"] = MTH_DS_HunterSpellsDBC[36531]
t["Strike Together"] = MTH_DS_HunterSpellsDBC[49557]
t["Striker's Arcane Shot Bonus"] = MTH_DS_HunterSpellsDBC[26173]
t["Striker's Rapid Fire Bonus"] = MTH_DS_HunterSpellsDBC[26174]
t["Superior Aspects"] = MTH_DS_HunterSpellsDBC[24416]
t["Superior Strikes"] = MTH_DS_HunterSpellsDBC[24269]
t["Surefooted"] = MTH_DS_HunterSpellsDBC[19290]
t["Survivalist"] = MTH_DS_HunterSpellsDBC[19255]
t["Swift Aspects"] = MTH_DS_HunterSpellsDBC[19552]
t["Swiftshot"] = MTH_DS_HunterSpellsDBC[19454]
t["Swipe"] = MTH_DS_HunterSpellsDBC[46287]
t["Tame Adult Plainstrider"] = MTH_DS_HunterSpellsDBC[19678]
t["Tame Armored Scorpid"] = MTH_DS_HunterSpellsDBC[19683]
t["Tame Ashfeather Swooper"] = MTH_DS_HunterSpellsDBC[46705]
t["Tame Blackvenom Scorpid"] = MTH_DS_HunterSpellsDBC[46701]
t["Tame Decrepit Darkhound"] = MTH_DS_HunterSpellsDBC[50904]
t["Tame Decrepit Darkhound."] = MTH_DS_HunterSpellsDBC[44007]
t["Tame Dire Mottled Boar"] = MTH_DS_HunterSpellsDBC[19681]
t["Tame Dire Mottled Boar."] = MTH_DS_HunterSpellsDBC[44013]
t["Tame Elder Crimson Lynx"] = MTH_DS_HunterSpellsDBC[46091]
t["Tame Greater Dusk Bat"] = MTH_DS_HunterSpellsDBC[50905]
t["Tame Greater Duskbat"] = MTH_DS_HunterSpellsDBC[44009]
t["Tame Ice Claw Bear"] = MTH_DS_HunterSpellsDBC[19597]
t["Tame Ivory Hawkstrider"] = MTH_DS_HunterSpellsDBC[46093]
t["Tame Large Crag Boar"] = MTH_DS_HunterSpellsDBC[19677]
t["Tame Large Crag Boar."] = MTH_DS_HunterSpellsDBC[44019]
t["Tame Lavender Mana Wyrm"] = MTH_DS_HunterSpellsDBC[46095]
t["Tame Muckreef Crawler"] = MTH_DS_HunterSpellsDBC[46703]
t["Tame Nightsaber Stalker"] = MTH_DS_HunterSpellsDBC[19685]
t["Tame Prairie Stalker"] = MTH_DS_HunterSpellsDBC[19679]
t["Tame Prowler"] = MTH_DS_HunterSpellsDBC[44003]
t["Tame Snow Leopard"] = MTH_DS_HunterSpellsDBC[19676]
t["Tame Stonetusk Boar"] = MTH_DS_HunterSpellsDBC[44001]
t["Tame Strigid Screecher"] = MTH_DS_HunterSpellsDBC[19686]
t["Tame Surf Crawler"] = MTH_DS_HunterSpellsDBC[19682]
t["Tame Swoop"] = MTH_DS_HunterSpellsDBC[19680]
t["Tame Tirisfal Plagued Bear"] = MTH_DS_HunterSpellsDBC[44011]
t["Tame Webwood Lurker"] = MTH_DS_HunterSpellsDBC[19684]
t["Tame Young Forest Bear"] = MTH_DS_HunterSpellsDBC[44005]
t["Tamed Pet Passive (DND)"] = MTH_DS_HunterSpellsDBC[8875]
t["Thick Hide"] = MTH_DS_HunterSpellsDBC[19609]
t["Throw"] = MTH_DS_HunterSpellsDBC[10277]
t["Thunderstomp"] = MTH_DS_HunterSpellsDBC[26090]
t["Tranquilizing Shot"] = MTH_DS_HunterSpellsDBC[19801]
t["Trap Mastery"] = MTH_DS_HunterSpellsDBC[19376]
t["Trueshot Aura"] = MTH_DS_HunterSpellsDBC[19506]
t["Unleashed Fury"] = MTH_DS_HunterSpellsDBC[19616]
t["Unseen Path Pet Bonus"] = MTH_DS_HunterSpellsDBC[26176]
t["Untamed Trapper"] = MTH_DS_HunterSpellsDBC[51586]
t["Vicious Strikes"] = MTH_DS_HunterSpellsDBC[51584]
t["Viper Sting"] = MTH_DS_HunterSpellsDBC[3034]
t["Viper Strike"] = MTH_DS_HunterSpellsDBC[45660]
t["Volley"] = MTH_DS_HunterSpellsDBC[1510]
t["Web"] = MTH_DS_HunterSpellsDBC[36533]
t["Widow Bite"] = MTH_DS_HunterSpellsDBC[26226]
t["Wing Clip"] = MTH_DS_HunterSpellsDBC[2974]
t["Wyvern Sting"] = MTH_DS_HunterSpellsDBC[19386]
end
+224
View File
@@ -0,0 +1,224 @@
-- ds-hunter-spells.lua
-- Hunter spell data for TWoW 1.18.1 "Nightmares of Ursol"
-- Used by: ExpAmmo cycle tracker, HunterBook (future spell reference tab)
--
-- TODO markers:
-- SPELL_ID : exact spell ID not yet extracted from Spell.dbc (needs MPQ)
-- ICON : icon texture not yet confirmed from game files
-- VERIFY : value observed in patch notes but subject to change on PTR/live
-- PENDING : mechanic not yet verified against live server
if not MTH_DS then MTH_DS = {} end
-- ============================================================
-- BASELINE HUNTER SPELLS (available to all specs from 1.18.1)
-- ============================================================
MTH_DS_HunterSpells = {
["baseline"] = {
{
["name"] = "Steady Shot",
["addedIn"] = "1.18.1",
["note"] = "Moved from Marksmanship talent tree to baseline at level 20.",
["castTime"] = "1.5s", -- TODO VERIFY
["cooldown"] = "n/a",
["school"] = "Physical",
["icon"] = "Ability_Hunter_SteadyShot", -- TODO ICON confirm
},
{
["name"] = "Trueshot Aura",
["addedIn"] = "1.18.1",
["note"] = "Moved from Marksmanship capstone to baseline. All hunters now get this.",
["castTime"] = "Instant",
["cooldown"] = "n/a",
["school"] = "Physical",
["icon"] = "Ability_Trueshot", -- TODO ICON confirm
},
{
["name"] = "Aspect of the Viper",
["addedIn"] = "1.18.1",
["note"] = "New baseline aspect learned at level 56. Regenerates mana while dealing reduced damage.",
["castTime"] = "Instant",
["cooldown"] = "n/a",
["school"] = "Nature",
["icon"] = "Spell_Nature_Poison", -- TODO ICON confirm
},
{
["name"] = "Volley",
["addedIn"] = "1.18.1",
["note"] = "Changed from channelled to a 3-second cast AoE. Now a proper cast-bar spell.",
["castTime"] = "3s", -- TODO VERIFY
["cooldown"] = "n/a",
["school"] = "Physical",
["icon"] = "Ability_Whirlwind", -- TODO ICON confirm
},
},
-- ============================================================
-- MARKSMANSHIP TALENTS (1.18.1 changes)
-- ============================================================
["marksmanship"] = {
{
["name"] = "Aimed Shot",
["addedIn"] = "1.18.1",
["note"] = "Now has a visible 2s cast bar. Triggers ExpAmmo cycle start (resets to FIRE state).",
["castTime"] = "2s",
["cooldown"] = "26s", -- TODO VERIFY vs Spell.dbc
["school"] = "Physical",
["icon"] = "Inv_Spear_07", -- TODO ICON confirm
["expAmmoTrigger"] = true, -- resets ExpAmmo cycle to state 1 (FIRE)
},
{
["name"] = "Lock and Load",
["addedIn"] = "1.18.1",
["talentRow"] = 7, -- MM capstone row
["note"] = "10s proc buff. Triggered by Aimed Shot crit or trap activation (TODO VERIFY exact condition).",
["castTime"] = "Instant",
["cooldown"] = "n/a",
["duration"] = "10s", -- TODO VERIFY
["school"] = "Physical",
["type"] = "buff_proc",
["icon"] = "Ability_Hunter_LockAndLoad", -- TODO ICON confirm
},
{
["name"] = "Rapid Fire",
["note"] = "15% haste, 15s duration, 5min CD. Unchanged in 1.18.1 baseline values.",
["castTime"] = "Instant",
["cooldown"] = "5min",
["duration"] = "15s",
["school"] = "Physical",
["type"] = "buff",
["icon"] = "Ability_Hunter_RapidKilling",
},
},
-- ============================================================
-- BEAST MASTERY TALENTS (1.18.1 changes)
-- ============================================================
["beastmastery"] = {
{
["name"] = "Bestial Wrath",
["note"] = "Returned to BM tree. 18s buff, 90s CD (1.5min). Pet immune to fear/CC during.",
["castTime"] = "Instant",
["cooldown"] = "90s",
["duration"] = "18s", -- TODO VERIFY
["school"] = "Physical",
["type"] = "buff",
["icon"] = "Ability_Druid_Ferociousbite", -- TODO ICON confirm
},
{
["name"] = "Kill Command",
["addedIn"] = "1.18.1",
["talentRow"] = 7, -- BM capstone row
["note"] = "8s CD, triggered on crit. Commands pet to inflict bonus damage on next hit.",
["castTime"] = "Instant",
["cooldown"] = "8s",
["school"] = "Physical",
["icon"] = "Ability_Hunter_KillCommand", -- TODO ICON confirm
},
{
["name"] = "Scent of Blood",
["addedIn"] = "1.18.1",
["talentRow"] = 5,
["note"] = "8s pet rage proc buff. Triggers when Bestial Wrath is active (TODO VERIFY exact condition).",
["castTime"] = "Instant (proc)",
["cooldown"] = "n/a",
["duration"] = "8s", -- TODO VERIFY
["school"] = "Physical",
["type"] = "buff_proc",
["icon"] = "Ability_Warrior_BloodFrenzy", -- TODO ICON confirm
},
{
["name"] = "Baited Shot",
["removedIn"] = "1.18.1",
["note"] = "REMOVED. Replaced by Kill Command as the BM capstone. Any saved data for this spell can be cleared.",
["deprecated"] = true,
},
},
-- ============================================================
-- SURVIVAL TALENTS (1.18.1 changes)
-- ============================================================
["survival"] = {
{
["name"] = "Alone Against the World",
["addedIn"] = "1.18.1",
["talentRow"] = 2,
["note"] = "New 2-point talent. Bonus to pet/self when no group members nearby (TODO VERIFY exact bonus values).",
["type"] = "passive",
["icon"] = "Ability_Hunter_AloneAgainstTheWorld", -- TODO ICON confirm
},
{
["name"] = "Lacerate",
["addedIn"] = "1.18.1",
["note"] = "40% Attack Power bleed, +15% from all bleed sides. (TODO VERIFY: rank values from Spell.dbc).",
["castTime"] = "Instant",
["school"] = "Physical",
["type"] = "dot",
["icon"] = "Ability_Hunter_Lacerate", -- TODO ICON confirm
},
},
-- ============================================================
-- EXPERIMENTAL AMMUNITION (new 1.18.1 mechanic)
-- States cycle: FIRE -> ARCANE -> NATURE -> (loop)
-- Aimed Shot resets cycle to FIRE each time it is fired.
-- ============================================================
["expAmmoStates"] = {
{
["state"] = 1,
["label"] = "Fire Ammo",
["school"] = "Fire",
["duration"] = "60s", -- TODO VERIFY from server
["consumeSpell"] = "Multi-Shot", -- TODO VERIFY spell that consumes this state
["icon"] = "Spell_Fire_FlameBolt", -- TODO ICON confirm
["buffName"] = nil, -- TODO: exact buff name from Spell.dbc
},
{
["state"] = 2,
["label"] = "Arcane Ammo",
["school"] = "Arcane",
["duration"] = "60s", -- TODO VERIFY
["consumeSpell"] = "Arcane Shot", -- TODO VERIFY
["icon"] = "Spell_Arcane_ArcaneMissiles", -- TODO ICON confirm
["buffName"] = nil, -- TODO: exact buff name from Spell.dbc
},
{
["state"] = 3,
["label"] = "Nature Ammo",
["school"] = "Nature",
["duration"] = "60s", -- TODO VERIFY
["consumeSpell"] = "Serpent Sting", -- TODO VERIFY
["icon"] = "Spell_Nature_NatureBolt", -- TODO ICON confirm
["buffName"] = nil, -- TODO: exact buff name from Spell.dbc
},
},
}
+5
View File
@@ -137,6 +137,11 @@ local MTH_DS_AtlasSources = {
[9426] = {["boss"] = true, },
[9456] = {["boss"] = true, },
[9487] = {["boss"] = true, },
[42198] = {["crafted"] = true, },
[42199] = {["crafted"] = true, },
[42200] = {["crafted"] = true, },
[42201] = {["crafted"] = true, },
[42202] = {["crafted"] = true, },
}
for itemId, sourceMap in pairs(MTH_DS_AtlasSources) do
+26
View File
@@ -6308,6 +6308,13 @@ MTH_DS_ItemOrigins = {
["level"] = 25,
["classification"] = 0,
},
{
["id"] = 62906,
["name"] = "Elendon Truebough",
["tag"] = "Weapons",
["level"] = 44,
["classification"] = 0,
},
},
},
},
@@ -11066,6 +11073,12 @@ MTH_DS_ItemOrigins = {
["level"] = 40,
["classification"] = 0,
},
{
["id"] = 62843,
["name"] = "Ornala",
["level"] = 53,
["classification"] = 0,
},
},
},
},
@@ -11747,6 +11760,19 @@ MTH_DS_ItemOrigins = {
["level"] = 25,
["classification"] = 0,
},
{
["id"] = 62906,
["name"] = "Elendon Truebough",
["tag"] = "Weapons",
["level"] = 44,
["classification"] = 0,
},
{
["id"] = 62843,
["name"] = "Ornala",
["level"] = 53,
["classification"] = 0,
},
},
},
},
+45
View File
@@ -1121,6 +1121,51 @@ MTH_DS_AmmoItems = {
[13219] = 0,
},
},
[42198] = {
["name"] = "Bright Wood Arrows",
["level"] = 35,
["reqlevel"] = 30,
["icon"] = "INV_Ammo_Arrow_02",
["subtype"] = "arrow",
["dps"] = 10.5,
["crafted"] = true,
},
[42199] = {
["name"] = "Shade Wood Arrows",
["level"] = 45,
["reqlevel"] = 40,
["icon"] = "INV_Ammo_Arrow_02",
["subtype"] = "arrow",
["dps"] = 14.5,
["crafted"] = true,
},
[42200] = {
["name"] = "Smooth Ironfeather Arrows",
["level"] = 55,
["reqlevel"] = 50,
["icon"] = "INV_Ammo_Arrow_02",
["subtype"] = "arrow",
["dps"] = 17.5,
["crafted"] = true,
},
[42201] = {
["name"] = "Starfeather Arrows",
["level"] = 60,
["reqlevel"] = 55,
["icon"] = "INV_Ammo_Arrow_02",
["subtype"] = "arrow",
["dps"] = 19,
["crafted"] = true,
},
[42202] = {
["name"] = "Enchanted Thorium Shells",
["level"] = 60,
["reqlevel"] = 55,
["icon"] = "INV_Ammo_Bullet_03",
["subtype"] = "bullet",
["dps"] = 19,
["crafted"] = true,
},
}
MTH_DS_Items = MTH_DS_AmmoItems
+125 -117
View File
@@ -2,120 +2,128 @@ if not MTH_DS then MTH_DS = {} end
MTH_DS_MinimapSizes = {
[0] = { 0, 0 },
[1] = { 4925, 3283.34 },
[3] = { 2487.5, 1658.34 },
[4] = { 3350, 2233.3 },
[8] = { 2293.75, 1529.17 },
[10] = { 2700, 1800.03 },
[11] = { 4135.42, 2756.25 },
[12] = { 3470.84, 2314.62 },
[14] = { 5287.5, 3525 },
[15] = { 5250, 3500 },
[16] = { 5070.84, 3381.25 },
[17] = { 10133.34, 6756.25 },
[25] = { 711.56, 468.68 },
[28] = { 4300, 2866.67 },
[33] = { 6381.25, 4254.1 },
[36] = { 2800, 1866.67 },
[38] = { 2758.33, 1839.58 },
[40] = { 3500, 2333.3 },
[41] = { 2500, 1666.63 },
[44] = { 2170.84, 1447.9 },
[45] = { 3600, 2400 },
[46] = { 2929.16, 1952.08 },
[47] = { 3850, 2566.67 },
[51] = { 2231.25, 1487.5 },
[85] = { 4518.75, 3012.5 },
[130] = { 4200, 2800 },
[139] = { 3870.83, 2581.25 },
[141] = { 5091.66, 3393.7 },
[148] = { 6550, 4366.66 },
[209] = { 381, 254 },
[215] = { 5137.5, 3425 },
[267] = { 3200, 2133.33 },
[331] = { 5766.67, 3843.75 },
[357] = { 6950, 4633.33 },
[361] = { 5750, 3833.33 },
[400] = { 4400, 2933.33 },
[405] = { 4495.83, 2997.91 },
[406] = { 6018, 4010 },
[408] = { 3092.1, 2047 },
[409] = { 2901.45, 1915.9 },
[440] = { 6900, 4600 },
[490] = { 3700, 2466.66 },
[491] = { 736.45, 490.96 },
[493] = { 2308.33, 1539.59 },
[616] = { 3206, 2142 },
[618] = { 7100, 4733.33 },
[717] = { 378.15, 252.1 },
[718] = { 572.78, 381.85 },
[719] = { 1221.87, 806.42 },
[721] = { 769.67, 513.11 },
[722] = { 881, 589 },
[796] = { 203.66, 135.04 },
[876] = { 828, 541 },
[1176] = { 1383.33, 922.91 },
[1337] = { 893.67, 595.78 },
[1377] = { 3483.33, 2322.92 },
[1477] = { 695.03, 463.35 },
[1497] = { 959.37, 640.11 },
[1519] = { 1737.5, 1158.34 },
[1537] = { 790.63, 527.61 },
[1581] = { 449.89, 300 },
[1583] = { 886.84, 591.23 },
[1584] = { 1985, 1268 },
[1637] = { 1402.61, 935.42 },
[1638] = { 1043.75, 695.83 },
[1657] = { 1058.33, 705.71 },
[1941] = { 1348.24, 888.09 },
[1977] = { 2120.83, 1414.6 },
[2017] = { 1185.35, 789.86 },
[2040] = { 1468, 976 },
[2057] = { 320.05, 213.37 },
[2100] = { 2112.09, 1410.89 },
[2159] = { 483.11, 322.08 },
[2257] = { 312, 208 },
[2366] = { 1085.86, 726.61 },
[2437] = { 738.86, 492.57 },
[2557] = { 1324, 869 },
[2597] = { 4237.5, 2825 },
[2677] = { 499.43, 332.95 },
[2717] = { 1495, 983 },
[3277] = { 1145.84, 764.58 },
[3358] = { 1756.25, 1170.83 },
[3428] = { 977.56, 651.7 },
[3429] = { 2512.5, 1675 },
[3456] = { 1991.69, 1318.42 },
[3457] = { 823, 549 },
[3478] = { 4139.02, 2946 },
[4012] = { 3159, 2108 },
[5023] = { 988.63, 1393.17 },
[5024] = { 1608, 1075 },
[5053] = { 1007.7, 671.8 },
[5077] = { 2643.21, 1751.16 },
[5086] = { 546.75, 391.9 },
[5087] = { 354.5, 234.74 },
[5097] = { 1273.1, 853.72 },
[5103] = { 752.12, 510.33 },
[5121] = { 3227, 2187 },
[5130] = { 1432, 977 },
[5135] = { 320.19, 213.46 },
[5136] = { 619.98, 413.32 },
[5138] = { 656.59, 434.97 },
[5144] = { 309, 208 },
[5147] = { 2777.54, 1851.69 },
[5148] = { 652.1, 439.67 },
[5153] = { 612.69, 408.46 },
[5163] = { 703.3, 468.87 },
[5179] = { 3666, 2442 },
[5204] = { 1271.99, 845.48 },
[5208] = { 1250.18, 837.44 },
[5225] = { 3082, 2061 },
[5536] = { 2472, 1665 },
[5557] = { 2800, 1868 },
[5561] = { 3098, 2068 },
[5581] = { 3241, 2157 },
[5601] = { 1634, 1073 },
[5602] = { 5387, 3584 },
[5628] = { 1980, 1350 },
}
[9] = { 5137.50, 3425.00 }, -- Mulgore
[11] = { 2487.50, 1658.34 }, -- Barrens
[15] = { 2170.84, 1447.90 }, -- Alterac
[19] = { 5287.50, 3525.00 }, -- BlastedLands
[20] = { 4518.75, 3012.50 }, -- Tirisfal
[21] = { 4200.00, 2800.00 }, -- Silverpine
[22] = { 2231.25, 1487.50 }, -- WesternPlaguelands
[23] = { 3870.83, 2581.25 }, -- EasternPlaguelands
[24] = { 3200.00, 2133.33 }, -- Hilsbrad
[26] = { 3850.00, 2566.67 }, -- Hinterlands
[27] = { 4925.00, 3283.34 }, -- DunMorogh
[29] = { 2929.16, 1952.08 }, -- BurningSteppes
[30] = { 3470.84, 2314.62 }, -- Elwynn
[32] = { 5091.66, 3393.70 }, -- DeadwindPass
[34] = { 2700.00, 1800.03 }, -- Duskwood
[35] = { 2293.75, 1529.17 }, -- LochModan
[37] = { 6381.25, 4254.10 }, -- Stranglethorn
[39] = { 4135.42, 2756.25 }, -- Westfall
[40] = { 10133.34, 6756.25 }, -- Wetlands
[41] = { 5250.00, 3500.00 }, -- Teldrassil
[42] = { 6550.00, 4366.66 }, -- Darkshore
[43] = { 5766.67, 3843.75 }, -- Ashenvale
[61] = { 4400.00, 2933.33 }, -- ThousandNeedles
[81] = { 6018.00, 4010.00 }, -- StonetalonMountains
[101] = { 4495.83, 2997.91 }, -- Desolace
[121] = { 6950.00, 4633.33 }, -- Feralas
[141] = { 2800.00, 1866.67 }, -- Dustwallow
[161] = { 6900.00, 4600.00 }, -- Tanaris
[181] = { 3600.00, 2400.00 }, -- Azshara
[182] = { 5750.00, 3833.33 }, -- Felwood
[201] = { 3700.00, 2466.66 }, -- UngoroCrater
[241] = { 2308.33, 1539.59 }, -- Moonglade
[261] = { 3483.33, 2322.92 }, -- Silithus
[281] = { 7100.00, 4733.33 }, -- Winterspring
[301] = { 1737.50, 1158.34 }, -- Stormwind
[321] = { 1402.61, 935.42 }, -- Orgrimmar
[341] = { 790.63, 527.61 }, -- Ironforge
[362] = { 1043.75, 695.83 }, -- ThunderBluff
[381] = { 1058.33, 705.71 }, -- Darnassus
[382] = { 959.37, 640.11 }, -- Undercity
[401] = { 4237.50, 2825.00 }, -- AlteracValley
[443] = { 1145.84, 764.58 }, -- WarsongGulch
[461] = { 1756.25, 1170.83 }, -- ArathiBasin
[500] = { 828.00, 541.00 }, -- pfQ876
[501] = { 3206.00, 2142.00 }, -- pfQ616
[502] = { 3159.00, 2108.00 }, -- pfQ4012
[503] = { 988.63, 1393.17 }, -- pfQ5023
[504] = { 2901.45, 1915.90 }, -- pfQ409
[505] = { 3092.10, 2047.00 }, -- pfQ408
[507] = { 3227.00, 2187.00 }, -- pfQ5121
[509] = { 1468.00, 976.00 }, -- pfQ2040
[510] = { 3666.00, 2442.00 }, -- pfQ5179
[511] = { 1608.00, 1075.00 }, -- pfQ5024
[512] = { 2472.00, 1665.00 }, -- pfQ5536
[513] = { 3082.00, 2061.00 }, -- pfQ5225
[514] = { 449.89, 300.00 }, -- pfQ1581
[516] = { 572.78, 381.85 }, -- pfQ718
[517] = { 2112.09, 1410.89 }, -- pfQ2100
[518] = { 769.67, 513.11 }, -- pfQ721
[519] = { 711.56, 468.68 }, -- pfQ25
[520] = { 203.66, 135.04 }, -- pfQ796
[521] = { 893.67, 595.78 }, -- pfQ1337
[522] = { 4139.02, 2946.00 }, -- pfQ3478
[523] = { 1348.24, 888.09 }, -- pfQ1941
[601] = { 1432.00, 977.00 }, -- pfQ5130
[603] = { 738.86, 492.57 }, -- pfQ2437
[605] = { 1383.33, 922.91 }, -- pfQ1176
[607] = { 695.03, 463.35 }, -- pfQ1477
[609] = { 1221.87, 806.42 }, -- pfQ719
[611] = { 378.15, 252.10 }, -- pfQ717
[617] = { 1495.00, 983.00 }, -- pfQ2717
[619] = { 2120.83, 1414.60 }, -- pfQ1977
[623] = { 1985.00, 1268.00 }, -- pfQ1584
[625] = { 2512.50, 1675.00 }, -- pfQ3429
[627] = { 483.11, 322.08 }, -- pfQ2159
[629] = { 886.84, 591.23 }, -- pfQ1583
[635] = { 499.43, 332.95 }, -- pfQ2677
[637] = { 656.59, 434.97 }, -- pfQ5138
[639] = { 881.00, 589.00 }, -- pfQ722
[641] = { 736.45, 490.96 }, -- pfQ491
[643] = { 619.98, 413.32 }, -- pfQ5136
[644] = { 320.19, 213.46 }, -- pfQ5135
[645] = { 612.69, 408.46 }, -- pfQ5153
[646] = { 703.30, 468.87 }, -- pfQ5163
[648] = { 320.05, 213.37 }, -- pfQ2057
[650] = { 381.00, 254.00 }, -- pfQ209
[652] = { 1185.35, 789.86 }, -- pfQ2017
[654] = { 977.56, 651.70 }, -- pfQ3428
[655] = { 2777.54, 1851.69 }, -- pfQ5147
[657] = { 619.00, 410.00 }, -- pfQ5723
[659] = { 312.00, 208.00 }, -- pfQ2257
[662] = { 1271.99, 845.48 }, -- pfQ5204
[663] = { 1085.86, 726.61 }, -- pfQ2366
[665] = { 1250.18, 837.44 }, -- pfQ5208
[667] = { 1991.69, 1318.42 }, -- pfQ3456
[668] = { 652.10, 439.67 }, -- pfQ5148
[670] = { 2643.21, 1751.16 }, -- pfQ5077
[672] = { 752.12, 510.33 }, -- pfQ5103
[674] = { 546.75, 391.90 }, -- pfQ5086
[676] = { 354.50, 234.74 }, -- pfQ5087
[678] = { 1273.10, 853.72 }, -- pfQ5097
[680] = { 1007.70, 671.80 }, -- pfQ5053
[682] = { 823.00, 549.00 }, -- pfQ3457
[683] = { 2800.00, 1868.00 }, -- pfQ5557
[684] = { 5387.00, 3584.00 }, -- pfQ5602
[685] = { 3098.00, 2068.00 }, -- pfQ5561
[686] = { 3241.00, 2157.00 }, -- pfQ5581
[689] = { 1324.00, 869.00 }, -- pfQ2557
[691] = { 1634.00, 1073.00 }, -- pfQ5601
[693] = { 1980.00, 1350.00 }, -- pfQ5628
[698] = { 1234.00, 829.00 }, -- pfQ5641
[699] = { 7856.00, 5241.00 }, -- pfQ5642
[700] = { 861.00, 607.00 }, -- pfQ1769
[701] = { 1347.00, 971.00 }, -- pfQ5640
[702] = { 1047.00, 694.00 }, -- pfQ5722
[704] = { 1011.00, 653.00 }, -- pfQ5734
[705] = { 440.00, 279.00 }, -- pfQ5731
[613] = { 1125.00, 740.00 },
[615] = { 893.67, 595.78 },
[621] = { 1919.00, 1250.00 },
[631] = { 1170.00, 785.00 },
[633] = { 2112.09, 1410.89 },
[696] = { 1425.00, 962.00 },
[5144] = { 309.00, 208.00 },
}
+140
View File
@@ -0,0 +1,140 @@
-- MetaHunt: Pet spell DBC acquire-method overlay (learnMethod, minRank, nextSpellId)
-- AUTO-GENERATED by .tools — do not edit by hand.
-- Source DBC(s): SkillLineAbility.dbc, SkillLine.dbc
-- Generated: 2026-03-20 13:57 UTC
-- This file patches MTH_DS_PetSpells.allSpells entries with DBC-authoritative
-- learnMethod, minRank, and nextSpellId values.
-- Load order: after ds-pet-spells.lua and ds-pet-spells-trainer.lua
if not MTH_DS_PetSpells or not MTH_DS_PetSpells.allSpells then return end
local _acquire = {
[1747] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[1748] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[1749] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[1750] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[1751] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[1853] = { learnMethod="unknown(1)", minRank=1, nextSpellId=0, skillLineId=261 },
[2975] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[2976] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[2977] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[2980] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[2981] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[2982] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[3666] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[3667] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[4195] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[4196] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[4197] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[4198] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[4199] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[4200] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[4201] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[4202] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[5048] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[5049] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[5149] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[7370] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[14922] = { learnMethod="unknown(1)", minRank=1, nextSpellId=0, skillLineId=261 },
[14923] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[14924] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[14925] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[14926] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[14927] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[16698] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[17254] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[17262] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[17263] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[17264] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[17265] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[17266] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[17267] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[17268] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[23100] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[23111] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[23112] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[23146] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[23149] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[23150] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[23163] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[23166] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[23167] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24424] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24440] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24441] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24451] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24454] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24455] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24463] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24464] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24475] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24476] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24477] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24478] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24490] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24494] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24495] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24508] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24509] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24510] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24511] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24512] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24513] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24514] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24515] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24516] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24547] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24556] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24557] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24558] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24559] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24560] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24561] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24562] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24580] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24581] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24582] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24584] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24588] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24589] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24599] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24607] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24608] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24609] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24631] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24632] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24641] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24845] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[25013] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[25014] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[25015] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[25016] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[25017] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[25077] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[26065] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[26094] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[26184] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[26185] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[26186] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[26189] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[26190] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[26202] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[28343] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[46023] = { learnMethod="trainer", minRank=0, nextSpellId=0, skillLineId=261 },
[46303] = { learnMethod="trainer", minRank=0, nextSpellId=0, skillLineId=261 },
[46307] = { learnMethod="trainer", minRank=0, nextSpellId=0, skillLineId=261 },
[51154] = { learnMethod="trainer", minRank=0, nextSpellId=0, skillLineId=261 },
[51155] = { learnMethod="trainer", minRank=0, nextSpellId=0, skillLineId=261 },
[51157] = { learnMethod="trainer", minRank=0, nextSpellId=0, skillLineId=261 },
}
for _, spell in ipairs(MTH_DS_PetSpells.allSpells) do
local meta = _acquire[spell.id]
if meta then
spell.learnMethod = meta.learnMethod
spell.minRank = meta.minRank
spell.nextSpellId = meta.nextSpellId
spell.skillLineId = meta.skillLineId
end
end
+45 -7
View File
@@ -25,6 +25,7 @@ MTH_DS_PetSpells = {
"Strider Presence",
"Thunderstomp",
"Web",
"Pollen Burst",
},
["allSpells"] = {
{
@@ -1804,15 +1805,30 @@ MTH_DS_PetSpells = {
"(6) Apply Aura #26: Root",
},
["icon"] = "Ability_Ensnare",
["id"] = 4167,
["id"] = 36533,
["name"] = "Web",
["range"] = "20 yards (Short Range)",
["rank"] = "",
["rankNumber"] = 0,
["school"] = "Physical",
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=4167",
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=36533",
["trainLevel"] = 22,
},
{
["ability"] = "Pollen Burst",
["castTime"] = "Instant",
["cooldown"] = "30 seconds",
["cost"] = "40 Focus",
["description"] = "Heal party members within 30 yards for 308 to 338 and reduce the attack speed of targets within 10 yards by 20% for 8 sec.",
["icon"] = "pollen_burst_2",
["id"] = 42051,
["name"] = "Pollen Burst",
["rank"] = "",
["rankNumber"] = 0,
["school"] = "Nature",
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=42051",
["trainLevel"] = 1,
},
},
["byAbility"] = {
["Bite"] = {
@@ -3744,17 +3760,39 @@ MTH_DS_PetSpells = {
"(6) Apply Aura #26: Root",
},
["icon"] = "Ability_Ensnare",
["id"] = 4167,
["id"] = 36533,
["name"] = "Web",
["range"] = "20 yards (Short Range)",
["rank"] = "",
["rankNumber"] = 0,
["school"] = "Physical",
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=4167",
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=36533",
["trainLevel"] = 22,
},
},
},
["Pollen Burst"] = {
["ability"] = "Pollen Burst",
["searchUrl"] = "https://database.turtlecraft.gg/?search=Pollen+Burst",
["spellCount"] = 1,
["spells"] = {
{
["ability"] = "Pollen Burst",
["castTime"] = "Instant",
["cooldown"] = "30 seconds",
["cost"] = "40 Focus",
["description"] = "Heal party members within 30 yards for 308 to 338 and reduce the attack speed of targets within 10 yards by 20% for 8 sec.",
["icon"] = "pollen_burst_2",
["id"] = 42051,
["name"] = "Pollen Burst",
["rank"] = "",
["rankNumber"] = 0,
["school"] = "Nature",
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=42051",
["trainLevel"] = 1,
},
},
},
},
["cleanedBy"] = "post-process quality rules v2 hard-gate",
["droppedDuplicates"] = {
@@ -4293,14 +4331,14 @@ MTH_DS_PetSpells = {
{
["ability"] = "Web",
["droppedId"] = 12023,
["keptId"] = 4167,
["keptId"] = 36533,
["mode"] = "lower-score",
["rankNumber"] = 0,
},
{
["ability"] = "Web",
["droppedId"] = 36533,
["keptId"] = 4167,
["droppedId"] = 4167,
["keptId"] = 36533,
["mode"] = "lower-score",
["rankNumber"] = 0,
},
+41
View File
@@ -0,0 +1,41 @@
-- MetaHunt: Stable slot prices
-- AUTO-GENERATED by .tools — do not edit by hand.
-- Source DBC(s): StableSlotPrices.dbc
-- Generated: 2026-03-20 13:57 UTC
if not MTH_DS then MTH_DS = {} end
MTH_DS_StableSlotPrices = {
[1] = {
["cost_copper"] = 500,
["cost_gold"] = 0,
["cost_silver"] = 5,
["cost_copper_remainder"] = 0,
["display"] = "0g 5s 0c",
["acquireMethod"] = "gold",
},
[2] = {
["cost_copper"] = 50000,
["cost_gold"] = 5,
["cost_silver"] = 0,
["cost_copper_remainder"] = 0,
["display"] = "5g 0s 0c",
["acquireMethod"] = "gold",
},
[3] = {
["cost_copper"] = 2500000,
["cost_gold"] = 250,
["cost_silver"] = 0,
["cost_copper_remainder"] = 0,
["display"] = "250g 0s 0c",
["acquireMethod"] = "gold",
},
[4] = {
["cost_copper"] = 0,
["cost_gold"] = 0,
["cost_silver"] = 0,
["cost_copper_remainder"] = 0,
["display"] = "Shop",
["acquireMethod"] = "shop",
},
}
+23493 -23493
View File
File diff suppressed because it is too large Load Diff
+223 -167
View File
File diff suppressed because it is too large Load Diff
+219 -219
View File
File diff suppressed because it is too large Load Diff
+97
View File
@@ -0,0 +1,97 @@
-- MetaHunt: Zone DBC enrichment (worldBounds, dbcAreaId, dbcMapId, dbcName)
-- AUTO-GENERATED by .tools — do not edit by hand.
-- Source DBC(s): WorldMapArea.dbc, AreaTable.dbc
-- Generated: 2026-03-20 13:57 UTC
if not MTH_DS_Zones then return end
-- WMA ID=301 map=0 'Stormwind'
if MTH_DS_Zones[1519] then
MTH_DS_Zones[1519]["dbcAreaId"] = 1519
MTH_DS_Zones[1519]["dbcMapId"] = 0
MTH_DS_Zones[1519]["dbcName"] = "Stormwind"
MTH_DS_Zones[1519]["worldBounds"] = {
["left"] = 1722.92,
["right"] = -14.58,
["top"] = -7995.8301,
["bottom"] = -9154.1699,
}
end
-- WMA ID=321 map=1 'Ogrimmar'
if MTH_DS_Zones[1637] then
MTH_DS_Zones[1637]["dbcAreaId"] = 1637
MTH_DS_Zones[1637]["dbcMapId"] = 1
MTH_DS_Zones[1637]["dbcName"] = "Ogrimmar"
MTH_DS_Zones[1637]["worldBounds"] = {
["left"] = -3680.6001,
["right"] = -5083.21,
["top"] = 2273.8799,
["bottom"] = 1338.46,
}
end
-- WMA ID=362 map=1 'ThunderBluff'
if MTH_DS_Zones[1638] then
MTH_DS_Zones[1638]["dbcAreaId"] = 1638
MTH_DS_Zones[1638]["dbcMapId"] = 1
MTH_DS_Zones[1638]["dbcName"] = "ThunderBluff"
MTH_DS_Zones[1638]["worldBounds"] = {
["left"] = 516.67,
["right"] = -527.08,
["top"] = -850.0,
["bottom"] = -1545.83,
}
end
-- WMA ID=381 map=1 'Darnassis'
if MTH_DS_Zones[1657] then
MTH_DS_Zones[1657]["dbcAreaId"] = 1657
MTH_DS_Zones[1657]["dbcMapId"] = 1
MTH_DS_Zones[1657]["dbcName"] = "Darnassis"
MTH_DS_Zones[1657]["worldBounds"] = {
["left"] = 2938.3601,
["right"] = 1880.03,
["top"] = 10238.3203,
["bottom"] = 9532.5898,
}
end
-- WMA ID=382 map=0 'Undercity'
if MTH_DS_Zones[1497] then
MTH_DS_Zones[1497]["dbcAreaId"] = 1497
MTH_DS_Zones[1497]["dbcMapId"] = 0
MTH_DS_Zones[1497]["dbcName"] = "Undercity"
MTH_DS_Zones[1497]["worldBounds"] = {
["left"] = 873.19,
["right"] = -86.18,
["top"] = 1877.95,
["bottom"] = 1237.84,
}
end
-- WMA ID=508 map=1 'AmaniAlor'
if MTH_DS_Zones[2041] then
MTH_DS_Zones[2041]["dbcAreaId"] = 2041
MTH_DS_Zones[2041]["dbcMapId"] = 1
MTH_DS_Zones[2041]["dbcName"] = "AmaniAlor"
MTH_DS_Zones[2041]["worldBounds"] = {
["left"] = 3112.0,
["right"] = 1599.0,
["top"] = 3437.0,
["bottom"] = 2434.0,
}
end
-- WMA ID=509 map=0 'AlahThalas'
if MTH_DS_Zones[2040] then
MTH_DS_Zones[2040]["dbcAreaId"] = 2040
MTH_DS_Zones[2040]["dbcMapId"] = 0
MTH_DS_Zones[2040]["dbcName"] = "AlahThalas"
MTH_DS_Zones[2040]["worldBounds"] = {
["left"] = -2169.0,
["right"] = -3637.0,
["top"] = 4907.0,
["bottom"] = 3931.0,
}
end
+2
View File
@@ -72,4 +72,6 @@ MTH_DS_ZoneNamesFallback = {
[5581] = "Northwind",
[5601] = "Dragonmaw Retreat",
[5602] = "Grim Reaches",
[5641] = "Windhorn Canyon",
[5642] = "Moonwhisper Coast",
}
File diff suppressed because it is too large Load Diff
+402
View File
@@ -0,0 +1,402 @@
-- MetaHunt: WMA (WorldMapArea.dbc) zone reference data
-- AUTO-GENERATED by .tools/migrate_wma.py — do not edit by hand.
-- Source: Haxxor WMA CSV (patch-9.mpq)
--
-- Provides MTH_MAP_PFQ_TO_WMA: pfQ AreaTable ID → WMA ID lookup
-- Used by map-markers.lua to translate GetCurrentMapAreaID() results
-- and by GetCurrentMapID() to return WMA zone IDs for coord matching.
--
-- Provides MTH_DS_WMAZones: WMA ID → worldBounds + pfqAreaId
-- Used for map overlay features without modifying the pfQ-keyed MTH_DS_Zones.
if not MTH_DS then MTH_DS = {} end
-- pfQ AreaTable ID → WMA ID runtime bridge
MTH_MAP_PFQ_TO_WMA = {
[1] = 27, -- DunMorogh
[3] = 17, -- Badlands
[4] = 19, -- BlastedLands
[8] = 38, -- SwampOfSorrows
[10] = 34, -- Duskwood
[11] = 40, -- Wetlands
[12] = 30, -- Elwynn
[14] = 4, -- Durotar
[15] = 141, -- Dustwallow
[16] = 181, -- Aszhara
[17] = 11, -- Barrens
[25] = 519, -- BlackrockMountain
[28] = 22, -- WesternPlaguelands
[33] = 37, -- Stranglethorn
[36] = 15, -- Alterac
[38] = 35, -- LochModan
[40] = 39, -- Westfall
[41] = 32, -- DeadwindPass
[44] = 36, -- Redridge
[45] = 16, -- Arathi
[46] = 29, -- BurningSteppes
[47] = 26, -- Hinterlands
[51] = 28, -- SearingGorge
[85] = 20, -- Tirisfal
[130] = 21, -- Silverpine
[139] = 23, -- EasternPlaguelands
[141] = 41, -- Teldrassil
[148] = 42, -- Darkshore
[209] = 650, -- ShadowfangKeep
[215] = 9, -- Mulgore
[267] = 24, -- Hilsbrad
[331] = 43, -- Ashenvale
[357] = 121, -- Feralas
[361] = 182, -- Felwood
[400] = 61, -- ThousandNeedles
[405] = 101, -- Desolace
[406] = 81, -- StonetalonMountains
[408] = 505, -- Gillijim
[409] = 504, -- Lapidis
[440] = 161, -- Tanaris
[490] = 201, -- UngoroCrater
[491] = 641, -- RazorfenKraul
[493] = 241, -- Moonglade
[616] = 501, -- Hyjal
[618] = 281, -- Winterspring
[717] = 611, -- TheStockade
[718] = 516, -- WailingCavernsEntrance
[719] = 609, -- BlackFathomDeeps
[721] = 518, -- GnomereganEntrance
[722] = 639, -- RazorfenDowns
[796] = 520, -- ScarletMonasteryEntrance
[876] = 500, -- GMIsland
[1176] = 605, -- ZulFarrak
[1337] = 521, -- UldamanEntrance
[1377] = 261, -- Silithus
[1477] = 607, -- TheTempleOfAtalHakkar
[1497] = 382, -- Undercity
[1519] = 301, -- Stormwind
[1537] = 341, -- Ironforge
[1581] = 514, -- DeadminesEntrance
[1583] = 629, -- BlackrockSpire
[1584] = 623, -- BlackrockDepths
[1637] = 321, -- Orgrimmar
[1638] = 362, -- ThunderBluff
[1657] = 381, -- Darnassus
[1769] = 700, -- TimbermawTunnels
[1941] = 523, -- CavernsOfTime
[1977] = 619, -- ZulGurub
[2017] = 652, -- Stratholme
[2040] = 509, -- AlahThalas
[2057] = 648, -- Scholomance
[2100] = 517, -- MaraudonEntrance
[2159] = 627, -- OnyxiasLair
[2257] = 659, -- DeeprunTram
[2366] = 663, -- BlackMorass2f
[2437] = 603, -- Ragefire
[2557] = 689, -- DireMaulEntrance
[2597] = 401, -- AlteracValley
[2677] = 635, -- BlackwingLair
[2717] = 617, -- MoltenCore
[3277] = 443, -- WarsongGulch
[3358] = 461, -- ArathiBasin
[3428] = 654, -- AhnQiraj
[3429] = 625, -- RuinsofAhnQiraj
[3456] = 667, -- Naxxramas
[3457] = 682, -- UpperKarazhan
[3478] = 522, -- AhnQirajEntrance
[4012] = 502, -- ScarletEnclave
[5023] = 503, -- Sunnyglade
[5024] = 511, -- Icepoint
[5053] = 680, -- Moomoo
[5077] = 670, -- CrescentGrove
[5086] = 674, -- KarazhanCrypt
[5087] = 676, -- StormwindVault
[5097] = 678, -- EmeraldSanctum
[5103] = 672, -- HateforgeQuarry
[5121] = 507, -- TelAbim
[5130] = 601, -- WinterVeilVale
[5135] = 644, -- ScarletMonastery2f
[5136] = 643, -- ScarletMonastery
[5138] = 637, -- TheDeadmines
[5147] = 655, -- AhnQiraj2f
[5148] = 668, -- Naxxramas2f
[5153] = 645, -- ScarletMonastery3f
[5163] = 646, -- ScarletMonastery4f
[5179] = 510, -- Gilneas
[5204] = 662, -- BlackMorass
[5208] = 665, -- GilneasCity
[5225] = 513, -- ThalassianHighlands
[5536] = 512, -- BlackstoneIsland
[5557] = 683, -- UpperKarazhan2f
[5561] = 685, -- Balor
[5581] = 686, -- Northwind
[5601] = 691, -- DragonmawRetreat
[5602] = 684, -- GrimReaches
[5628] = 693, -- StormwroughtRuins
[5640] = 701, -- TimbermawEntrance
[5641] = 698, -- WindhornCanyon
[5642] = 699, -- Moonwhisper
[5722] = 702, -- ThornGorge
[5723] = 657, -- Karazhan
[5731] = 705, -- WindhornCanyonEntrance
[5734] = 704, -- FrostmaneHollow
}
-- WMA ID → pfQ AreaTable ID (for fallback lookups)
MTH_MAP_WMA_TO_PFQ = {
[4] = 14, -- Durotar
[9] = 215, -- Mulgore
[11] = 17, -- Barrens
[15] = 36, -- Alterac
[16] = 45, -- Arathi
[17] = 3, -- Badlands
[19] = 4, -- BlastedLands
[20] = 85, -- Tirisfal
[21] = 130, -- Silverpine
[22] = 28, -- WesternPlaguelands
[23] = 139, -- EasternPlaguelands
[24] = 267, -- Hilsbrad
[26] = 47, -- Hinterlands
[27] = 1, -- DunMorogh
[28] = 51, -- SearingGorge
[29] = 46, -- BurningSteppes
[30] = 12, -- Elwynn
[32] = 41, -- DeadwindPass
[34] = 10, -- Duskwood
[35] = 38, -- LochModan
[36] = 44, -- Redridge
[37] = 33, -- Stranglethorn
[38] = 8, -- SwampOfSorrows
[39] = 40, -- Westfall
[40] = 11, -- Wetlands
[41] = 141, -- Teldrassil
[42] = 148, -- Darkshore
[43] = 331, -- Ashenvale
[61] = 400, -- ThousandNeedles
[81] = 406, -- StonetalonMountains
[101] = 405, -- Desolace
[121] = 357, -- Feralas
[141] = 15, -- Dustwallow
[161] = 440, -- Tanaris
[181] = 16, -- Aszhara
[182] = 361, -- Felwood
[201] = 490, -- UngoroCrater
[241] = 493, -- Moonglade
[261] = 1377, -- Silithus
[281] = 618, -- Winterspring
[301] = 1519, -- Stormwind
[321] = 1637, -- Orgrimmar
[341] = 1537, -- Ironforge
[362] = 1638, -- ThunderBluff
[381] = 1657, -- Darnassus
[382] = 1497, -- Undercity
[401] = 2597, -- AlteracValley
[443] = 3277, -- WarsongGulch
[461] = 3358, -- ArathiBasin
[500] = 876, -- GMIsland
[501] = 616, -- Hyjal
[502] = 4012, -- ScarletEnclave
[503] = 5023, -- Sunnyglade
[504] = 409, -- Lapidis
[505] = 408, -- Gillijim
[507] = 5121, -- TelAbim
[509] = 2040, -- AlahThalas
[510] = 5179, -- Gilneas
[511] = 5024, -- Icepoint
[512] = 5536, -- BlackstoneIsland
[513] = 5225, -- ThalassianHighlands
[514] = 1581, -- DeadminesEntrance
[516] = 718, -- WailingCavernsEntrance
[517] = 2100, -- MaraudonEntrance
[518] = 721, -- GnomereganEntrance
[519] = 25, -- BlackrockMountain
[520] = 796, -- ScarletMonasteryEntrance
[521] = 1337, -- UldamanEntrance
[522] = 3478, -- AhnQirajEntrance
[523] = 1941, -- CavernsOfTime
[601] = 5130, -- WinterVeilVale
[603] = 2437, -- Ragefire
[605] = 1176, -- ZulFarrak
[607] = 1477, -- TheTempleOfAtalHakkar
[609] = 719, -- BlackFathomDeeps
[611] = 717, -- TheStockade
[617] = 2717, -- MoltenCore
[619] = 1977, -- ZulGurub
[623] = 1584, -- BlackrockDepths
[625] = 3429, -- RuinsofAhnQiraj
[627] = 2159, -- OnyxiasLair
[629] = 1583, -- BlackrockSpire
[635] = 2677, -- BlackwingLair
[637] = 5138, -- TheDeadmines
[639] = 722, -- RazorfenDowns
[641] = 491, -- RazorfenKraul
[643] = 5136, -- ScarletMonastery
[644] = 5135, -- ScarletMonastery2f
[645] = 5153, -- ScarletMonastery3f
[646] = 5163, -- ScarletMonastery4f
[648] = 2057, -- Scholomance
[650] = 209, -- ShadowfangKeep
[652] = 2017, -- Stratholme
[654] = 3428, -- AhnQiraj
[655] = 5147, -- AhnQiraj2f
[657] = 5723, -- Karazhan
[659] = 2257, -- DeeprunTram
[662] = 5204, -- BlackMorass
[663] = 2366, -- BlackMorass2f
[665] = 5208, -- GilneasCity
[667] = 3456, -- Naxxramas
[668] = 5148, -- Naxxramas2f
[670] = 5077, -- CrescentGrove
[672] = 5103, -- HateforgeQuarry
[674] = 5086, -- KarazhanCrypt
[676] = 5087, -- StormwindVault
[678] = 5097, -- EmeraldSanctum
[680] = 5053, -- Moomoo
[682] = 3457, -- UpperKarazhan
[683] = 5557, -- UpperKarazhan2f
[684] = 5602, -- GrimReaches
[685] = 5561, -- Balor
[686] = 5581, -- Northwind
[689] = 2557, -- DireMaulEntrance
[691] = 5601, -- DragonmawRetreat
[693] = 5628, -- StormwroughtRuins
[698] = 5641, -- WindhornCanyon
[699] = 5642, -- Moonwhisper
[700] = 1769, -- TimbermawTunnels
[701] = 5640, -- TimbermawEntrance
[702] = 5722, -- ThornGorge
[704] = 5734, -- FrostmaneHollow
[705] = 5731, -- WindhornCanyonEntrance
}
-- WMA ID → worldBounds + pfqAreaId (for map overlay and minimap calibration)
MTH_DS_WMAZones = {
[4] = { pfqAreaId=14, mapId=1, left=-1962.50, right=-7250.00, top=1808.33, bottom=-1716.67, width=5287.50, height=3525.00 }, -- Durotar
[9] = { pfqAreaId=215, mapId=1, left=2047.92, right=-3089.58, top=-272.92, bottom=-3697.92, width=5137.50, height=3425.00 }, -- Mulgore
[11] = { pfqAreaId=17, mapId=1, left=2622.92, right=-7510.42, top=1612.50, bottom=-5143.75, width=10133.34, height=6756.25 }, -- Barrens
[13] = { pfqAreaId=0, mapId=1, left=17066.60, right=-19733.21, top=12799.90, bottom=-11733.30, width=36799.81, height=24533.20 }, -- Kalimdor
[14] = { pfqAreaId=0, mapId=0, left=16000.00, right=-19199.90, top=7466.60, bottom=-16000.00, width=35199.90, height=23466.60 }, -- Azeroth
[15] = { pfqAreaId=36, mapId=0, left=783.33, right=-2016.67, top=1500.00, bottom=-366.67, width=2800.00, height=1866.67 }, -- Alterac
[16] = { pfqAreaId=45, mapId=0, left=-866.67, right=-4466.67, top=-133.33, bottom=-2533.33, width=3600.00, height=2400.00 }, -- Arathi
[17] = { pfqAreaId=3, mapId=0, left=-2079.17, right=-4566.67, top=-5889.58, bottom=-7547.92, width=2487.50, height=1658.34 }, -- Badlands
[19] = { pfqAreaId=4, mapId=0, left=-1241.67, right=-4591.67, top=-10566.67, bottom=-12800.00, width=3350.00, height=2233.33 }, -- BlastedLands
[20] = { pfqAreaId=85, mapId=0, left=3033.33, right=-1485.42, top=3837.50, bottom=825.00, width=4518.75, height=3012.50 }, -- Tirisfal
[21] = { pfqAreaId=130, mapId=0, left=3450.00, right=-750.00, top=1666.67, bottom=-1133.33, width=4200.00, height=2800.00 }, -- Silverpine
[22] = { pfqAreaId=28, mapId=0, left=416.67, right=-3883.33, top=3366.67, bottom=500.00, width=4300.00, height=2866.67 }, -- WesternPlaguelands
[23] = { pfqAreaId=139, mapId=0, left=-2185.42, right=-6056.25, top=3800.00, bottom=1218.75, width=3870.83, height=2581.25 }, -- EasternPlaguelands
[24] = { pfqAreaId=267, mapId=0, left=1066.67, right=-2133.33, top=400.00, bottom=-1733.33, width=3200.00, height=2133.33 }, -- Hilsbrad
[26] = { pfqAreaId=47, mapId=0, left=-1575.00, right=-5425.00, top=1466.67, bottom=-1100.00, width=3850.00, height=2566.67 }, -- Hinterlands
[27] = { pfqAreaId=1, mapId=0, left=1802.08, right=-3122.92, top=-3877.08, bottom=-7160.42, width=4925.00, height=3283.34 }, -- DunMorogh
[28] = { pfqAreaId=51, mapId=0, left=-322.92, right=-2554.17, top=-6100.00, bottom=-7587.50, width=2231.25, height=1487.50 }, -- SearingGorge
[29] = { pfqAreaId=46, mapId=0, left=-266.67, right=-3195.83, top=-7031.25, bottom=-8983.33, width=2929.16, height=1952.08 }, -- BurningSteppes
[30] = { pfqAreaId=12, mapId=0, left=1535.42, right=-1935.42, top=-7939.58, bottom=-10254.17, width=3470.84, height=2314.59 }, -- Elwynn
[32] = { pfqAreaId=41, mapId=0, left=-833.33, right=-3333.33, top=-9866.67, bottom=-11533.33, width=2500.00, height=1666.66 }, -- DeadwindPass
[34] = { pfqAreaId=10, mapId=0, left=833.33, right=-1866.67, top=-9716.67, bottom=-11516.67, width=2700.00, height=1800.00 }, -- Duskwood
[35] = { pfqAreaId=38, mapId=0, left=-1993.75, right=-4752.08, top=-4487.50, bottom=-6327.08, width=2758.33, height=1839.58 }, -- LochModan
[36] = { pfqAreaId=44, mapId=0, left=-1570.83, right=-3741.67, top=-8575.00, bottom=-10022.92, width=2170.84, height=1447.92 }, -- Redridge
[37] = { pfqAreaId=33, mapId=0, left=2220.83, right=-4160.42, top=-11168.75, bottom=-15422.92, width=6381.25, height=4254.17 }, -- Stranglethorn
[38] = { pfqAreaId=8, mapId=0, left=-2222.92, right=-4516.67, top=-9620.83, bottom=-11150.00, width=2293.75, height=1529.17 }, -- SwampOfSorrows
[39] = { pfqAreaId=40, mapId=0, left=3016.67, right=-483.33, top=-9400.00, bottom=-11733.33, width=3500.00, height=2333.33 }, -- Westfall
[40] = { pfqAreaId=11, mapId=0, left=-389.58, right=-4525.00, top=-2147.92, bottom=-4904.17, width=4135.42, height=2756.25 }, -- Wetlands
[41] = { pfqAreaId=141, mapId=1, left=3814.58, right=-1277.08, top=11831.25, bottom=8437.50, width=5091.66, height=3393.75 }, -- Teldrassil
[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
[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
[161] = { pfqAreaId=440, mapId=1, left=-218.75, right=-7118.75, top=-5875.00, bottom=-10475.00, width=6900.00, height=4600.00 }, -- Tanaris
[181] = { pfqAreaId=16, mapId=1, left=-3277.08, right=-8347.92, top=5341.67, bottom=1960.42, width=5070.84, height=3381.25 }, -- Aszhara
[182] = { pfqAreaId=361, mapId=1, left=1641.67, right=-4108.33, top=7133.33, bottom=3300.00, width=5750.00, height=3833.33 }, -- Felwood
[201] = { pfqAreaId=490, mapId=1, left=533.33, right=-3166.67, top=-5966.67, bottom=-8433.33, width=3700.00, height=2466.66 }, -- UngoroCrater
[241] = { pfqAreaId=493, mapId=1, left=-1381.25, right=-3689.58, top=8491.67, bottom=6952.08, width=2308.33, height=1539.59 }, -- Moonglade
[261] = { pfqAreaId=1377, mapId=1, left=2537.50, right=-945.83, top=-5958.33, bottom=-8281.25, width=3483.33, height=2322.92 }, -- Silithus
[281] = { pfqAreaId=618, mapId=1, left=-316.67, right=-7416.67, top=8533.33, bottom=3800.00, width=7100.00, height=4733.33 }, -- Winterspring
[301] = { pfqAreaId=1519, mapId=0, left=1722.92, right=-14.58, top=-7995.83, bottom=-9154.17, width=1737.50, height=1158.34 }, -- Stormwind
[321] = { pfqAreaId=1637, mapId=1, left=-3680.60, right=-5083.21, top=2273.88, bottom=1338.46, width=1402.61, height=935.42 }, -- Orgrimmar
[341] = { pfqAreaId=1537, mapId=0, left=-713.59, right=-1504.22, top=-4569.24, bottom=-5096.85, width=790.63, height=527.61 }, -- Ironforge
[362] = { pfqAreaId=1638, mapId=1, left=516.67, right=-527.08, top=-850.00, bottom=-1545.83, width=1043.75, height=695.83 }, -- ThunderBluff
[381] = { pfqAreaId=1657, mapId=1, left=2938.36, right=1880.03, top=10238.32, bottom=9532.59, width=1058.33, height=705.73 }, -- Darnassus
[382] = { pfqAreaId=1497, mapId=0, left=873.19, right=-86.18, top=1877.95, bottom=1237.84, width=959.37, height=640.11 }, -- Undercity
[401] = { pfqAreaId=2597, mapId=30, left=1781.25, right=-2456.25, top=1085.42, bottom=-1739.58, width=4237.50, height=2825.00 }, -- AlteracValley
[443] = { pfqAreaId=3277, mapId=489, left=2041.67, right=895.83, top=1627.08, bottom=862.50, width=1145.84, height=764.58 }, -- WarsongGulch
[461] = { pfqAreaId=3358, mapId=529, left=1858.33, right=102.08, top=1508.33, bottom=337.50, width=1756.25, height=1170.83 }, -- ArathiBasin
[500] = { pfqAreaId=876, mapId=1, left=16748.00, right=15920.00, top=16525.00, bottom=15984.00, width=828.00, height=541.00 }, -- GMIsland
[501] = { pfqAreaId=616, mapId=1, left=-1080.00, right=-4286.00, top=6125.00, bottom=3983.00, width=3206.00, height=2142.00 }, -- Hyjal
[502] = { pfqAreaId=4012, mapId=0, left=-4050.00, right=-7209.00, top=3087.00, bottom=979.00, width=3159.00, height=2108.00 }, -- ScarletEnclave
[503] = { pfqAreaId=5023, mapId=27, left=1013.00, right=-435.00, top=1823.00, bottom=858.00, width=1448.00, height=965.00 }, -- Sunnyglade
[504] = { pfqAreaId=409, mapId=0, left=4933.33, right=2031.88, top=-11043.48, bottom=-12959.42, width=2901.45, height=1915.94 }, -- Lapidis
[505] = { pfqAreaId=408, mapId=0, left=4436.22, right=1344.12, top=-12528.71, bottom=-14575.72, width=3092.10, height=2047.01 }, -- Gillijim
[507] = { pfqAreaId=5121, mapId=1, left=-5179.00, right=-8406.00, top=-7184.00, bottom=-9371.00, width=3227.00, height=2187.00 }, -- TelAbim
[509] = { pfqAreaId=2040, mapId=0, left=-2169.00, right=-3637.00, top=4907.00, bottom=3931.00, width=1468.00, height=976.00 }, -- AlahThalas
[510] = { pfqAreaId=5179, mapId=0, left=3253.00, right=-413.00, top=-359.00, bottom=-2801.00, width=3666.00, height=2442.00 }, -- Gilneas
[511] = { pfqAreaId=5024, mapId=1, left=-5595.00, right=-7203.00, top=14405.00, bottom=13330.00, width=1608.00, height=1075.00 }, -- Icepoint
[512] = { pfqAreaId=5536, mapId=1, left=-6274.00, right=-8746.00, top=799.00, bottom=-866.00, width=2472.00, height=1665.00 }, -- BlackstoneIsland
[513] = { pfqAreaId=5225, mapId=0, left=-1005.00, right=-4087.00, top=4952.00, bottom=2891.00, width=3082.00, height=2061.00 }, -- ThalassianHighlands
[514] = { pfqAreaId=1581, mapId=0, left=1793.18, right=1343.29, top=-11054.94, bottom=-11354.86, width=449.89, height=299.92 }, -- DeadminesEntrance
[516] = { pfqAreaId=718, mapId=1, left=-1900.87, right=-2473.65, top=-488.64, bottom=-870.49, width=572.78, height=381.85 }, -- WailingCavernsEntrance
[517] = { pfqAreaId=2100, mapId=1, left=3208.00, right=2384.00, top=-1096.00, bottom=-1646.00, width=824.00, height=550.00 }, -- MaraudonEntrance
[518] = { pfqAreaId=721, mapId=0, left=1027.64, right=456.45, top=-4806.32, bottom=-5185.46, width=571.19, height=379.14 }, -- GnomereganEntrance
[519] = { pfqAreaId=25, mapId=0, left=-760.94, right=-1472.50, top=-7327.82, bottom=-7796.50, width=711.56, height=468.68 }, -- BlackrockMountain
[520] = { pfqAreaId=796, mapId=0, left=-659.96, right=-863.62, top=2947.37, bottom=2812.33, width=203.66, height=135.04 }, -- ScarletMonasteryEntrance
[521] = { pfqAreaId=1337, mapId=0, left=-2747.15, right=-3310.46, top=-5953.41, bottom=-6329.51, width=563.31, height=376.10 }, -- UldamanEntrance
[522] = { pfqAreaId=3478, mapId=1, left=3932.67, right=-206.35, top=-8005.20, bottom=-10951.25, width=4139.02, height=2946.05 }, -- AhnQirajEntrance
[523] = { pfqAreaId=1941, mapId=1, left=-3695.85, right=-5044.09, top=-8023.09, bottom=-8911.18, width=1348.24, height=888.09 }, -- CavernsOfTime
[601] = { pfqAreaId=5130, mapId=813, left=1916.00, right=484.00, top=-2332.00, bottom=-3309.00, width=1432.00, height=977.00 }, -- WinterVeilVale
[603] = { pfqAreaId=2437, mapId=389, left=452.87, right=-285.99, top=39.62, bottom=-452.95, width=738.86, height=492.57 }, -- Ragefire
[605] = { pfqAreaId=1176, mapId=209, left=1625.00, right=241.67, top=2052.08, bottom=1129.17, width=1383.33, height=922.91 }, -- ZulFarrak
[607] = { pfqAreaId=1477, mapId=109, left=442.76, right=-252.27, top=-255.17, bottom=-718.52, width=695.03, height=463.35 }, -- TheTempleOfAtalHakkar
[609] = { pfqAreaId=719, mapId=48, left=485.41, right=-736.46, top=-107.91, bottom=-914.33, width=1221.87, height=806.42 }, -- BlackFathomDeeps
[611] = { pfqAreaId=717, mapId=34, left=189.82, right=-188.33, top=220.63, bottom=-31.47, width=378.15, height=252.10 }, -- TheStockade
[613] = { pfqAreaId=721, mapId=90, left=863.00, right=-262.00, top=-245.00, bottom=-985.00, width=1125.00, height=740.00 }, -- Gnomeregan
[615] = { pfqAreaId=1337, mapId=70, left=645.12, right=-248.55, top=204.31, bottom=-391.47, width=893.67, height=595.78 }, -- Uldaman
[617] = { pfqAreaId=2717, mapId=409, left=-11.00, right=-1506.00, top=1439.00, bottom=456.00, width=1495.00, height=983.00 }, -- MoltenCore
[619] = { pfqAreaId=1977, mapId=309, left=-612.50, right=-2733.33, top=-11225.00, bottom=-12639.58, width=2120.83, height=1414.58 }, -- ZulGurub
[621] = { pfqAreaId=2557, mapId=429, left=1007.00, right=-912.00, top=955.00, bottom=-295.00, width=1919.00, height=1250.00 }, -- DireMaul
[623] = { pfqAreaId=1584, mapId=230, left=875.00, right=-1110.00, top=1513.00, bottom=245.00, width=1985.00, height=1268.00 }, -- BlackrockDepths
[625] = { pfqAreaId=3429, mapId=509, left=3035.42, right=522.92, top=-8233.33, bottom=-9908.33, width=2512.50, height=1675.00 }, -- RuinsofAhnQiraj
[627] = { pfqAreaId=2159, mapId=249, left=155.00, right=-366.00, top=86.00, bottom=-262.00, width=521.00, height=348.00 }, -- OnyxiasLair
[629] = { pfqAreaId=1583, mapId=229, left=10.59, right=-876.25, top=304.40, bottom=-286.83, width=886.84, height=591.23 }, -- BlackrockSpire
[631] = { pfqAreaId=718, mapId=43, left=790.00, right=-380.00, top=215.00, bottom=-570.00, width=1170.00, height=785.00 }, -- WailingCaverns
[633] = { pfqAreaId=2100, mapId=349, left=485.41, right=-1626.68, top=1199.76, bottom=-211.13, width=2112.09, height=1410.89 }, -- Maraudon
[635] = { pfqAreaId=2677, mapId=469, left=-645.00, right=-1422.00, top=-7255.00, bottom=-7760.00, width=777.00, height=505.00 }, -- BlackwingLair
[637] = { pfqAreaId=5138, mapId=36, left=-322.94, right=-979.53, top=36.93, bottom=-398.04, width=656.59, height=434.97 }, -- TheDeadmines
[639] = { pfqAreaId=722, mapId=129, left=1271.00, right=390.00, top=2683.00, bottom=2094.00, width=881.00, height=589.00 }, -- RazorfenDowns
[641] = { pfqAreaId=491, mapId=47, left=2058.92, right=1322.47, top=2349.64, bottom=1858.68, width=736.45, height=490.96 }, -- RazorfenKraul
[643] = { pfqAreaId=5136, mapId=189, left=1567.97, right=947.99, top=2030.18, bottom=1616.86, width=619.98, height=413.32 }, -- ScarletMonastery
[644] = { pfqAreaId=5135, mapId=189, left=-162.27, right=-482.46, top=307.37, bottom=93.91, width=320.19, height=213.46 }, -- ScarletMonastery2f
[645] = { pfqAreaId=5153, mapId=189, left=50.27, right=-562.42, top=2009.10, bottom=1600.64, width=612.69, height=408.46 }, -- ScarletMonastery3f
[646] = { pfqAreaId=5163, mapId=189, left=1743.99, right=1040.69, top=1281.29, bottom=812.42, width=703.30, height=468.87 }, -- ScarletMonastery4f
[648] = { pfqAreaId=2057, mapId=289, left=251.41, right=-68.64, top=317.70, bottom=104.33, width=320.05, height=213.37 }, -- Scholomance
[650] = { pfqAreaId=209, mapId=33, left=2436.00, right=2055.00, top=-61.00, bottom=-315.00, width=381.00, height=254.00 }, -- ShadowfangKeep
[652] = { pfqAreaId=2017, mapId=329, left=-2639.83, right=-3825.18, top=4163.90, bottom=3374.04, width=1185.35, height=789.86 }, -- Stratholme
[654] = { pfqAreaId=3428, mapId=531, left=2515.63, right=1538.07, top=-8051.80, bottom=-8703.50, width=977.56, height=651.70 }, -- AhnQiraj
[655] = { pfqAreaId=5147, mapId=531, left=2915.62, right=138.08, top=-7659.29, bottom=-9510.98, width=2777.54, height=1851.69 }, -- AhnQiraj2f
[657] = { pfqAreaId=5723, mapId=532, left=-1546.00, right=-2165.00, top=-10789.00, bottom=-11199.00, width=619.00, height=410.00 }, -- Karazhan
[659] = { pfqAreaId=2257, mapId=369, left=2623.50, right=2311.50, top=95.50, bottom=-112.50, width=312.00, height=208.00 }, -- DeeprunTram
[662] = { pfqAreaId=5204, mapId=269, left=7338.47, right=6066.48, top=-1247.55, bottom=-2093.03, width=1271.99, height=845.48 }, -- BlackMorass
[663] = { pfqAreaId=2366, mapId=269, left=7651.85, right=6565.99, top=-1500.47, bottom=-2227.08, width=1085.86, height=726.61 }, -- BlackMorass2f
[665] = { pfqAreaId=5208, mapId=815, left=3290.32, right=2040.14, top=-760.72, bottom=-1598.16, width=1250.18, height=837.44 }, -- GilneasCity
[667] = { pfqAreaId=3456, mapId=533, left=-2375.27, right=-4366.96, top=3656.70, bottom=2338.28, width=1991.69, height=1318.42 }, -- Naxxramas
[668] = { pfqAreaId=5148, mapId=533, left=-4869.36, right=-5521.46, top=3816.56, bottom=3376.89, width=652.10, height=439.67 }, -- Naxxramas2f
[670] = { pfqAreaId=5077, mapId=802, left=1207.72, right=-1435.49, top=882.98, bottom=-868.18, width=2643.21, height=1751.16 }, -- CrescentGrove
[672] = { pfqAreaId=5103, mapId=808, left=-3024.33, right=-3776.45, top=-7888.28, bottom=-8398.61, width=752.12, height=510.33 }, -- HateforgeQuarry
[674] = { pfqAreaId=5086, mapId=800, left=-1302.51, right=-1849.26, top=-10916.17, bottom=-11308.14, width=546.75, height=391.97 }, -- KarazhanCrypt
[676] = { pfqAreaId=5087, mapId=35, left=314.34, right=-40.16, top=89.30, bottom=-145.44, width=354.50, height=234.74 }, -- StormwindVault
[678] = { pfqAreaId=5097, mapId=807, left=3939.10, right=2666.00, top=3519.72, bottom=2666.00, width=1273.10, height=853.72 }, -- EmeraldSanctum
[680] = { pfqAreaId=5053, mapId=801, left=17296.30, right=16288.62, top=17158.52, bottom=16486.73, width=1007.68, height=671.79 }, -- Moomoo
[682] = { pfqAreaId=3457, mapId=814, left=-1378.00, right=-2201.00, top=-10887.00, bottom=-11436.00, width=823.00, height=549.00 }, -- UpperKarazhan
[683] = { pfqAreaId=5557, mapId=814, left=-1483.00, right=-4283.00, top=-5800.00, bottom=-7668.00, width=2800.00, height=1868.00 }, -- UpperKarazhan2f
[684] = { pfqAreaId=5602, mapId=0, left=-2072.00, right=-7459.00, top=-2904.00, bottom=-6488.00, width=5387.00, height=3584.00 }, -- GrimReaches
[685] = { pfqAreaId=5561, mapId=0, left=4728.00, right=1630.00, top=-7577.00, bottom=-9645.00, width=3098.00, height=2068.00 }, -- Balor
[686] = { pfqAreaId=5581, mapId=0, left=2258.00, right=-983.00, top=-6327.00, bottom=-8484.00, width=3241.00, height=2157.00 }, -- Northwind
[689] = { pfqAreaId=2557, mapId=1, left=2014.00, right=690.00, top=-3401.00, bottom=-4270.00, width=1324.00, height=869.00 }, -- DireMaulEntrance
[691] = { pfqAreaId=5601, mapId=816, left=-3428.00, right=-5062.00, top=-5599.00, bottom=-6672.00, width=1634.00, height=1073.00 }, -- DragonmawRetreat
[693] = { pfqAreaId=5628, mapId=818, left=-3365.00, right=-5345.00, top=-5424.00, bottom=-6774.00, width=1980.00, height=1350.00 }, -- StormwroughtRuins
[694] = { pfqAreaId=0, mapId=0, left=0.00, right=0.00, top=0.00, bottom=0.00, width=0.00, height=0.00 }, -- World
[696] = { pfqAreaId=5640, mapId=819, left=-2991.00, right=-4416.00, top=-7195.00, bottom=-8157.00, width=1425.00, height=962.00 }, -- Timbermaw
[698] = { pfqAreaId=5641, mapId=820, left=-3184.00, right=-4418.00, top=-7163.00, bottom=-7992.00, width=1234.00, height=829.00 }, -- WindhornCanyon
[699] = { pfqAreaId=5642, mapId=1, left=-1354.00, right=-9210.00, top=10204.00, bottom=4963.00, width=7856.00, height=5241.00 }, -- Moonwhisper
[700] = { pfqAreaId=1769, mapId=1, left=-1862.00, right=-2723.00, top=7395.00, bottom=6788.00, width=861.00, height=607.00 }, -- TimbermawTunnels
[701] = { pfqAreaId=5640, mapId=1, left=-4646.00, right=-5993.00, top=5175.00, bottom=4204.00, width=1347.00, height=971.00 }, -- TimbermawEntrance
[702] = { pfqAreaId=5722, mapId=821, left=2105.00, right=1058.00, top=2522.00, bottom=1828.00, width=1047.00, height=694.00 }, -- ThornGorge
[704] = { pfqAreaId=5734, mapId=822, left=-3157.00, right=-4168.00, top=-7453.00, bottom=-8106.00, width=1011.00, height=653.00 }, -- FrostmaneHollow
[705] = { pfqAreaId=5731, mapId=1, left=-3231.00, right=-3671.00, top=-5053.00, bottom=-5332.00, width=440.00, height=279.00 }, -- WindhornCanyonEntrance
}
+5
View File
@@ -56,6 +56,11 @@ local function MTH_DS_ApplyCapturedProjectileQualities()
[18042] = 2,
[19316] = 2,
[19317] = 2,
[42198] = 2,
[42199] = 2,
[42200] = 2,
[42201] = 3,
[42202] = 3,
}
for itemId, quality in pairs(capturedQualityByItemId) do
+7 -1
View File
@@ -1,7 +1,6 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\FrameXML\UI.xsd">
<Include file="..\api\constants.lua"/>
<Include file="..\api\debug-frame.lua"/>
<Include file="..\api\minimap-button.lua"/>
<Include file="..\api\AutoStrip.xml"/>
<Include file="..\api\AutoStrip.lua"/>
@@ -17,6 +16,9 @@
<Include file="..\api\options-autobuy.lua"/>
<Include file="..\api\options-autoquest.lua"/>
<Include file="..\api\options-icu.lua"/>
<Include file="..\api\options-expammo.lua"/>
<Include file="..\api\profile.lua"/>
<Include file="..\api\options-profiles.lua"/>
<Include file="..\api\options-shell.lua"/>
<Include file="..\api\options-zbuttons.lua"/>
<Include file="..\api\options-feedomatic.lua"/>
@@ -29,7 +31,9 @@
<Include file="..\api\hunterbook-tab-npcfinder.lua"/>
<Include file="..\api\hunterbook-tab-stable.lua"/>
<Include file="..\api\hunterbook-tab-pethistory.lua"/>
<Include file="..\api\hunterbook-model-viewer.lua"/>
<Include file="..\api\map-markers.lua"/>
<Include file="..\api\debug-frame.lua"/>
<Include file="..\api\core-framework.lua"/>
<Include file="..\api\core-scan-stablemaster.lua"/>
<Include file="..\api\pets-service-lifecycle.lua"/>
@@ -38,6 +42,8 @@
<Include file="..\api\pets-service-stablescan.lua"/>
<Include file="..\api\core-scan-beasttraining.lua"/>
<Include file="..\api\core-scan-spellbookpet.lua"/>
<Include file="..\api\beastlore-scan.lua"/>
<Include file="..\api\core-scan-huntertalents.lua"/>
<Include file="..\api\core.lua"/>
<Include file="..\api\version-check.lua"/>
<Include file="..\api\localization.lua"/>
+3
View File
@@ -26,4 +26,7 @@ MetaHunt Module Loader
<!-- ICU Module - ENABLED (runtime default: disabled) -->
<Include file="..\modules\icu\loader.xml"/>
<!-- Experimental Ammunition Module - ENABLED -->
<Include file="..\modules\expammo\loader.xml"/>
</Ui>
+5
View File
@@ -2,6 +2,11 @@ MTH_LocaleData = MTH_LocaleData or {}
MTH_LocaleData.spells = MTH_LocaleData.spells or {}
MTH_LocaleData.spells.deDE = {
["Aimed Shot"] = "Gezielter Schuss",
["Kill Command"] = "Todesbefehl",
["Lock and Load"] = "Lock and Load",
["Scent of Blood"] = "Blutgeruch",
["Aspect of the Viper"] = "Aspekt der Viper",
["Banish"] = "Verbannen",
["Bestial Wrath"] = "Zorn des Wildtiers",
["Concussive Shot"] = "Erschütternder Schuss",
+5
View File
@@ -2,6 +2,11 @@ MTH_LocaleData = MTH_LocaleData or {}
MTH_LocaleData.spells = MTH_LocaleData.spells or {}
MTH_LocaleData.spells.enUS = {
["Aimed Shot"] = "Aimed Shot",
["Kill Command"] = "Kill Command",
["Lock and Load"] = "Lock and Load",
["Scent of Blood"] = "Scent of Blood",
["Aspect of the Viper"] = "Aspect of the Viper",
["Banish"] = "Banish",
["Bestial Wrath"] = "Bestial Wrath",
["Concussive Shot"] = "Concussive Shot",
+5
View File
@@ -2,6 +2,11 @@ MTH_LocaleData = MTH_LocaleData or {}
MTH_LocaleData.spells = MTH_LocaleData.spells or {}
MTH_LocaleData.spells.esES = {
["Aimed Shot"] = "Aimed Shot",
["Aspect of the Viper"] = "Aspect of the Viper",
["Kill Command"] = "Kill Command",
["Lock and Load"] = "Lock and Load",
["Scent of Blood"] = "Scent of Blood",
["Banish"] = "Banish",
["Bestial Wrath"] = "Bestial Wrath",
["Concussive Shot"] = "Concussive Shot",
+5
View File
@@ -2,6 +2,11 @@ MTH_LocaleData = MTH_LocaleData or {}
MTH_LocaleData.spells = MTH_LocaleData.spells or {}
MTH_LocaleData.spells.ptBR = {
["Aimed Shot"] = "Aimed Shot",
["Aspect of the Viper"] = "Aspect of the Viper",
["Kill Command"] = "Kill Command",
["Lock and Load"] = "Lock and Load",
["Scent of Blood"] = "Scent of Blood",
["Banish"] = "Banish",
["Bestial Wrath"] = "Bestial Wrath",
["Concussive Shot"] = "Concussive Shot",
+5
View File
@@ -2,6 +2,11 @@ MTH_LocaleData = MTH_LocaleData or {}
MTH_LocaleData.spells = MTH_LocaleData.spells or {}
MTH_LocaleData.spells.ruRU = {
["Aimed Shot"] = "Прицельный выстрел",
["Aspect of the Viper"] = "Аспект гадюки",
["Kill Command"] = "Приказ убивать",
["Lock and Load"] = "Lock and Load",
["Scent of Blood"] = "Запах крови",
["Banish"] = "Изгнание",
["Bestial Wrath"] = "Звериный гнев",
["Concussive Shot"] = "Контузящий выстрел",
+5
View File
@@ -2,6 +2,11 @@ MTH_LocaleData = MTH_LocaleData or {}
MTH_LocaleData.spells = MTH_LocaleData.spells or {}
MTH_LocaleData.spells.zhCN = {
["Aimed Shot"] = "瞄准射击",
["Aspect of the Viper"] = "毒蛇图腾",
["Kill Command"] = "猎杀命令",
["Lock and Load"] = "Lock and Load",
["Scent of Blood"] = "血腥气息",
["Banish"] = "放逐术",
["Bestial Wrath"] = "狂野怒火",
["Concussive Shot"] = "震荡射击",
-25
View File
@@ -9,9 +9,6 @@ local Engine = MTH_AutoBuyEngine
local AB_AMMO_BY_NAME = nil
local AB_FOOD_DIET_BY_ITEM_ID = nil
local function AB_Trace(message)
return
end
local function AB_ParseItemIdFromLink(link)
if type(link) ~= "string" then
@@ -1180,7 +1177,6 @@ end
function Engine:ExecutePlan(plan)
if type(plan) ~= "table" or type(plan.actions) ~= "table" then
AB_Trace("ExecutePlan skipped: invalid plan table")
return 0, 0
end
local executedActions = 0
@@ -1189,10 +1185,6 @@ function Engine:ExecutePlan(plan)
if type(action) == "table" and action.index then
local stacksToBuy = math.floor(tonumber(action.quantityStacks) or 0)
if stacksToBuy > 0 then
AB_Trace("ExecutePlan action index=" .. tostring(action.index)
.. " itemId=" .. tostring(action.itemId)
.. " stacks=" .. tostring(stacksToBuy)
.. " reason=" .. tostring(plan.reason or ""))
for i = 1, stacksToBuy do
BuyMerchantItem(action.index)
end
@@ -1201,7 +1193,6 @@ function Engine:ExecutePlan(plan)
end
end
end
AB_Trace("ExecutePlan done actions=" .. tostring(executedActions) .. " stacks=" .. tostring(requestedStacks) .. " reason=" .. tostring(plan.reason or ""))
return executedActions, requestedStacks
end
@@ -1264,42 +1255,30 @@ local function AB_AnnouncePurchases(actions)
end
function Engine:OnMerchantEvent(eventName)
AB_Trace("OnMerchantEvent start event=" .. tostring(eventName)
.. " sessionExecuted=" .. tostring(self._merchantSessionExecuted and true or false)
.. " engineEnabled=" .. tostring(self:IsEnabled() and true or false))
if eventName == "MERCHANT_SHOW" then
self._merchantSessionExecuted = false
AB_Trace("MERCHANT_SHOW resets session execution flag")
elseif eventName == "MERCHANT_UPDATE" and self._merchantSessionExecuted then
AB_Trace("MERCHANT_UPDATE ignored: already executed this merchant session")
return
end
if not self:IsEnabled() then
AB_Trace("event ignored: autobuy module disabled")
return
end
local snapshot = self:ScanMerchant()
AB_Trace("snapshot rows=" .. tostring(snapshot and snapshot.count or 0))
local foodPlan = self:BuildFoodPlan(snapshot)
local ammoPlan = self:BuildAmmoPlan(snapshot)
AB_Trace("foodPlan reason=" .. tostring(foodPlan.reason) .. " actions=" .. tostring(table.getn(foodPlan.actions or {})))
for i = 1, table.getn(foodPlan.debugLines or {}) do
AB_Trace("food#" .. tostring(i) .. " " .. tostring(foodPlan.debugLines[i]))
end
AB_Trace("ammoPlan reason=" .. tostring(ammoPlan.reason) .. " actions=" .. tostring(table.getn(ammoPlan.actions or {})))
for i = 1, table.getn(ammoPlan.debugLines or {}) do
AB_Trace("ammo#" .. tostring(i) .. " " .. tostring(ammoPlan.debugLines[i]))
end
local totalRequestedStacks = 0
if table.getn(foodPlan.actions or {}) > 0 then
local _, requestedStacks = self:ExecutePlan(foodPlan)
totalRequestedStacks = totalRequestedStacks + requestedStacks
AB_Trace("food plan executed requestedStacks=" .. tostring(requestedStacks))
if requestedStacks > 0 then
AB_AnnouncePurchases(foodPlan.actions)
end
@@ -1308,19 +1287,15 @@ function Engine:OnMerchantEvent(eventName)
if table.getn(ammoPlan.actions or {}) > 0 then
local _, requestedStacks = self:ExecutePlan(ammoPlan)
totalRequestedStacks = totalRequestedStacks + requestedStacks
AB_Trace("ammo plan executed requestedStacks=" .. tostring(requestedStacks))
if requestedStacks > 0 then
local moved = AB_MoveAmmoStacksToSpecialBags(300)
AB_Trace("ammo restack moved=" .. tostring(moved))
AB_AnnouncePurchases(ammoPlan.actions)
end
end
if totalRequestedStacks > 0 then
self._merchantSessionExecuted = true
AB_Trace("session marked executed requestedStacks=" .. tostring(totalRequestedStacks))
else
AB_Trace("no purchases requested in this event")
end
end
+2 -30
View File
@@ -6,7 +6,7 @@
local MTH_AutoBuy = {
name = "autobuy",
enabled = false,
version = "1.2.0",
version = "1.4.1",
events = {
"VARIABLES_LOADED",
"MERCHANT_SHOW",
@@ -24,9 +24,6 @@ end
local AB_Engine
local function AB_Trace(message)
return
end
local function AB_EnsureBridgeFrame(moduleRef)
if MTH_AutoBuy._bridgeFrame then
@@ -46,15 +43,12 @@ local function AB_EnsureBridgeFrame(moduleRef)
local eventName = event
local engine = AB_Engine()
if not engine then
AB_Trace("bridge event=" .. tostring(eventName) .. " engine missing")
return
end
if eventName == "MERCHANT_SHOW" or eventName == "MERCHANT_UPDATE" then
AB_Trace("bridge " .. tostring(eventName) .. " -> engine")
engine:OnMerchantEvent(eventName)
elseif eventName == "MERCHANT_CLOSED" then
AB_Trace("bridge MERCHANT_CLOSED -> engine")
if engine.OnMerchantClosed then
engine:OnMerchantClosed()
end
@@ -77,10 +71,8 @@ local function AB_SetBridgeActive(active)
frame:RegisterEvent("MERCHANT_UPDATE")
frame:RegisterEvent("MERCHANT_CLOSED")
MTH_AutoBuy._bridgeActive = true
AB_Trace("bridge active=true")
else
MTH_AutoBuy._bridgeActive = false
AB_Trace("bridge active=false (frame missing)")
end
return
end
@@ -91,7 +83,6 @@ local function AB_SetBridgeActive(active)
frame:UnregisterEvent("MERCHANT_CLOSED")
end
MTH_AutoBuy._bridgeActive = false
AB_Trace("bridge active=false")
end
AB_Engine = function()
@@ -102,15 +93,12 @@ function MTH_AutoBuy:init()
local engine = AB_Engine()
if not engine then
AB_Log("init failed: engine missing", "error")
AB_Trace("init failed: engine missing")
return
end
engine:Init()
self.initialized = true
AB_SetBridgeActive(self.enabled and true or false)
AB_Trace("init done")
if self.enabled then
AB_Log("module initialized", "debug")
end
end
@@ -120,53 +108,38 @@ function MTH_AutoBuy:setEnabled(enabled)
end
local engine = AB_Engine()
if not engine then
AB_Trace("setEnabled ignored: engine missing")
return
end
local store = engine:EnsureDefaults()
store.enabled = enabled and true or false
AB_SetBridgeActive(store.enabled and true or false)
AB_Trace("setEnabled store.enabled=" .. tostring(store.enabled))
end
function MTH_AutoBuy:onEvent(event)
local engine = AB_Engine()
if not engine then
AB_Log("onEvent ignored: engine missing, event=" .. tostring(event), "debug")
AB_Trace("engine missing, event=" .. tostring(event))
return
end
if self._bridgeActive and (event == "MERCHANT_SHOW" or event == "MERCHANT_UPDATE" or event == "MERCHANT_CLOSED") then
return
end
AB_Log("onEvent event=" .. tostring(event) .. " module.enabled=" .. tostring(self.enabled), "debug")
if event == "VARIABLES_LOADED" then
engine:Init()
AB_SetBridgeActive(self.enabled and true or false)
AB_Log("onEvent VARIABLES_LOADED -> engine init", "debug")
local store = engine:EnsureDefaults()
AB_Trace("VARIABLES_LOADED module.enabled=" .. tostring(self.enabled)
.. " store.enabled=" .. tostring(store and store.enabled)
.. " projectiles.enabled=" .. tostring(store and store.projectiles and store.projectiles.enabled))
engine:EnsureDefaults()
return
end
if not self.enabled then
AB_Log("onEvent ignored: module disabled", "debug")
if event == "MERCHANT_SHOW" or event == "MERCHANT_UPDATE" then
AB_Trace(tostring(event) .. " ignored: module disabled")
end
return
end
if event == "MERCHANT_SHOW" or event == "MERCHANT_UPDATE" then
AB_Log("onEvent merchant event forwarded: " .. tostring(event), "debug")
AB_Trace("forwarding " .. tostring(event) .. " to engine")
engine:OnMerchantEvent(event)
elseif event == "MERCHANT_CLOSED" then
AB_Trace("forwarding MERCHANT_CLOSED to engine")
if engine.OnMerchantClosed then
engine:OnMerchantClosed()
end
@@ -174,7 +147,6 @@ function MTH_AutoBuy:onEvent(event)
end
function MTH_AutoBuy:cleanup()
AB_Log("module cleanup", "debug")
end
MTH:RegisterModule("autobuy", MTH_AutoBuy)
+1 -1
View File
@@ -6,7 +6,7 @@
local MTH_AutoQuest = {
name = "autoquest",
enabled = false,
version = "1.2.0",
version = "1.4.1",
events = {
"GOSSIP_SHOW",
"QUEST_GREETING",
+16 -4
View File
@@ -28,6 +28,8 @@ 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 } })
engine:AddTimer(engine.SPELL, BS["Scatter Shot"], 4, 1, 0, 0, { cr = "MAROON" })
@@ -38,16 +40,26 @@ function MTH_ChronometerHunterInstallData(engine)
engine:AddTimer(engine.SPELL, BS["Wyvern Sting"], 12, 1, 0, 0, { gr = 2, cr = "MAROON", rc = true })
engine:AddTimer(engine.SPELL, BS["Feed Pet"], 20, 0, 0, 0, { cr = "MAROON" })
engine:AddTimer(engine.EVENT, BS["Explosive Trap Effect"], 20, 1, 0, 1, { gr = 1, cr = "ORANGE", a = 1, xn = BS["Explosive Trap"] })
engine:AddTimer(engine.EVENT, BS["Freezing Trap Effect"], 10, 1, 0, 1, { gr = 1, cr = "CYAN", a = 1, d = { rs = 5, tn = BS["Clever Traps"], tb = 0.15, tp = 1 }, xn = BS["Freezing Trap"] })
engine:AddTimer(engine.EVENT, BS["Frost Trap Aura"], 30, 0, 0, 0, { gr = 1, cr = "CYAN", a = 1, d = { tn = BS["Clever Traps"], tb = 0.15, tp = 1 }, xn = BS["Frost Trap"] })
engine:AddTimer(engine.EVENT, BS["Immolation Trap Effect"], 15, 1, 0, 1, { gr = 1, cr = "ORANGE", a = 1, xn = BS["Immolation Trap"] })
-- Trap effects: no "a=1" here — primed by the SPELL timer's "ea" chain (cast hook sets timer.v
-- and timer.t). Using "a=1" would trigger on any hunter's affliction message since
-- CHAT_MSG_SPELL_PERIODIC_CREATURE_BUFFS never embeds a caster name.
engine:AddTimer(engine.EVENT, BS["Explosive Trap Effect"], 20, 1, 0, 1, { gr = 1, cr = "ORANGE", xn = BS["Explosive Trap"] })
engine:AddTimer(engine.EVENT, BS["Freezing Trap Effect"], 10, 1, 0, 1, { gr = 1, cr = "CYAN", d = { rs = 5, tn = BS["Clever Traps"], tb = 0.15, tp = 1 }, xn = BS["Freezing Trap"] })
engine:AddTimer(engine.EVENT, BS["Frost Trap Aura"], 30, 0, 0, 0, { gr = 1, cr = "CYAN", d = { tn = BS["Clever Traps"], tb = 0.15, tp = 1 }, xn = BS["Frost Trap"] })
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["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" })
engine:AddTimer(engine.EVENT, BS["Quick Shots"], 12, 0, 1, 1, { a = 1, cr = "MAROON", tx = "Interface\\Icons\\Ability_Warrior_InnerRage" })
engine:AddTimer(engine.EVENT, BS["Wing Clip"], 10, 1, 0, 1, { a = 1, cr = "YELLOW" })
-- Experimental Ammunition: target-side debuffs (from consumed ammo state)
-- Poisonous Ammunition consumed: reduces target armor 15s (Nature)
engine:AddTimer(engine.EVENT, BS["Poisonous Ammunition"], 15, 1, 0, 1, { a = 1, cr = "GREEN", tx = "Interface\\Icons\\Ability_PoisonArrow" })
-- Enchanted Ammunition consumed: magic resist debuff 6s (Arcane)
engine:AddTimer(engine.EVENT, BS["Enchanted Ammunition"], 6, 1, 0, 1, { a = 1, cr = "PURPLE", tx = "Interface\\Icons\\Ability_TheBlackArrow" })
engine:AddTimer(engine.EVENT, BS["Scorpid Poison"], 10, 1, 0, 0, { gr = 3, cr = "PURPLE", a = 1, dn = 1 })
engine:AddTimer(engine.EVENT, BS["Devilsaur Fury"], 20, 0, 1, 1, { a = 1, cr = "PURPLE", xn = BS["Devilsaur Eye"], tx = "Interface\\Icons\\Ability_Hunter_Pet_Raptor" })
engine:AddTimer(engine.EVENT, BS["Restless Strength"], 20, 0, 1, 1, { a = 1, cr = "PURPLE", xn = BS["Zandalarian Hero Medallion"], tx = "Interface\\Icons\\INV_Jewelry_Necklace_13" })
+43 -48
View File
@@ -59,18 +59,7 @@ 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"
local PARSER_OWNER = "MTHChronometerHunter"
local MTH_CHRON_TRACE_ENABLED = false
local function MTH_CHRON_Trace(msg)
if not MTH_CHRON_TRACE_ENABLED then
return
end
if MTH and MTH.Print then
MTH:Print("[CHRONTRACE] " .. tostring(msg), "debug")
elseif DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.AddMessage then
DEFAULT_CHAT_FRAME:AddMessage("[CHRONTRACE] " .. tostring(msg))
end
end
local function MTH_CHRON_FormatBarPosition(profile)
local bp = profile and profile.barposition
@@ -195,13 +184,11 @@ end
local function ensureProfile()
if not MTH or not MTH.GetModuleCharSavedVariables then
MTH_CHRON_Trace("ensureProfile fallback defaults (no MTH store)")
return shallowCopy(DEFAULTS)
end
local store = MTH:GetModuleCharSavedVariables("chronometer")
if type(store) ~= "table" then
MTH_CHRON_Trace("ensureProfile fallback defaults (invalid store)")
return shallowCopy(DEFAULTS)
end
if type(store.profile) ~= "table"
@@ -211,10 +198,8 @@ local function ensureProfile()
local legacyChar = MTH_CharSavedVariables.chronometer
if type(legacyChar.profile) == "table" then
store.profile = deepCopyValue(legacyChar.profile)
MTH_CHRON_Trace("ensureProfile migrated legacy MTH_CharSavedVariables.chronometer.profile")
elseif type(legacyChar.barposition) == "table" then
store.profile = { barposition = deepCopyValue(legacyChar.barposition) }
MTH_CHRON_Trace("ensureProfile migrated legacy MTH_CharSavedVariables.chronometer.barposition")
end
end
if type(store.profile) ~= "table"
@@ -224,17 +209,14 @@ local function ensureProfile()
local legacyAccount = MTH_SavedVariables.chronometer
if type(legacyAccount.profile) == "table" then
store.profile = deepCopyValue(legacyAccount.profile)
MTH_CHRON_Trace("ensureProfile migrated legacy MTH_SavedVariables.chronometer.profile")
elseif type(legacyAccount.barposition) == "table" then
store.profile = { barposition = deepCopyValue(legacyAccount.barposition) }
MTH_CHRON_Trace("ensureProfile migrated legacy MTH_SavedVariables.chronometer.barposition")
end
end
if type(store.profile) ~= "table" and MTH.GetModuleSavedVariables then
local accountStore = MTH:GetModuleSavedVariables("chronometer")
if type(accountStore) == "table" and type(accountStore.profile) == "table" then
store.profile = deepCopyValue(accountStore.profile)
MTH_CHRON_Trace("ensureProfile copied account profile into char store")
end
end
if type(store.profile) ~= "table" then
@@ -242,17 +224,14 @@ local function ensureProfile()
end
if type(store.profile.barposition) ~= "table" and type(store.barposition) == "table" then
store.profile.barposition = deepCopyValue(store.barposition)
MTH_CHRON_Trace("ensureProfile migrated legacy char barposition -> profile.barposition")
end
if type(store.profile.barposition) ~= "table" and MTH.GetModuleSavedVariables then
local accountStore = MTH:GetModuleSavedVariables("chronometer")
if type(accountStore) == "table" then
if type(accountStore.barposition) == "table" then
store.profile.barposition = deepCopyValue(accountStore.barposition)
MTH_CHRON_Trace("ensureProfile migrated legacy account barposition -> profile.barposition")
elseif type(accountStore.profile) == "table" and type(accountStore.profile.barposition) == "table" then
store.profile.barposition = deepCopyValue(accountStore.profile.barposition)
MTH_CHRON_Trace("ensureProfile copied account profile.barposition -> char profile.barposition")
end
end
end
@@ -282,7 +261,6 @@ local function ensureProfile()
profile.disabledSpells.HUNTER = {}
end
MTH_CHRON_Trace("ensureProfile result barposition=" .. MTH_CHRON_FormatBarPosition(profile))
return profile
end
@@ -309,8 +287,6 @@ local function MTH_CHRON_PersistAnchorPosition(self, reason)
self.profile.barposition.relativePoint = relPoint
self.profile.barposition.x = x
self.profile.barposition.y = y
MTH_CHRON_Trace("persist-anchor reason=" .. tostring(reason or "")
.. " profile=" .. MTH_CHRON_FormatBarPosition(self.profile))
return true
end
@@ -330,8 +306,6 @@ local function MTH_CHRON_RestoreAnchorFromProfile(self, reason)
local ppoint = tostring(bp.point or "")
local prelpoint = tostring(bp.relativePoint or "")
if not (px and py and ppoint ~= "") then
MTH_CHRON_Trace("restore-anchor skipped reason=" .. tostring(reason or "")
.. " profile=" .. MTH_CHRON_FormatBarPosition(self.profile))
return false
end
if prelpoint == "" then
@@ -339,8 +313,6 @@ local function MTH_CHRON_RestoreAnchorFromProfile(self, reason)
end
self.anchor:ClearAllPoints()
self.anchor:SetPoint(ppoint, UIParent, prelpoint, px, py)
MTH_CHRON_Trace("restore-anchor applied reason=" .. tostring(reason or "")
.. " frame=" .. MTH_CHRON_DescribePoint(self.anchor))
return true
end
@@ -527,7 +499,6 @@ function Chronometer:MTH_Initialize()
end
self.profile = ensureProfile()
MTH_CHRON_Trace("MTH_Initialize profile=" .. MTH_CHRON_FormatBarPosition(self.profile))
self.parser = ParserLib:GetInstance("1.1")
self.COLOR_MAP = {
@@ -538,7 +509,6 @@ function Chronometer:MTH_Initialize()
}
self.anchor = self:CreateAnchor("MetaHunt Chronometer", 0, 1, 0)
MTH_CHRON_Trace("MTH_Initialize anchor-created point=" .. MTH_CHRON_DescribePoint(self.anchor))
self:RegisterCandyBarGroup(BAR_GROUP)
self:SetCandyBarGroupPoint(BAR_GROUP, "TOP", self.anchor, "BOTTOM", 0, 0)
@@ -547,19 +517,16 @@ end
function Chronometer:MTH_Enable()
self._mth_explicitEnable = true
MTH_CHRON_Trace("MTH_Enable called")
self:MTH_Initialize()
self:OnEnable()
self._mth_explicitEnable = nil
end
function Chronometer:MTH_Disable()
MTH_CHRON_Trace("MTH_Disable called")
self:OnDisable()
end
function Chronometer:OnEnable()
MTH_CHRON_Trace("OnEnable begin enabled=" .. tostring(self._mth_enabled and true or false))
if self._mth_enabled then
return
end
@@ -589,11 +556,9 @@ function Chronometer:OnEnable()
self._mth_enabled = true
self.profile = ensureProfile()
MTH_CHRON_Trace("OnEnable profile-after-ensure=" .. MTH_CHRON_FormatBarPosition(self.profile))
self:SetCandyBarGroupGrowth(BAR_GROUP, self.profile.growup and true or false)
self:SetCandyBarGroupVerticalSpacing(BAR_GROUP, self.profile.spacing or 0)
if self.anchor then
MTH_CHRON_Trace("OnEnable anchor-current-point=" .. MTH_CHRON_DescribePoint(self.anchor))
end
MTH_CHRON_RestoreAnchorFromProfile(self, "OnEnable")
@@ -615,14 +580,18 @@ function Chronometer:OnEnable()
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_AURA_GONE_SELF", function(event, info) self:SPELL_FADE(event, info) end)
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_AURA_GONE_OTHER", function(event, info) self:SPELL_FADE(event, info) end)
-- Registered events:
-- SELF_BUFFS / SELF_DAMAGE → our own procs & self-damage (source == ParserLib_SELF confirmed)
-- CREATURE_DAMAGE → DOT ticks on creatures ("from your X" embeds source; filtered in SPELL_PERIODIC)
-- CREATURE_BUFFS → debuff APPLICATION on creatures ("X is afflicted by Y"; no source embedded).
-- Needed for non-ticking debuffs: Poisonous/Enchanted Ammunition, Piercing Shot.
-- Foreign-bar risk is mitigated in SPELL_PERIODIC by requiring victim == target
-- when source is nil and no confirmed cast record exists.
-- FRIENDLYPLAYER_*, HOSTILEPLAYER_* are other players' events → intentionally NOT registered.
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE", function(event, info) self:SPELL_PERIODIC(event, info) end)
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_PERIODIC_CREATURE_BUFFS", function(event, info) self:SPELL_PERIODIC(event, info) end)
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_BUFFS", function(event, info) self:SPELL_PERIODIC(event, info) end)
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE", function(event, info) self:SPELL_PERIODIC(event, info) end)
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS", function(event, info) self:SPELL_PERIODIC(event, info) end)
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE", function(event, info) self:SPELL_PERIODIC(event, info) end)
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS", function(event, info) self:SPELL_PERIODIC(event, info) end)
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_DAMAGE", function(event, info) self:SPELL_PERIODIC(event, info) end)
local enableRoM = false
for _, timer in pairs(self.timers[self.SPELL] or {}) do
@@ -701,9 +670,7 @@ function Chronometer:OnDisable()
if not self._mth_enabled then
return
end
MTH_CHRON_Trace("OnDisable begin profile=" .. MTH_CHRON_FormatBarPosition(self.profile))
if self.anchor then
MTH_CHRON_Trace("OnDisable anchor-point=" .. MTH_CHRON_DescribePoint(self.anchor))
end
MTH_CHRON_PersistAnchorPosition(self, "OnDisable")
self._mth_enabled = false
@@ -1209,7 +1176,6 @@ function Chronometer:CreateAnchor(text, r, g, b)
local py = tonumber(self.profile and self.profile.barposition and self.profile.barposition.y)
local ppoint = tostring(self.profile and self.profile.barposition and self.profile.barposition.point or "")
local prelpoint = tostring(self.profile and self.profile.barposition and self.profile.barposition.relativePoint or "")
MTH_CHRON_Trace("CreateAnchor saved-in profile=" .. MTH_CHRON_FormatBarPosition(self.profile))
if px ~= nil and py ~= nil then
if ppoint == "" then
ppoint = "TOPLEFT"
@@ -1219,17 +1185,14 @@ function Chronometer:CreateAnchor(text, r, g, b)
end
frame:ClearAllPoints()
frame:SetPoint(ppoint, UIParent, prelpoint, px, py)
MTH_CHRON_Trace("CreateAnchor restored point=" .. MTH_CHRON_DescribePoint(frame))
else
frame:SetPoint("CENTER", UIParent, "CENTER", 0, 50)
MTH_CHRON_Trace("CreateAnchor default point=" .. MTH_CHRON_DescribePoint(frame))
end
frame:SetScript("OnDragStart", function()
if not this or not this.StartMoving then
return
end
MTH_CHRON_Trace("anchor drag-start point=" .. MTH_CHRON_DescribePoint(this))
this:StartMoving()
end)
frame:SetScript("OnDragStop", function()
@@ -1258,8 +1221,6 @@ function Chronometer:CreateAnchor(text, r, g, b)
this.owner.profile.barposition.relativePoint = point or "TOPLEFT"
this.owner.profile.barposition.x = x
this.owner.profile.barposition.y = y
MTH_CHRON_Trace("anchor drag-stop saved profile=" .. MTH_CHRON_FormatBarPosition(this.owner.profile)
.. " frame=" .. MTH_CHRON_DescribePoint(this))
end
end)
@@ -1305,9 +1266,11 @@ function Chronometer:SPELL_FADE(event, info)
if not self.profile.fadeonfade then return end
if info.skill == BS["Banish"] then return end
if info.type == "fade" then
-- Only act when the fade is on the player or on their target, not on other players
if info.victim == ParserLib_SELF then
return self:KillBar(info.skill)
end
if info.source and info.source ~= ParserLib_SELF then return end
return self:KillBar(info.skill, info.victim)
end
end
@@ -1315,8 +1278,20 @@ end
function Chronometer:SPELL_PERIODIC(event, info)
local aura, rank, unit, isgain
-- Primary filter: explicit non-self source → always ignore (other player's confirmed event)
if info.source and info.source ~= ParserLib_SELF then return end
-- Secondary filter: creature/friendly periodic events do NOT embed a caster name,
-- so info.source is nil even for other players' effects. Track whether this event
-- was provably ours (SELF_BUFFS / SELF_DAMAGE events give source == ParserLib_SELF).
local sourceIsConfirmedSelf = (info.source == ParserLib_SELF)
if info.type == "buff" then
isgain = 1
-- Buff gained by someone other than self with no source confirmation → another player's proc
if not sourceIsConfirmedSelf and info.victim ~= nil and info.victim ~= ParserLib_SELF then
return
end
elseif info.type == "debuff" then
isgain = nil
elseif not info.isDOT then
@@ -1344,7 +1319,27 @@ function Chronometer:SPELL_PERIODIC(event, info)
if timer.k.t then
if not unit then unit = UnitName("player") end
if timer.k.s then
if timer.t and timer.t ~= unit then return end
-- Selforselect: four-tier confirmation.
-- 1. Confirmed cast record (timer.t set by CastSpell hook): require target match.
-- 2. Confirmed self source (SELF_BUFFS / SELF_DAMAGE events, source == SELF): accept.
-- 3. Cast-window confirmed (timer.v fresh, timer.t nil): untargeted spells like traps
-- where the caster is known but the trigger victim cannot be predicted in advance.
-- Accept unconditionally — the mob that walked into the trap may not be our target.
-- 4. Source unconfirmed AND no cast record (CREATURE_BUFFS affliction messages never
-- embed a caster name): fall back to target-check. This lets non-ticking debuffs
-- (Poisonous Ammo, Enchanted Ammo, Piercing Shot…) show when applied to our current
-- target, while blocking the same debuff on a mob we are NOT targeting (i.e. another
-- player's debuff on a different dummy).
if timer.t then
if timer.t ~= unit then return end
elseif sourceIsConfirmedSelf then
-- source confirmed → accept unconditionally
elseif timer.v and timer.v > GetTime() then
-- cast-window confirmed, timer.t nil → untargeted cast (e.g. trap placed on ground)
-- victim unknown at cast time; accept whatever mob triggered it
elseif not UnitExists("target") or unit ~= UnitName("target") then
return
end
else
if not UnitExists("target") or unit ~= UnitName("target") then return end
end
+1 -19
View File
@@ -1,28 +1,15 @@
local MTH_ChronometerModule = {
name = "chronometer",
enabled = true,
version = "1.2.0",
version = "1.4.1",
events = {},
initialized = false,
}
local MTH_CHRON_MODULE_TRACE_ENABLED = false
local function MTH_CHRON_ModuleTrace(msg)
if not MTH_CHRON_MODULE_TRACE_ENABLED then
return
end
if MTH and MTH.Print then
MTH:Print("[CHRONTRACE] " .. tostring(msg), "debug")
elseif DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.AddMessage then
DEFAULT_CHAT_FRAME:AddMessage("[CHRONTRACE] " .. tostring(msg))
end
end
local function MTH_CHRON_ApplyRuntimeEnabledState(enabled)
MTH_CHRON_ModuleTrace("module apply runtime enabled=" .. tostring(enabled and true or false))
if not MTH_ChronometerHunter then
MTH_CHRON_ModuleTrace("module apply runtime skipped (engine unavailable)")
return
end
if enabled then
@@ -37,18 +24,14 @@ local function MTH_CHRON_ApplyRuntimeEnabledState(enabled)
end
function MTH_ChronometerModule:init()
MTH_CHRON_ModuleTrace("module init begin enabled=" .. tostring(self.enabled and true or false))
if MTH_ChronometerHunter and MTH_ChronometerHunter.MTH_Initialize then
MTH_ChronometerHunter:MTH_Initialize()
end
MTH_CHRON_ApplyRuntimeEnabledState(self.enabled and true or false)
self.initialized = true
MTH_CHRON_ModuleTrace("module init end initialized=true")
end
function MTH_ChronometerModule:setEnabled(enabled)
MTH_CHRON_ModuleTrace("module setEnabled enabled=" .. tostring(enabled and true or false)
.. " initialized=" .. tostring(self.initialized and true or false))
if not self.initialized then
self:init()
end
@@ -56,7 +39,6 @@ function MTH_ChronometerModule:setEnabled(enabled)
end
function MTH_ChronometerModule:cleanup()
MTH_CHRON_ModuleTrace("module cleanup")
if MTH_ChronometerHunter and MTH_ChronometerHunter.MTH_Disable then
MTH_ChronometerHunter:MTH_Disable()
end
File diff suppressed because it is too large Load Diff
+6
View File
@@ -0,0 +1,6 @@
<!--
MetaHunt: Experimental Ammunition Module Loader
-->
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<Include file="engine.lua"/>
</Ui>
+71 -252
View File
@@ -56,12 +56,7 @@ FOM_LastChoiceReason = nil;
FOM_RealmPlayer = nil;
FOM_LastPetName = nil;
FOM_LastFeedAttempt = nil;
FOM_TRACE_ENABLED = false;
local FOM_CORE_ATTEMPT_TRACKING_ENABLED = false;
local FOM_PERF_TRACE_ENABLED = false;
local FOM_PERF_TRACE_HISTORY_LIMIT = 80;
local FOM_PERF_TRACE_SPIKE_MS = 12;
local FOM_PERF_TRACE_HISTORY = {};
local FOM_CORE_ATTEMPT_TRACKING_ENABLED = true;
local FOM_LAST_SETUP_AT = 0;
local FOM_LAST_SETUP_PET = nil;
local FOM_LAST_QUEST_SCAN_AT = 0;
@@ -125,79 +120,10 @@ FOM_FamilyToLegacyMap = {
["Wolves"] = WOLF,
};
local function FOM_Trace(message)
return;
end
local function FOM_PerfNowMs()
if (type(debugprofilestop) == "function") then
local raw = tonumber(debugprofilestop()) or 0;
return raw / 1000;
end
if (type(GetTime) == "function") then
return (tonumber(GetTime()) or 0) * 1000;
end
return 0;
end
local function FOM_PerfOut(message, forceShow)
local text = tostring(message or "");
if (text == "") then
return;
end
if (type(MTH_DebugFrame) == "table" and type(MTH_DebugFrame.AddInfo) == "function") then
if (forceShow and type(MTH_DebugFrame.Show) == "function") then
MTH_DebugFrame:Show();
end
MTH_DebugFrame:AddInfo(text);
return;
end
if (MTH and type(MTH.Print) == "function") then
MTH:Print(text, "debug");
return;
end
if (type(GFWUtils) == "table" and type(GFWUtils.Print) == "function") then
GFWUtils.Print(text);
end
end
local function FOM_PerfRecord(label, elapsedMs, detail, forcePrint)
if (not FOM_PERF_TRACE_ENABLED) then
return;
end
local numericElapsed = tonumber(elapsedMs) or 0;
if (numericElapsed > 10000) then
numericElapsed = numericElapsed / 1000;
end
local row = {
ts = time and time() or 0,
label = tostring(label or "unknown"),
ms = numericElapsed,
detail = tostring(detail or ""),
};
table.insert(FOM_PERF_TRACE_HISTORY, row);
if (table.getn(FOM_PERF_TRACE_HISTORY) > FOM_PERF_TRACE_HISTORY_LIMIT) then
table.remove(FOM_PERF_TRACE_HISTORY, 1);
end
return;
end
local function FOM_PerfDump()
if (table.getn(FOM_PERF_TRACE_HISTORY) <= 0) then
FOM_PerfOut("[FOM PERF] no samples.", true);
return;
end
FOM_PerfOut("[FOM PERF] recent samples: " .. tostring(table.getn(FOM_PERF_TRACE_HISTORY)), true);
for i = 1, table.getn(FOM_PERF_TRACE_HISTORY) do
local row = FOM_PERF_TRACE_HISTORY[i];
if (type(row) == "table") then
FOM_PerfOut("[FOM PERF] #" .. tostring(i)
.. " " .. tostring(row.label)
.. " " .. string.format("%.2f", tonumber(row.ms) or 0) .. "ms"
.. (tostring(row.detail or "") ~= "" and (" | " .. tostring(row.detail)) or ""), false);
end
end
end
local function FOM_GetServerProfile()
return FOM_ServerProfiles[FOM_SERVER_PROFILE] or {};
@@ -288,9 +214,6 @@ local function FOM_RegisterNoBuffQuarantine(itemID, petInfo)
end
row.lastObservedAt = time and time() or 0;
row.reason = "no-buff";
FOM_Trace("quarantine add family='" .. tostring(familyKey)
.. "' itemId=" .. tostring(numericItem)
.. " minRejectPetLevel=" .. tostring(row.minRejectPetLevel));
end
local function FOM_IsQuarantined(itemID, petLevel, familyKey)
@@ -346,29 +269,6 @@ local function FOM_IsItemPetLevelCompatible(itemID, petLevel)
return true;
end
function FOM_CommandTrace(mode)
local arg = string.lower(tostring(mode or ""));
if (arg == "perf on") then
FOM_PERF_TRACE_ENABLED = true;
FOM_PerfOut("[FOM PERF] enabled.", true);
return;
end
if (arg == "perf off") then
FOM_PERF_TRACE_ENABLED = false;
FOM_PerfOut("[FOM PERF] disabled.", true);
return;
end
if (arg == "perf clear") then
FOM_PERF_TRACE_HISTORY = {};
FOM_PerfOut("[FOM PERF] cleared.", true);
return;
end
if (arg == "perf dump") then
FOM_PerfDump();
return;
end
FOM_PerfOut("Usage: /fomtrace perf on|off|dump|clear", true);
end
local function FOM_CoreFeedReady()
if (not FOM_CORE_ATTEMPT_TRACKING_ENABLED) then
@@ -454,7 +354,7 @@ function FOM_BuildFamilyAliasMap()
FOM_AddFamilyAlias("Carrion Bird", "Carrion Birds");
FOM_AddFamilyAlias("Wind Serpent", "Wind Serpents");
FOM_AddFamilyAlias("Serpent", "Serpents (Cobra)");
FOM_AddFamilyAlias("Serpent", "Serpents");
end
function FOM_GetCanonicalPetFamily(rawFamily)
@@ -639,7 +539,6 @@ local function FOM_PruneUnknownFoodNoise(force)
if (type(time) == "function") then
store.updatedAt = time();
end
FOM_Trace("pruned unknown vendor-only non-food rows=" .. tostring(removed));
end
return removed;
@@ -685,7 +584,6 @@ local function FOM_PruneExceptionNoise()
if (removed > 0 and store ~= nil and type(time) == "function") then
store.updatedAt = time();
FOM_Trace("pruned inert exception rows=" .. tostring(removed));
end
return removed;
end
@@ -824,10 +722,6 @@ function FOM_OnLoad()
SLASH_FEEDOMATIC3 = "/feed";
SLASH_FEEDOMATIC4 = "/petfeed"; -- Rauen's PetFeed compatibility
SLASH_FEEDOMATIC5 = "/pf";
SLASH_FOMTRACE1 = "/fomtrace";
SlashCmdList["FOMTRACE"] = function(msg)
FOM_CommandTrace(msg);
end
SlashCmdList["FEEDOMATIC"] = function(msg)
if MTH_IsModuleEnabled and not MTH_IsModuleEnabled("feedomatic", false) then
if DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.AddMessage then
@@ -1032,7 +926,7 @@ function FOM_OnEvent(event, arg1)
end
if (foodID) then
FOM_LastFood = GFWUtils.ItemLink(foodID);
GFWUtils.DebugLog("Manually fed "..FOM_LastFood);
FOM_DebugLog("Manually fed "..FOM_LastFood);
end
end
return;
@@ -1072,8 +966,6 @@ function FOM_OnEvent(event, arg1)
elseif ( event == "UI_ERROR_MESSAGE" ) then
local lowerError = string.lower(tostring(arg1 or ""));
if (FOM_LastFood) then
FOM_Trace("ui error while feeding message='" .. tostring(arg1 or "")
.. "' lower='" .. tostring(lowerError) .. "'")
end
local isLowLevelError = (arg1 and SPELL_FAILED_FOOD_LOWLEVEL and string.find(arg1, SPELL_FAILED_FOOD_LOWLEVEL))
or string.find(lowerError, "low level")
@@ -1083,13 +975,8 @@ function FOM_OnEvent(event, arg1)
or string.find(lowerError, "wrong pet food")
or string.find(lowerError, "doesn't like")
or string.find(lowerError, "does not like")
FOM_Trace("ui error classify lowLevel=" .. tostring(isLowLevelError and true or false)
.. " wrongFood=" .. tostring(isWrongFoodError and true or false)
.. " hasAttempt=" .. tostring(type(FOM_LastFeedAttempt) == "table" and true or false)
.. " lastFood='" .. tostring(FOM_LastFood or "") .. "'")
if (isLowLevelError) then
FOM_Trace("reject reason=low-level")
if (type(FOM_LastFeedAttempt) == "table") then
FOM_CoreRecordReject(FOM_LastFeedAttempt.coreAttemptId, "low-level", arg1);
FOM_CoreFinalizeAttempt(FOM_LastFeedAttempt.coreAttemptId, "rejected", "low-level");
@@ -1107,7 +994,6 @@ function FOM_OnEvent(event, arg1)
end
elseif (isWrongFoodError) then
FOM_Trace("reject reason=wrong-food")
if (type(FOM_LastFeedAttempt) == "table") then
FOM_CoreRecordReject(FOM_LastFeedAttempt.coreAttemptId, "wrong-food", arg1);
FOM_CoreFinalizeAttempt(FOM_LastFeedAttempt.coreAttemptId, "rejected", "wrong-food");
@@ -1116,9 +1002,6 @@ function FOM_OnEvent(event, arg1)
if (FOM_LastFood) then
local alertPetName = MTH_FOM_GetTrackedPetName() or "Your pet";
local itemID = FOM_IDFromLink(FOM_LastFood);
FOM_Trace("learn reject wrongfood pet='" .. tostring(FOM_LastPetName or alertPetName)
.. "' itemId=" .. tostring(itemID)
.. " link='" .. tostring(FOM_LastFood) .. "'")
if ( FOM_Config.Alert == "chat") then
GFWUtils.Print(string.format(FOM_FEEDING_EAT_ANOTHER, alertPetName));
@@ -1278,10 +1161,6 @@ function FOM_OnUpdate(elapsed)
local elapsedSinceFeed = now - startedAt;
if (elapsedSinceFeed >= 0.10 and not FOM_LastFeedAttempt.loggedFirstCheck) then
FOM_LastFeedAttempt.loggedFirstCheck = true;
FOM_Trace("feed outcome check itemId=" .. tostring(FOM_LastFeedAttempt.itemId)
.. " pet='" .. tostring(FOM_LastFeedAttempt.petName or "")
.. "' hasFeedBuff=" .. tostring(FOM_HasFeedEffect() and true or false)
.. " elapsed=" .. string.format("%.2f", elapsedSinceFeed));
end
if (FOM_HasFeedEffect()) then
@@ -1297,10 +1176,8 @@ function FOM_OnUpdate(elapsed)
SendChatMessage(string.format(FOM_FEEDING_FEED, confirmedPetName, confirmedFoodLink).. FOM_RandomEmote(), "EMOTE");
end
FOM_CoreFinalizeAttempt(FOM_LastFeedAttempt.coreAttemptId, "accepted", "accepted");
FOM_Trace("feed outcome accepted-with-buff itemId=" .. tostring(FOM_LastFeedAttempt.itemId)
.. " pet='" .. tostring(FOM_LastFeedAttempt.petName or "")
.. "' elapsed=" .. string.format("%.2f", elapsedSinceFeed));
FOM_LastFeedAttempt = nil;
FOM_LastFood = nil;
elseif (elapsedSinceFeed >= 2.50) then
local failedAttempt = FOM_LastFeedAttempt;
if (FOM_LastFeedAttempt.coreAttemptId and type(MTH_FEED_RecordBuffOutcome) == "function") then
@@ -1316,10 +1193,6 @@ function FOM_OnUpdate(elapsed)
tonumber(currentPetInfo and currentPetInfo.level) or nil,
"no-buff");
end
FOM_Trace("feed outcome no-eating-buff itemId=" .. tostring(FOM_LastFeedAttempt.itemId)
.. " pet='" .. tostring(FOM_LastFeedAttempt.petName or "")
.. "' elapsed=" .. string.format("%.2f", elapsedSinceFeed)
.. " (likely not accepted by pet)");
FOM_LastFood = nil;
FOM_LastFeedAttempt = nil;
@@ -1346,8 +1219,6 @@ function FOM_OnUpdate(elapsed)
excludedCount = excludedCount + 1;
end
end
FOM_Trace("feed retry scheduled after no-buff retry=" .. tostring(retryCount + 1)
.. " excludedCount=" .. tostring(excludedCount));
FOM_Feed(nil, {
retryCount = retryCount + 1,
excludedItemIds = excludedItemIds,
@@ -1374,31 +1245,31 @@ end
function FOM_CanFeed()
local petInfo = MTH_FOM_GetCorePetInfo();
if ( not (petInfo and petInfo.liveExists) ) then
GFWUtils.DebugLog("Can't feed; pet doesn't exist.");
FOM_DebugLog("Can't feed; pet doesn't exist.");
return false;
end
if ( tonumber(petInfo.health) and tonumber(petInfo.health) <= 0 ) then
GFWUtils.DebugLog("Can't feed; pet is dead.");
FOM_DebugLog("Can't feed; pet is dead.");
return false;
end
if ( UnitHealth("player") <= 0 ) then
GFWUtils.DebugLog("Can't feed; I'm dead.");
FOM_DebugLog("Can't feed; I'm dead.");
return false;
end
if ( CastingBarFrameStatusBar:IsVisible() ) then
GFWUtils.DebugLog("Can't feed; casting a spell / tradeksill.");
FOM_DebugLog("Can't feed; casting a spell / tradeksill.");
return false;
end
if ( UnitOnTaxi("player") ) then
GFWUtils.DebugLog("Can't feed; flying.");
FOM_DebugLog("Can't feed; flying.");
return false;
end
if ( FOM_State.InCombat ) or ( PlayerFrame.inCombat ) then
GFWUtils.DebugLog("Can't feed; in combat.");
FOM_DebugLog("Can't feed; in combat.");
return false;
end
if ( LootFrame:IsVisible() ) then
GFWUtils.DebugLog("Shouldn't feed; loot window is open.");
FOM_DebugLog("Shouldn't feed; loot window is open.");
return false;
end
@@ -1419,7 +1290,7 @@ function FOM_CanFeed()
if ( buff ~= nil) then
for _, buffTexture in dontFeedBuffTextures do
if ( buff == buffTexture ) then
GFWUtils.DebugLog("Can't feed; currently, eating, drinking, or feigning death.");
FOM_DebugLog("Can't feed; currently, eating, drinking, or feigning death.");
return false;
end
end
@@ -1432,7 +1303,7 @@ function FOM_CanFeed()
msg = string.lower(msg);
for _, mountName in FOM_MOUNT_NAME_SUBSTRINGS do
if (string.find(msg, mountName)) then
GFWUtils.DebugLog("Can't feed; mounted.");
FOM_DebugLog("Can't feed; mounted.");
return false;
end
end
@@ -1952,6 +1823,7 @@ function FOM_ChatCommandHandler(msg)
if ( cmd == "add" ) then
for _, food in inputFoods do
local foodID = FOM_IDFromLink(food);
FOM_ClearItemBans(foodID);
if ( FOM_AddFood(diet, tonumber(foodID)) ) then
GFWUtils.Print("Added "..food.." to "..GFWUtils.Hilite(capDiet).." list.");
else
@@ -1984,13 +1856,13 @@ end
function FOM_AddFood(diet, food)
if (FOM_Foods[diet] == nil) then
GFWUtils.DebugLog("FOM_Foods[diet] == nil");
FOM_DebugLog("FOM_Foods[diet] == nil");
end
if (FOM_AddedFoods == nil or FOM_AddedFoods[diet] == nil) then
GFWUtils.DebugLog("FOM_AddedFoods == nil or FOM_AddedFoods[diet] == nil");
FOM_DebugLog("FOM_AddedFoods == nil or FOM_AddedFoods[diet] == nil");
end
if (FOM_RemovedFoods == nil or FOM_RemovedFoods[diet] == nil) then
GFWUtils.DebugLog("FOM_RemovedFoods == nil or FOM_RemovedFoods[diet] == nil");
FOM_DebugLog("FOM_RemovedFoods == nil or FOM_RemovedFoods[diet] == nil");
end
if ( GFWTable.IndexOf(FOM_Foods[diet], food) == 0 ) then
if (FOM_AddedFoods == nil) then
@@ -2011,6 +1883,12 @@ function FOM_AddFood(diet, food)
return false;
end
else
-- Item is in the base list; clear it from RemovedFoods if present so it isn't banned
if (FOM_RemovedFoods and FOM_RemovedFoods[diet] and GFWTable.IndexOf(FOM_RemovedFoods[diet], food) ~= 0) then
table.remove( FOM_RemovedFoods[diet], GFWTable.IndexOf(FOM_RemovedFoods[diet], food) );
table.sort( FOM_RemovedFoods[diet] );
return true;
end
return false;
end
@@ -2018,15 +1896,15 @@ end
-- Remove a food from a list
function FOM_RemoveFood(diet, food)
if (FOM_Foods[diet] == nil) then
GFWUtils.DebugLog("FOM_Foods[diet] == nil");
FOM_DebugLog("FOM_Foods[diet] == nil");
end
if (FOM_AddedFoods == nil or FOM_AddedFoods[diet] == nil) then
GFWUtils.DebugLog("FOM_AddedFoods == nil or FOM_AddedFoods[diet] == nil");
FOM_DebugLog("FOM_AddedFoods == nil or FOM_AddedFoods[diet] == nil");
end
if (FOM_RemovedFoods == nil or FOM_RemovedFoods[diet] == nil) then
GFWUtils.DebugLog("FOM_RemovedFoods == nil or FOM_RemovedFoods[diet] == nil");
FOM_DebugLog("FOM_RemovedFoods == nil or FOM_RemovedFoods[diet] == nil");
end
if ( GFWTable.IndexOf(FOM_Foods[diet], food) ~= 0 ) then
if (FOM_RemovedFoods == nil) then
@@ -2057,6 +1935,42 @@ 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)
local numericItem = tonumber(itemID);
if (numericItem == nil) then return; end
-- Clear from RemovedFoods across all diet lists
if (type(FOM_RemovedFoods) == "table") then
for diet, list in FOM_RemovedFoods do
if (type(list) == "table") then
local idx = GFWTable.IndexOf(list, numericItem);
if (idx ~= 0) then
table.remove(list, idx);
end
end
end
end
-- Clear FOM quarantine (all families)
local store = FOM_EnsureQuarantineStore();
if (type(store) == "table" and type(store.byFamily) == "table") then
for _, byItem in store.byFamily do
if (type(byItem) == "table") then
byItem[numericItem] = nil;
end
end
end
-- Clear core exception/block
if (type(MTH_FEED_GetStore) == "function") then
local coreStore = MTH_FEED_GetStore();
if (coreStore and coreStore.exceptions and type(coreStore.exceptions.byItemId) == "table") then
coreStore.exceptions.byItemId[numericItem] = nil;
coreStore.exceptions.byItemId[tostring(numericItem)] = nil;
end
end
-- Invalidate precompute cache so next feed re-scans
FOM_FEED_SCAN_CACHE = nil;
end
function FOM_IsBGActive()
local bgNum = 1;
local status;
@@ -2286,7 +2200,6 @@ local function FOM_TryPickSinglePrecomputedFood(foodList, excludedItemIds, allow
end
function FOM_Feed(aFood, options)
local feedStartMs = FOM_PerfNowMs();
options = options or {};
local retryCount = tonumber(options.retryCount) or 0;
local excludedItemIds = FOM_CloneSet(options.excludedItemIds);
@@ -2295,7 +2208,6 @@ function FOM_Feed(aFood, options)
if MTH_IsModuleEnabled and not MTH_IsModuleEnabled("feedomatic", false) then
if DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.AddMessage then
MTH:Print("[FOM WRAP] FOM_Feed blocked: module disabled", "debug")
end
return false;
end
@@ -2321,19 +2233,9 @@ function FOM_Feed(aFood, options)
-- Assign Variable
local pet = (MTH_FOM_IsValidPetName(petInfo.name) and petInfo.name) or "Your pet";
FOM_Trace("feed request pet='" .. tostring(pet)
.. "' level=" .. tostring(petInfo.level)
.. " happiness=" .. tostring(petInfo.happiness)
.. " manualFood='" .. tostring(aFood or "") .. "'"
.. " retry=" .. tostring(retryCount)
.. " excluded=" .. tostring(FOM_CountSetEntries(excludedItemIds)))
local checkSetupStartMs = FOM_PerfNowMs();
FOM_CheckSetup();
FOM_PerfRecord("FOM_CheckSetup", FOM_PerfNowMs() - checkSetupStartMs, "retry=" .. tostring(retryCount), false);
if (FOM_LastPetName == nil or FOM_LastPetName == "") then
GFWUtils.DebugLog("Can't get pet info.");
FOM_PerfRecord("FOM_Feed total", FOM_PerfNowMs() - feedStartMs, "result=no-pet-name", true);
return false;
end
@@ -2357,24 +2259,18 @@ function FOM_Feed(aFood, options)
if ( foodBag == nil) then
-- No Food Could be Found
GFWUtils.Print(string.format(FOM_ERROR_FOOD_NOT_FOUND, pet, aFood));
FOM_PerfRecord("FOM_Feed total", FOM_PerfNowMs() - feedStartMs, "result=manual-not-found", true);
return false;
end
FOM_LastChoiceReason = "manual selection";
else
local cachedChoiceStartMs = FOM_PerfNowMs();
local cachedBag, cachedSlot, cachedItemId = FOM_GetCachedAutoChoice(excludedItemIds);
if (cachedBag ~= nil and cachedSlot ~= nil) then
foodBag, foodItem = cachedBag, cachedSlot;
FOM_LastChoiceReason = "cached previous food";
FOM_PerfRecord("FOM_AutoChoice cache", FOM_PerfNowMs() - cachedChoiceStartMs,
"hit itemId=" .. tostring(cachedItemId), false);
else
FOM_PerfRecord("FOM_AutoChoice cache", FOM_PerfNowMs() - cachedChoiceStartMs, "miss", false);
end
if (foodBag == nil) then
local precomputeStartMs = FOM_PerfNowMs();
local cacheNow = (type(GetTime) == "function") and (GetTime() or 0) or 0;
if (retryCount <= 0
and type(FOM_FEED_SCAN_CACHE) == "table"
@@ -2382,8 +2278,6 @@ function FOM_Feed(aFood, options)
and type(FOM_FEED_SCAN_CACHE.foods) == "table") then
precomputedFoodList = FOM_CloneFoodList(FOM_FEED_SCAN_CACHE.foods);
openSlots = tonumber(FOM_FEED_SCAN_CACHE.openSlots) or 0;
FOM_PerfRecord("FOM_Feed precompute", FOM_PerfNowMs() - precomputeStartMs,
"cache-hit foods=" .. tostring(table.getn(precomputedFoodList)) .. " openSlots=" .. tostring(openSlots), false);
else
precomputedFoodList = FOM_FlatFoodList();
openSlots = FOM_NumOpenBagSlots();
@@ -2393,32 +2287,23 @@ function FOM_Feed(aFood, options)
foods = FOM_CloneFoodList(precomputedFoodList),
openSlots = openSlots,
};
FOM_PerfRecord("FOM_Feed precompute", FOM_PerfNowMs() - precomputeStartMs,
"cache-miss foods=" .. tostring(table.getn(precomputedFoodList)) .. " openSlots=" .. tostring(openSlots), false);
end
local fastPickStartMs = FOM_PerfNowMs();
foodBag, foodItem = FOM_TryPickSinglePrecomputedFood(precomputedFoodList, excludedItemIds, (FOM_Config.AvoidUsefulFood and false or true));
if (foodBag ~= nil) then
FOM_LastChoiceReason = "single candidate fast-path";
FOM_PerfRecord("FOM_SingleCandidate", FOM_PerfNowMs() - fastPickStartMs, "hit", false);
else
FOM_PerfRecord("FOM_SingleCandidate", FOM_PerfNowMs() - fastPickStartMs, "miss", false);
end
if (foodBag == nil) then
local findStartMs = FOM_PerfNowMs();
foodBag, foodItem = FOM_NewFindFood(nil, excludedItemIds, precomputedFoodList, openSlots);
FOM_PerfRecord("FOM_NewFindFood primary", FOM_PerfNowMs() - findStartMs, "found=" .. tostring(foodBag ~= nil), false);
end
end
end
local fallbackBag, fallbackItem = nil, nil;
if (not manualFood and foodBag == nil and FOM_Config.Fallback) then
local fallbackStartMs = FOM_PerfNowMs();
fallbackBag, fallbackItem = FOM_NewFindFood(1, excludedItemIds, precomputedFoodList, openSlots);
FOM_PerfRecord("FOM_NewFindFood fallback", FOM_PerfNowMs() - fallbackStartMs, "found=" .. tostring(fallbackBag ~= nil), false);
end
if ( foodBag == nil) then
@@ -2433,12 +2318,10 @@ function FOM_Feed(aFood, options)
-- No Food Could be Found
GFWUtils.Print(string.format(FOM_ERROR_NO_FOOD, pet));
FOM_LastChoiceReason = nil;
FOM_PerfRecord("FOM_Feed total", FOM_PerfNowMs() - feedStartMs, "result=no-food", true);
return false;
end
end
local prepareStartMs = FOM_PerfNowMs();
FOM_LastFood = GetContainerItemLink(foodBag, foodItem);
local selectedId = FOM_IDFromLink(FOM_LastFood)
FOM_SetCachedAutoChoice(foodBag, foodItem, selectedId)
@@ -2457,26 +2340,15 @@ function FOM_Feed(aFood, options)
petLevel = tonumber(petInfo and petInfo.level) or nil,
foodLevel = selectedFoodLevel,
})
FOM_PerfRecord("FOM_Feed prepare", FOM_PerfNowMs() - prepareStartMs, "itemId=" .. tostring(selectedId), false);
FOM_Trace("selected food link='" .. tostring(FOM_LastFood)
.. "' itemId=" .. tostring(selectedId)
.. " bag=" .. tostring(foodBag)
.. " slot=" .. tostring(foodItem)
.. " reason='" .. tostring(FOM_LastChoiceReason or "") .. "'")
GFWUtils.DebugLog("Picked "..FOM_LastFood.." (bag "..foodBag..", slot "..foodItem..") for feeding.");
FOM_DebugLog("Picked "..FOM_LastFood.." (bag "..foodBag..", slot "..foodItem..") for feeding.");
if (FOM_Config.Debug) then
-- don't actually feed anything, just show what we would choose
FOM_PerfRecord("FOM_Feed total", FOM_PerfNowMs() - feedStartMs, "result=debug", true);
return false;
end
-- Actually feed the item to the pet
local dropStartMs = FOM_PerfNowMs();
PickupContainerItem(foodBag, foodItem);
FOM_Trace("feed execute pickup bag=" .. tostring(foodBag)
.. " slot=" .. tostring(foodItem)
.. " cursorHasItem=" .. tostring(CursorHasItem() and true or false))
if ( CursorHasItem() ) then
MTH_FEED_SuppressChatUntil = (GetTime() or 0) + 2.0;
MTH_FEED_SuppressChatFoodName = FOM_NameFromLink(FOM_LastFood);
@@ -2484,26 +2356,19 @@ function FOM_Feed(aFood, options)
local ok = pcall(DropItemOnUnit, "pet");
MTH_FEED_SuppressCoreDropHook = nil;
if (not ok) then
FOM_Trace("feed execute drop on pet failed during protected call");
end
FOM_Trace("feed execute drop on pet cursorHasItemAfterDrop=" .. tostring(CursorHasItem() and true or false))
end
FOM_PerfRecord("FOM_Feed drop", FOM_PerfNowMs() - dropStartMs, "cursor=" .. tostring(CursorHasItem() and true or false), false);
if (coreAttemptId and type(MTH_FEED_RecordClientDropResult) == "function") then
pcall(MTH_FEED_RecordClientDropResult, coreAttemptId, {
consumedFromCursor = (CursorHasItem() and false or true),
})
end
if ( CursorHasItem() ) then
FOM_Trace("feed execute immediate-fail item stayed on cursor; likely rejected before combat log/error parse")
PickupContainerItem(foodBag, foodItem);
FOM_CoreRecordReject(coreAttemptId, "client-drop-fail", nil);
FOM_CoreFinalizeAttempt(coreAttemptId, "rejected", "client-drop-fail");
FOM_Trace("feed execute returned item to bag")
FOM_PerfRecord("FOM_Feed total", FOM_PerfNowMs() - feedStartMs, "result=cursor-fail", true);
return false;
else
FOM_Trace("feed execute accepted by client (item consumed from cursor)")
if (selectedId ~= nil) then
excludedItemIds[selectedId] = true;
end
@@ -2521,10 +2386,6 @@ function FOM_Feed(aFood, options)
loggedFirstCheck = false,
}
FOM_State.ShouldFeed = nil;
FOM_Trace("feed alert deferred until confirmation itemId=" .. tostring(selectedId)
.. " pet='" .. tostring(pet or "")
.. "' mode='" .. tostring(FOM_Config.Alert or "") .. "'")
FOM_PerfRecord("FOM_Feed total", FOM_PerfNowMs() - feedStartMs, "result=ok itemId=" .. tostring(selectedId), true);
return true;
end
end
@@ -2609,7 +2470,6 @@ local function FOM_EnsureQuestScanFresh(maxAgeSeconds)
end
function FOM_FlatFoodList()
local flatStartMs = FOM_PerfNowMs();
local foodList = {};
local overflowFoodList = {};
local scanned = 0;
@@ -2699,27 +2559,10 @@ function FOM_FlatFoodList()
if (table.getn(foodList) == 0 and table.getn(overflowFoodList) > 0) then
foodList = overflowFoodList;
end
FOM_Trace("flat food scan scanned=" .. tostring(scanned)
.. " inDiet=" .. tostring(inDiet)
.. " compatible=" .. tostring(compatibleCount)
.. " overflowCompatible=" .. tostring(overflowCompatibleCount)
.. " exceptionRejects=" .. tostring(rejectedByException)
.. " levelOverrideRejects=" .. tostring(rejectedByLevelOverride)
.. " quarantineRejects=" .. tostring(rejectedByQuarantine)
.. " unknownLevel=" .. tostring(unknownLevelCandidates)
.. " candidates=" .. tostring(table.getn(foodList)))
if (FOM_PERF_TRACE_ENABLED) then
FOM_PerfRecord("FOM_FlatFoodList", FOM_PerfNowMs() - flatStartMs,
"scanned=" .. tostring(scanned)
.. " inDiet=" .. tostring(inDiet)
.. " candidates=" .. tostring(table.getn(foodList)),
false);
end
return foodList;
end
function FOM_NewFindFood(fallback, excludedItemIds, precomputedFoodList, openSlotsOverride)
local findStartMs = FOM_PerfNowMs();
local FlatFoodList = nil;
if (type(precomputedFoodList) == "table") then
FlatFoodList = FOM_CloneFoodList(precomputedFoodList);
@@ -2730,12 +2573,6 @@ function FOM_NewFindFood(fallback, excludedItemIds, precomputedFoodList, openSlo
local reasonParts = {};
if (initialCount <= 0) then
FOM_LastChoiceReason = nil;
if (FOM_PERF_TRACE_ENABLED) then
FOM_PerfRecord("FOM_NewFindFood", FOM_PerfNowMs() - findStartMs,
"fallback=" .. tostring(fallback and true or false)
.. " initial=0 final=0",
false);
end
return nil;
end
local petInfo = MTH_FOM_GetCorePetInfo();
@@ -2822,18 +2659,6 @@ function FOM_NewFindFood(fallback, excludedItemIds, precomputedFoodList, openSlo
table.insert(reasonParts, "fallback allows useful food");
end
end
FOM_Trace("find food fallback=" .. tostring(fallback and true or false)
.. " initial=" .. tostring(initialCount)
.. " postConjured=" .. tostring(postConjuredCount)
.. " postFilters=" .. tostring(table.getn(FlatFoodList))
.. " reasons='" .. tostring(table.concat(reasonParts, ", ")) .. "'")
if (FOM_PERF_TRACE_ENABLED) then
FOM_PerfRecord("FOM_NewFindFood", FOM_PerfNowMs() - findStartMs,
"fallback=" .. tostring(fallback and true or false)
.. " initial=" .. tostring(initialCount)
.. " final=" .. tostring(table.getn(FlatFoodList)),
false);
end
for _, foodInfo in FlatFoodList do
if (foodInfo.overflow) then
@@ -2847,15 +2672,9 @@ function FOM_NewFindFood(fallback, excludedItemIds, precomputedFoodList, openSlo
else
FOM_LastChoiceReason = nil;
end
FOM_Trace("find food result bag=" .. tostring(foodInfo.bag)
.. " slot=" .. tostring(foodInfo.slot)
.. " quality=" .. tostring(foodInfo.quality)
.. " useful=" .. tostring(foodInfo.useful and true or false)
.. " temp=" .. tostring(foodInfo.temp and true or false))
return foodInfo.bag, foodInfo.slot;
end
FOM_LastChoiceReason = nil;
FOM_Trace("find food result: none")
return nil;
end
@@ -2889,12 +2708,12 @@ end
function FOM_IsUsefulFood(itemID, quantity)
local foodName = GetItemInfo(itemID);
if (foodName == nil) then
GFWUtils.DebugLog("Can't get info for item ID "..itemID..", assuming it's OK to eat.");
FOM_DebugLog("Can't get info for item ID "..itemID..", assuming it's OK to eat.");
return false;
end
if (FOM_Cooking and FOM_Cooking[FOM_RealmPlayer] and FOM_Cooking[FOM_RealmPlayer][itemID]) then
if (FOM_Cooking[FOM_RealmPlayer][itemID] >= FOM_Config.SaveForCookingLevel) then
GFWUtils.DebugLog("Skipping "..quantity.."x "..foodName.."; is good for cooking.");
FOM_DebugLog("Skipping "..quantity.."x "..foodName.."; is good for cooking.");
return true;
end
end
@@ -2906,19 +2725,19 @@ function FOM_IsUsefulFood(itemID, quantity)
FOM_Quantity[foodName] = FOM_Quantity[foodName] + quantity;
end
if (FOM_Quantity[foodName] > FOM_QuestFood[FOM_RealmPlayer][foodName]) then
GFWUtils.DebugLog("Not skipping "..quantity.."x "..foodName.."; is needed for quest, but we have more than enough.");
FOM_DebugLog("Not skipping "..quantity.."x "..foodName.."; is needed for quest, but we have more than enough.");
return false;
else
GFWUtils.DebugLog("Skipping "..quantity.."x "..foodName.."; is needed for quest.");
FOM_DebugLog("Skipping "..quantity.."x "..foodName.."; is needed for quest.");
return true;
end
end
end
if (FOM_Config.AvoidBonusFood and FOM_IsInDiet(itemID, FOM_DIET_BONUS)) then
GFWUtils.DebugLog("Skipping "..quantity.."x "..foodName.."; has bonus effect when eaten by player.");
FOM_DebugLog("Skipping "..quantity.."x "..foodName.."; has bonus effect when eaten by player.");
return true;
end
--GFWUtils.DebugLog("Not skipping "..quantity.."x "..foodName.."; doesn't have other uses.");
--FOM_DebugLog("Not skipping "..quantity.."x "..foodName.."; doesn't have other uses.");
return false;
end
@@ -2956,7 +2775,7 @@ function FOM_IsInDiet(food, dietList)
diet = "";
end
if (FOM_Foods[diet] == nil) then
GFWUtils.DebugLog("FOM_Foods[diet] == nil");
FOM_DebugLog("FOM_Foods[diet] == nil");
end
if (FOM_RemovedFoods ~= nil and FOM_RemovedFoods[diet] ~= nil and GFWTable.IndexOf(FOM_RemovedFoods[diet], food) ~= 0) then
return false;
+1 -1
View File
@@ -80,7 +80,7 @@ FOM_Emotes = {
["Foxes"] = {
"Clever fox, clever snack choice.",
},
["Serpents (Cobra)"] = {
["Serpents"] = {
"No hissing with your mouth full.",
},
};
-13
View File
@@ -53,16 +53,6 @@ function GFWUtils_temp_PrintOnce(message, interval, r, g, b)
GFWUtils.PrintOnceCache[message] = GetTime();
end
-- Prints a message to the chat frame only if Debug is set
function GFWUtils_temp_DebugLog(message)
if (GFWUtils.Debug) then
if MTH and MTH.Print then
MTH:Print(message, "debug")
else
DEFAULT_CHAT_FRAME:AddMessage(message, GFW_DEBUG_COLOR.r, GFW_DEBUG_COLOR.g, GFW_DEBUG_COLOR.b);
end
end
end
-- Prints a message in yellow to the floating messages frame
function GFWUtils_temp_Note(message)
@@ -177,8 +167,6 @@ if (G.Version == nil or (tonumber(G.Version) ~= nil and G.Version < GFWUTILS_THI
-- Constants
GFW_FONT_COLOR = {r=0.25, g=1.0, b=1.0};
GFW_DEBUG_COLOR = {r=1.0, g=0.75, b=0.25};
G.Debug = false;
-- Functions
G.Hilite = GFWUtils_temp_HiliteText;
@@ -187,7 +175,6 @@ if (G.Version == nil or (tonumber(G.Version) ~= nil and G.Version < GFWUTILS_THI
G.LtY = GFWUtils_temp_LightYellowText;
G.Print = GFWUtils_temp_Print;
G.PrintOnce = GFWUtils_temp_PrintOnce;
G.DebugLog = GFWUtils_temp_DebugLog;
G.Note = GFWUtils_temp_Note;
G.FormatToPattern = GFWUtils_temp_FormatToPattern;
G.Split = GFWUtils_temp_Split;
+2 -12
View File
@@ -6,7 +6,7 @@
local MTH_FeedOMatic = {
name = "feedomatic",
enabled = false,
version = "1.2.0",
version = "1.4.1",
events = {
"VARIABLES_LOADED",
"MERCHANT_SHOW",
@@ -112,10 +112,6 @@ local function MTH_FeedOMatic_CallLegacyOnEvent(evt, a1, a2, a3, a4, a5, a6, a7,
event = oldEvent
arg1, arg2, arg3, arg4, arg5 = oldArg1, oldArg2, oldArg3, oldArg4, oldArg5
arg6, arg7, arg8, arg9 = oldArg6, oldArg7, oldArg8, oldArg9
if not ok and MTH and MTH.DebugPrint then
MTH:DebugPrint("FeedOMatic legacy OnEvent call failed for event: " .. tostring(evt))
end
end
local function MTH_FeedOMatic_EnsureVariablesLoaded(module)
@@ -166,8 +162,7 @@ end
local function MTH_FeedOMatic_Log(message, severity)
if type(MTH_Log) == "function" then
local logSeverity = severity or "debug"
MTH_Log("[FeedOMatic] " .. tostring(message or ""), logSeverity)
MTH_Log("[FeedOMatic] " .. tostring(message or ""), severity)
end
end
@@ -198,10 +193,8 @@ local function MTH_FeedOMatic_DebugMerchantScan(sourceEvent)
if type(rows) == "table" then
dietCount = table.getn(rows)
end
MTH_FeedOMatic_Log("diet " .. tostring(dietName) .. ": " .. tostring(dietCount) .. " item(s)", "debug")
if type(rows) == "table" then
for _, row in rows do
MTH_FeedOMatic_Log(" - [" .. tostring(row.id) .. "] " .. tostring(row.name) .. " (slot=" .. tostring(row.index) .. ")", "debug")
end
end
end
@@ -209,7 +202,6 @@ local function MTH_FeedOMatic_DebugMerchantScan(sourceEvent)
if unknownCount > 0 and type(merchantFoods.unknown) == "table" then
for _, row in merchantFoods.unknown do
MTH_FeedOMatic_Log("unknown map: [" .. tostring(row.id) .. "] " .. tostring(row.name) .. " (slot=" .. tostring(row.index) .. ")", "debug")
end
end
end
@@ -228,7 +220,6 @@ local function MTH_FeedOMatic_SetMerchantProbeEnabled(enabled)
MTH_FOM_MerchantProbe:SetScript("OnEvent", function()
local evt = event
if evt == "MERCHANT_SHOW" or evt == "MERCHANT_UPDATE" then
MTH_FeedOMatic_Log("probe event received: " .. tostring(evt), "debug")
MTH_FeedOMatic_DebugMerchantScan(evt)
end
end)
@@ -236,7 +227,6 @@ local function MTH_FeedOMatic_SetMerchantProbeEnabled(enabled)
MTH_FOM_MerchantProbe:RegisterEvent("MERCHANT_SHOW")
MTH_FOM_MerchantProbe:RegisterEvent("MERCHANT_UPDATE")
MTH_FeedOMatic_Log("merchant probe enabled", "debug")
end
function MTH_FeedOMatic:init()
+1 -1
View File
@@ -6,7 +6,7 @@
local MTH_ICU = {
name = "icu",
enabled = false,
version = "1.2.0",
version = "1.4.1",
events = {
"VARIABLES_LOADED",
},
+68 -36
View File
@@ -48,10 +48,25 @@ end
local MTHSmartAmmo_InitializeHooks
MTHSmartAmmo_EnsureHooks = nil
local MTH_SA_SPELL_EVENTS = {
"START_AUTOREPEAT_SPELL",
"STOP_AUTOREPEAT_SPELL",
"SPELLCAST_INTERRUPTED",
"SPELLCAST_FAILED",
"SPELLCAST_STOP",
"SPELLCAST_DELAYED",
}
function MTHSmartAmmo_SetSmartEnabled(enabled, silent)
local saved = MTHSmartAmmo_GetSaved()
if enabled then
saved["enabled"] = 1
local evFrame = getglobal("MTH_SA_EventFrame")
if evFrame then
for _, ev in ipairs(MTH_SA_SPELL_EVENTS) do
evFrame:RegisterEvent(ev)
end
end
if MTHSmartAmmo_InitializeHooks then
MTHSmartAmmo_InitializeHooks()
end
@@ -63,6 +78,12 @@ function MTHSmartAmmo_SetSmartEnabled(enabled, silent)
end
else
saved["enabled"] = false
local evFrame = getglobal("MTH_SA_EventFrame")
if evFrame then
for _, ev in ipairs(MTH_SA_SPELL_EVENTS) do
evFrame:UnregisterEvent(ev)
end
end
if not silent and DEFAULT_CHAT_FRAME then
MTH_SA_Print("Smart Ammo Disabled.")
end
@@ -137,17 +158,23 @@ end
local function MTHSmartAmmo_ForceHookGlobals()
if type(MTHSmartAmmo_CastSpell_Hook) == "function" and CastSpell ~= MTHSmartAmmo_CastSpell_Hook then
MTH_SA_CastSpell = CastSpell
CastSpell = MTHSmartAmmo_CastSpell_Hook
end
if type(MTHSmartAmmo_UseAction_Hook) == "function" and UseAction ~= MTHSmartAmmo_UseAction_Hook then
MTH_SA_UseAction = UseAction
UseAction = MTHSmartAmmo_UseAction_Hook
end
if type(MTHSmartAmmo_CastSpellByName_Hook) == "function" and CastSpellByName ~= MTHSmartAmmo_CastSpellByName_Hook then
MTH_SA_CastSpellByName = CastSpellByName
CastSpellByName = MTHSmartAmmo_CastSpellByName_Hook
end
if CastSpell == MTHSmartAmmo_OriginalCastSpell or MTH_SA_CastSpell == nil then
MTH_SA_CastSpell = CastSpell
CastSpell = MTHSmartAmmo_CastSpell_Hook
end
end
if type(MTHSmartAmmo_UseAction_Hook) == "function" and UseAction ~= MTHSmartAmmo_UseAction_Hook then
if UseAction == MTHSmartAmmo_OriginalUseAction or MTH_SA_UseAction == nil then
MTH_SA_UseAction = UseAction
UseAction = MTHSmartAmmo_UseAction_Hook
end
end
if type(MTHSmartAmmo_CastSpellByName_Hook) == "function" and CastSpellByName ~= MTHSmartAmmo_CastSpellByName_Hook then
if CastSpellByName == MTHSmartAmmo_OriginalCastSpellByName or MTH_SA_CastSpellByName == nil then
MTH_SA_CastSpellByName = CastSpellByName
CastSpellByName = MTHSmartAmmo_CastSpellByName_Hook
end
end
end
MTH_SA = {}
@@ -423,13 +450,6 @@ local MTH_SA_OnUpdateElapsed = 0
frame:SetScript("OnUpdate", function()
local dt = arg1 or 0
MTHSmartAmmo_HookEnsureElapsed = MTHSmartAmmo_HookEnsureElapsed + dt
if MTHSmartAmmo_HookEnsureElapsed >= 0.5 then
MTHSmartAmmo_HookEnsureElapsed = 0
if MTH_SA_IsModuleEnabled() then
MTHSmartAmmo_InitializeHooks()
end
end
MTH_SA_OnUpdateElapsed = MTH_SA_OnUpdateElapsed + dt
if MTH_SA_OnUpdateElapsed < 0.10 then
return
@@ -463,11 +483,10 @@ function MTH_SA_CountEquippedAmmo()
local ammo = GetInventoryItemCount("player", 0)
local equippedName = MTH_SA_GetEquippedAmmoName()
if equippedName and equippedName ~= "" then
MTH_SA_LastKnownEquippedName = equippedName
end
if not ammo then
MTH_SA_Quantity=0
return
if equippedName ~= MTH_SA_LastKnownEquippedName then
MTH_SA_LastKnownEquippedName = equippedName
MTHSmartAmmo_RefreshAmmoButtonDisplay()
end
elseif ammo ~= MTH_SA_Quantity then
MTH_SA_Quantity = ammo
end
@@ -663,12 +682,15 @@ MTHSmartAmmo_UseAction_Hook = function(slot, checkCursor, onSelf)
end
MTHSmartAmmo_InUseActionHook = true
if not GetActionText(slot) and MTHSmartAmmo_IsSmartEnabled() then
local _actionText = GetActionText(slot)
local _smart = MTHSmartAmmo_IsSmartEnabled()
if not _actionText and _smart then
MTH_SA_Tooltip:SetOwner(UIParent, "ANCHOR_NONE")
MTH_SA_Tooltip:SetAction(slot)
local name = MTH_SA_TooltipTextLeft1:GetText()
MTH_SA_LastSpell = name
if name and MTH_AMMO_JUNKSHOT_SET and MTH_AMMO_JUNKSHOT_SET[name] then
local _inSet = name and MTH_AMMO_JUNKSHOT_SET and MTH_AMMO_JUNKSHOT_SET[name]
if _inSet then
if MTH_SA_Check() or MTH_SA_FindAmmo() then
MTH_SA_EquipAmmo(1)
end
@@ -724,19 +746,29 @@ MTHSmartAmmo_InitializeHooks = function()
return
end
if CastSpell ~= MTHSmartAmmo_CastSpell_Hook then
MTH_SA_CastSpell = CastSpell
CastSpell = MTHSmartAmmo_CastSpell_Hook
end
-- Only install a hook if vanilla is on top (first-time install) or MetaHunt
-- is already on top. If another addon (e.g. Quiver) is on top, it has already
-- captured MetaHunt's hook as its super — reinstalling would create a double
-- invocation and break addons that rely on IsCurrentAction after their super call.
if CastSpell ~= MTHSmartAmmo_CastSpell_Hook then
if CastSpell == MTHSmartAmmo_OriginalCastSpell or MTH_SA_CastSpell == nil then
MTH_SA_CastSpell = CastSpell
CastSpell = MTHSmartAmmo_CastSpell_Hook
end
end
if UseAction ~= MTHSmartAmmo_UseAction_Hook then
MTH_SA_UseAction = UseAction
UseAction = MTHSmartAmmo_UseAction_Hook
end
if UseAction ~= MTHSmartAmmo_UseAction_Hook then
if UseAction == MTHSmartAmmo_OriginalUseAction or MTH_SA_UseAction == nil then
MTH_SA_UseAction = UseAction
UseAction = MTHSmartAmmo_UseAction_Hook
end
end
if CastSpellByName ~= MTHSmartAmmo_CastSpellByName_Hook then
MTH_SA_CastSpellByName = CastSpellByName
CastSpellByName = MTHSmartAmmo_CastSpellByName_Hook
if CastSpellByName ~= MTHSmartAmmo_CastSpellByName_Hook then
if CastSpellByName == MTHSmartAmmo_OriginalCastSpellByName or MTH_SA_CastSpellByName == nil then
MTH_SA_CastSpellByName = CastSpellByName
CastSpellByName = MTHSmartAmmo_CastSpellByName_Hook
end
end
end
+1 -1
View File
@@ -7,7 +7,7 @@ MTH_SA_MANAGED_HOOKS = true
local MTH_SmartAmmo = {
name = "smartammo",
enabled = true,
version = "1.2.0",
version = "1.4.1",
events = {
"VARIABLES_LOADED",
-- PLAYER_ENTERING_WORLD handled by engine's own frame
+26 -9
View File
@@ -6,7 +6,7 @@
local MTH_Tooltips = {
name = "tooltips",
enabled = true,
version = "1.2.0",
version = "1.4.1",
events = {
"UPDATE_MOUSEOVER_UNIT",
"UNIT_NAME_UPDATE",
@@ -1389,7 +1389,6 @@ local function MTH_TT_AddPetActionNotLearnedHint()
return
end
local ownerName = MTH_TT_GetTooltipOwnerName()
local ownerMatch = MTH_TT_IsPetActionTooltipOwner()
local barMatch = MTH_TT_IsSpellOnPetActionBar(spellName)
if not ownerMatch and not barMatch then
@@ -1406,11 +1405,13 @@ local function MTH_TT_AddPetActionNotLearnedHint()
end
local canonicalLower = MTH_TT_Lower(canonical)
local known = MTH_TT_HunterKnowsAbility(canonicalLower, rankNumber)
if not known then
known = MTH_TT_CurrentPetKnowsAbility(canonicalLower, rankNumber)
end
if known then
local hunterKnown = MTH_TT_HunterKnowsAbility(canonicalLower, rankNumber)
-- Only fall back to petKnown for ranked abilities: rankless abilities have no rank
-- filter in CurrentPetKnowsAbility, so they always match on name alone -> false positive.
local petKnown = (not hunterKnown)
and MTH_TT_AbilityHasPositiveRanks(canonical)
and MTH_TT_CurrentPetKnowsAbility(canonicalLower, rankNumber)
if hunterKnown or petKnown then
return
end
@@ -1485,8 +1486,11 @@ 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 then
if not row and not vendorInfo and not scorpokAdded and not savedEntry 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) .. "'")
@@ -1510,8 +1514,16 @@ 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
local abilities = MTH_TT_ParseAbilities(row.abilities)
abilitiesSource = row.abilities
elseif savedEntry and savedEntry.abilities and savedEntry.abilities ~= "" then
abilitiesSource = savedEntry.abilities
end
if abilitiesSource then
local abilities = MTH_TT_ParseAbilities(abilitiesSource)
if table.getn(abilities) == 0 then
MTH_TT_StateSet("skip-no-abilities", name, normalized)
MTH_TT_Log("skip: beast has no abilities ('" .. tostring(name) .. "')")
@@ -1534,6 +1546,11 @@ 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
+1 -18
View File
@@ -37,13 +37,6 @@ local function ZHunterMod_RunButtonInit(frame, onEventFunc)
event = oldEvent
end
local function ZHunterMod_LogDeferred(msg)
local deferredDebug = _G and _G["MTH_ZH_DEFERRED_DEBUG"]
if not (MTH and MTH.debug and deferredDebug) then
return
end
MTH_ZH_Print("[ZHUNTER] " .. tostring(msg), "error")
end
local function ZHunterMod_EnsureButtonFrame(buttonName)
local frame = getglobal(buttonName)
@@ -77,7 +70,6 @@ local function ZHunterMod_EnsureButtonFrame(buttonName)
else
created:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
end
ZHunterMod_LogDeferred("DeferredInit: created missing frame " .. tostring(buttonName))
end
return created
end
@@ -94,15 +86,6 @@ function ZHunterMod_DeferredInit()
local companions = ZHunterMod_EnsureButtonFrame("zButtonCompanions")
local toys = ZHunterMod_EnsureButtonFrame("zButtonToys")
ZHunterMod_LogDeferred("DeferredInit: Pet=" .. tostring(pet ~= nil))
ZHunterMod_LogDeferred("DeferredInit: Aspect=" .. tostring(aspect ~= nil))
ZHunterMod_LogDeferred("DeferredInit: Track=" .. tostring(track ~= nil))
ZHunterMod_LogDeferred("DeferredInit: Trap=" .. tostring(trap ~= nil))
ZHunterMod_LogDeferred("DeferredInit: Ranged=" .. tostring(ranged ~= nil))
ZHunterMod_LogDeferred("DeferredInit: Ammo=" .. tostring(ammo ~= nil))
ZHunterMod_LogDeferred("DeferredInit: Mounts=" .. tostring(mounts ~= nil))
ZHunterMod_LogDeferred("DeferredInit: Companions=" .. tostring(companions ~= nil))
ZHunterMod_LogDeferred("DeferredInit: Toys=" .. tostring(toys ~= nil))
-- Continue with all frames that exist; do not hard-abort when one optional frame is missing.
local missing = {}
@@ -202,7 +185,7 @@ function ZHunterMod_DeferredInit()
if type(MTH_ZH_OnDeferredInitComplete) == "function" then
local ok, err = pcall(MTH_ZH_OnDeferredInitComplete)
if not ok and MTH_DebugFrame and MTH_DebugFrame.AddError then
if not ok then
MTH_ZH_Print("[ZHUNTER] DeferredInit callback error: " .. tostring(err), "error")
end
end
+1
View File
@@ -86,6 +86,7 @@ ZHUNTER_TRACK_GIANTS = "Track Giants"
ZHUNTER_TRACK_MINERALS = "Find Minerals"
ZHUNTER_TRACK_HERBS = "Find Herbs"
ZHUNTER_TRACK_TREASURE = "Find Treasure"
ZHUNTER_TRACK_TREES = "Find Trees"
ZHUNTER_TRAP_FREEZING = "Freezing Trap"
ZHUNTER_TRAP_FROST = "Frost Trap"
-46
View File
@@ -14,7 +14,6 @@ BINDING_HEADER_ZToysHeader = "ZToys Buttons"
ZHunterModTooltip = CreateFrame("GameTooltip", "MTH_ZH_AbilityProbe", nil, "GameTooltipTemplate")
ZHunterModTooltipTextLeft1 = getglobal("MTH_ZH_AbilityProbeTextLeft1")
MTH_ZH_TRACE_ENABLED = false
local function MTH_ZH_DescribePoint(frame)
if not (frame and frame.GetPoint) then
@@ -33,51 +32,8 @@ local function MTH_ZH_DescribePoint(frame)
.. " @(" .. tostring(x) .. "," .. tostring(y) .. ")"
end
function MTH_ZH_Trace(msg)
if not MTH_ZH_TRACE_ENABLED then
return
end
if MTH and MTH.Print then
MTH:Print("[ZHTRACE] " .. tostring(msg), "debug")
elseif DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.AddMessage then
DEFAULT_CHAT_FRAME:AddMessage("[ZHTRACE] " .. tostring(msg))
end
end
function MTH_ZH_TraceButtonPoint(frame, reason)
if not MTH_ZH_TRACE_ENABLED then
return
end
if not frame then
MTH_ZH_Trace("point " .. tostring(reason or "") .. " frame=nil")
return
end
local name = frame.GetName and frame:GetName() or "<unnamed>"
MTH_ZH_Trace("point " .. tostring(reason or "") .. " " .. tostring(name) .. " " .. MTH_ZH_DescribePoint(frame))
end
function MTH_ZH_TraceAllButtonPoints(reason)
if not MTH_ZH_TRACE_ENABLED then
return
end
local names = {
"zButtonAspect",
"zButtonAmmo",
"zButtonTrack",
"zButtonTrap",
"zButtonRanged",
"zButtonPet",
"zButtonMounts",
"zButtonCompanions",
"zButtonToys",
}
MTH_ZH_Trace("snapshot begin reason=" .. tostring(reason or ""))
for i = 1, table.getn(names) do
local frame = getglobal(names[i])
MTH_ZH_TraceButtonPoint(frame, tostring(reason or "") .. ":" .. tostring(names[i]))
end
MTH_ZH_Trace("snapshot end reason=" .. tostring(reason or ""))
end
function MTH_ZH_IsModuleEnabled()
if not (MTH and MTH.IsModuleEnabled) then
@@ -170,7 +126,6 @@ function MTH_ZH_HandleDisabledSlash(message, tableKey, fieldKey)
end
function ZHunterMod_AlignButtons()
MTH_ZH_TraceAllButtonPoints("AlignButtons:before")
zButtonAmmo:ClearAllPoints()
zButtonAmmo:SetPoint("TOP", zButtonAspect, "BOTTOM", 0, -15)
zButtonTrack:ClearAllPoints()
@@ -189,7 +144,6 @@ function ZHunterMod_AlignButtons()
AutoStripDisplay:ClearAllPoints()
AutoStripDisplay:SetPoint("TOP", zButtonToys, "BOTTOM", 0, -10)
end
MTH_ZH_TraceAllButtonPoints("AlignButtons:after")
end
function ZMarkTarget()
-23
View File
@@ -536,13 +536,6 @@ local function ZSpellButton_SaveParentPosition(parent)
saved["parent"]["relativePoint"] = tostring(relPoint)
saved["parent"]["x"] = math.floor(x + 0.5)
saved["parent"]["y"] = math.floor(y + 0.5)
if type(MTH_ZH_Trace) == "function" then
MTH_ZH_Trace("persist-parent-position key=" .. tostring(key)
.. " point=" .. tostring(saved["parent"]["point"])
.. " rel=" .. tostring(saved["parent"]["relativePoint"])
.. " x=" .. tostring(saved["parent"]["x"])
.. " y=" .. tostring(saved["parent"]["y"]))
end
end
local function ZSpellButton_RestoreParentPosition(parent)
@@ -580,13 +573,6 @@ local function ZSpellButton_RestoreParentPosition(parent)
parent:ClearAllPoints()
parent:SetPoint(point, UIParent, relPoint, px, py)
if type(MTH_ZH_Trace) == "function" then
MTH_ZH_Trace("restore-parent-position key=" .. tostring(key)
.. " point=" .. tostring(point)
.. " rel=" .. tostring(relPoint)
.. " x=" .. tostring(px)
.. " y=" .. tostring(py))
end
return true
end
@@ -697,9 +683,6 @@ function ZSpellButtonParent_OnLoad()
if this.SetScript then
this:SetScript("OnDragStart", function()
if IsAltKeyDown() then
if type(MTH_ZH_TraceButtonPoint) == "function" then
MTH_ZH_TraceButtonPoint(this, "drag-start")
end
this:StartMoving()
this.isMoving = true
end
@@ -713,9 +696,6 @@ function ZSpellButtonParent_OnLoad()
end
ZSpellButton_SaveParentPosition(this)
this.isMoving = false
if type(MTH_ZH_TraceButtonPoint) == "function" then
MTH_ZH_TraceButtonPoint(this, "drag-stop")
end
end)
this:SetScript("OnMouseUp", function()
if this and this.isMoving then
@@ -724,9 +704,6 @@ function ZSpellButtonParent_OnLoad()
end
ZSpellButton_SaveParentPosition(this)
this.isMoving = false
if type(MTH_ZH_TraceButtonPoint) == "function" then
MTH_ZH_TraceButtonPoint(this, "mouse-up-stop")
end
end
end)
end
+1 -46
View File
@@ -8,7 +8,7 @@ MTH_ZH_MANAGED_HOOKS = true
local MTH_ZHunter = {
name = "zhunter",
enabled = true,
version = "1.2.0",
version = "1.4.1",
events = {
"VARIABLES_LOADED",
-- PLAYER_ENTERING_WORLD handled by bootstrap frame + adjustment frames
@@ -24,19 +24,6 @@ local MTH_ZHunter = {
}
}
local function MTH_ZH_Log(msg)
if not (MTH and MTH.debug) then
return
end
MTH:Print("[ZH MODULE] " .. tostring(msg), "debug")
end
local function MTH_ZH_TraceLocal(msg)
if type(MTH_ZH_Trace) == "function" then
MTH_ZH_Trace(msg)
end
end
local MTH_ZH_PostLoginRestore = nil
local MTH_ZH_BootstrapFrame = nil
local MTH_ZH_SyncSavedVariables
@@ -80,7 +67,6 @@ local function MTH_ZH_ApplyDefaultWidgetSpawnLayoutOnce()
local orderedCount = table.getn(orderedButtons)
for i = 1, orderedCount do
if not getglobal(orderedButtons[i]) then
MTH_ZH_Log("skipped one-time default widget spawn layout (missing frame: " .. tostring(orderedButtons[i]) .. ")")
return false
end
end
@@ -95,7 +81,6 @@ local function MTH_ZH_ApplyDefaultWidgetSpawnLayoutOnce()
end
savedRoot["_mth_widget_spawn_layout_v1"] = 1
MTH_ZH_Log("applied one-time default widget spawn layout")
return true
end
@@ -107,7 +92,6 @@ function MTH_ZH_OnDeferredInitComplete()
if MTH_ZH_SetAuxFramesVisible then
MTH_ZH_SetAuxFramesVisible(false)
end
MTH_ZH_Log("deferred init applied disabled-state hide")
return
end
MTH_ZH_SyncSavedVariables()
@@ -137,10 +121,6 @@ MTH_ZH_QueuePostLoginRestore = function(tag)
MTH_ZH_SyncSavedVariables()
MTH_ZH_RestoreRuntimeFeatureFlags()
if type(MTH_ZH_TraceAllButtonPoints) == "function" then
MTH_ZH_TraceAllButtonPoints("post-login-restore:" .. tostring(tag))
end
MTH_ZH_Log("post-login restore applied: " .. tostring(tag))
end)
end
@@ -178,17 +158,10 @@ local function MTH_ZH_EnsureBootstrapRestoreFrame()
end
MTH_ZH_ApplyEnabledRuntimeState = function(source)
MTH_ZH_TraceLocal("apply-enabled-runtime-state source=" .. tostring(source or ""))
if type(MTH_ZH_TraceAllButtonPoints) == "function" then
MTH_ZH_TraceAllButtonPoints("apply-enabled:before:" .. tostring(source or ""))
end
MTH_ZH_SyncSavedVariables()
MTH_ZH_SetButtonsVisible(true)
MTH_ZH_SetAuxFramesVisible(true)
MTH_ZH_RestoreRuntimeFeatureFlags()
if type(MTH_ZH_TraceAllButtonPoints) == "function" then
MTH_ZH_TraceAllButtonPoints("apply-enabled:after:" .. tostring(source or ""))
end
end
MTH_ZH_SyncSavedVariables = function()
@@ -197,22 +170,17 @@ MTH_ZH_SyncSavedVariables = function()
end
local moduleStore = MTH:GetModuleCharSavedVariables("zhunter")
MTH_ZH_TraceLocal("sync-saved begin zhuntermod_saved=" .. tostring(type(ZHunterMod_Saved)))
if type(ZHunterMod_Saved) == "table" then
if MTH_CharSavedVariables and MTH_CharSavedVariables.modules then
MTH_CharSavedVariables.modules.zhunter = ZHunterMod_Saved
MTH_ZH_TraceLocal("sync-saved wrote ZHunterMod_Saved -> MTH_CharSavedVariables.modules.zhunter")
end
elseif type(moduleStore) == "table" then
ZHunterMod_Saved = moduleStore
MTH_ZH_TraceLocal("sync-saved loaded moduleStore -> ZHunterMod_Saved")
end
MTH_ZH_TraceLocal("sync-saved end")
end
MTH_ZH_SetButtonsVisible = function(visible)
MTH_ZH_TraceLocal("set-buttons-visible visible=" .. tostring(visible and true or false))
local buttonNames = {
"zButtonAspect",
"zButtonTrack",
@@ -294,9 +262,6 @@ MTH_ZH_SetButtonsVisible = function(visible)
for _, buttonName in ipairs(buttonNames) do
local button = getglobal(buttonName)
if button then
if type(MTH_ZH_TraceButtonPoint) == "function" then
MTH_ZH_TraceButtonPoint(button, "set-buttons-visible:before:" .. tostring(buttonName))
end
if visible then
-- Respect per-button enabled state
local getterName = buttonName .. "_GetSaved"
@@ -319,9 +284,6 @@ MTH_ZH_SetButtonsVisible = function(visible)
MTH_ZH_SetChildButtonsVisible(button, buttonName, false)
button:Hide()
end
if type(MTH_ZH_TraceButtonPoint) == "function" then
MTH_ZH_TraceButtonPoint(button, "set-buttons-visible:after:" .. tostring(buttonName))
end
end
end
end
@@ -400,7 +362,6 @@ function MTH_ZHunter:init()
end
function MTH_ZHunter:setEnabled(enabled)
MTH_ZH_TraceLocal("module setEnabled enabled=" .. tostring(enabled and true or false))
MTH_ZH_SyncSavedVariables()
if enabled then
MTH_ZH_EnsureBootstrapRestoreFrame()
@@ -409,14 +370,8 @@ function MTH_ZHunter:setEnabled(enabled)
else
MTH_ZH_ClearRuntimeFeatureFlags()
MTH_ZH_SetAdjustmentHandlersActive(false)
if type(MTH_ZH_TraceAllButtonPoints) == "function" then
MTH_ZH_TraceAllButtonPoints("setEnabled:false:before-hide")
end
MTH_ZH_SetButtonsVisible(false)
MTH_ZH_SetAuxFramesVisible(false)
if type(MTH_ZH_TraceAllButtonPoints) == "function" then
MTH_ZH_TraceAllButtonPoints("setEnabled:false:after-hide")
end
end
end
+5
View File
@@ -121,9 +121,13 @@ local zButtonAmmo_ShortArrowLabels = {
[ARROWS_ROUGH] = "Rough",
[ARROWS_SHARP] = "Sharp",
[ARROWS_RAZOR] = "Razor",
[ARROWS_BRIGHT] = "Bright",
[ARROWS_JAGGED] = "Jagged",
[ARROWS_SHADE] = "Shade",
[ARROWS_ICETHREADED] = "Ice",
[ARROWS_SMOOTH] = "Smooth",
[ARROWS_THORIUM] = "Thorium",
[ARROWS_STAR] = "Star",
[ARROWS_DOOMSHOT] = "Doom",
}
@@ -143,6 +147,7 @@ local zButtonAmmo_ShortBulletLabels = {
[BULLETS_ACCURATE] = "Accur",
[BULLETS_GYROSHOT] = "Gyro",
[BULLETS_THORIUM] = "Thorium",
[BULLETS_CRAFTEDTHORIUM] = "Ench",
[BULLETS_ICETHREADED] = "Ice",
}
+3 -2
View File
@@ -5,7 +5,7 @@ end
local root = zButtonAspect_GetRoot()
if not root["zButtonAspect"] then
root["zButtonAspect"] = {}
root["zButtonAspect"]["spells"] = {1, 2, 3, 4, 5, 6, 7}
root["zButtonAspect"]["spells"] = {1, 2, 3, 4, 5, 6, 7, 8}
root["zButtonAspect"]["rows"] = 1
root["zButtonAspect"]["horizontal"] = nil
root["zButtonAspect"]["vertical"] = nil
@@ -28,7 +28,8 @@ ZHunterMod_Aspect_Spells = {
ZHUNTER_ASPECT_CHEETAH,
ZHUNTER_ASPECT_PACK,
ZHUNTER_ASPECT_WILD,
ZHUNTER_ASPECT_BEAST
ZHUNTER_ASPECT_BEAST,
ZHUNTER_ASPECT_VIPER
}
local ZHUNTER_ASPECT_MAX = table.getn(ZHunterMod_Aspect_Spells)
+5 -2
View File
@@ -249,7 +249,8 @@ end
function zButtonMounts_SetupSizeAndPosition()
local saved = zButtonMounts_GetSaved()
if saved["enabled"] == false or saved["enabled"] == 0 then
if MTH_ZH_MountsAdjust and MTH_ZH_MountsAdjust.SetScript then
if MTH_ZH_MountsAdjust then
MTH_ZH_MountsAdjust:UnregisterAllEvents()
MTH_ZH_MountsAdjust:SetScript("OnEvent", nil)
end
if zButtonMounts and zButtonMounts.Hide then
@@ -257,7 +258,9 @@ function zButtonMounts_SetupSizeAndPosition()
end
return
end
if MTH_ZH_MountsAdjust and MTH_ZH_MountsAdjust.SetScript then
if MTH_ZH_MountsAdjust then
MTH_ZH_MountsAdjust:RegisterEvent("SPELLS_CHANGED")
MTH_ZH_MountsAdjust:RegisterEvent("PLAYER_ENTERING_WORLD")
MTH_ZH_MountsAdjust:SetScript("OnEvent", MTH_ZH_MountsAdjust_OnEvent)
end
local displayCount = zButtonMounts.found or 0
+10 -2
View File
@@ -235,7 +235,8 @@ end
function zButtonPet_SetupSizeAndPosition()
local saved = zButtonPet_GetSaved()
if saved["enabled"] == false or saved["enabled"] == 0 then
if MTH_ZH_PetAdjust and MTH_ZH_PetAdjust.SetScript then
if MTH_ZH_PetAdjust then
MTH_ZH_PetAdjust:UnregisterAllEvents()
MTH_ZH_PetAdjust:SetScript("OnEvent", nil)
end
if zButtonPet and zButtonPet.Hide then
@@ -243,7 +244,14 @@ function zButtonPet_SetupSizeAndPosition()
end
return
end
if MTH_ZH_PetAdjust and MTH_ZH_PetAdjust.SetScript then
if MTH_ZH_PetAdjust then
MTH_ZH_PetAdjust:RegisterEvent("UNIT_HEALTH")
MTH_ZH_PetAdjust:RegisterEvent("UNIT_HAPPINESS")
MTH_ZH_PetAdjust:RegisterEvent("UNIT_PET")
MTH_ZH_PetAdjust:RegisterEvent("PET_BAR_UPDATE")
MTH_ZH_PetAdjust:RegisterEvent("PLAYER_ENTERING_WORLD")
MTH_ZH_PetAdjust:RegisterEvent("SPELLS_CHANGED")
MTH_ZH_PetAdjust:RegisterEvent("LEARNED_SPELL_IN_TAB")
MTH_ZH_PetAdjust:SetScript("OnEvent", MTH_ZH_PetAdjust_OnEvent)
end
local displayCount = zButtonPet.found or ZHUNTER_PET_MAX
-4
View File
@@ -97,10 +97,6 @@ local function zButtonRanged_IsRangedItem(subtype, equipLoc)
if subtypeText == tostring(MTH_WEAPON_BOWS or "Bows") then return true end
if subtypeText == tostring(MTH_WEAPON_GUNS or "Guns") then return true end
if subtypeText == tostring(MTH_WEAPON_CROSSBOWS or "Crossbows") then return true end
if subtypeText == "Thrown" then return true end
if equipLoc == "INVTYPE_RANGED" or equipLoc == "INVTYPE_RANGEDRIGHT" or equipLoc == "INVTYPE_THROWN" then
return true
end
return false
end
+5 -2
View File
@@ -249,7 +249,8 @@ end
function zButtonToys_SetupSizeAndPosition()
local saved = zButtonToys_GetSaved()
if saved["enabled"] == false or saved["enabled"] == 0 then
if MTH_ZH_ToysAdjust and MTH_ZH_ToysAdjust.SetScript then
if MTH_ZH_ToysAdjust then
MTH_ZH_ToysAdjust:UnregisterAllEvents()
MTH_ZH_ToysAdjust:SetScript("OnEvent", nil)
end
if zButtonToys and zButtonToys.Hide then
@@ -257,7 +258,9 @@ function zButtonToys_SetupSizeAndPosition()
end
return
end
if MTH_ZH_ToysAdjust and MTH_ZH_ToysAdjust.SetScript then
if MTH_ZH_ToysAdjust then
MTH_ZH_ToysAdjust:RegisterEvent("SPELLS_CHANGED")
MTH_ZH_ToysAdjust:RegisterEvent("PLAYER_ENTERING_WORLD")
MTH_ZH_ToysAdjust:SetScript("OnEvent", MTH_ZH_ToysAdjust_OnEvent)
end
local displayCount = zButtonToys.found or 0
+11 -4
View File
@@ -5,7 +5,7 @@ end
local root = zButtonTrack_GetRoot()
if not root["zButtonTrack"] then
root["zButtonTrack"] = {}
root["zButtonTrack"]["spells"] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
root["zButtonTrack"]["spells"] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
root["zButtonTrack"]["rows"] = 1
root["zButtonTrack"]["horizontal"] = nil
root["zButtonTrack"]["vertical"] = nil
@@ -32,7 +32,8 @@ ZHunterMod_Track_Spells = {
ZHUNTER_TRACK_GIANTS,
ZHUNTER_TRACK_MINERALS,
ZHUNTER_TRACK_HERBS,
ZHUNTER_TRACK_TREASURE
ZHUNTER_TRACK_TREASURE,
ZHUNTER_TRACK_TREES
}
local ZHUNTER_TRACK_MAX = table.getn(ZHunterMod_Track_Spells)
@@ -311,7 +312,8 @@ function zButtonTrack_SetupSizeAndPosition()
zButtonTrack_EnsureConfig()
local saved = zButtonTrack_GetSaved()
if saved["enabled"] == false or saved["enabled"] == 0 then
if MTH_ZH_TrackAdjust and MTH_ZH_TrackAdjust.SetScript then
if MTH_ZH_TrackAdjust then
MTH_ZH_TrackAdjust:UnregisterAllEvents()
MTH_ZH_TrackAdjust:SetScript("OnEvent", nil)
end
if zButtonTrack and zButtonTrack.Hide then
@@ -319,7 +321,12 @@ function zButtonTrack_SetupSizeAndPosition()
end
return
end
if MTH_ZH_TrackAdjust and MTH_ZH_TrackAdjust.SetScript then
if MTH_ZH_TrackAdjust then
MTH_ZH_TrackAdjust:RegisterEvent("MINIMAP_UPDATE_TRACKING")
MTH_ZH_TrackAdjust:RegisterEvent("PLAYER_ENTERING_WORLD")
MTH_ZH_TrackAdjust:RegisterEvent("SPELLS_CHANGED")
MTH_ZH_TrackAdjust:RegisterEvent("CHARACTER_POINTS_CHANGED")
MTH_ZH_TrackAdjust:RegisterEvent("LEARNED_SPELL_IN_TAB")
MTH_ZH_TrackAdjust:SetScript("OnEvent", MTH_ZH_TrackAdjust_OnEvent)
end
local displayCount = zButtonTrack.found or ZHUNTER_TRACK_MAX
+8 -2
View File
@@ -154,7 +154,8 @@ function zButtonTrap_SetupSizeAndPosition()
zButtonTrap_EnsureConfig()
local saved = zButtonTrap_GetSaved()
if saved["enabled"] == false or saved["enabled"] == 0 then
if MTH_ZH_TrapAdjust and MTH_ZH_TrapAdjust.SetScript then
if MTH_ZH_TrapAdjust then
MTH_ZH_TrapAdjust:UnregisterAllEvents()
MTH_ZH_TrapAdjust:SetScript("OnEvent", nil)
end
if zButtonTrap and zButtonTrap.Hide then
@@ -162,7 +163,12 @@ function zButtonTrap_SetupSizeAndPosition()
end
return
end
if MTH_ZH_TrapAdjust and MTH_ZH_TrapAdjust.SetScript then
if MTH_ZH_TrapAdjust then
MTH_ZH_TrapAdjust:RegisterEvent("PLAYER_REGEN_ENABLED")
MTH_ZH_TrapAdjust:RegisterEvent("PLAYER_REGEN_DISABLED")
MTH_ZH_TrapAdjust:RegisterEvent("PLAYER_ENTERING_WORLD")
MTH_ZH_TrapAdjust:RegisterEvent("SPELLS_CHANGED")
MTH_ZH_TrapAdjust:RegisterEvent("CHARACTER_POINTS_CHANGED")
MTH_ZH_TrapAdjust:SetScript("OnEvent", MTH_ZH_TrapAdjust_OnEvent)
end
local displayCount = zButtonTrap.found or ZHUNTER_TRAP_MAX