11 Commits

Author SHA1 Message Date
DuvelCorp 6c007f2c17 1.5.1 2026-04-04 23:43:46 +02:00
DuvelCorp c4911ef9fe 1.5.1 2026-04-04 23:38:34 +02:00
DuvelCorp 51d4c785fa 1.5.0 2026-03-31 21:25:35 +02: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
DuvelCorp 8d80d0663c V1.4.1 performance fixes 2026-03-24 14:14:33 +01:00
DuvelCorp 8479607a13 v1.4.0 Twow 1.18.1 2026-03-23 22:25:10 +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
DuvelCorp c0c2c275eb v1.2.0 2026-03-11 00:40:35 +01:00
Duvel ab34946775 Fix typos and enhance clarity in README
Corrected typos and improved wording in the README.
2026-03-09 15:18:11 +01:00
119 changed files with 88907 additions and 67171 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>
+207
View File
@@ -2,6 +2,213 @@
All notable changes to MetaHunt will be documented in this file.
## [1.5.1] - 2026-04-01
### Added
- **zAmmo — bag ammo labels**: Quality-colored short-name labels overlaid on ammo items in bags and bank, with optional heat-colored DPS damage indicator. Three new checkboxes General options: bag labels, bank labels, and damage display. Works with both default bags and pfUI bags.
- **MM Widget — immunity detection**: The MM Widget keybind now detects when the current target is immune to a damage school (Fire / Nature / Arcane) and automatically skips the useless shot, casting Aimed or Steady Shot instead. Immunity is detected in two ways: reactively via combat log "immune" messages, and proactively via hardcoded creature-type rules (e.g. Elementals are always flagged Nature-immune on target). Immune cells show a red border.
- **MM Widget — rotation toggles**: Three new checkboxes in Options → ExpAmmo → Rotation let you permanently disable individual shots (Multi-Shot, Serpent Sting, Arcane Shot) — unchecked shots are skipped during their proc window.
### Fixed
- **Zone mapping — 22 beasts with raw zone IDs**: Fixed 8 unmapped zone IDs (Blackrock Depths, Molten Core, Wailing Caverns, Maraudon, Sunnyglade, Icepoint Rock, Winter Veil Vale, Gilneas City, Karazhan) that caused 22 beasts to display as "Zone xxx" instead of their proper zone name in the Hunter Book.
- **MM Widget — IsStateBlocked nil error**: Moved `MTH_EA_IsStateBlocked` definition before its first use, fixing a nil function call on load.
## [1.5.0] - 2026-03-31
### Changed
- **Beast spawn coordinates — custom-zone remap**: Reworked 63 beast coordinate sets in the datastore. The largest changes move placeholder vanilla-zone coords onto the proper Turtle WoW custom zones for **Grim Reaches**, **Northwind**, and **Balor**, and clean up prairie beasts that were incorrectly spilling into **Thunder Bluff**.
- **Beast spawn coordinates — Stonetalon recalibration**: Recalibrated several **Stonetalon Mountains** beast spawn clusters to match the Twow updated zone bounds and improve marker placement consistency.
- **MM Widget — ClickThrough **: The whole widget is now clickable through, and ALT-drag to reposition it is thus no longer possible
- **MM Widget — clearer bottom-cell display**: The bottom cell now shows the cooldown of the current shot you would use, and only shows `USE` when that shot is ready.
- **Feed-O-Matic — NO-BUFF quarantine disabled**: The mechanism that permanently blacklisted a food item per pet family after a failed feed (no buff detected within 2.5 s) has been disabled. Foods are no longer quarantined on a no-buff timeout — the feed will simply retry without banning the item. This avoids false positives caused by server lag or client timeouts.
### Added
- **zCraft — new zButton for crafting professions**: A new `zCraft` button (Options → zButtons → zCraft) scans your spellbook and shows a child button for each crafting profession you know — Alchemy, Blacksmithing, Cooking, Disenchanting, Enchanting, Engineering, Fishing, First Aid, Herbalism, Jewelcrafting, Leatherworking, Mining (Smelting), Skinning, Survival, and Tailoring. Clicking a child opens the corresponding tradeskill window. Disabled by default.
- **zBar — unified draggable toolbar for all zButtons**: A new "zBar" tab (Options → zButtons → zBar) groups all enabled zButton bars into a single draggable anchor. Alt-drag the zBar to move the whole group together. Options: Horizontal or Vertical layout; which side children pop out (Up/Down for horizontal, Left/Right for vertical); spacing between buttons; anchor icon size; per-bar include/exclude toggle and drag-to-reorder. Disabled by default — existing layouts are untouched until you enable it.
- **zAspect / zTrack — Smart Parent**: A new "Smart Parent" checkbox in Options → zButtons → zAspect and zTrack. When enabled, the parent button automatically toggles between your 1st and 2nd spell: if the 1st is active, the parent shows the 2nd, and vice-versa. Perfect for PvP — put Track Hidden and Track Humanoids as your first two tracking spells, and the parent always shows the one you're *not* currently using. When disabled, the parent always shows the 1st spell. Enabled by default.
- **zPet — Smart Parent**: A new "Smart Parent" checkbox in Options → zButtons → zPet. When enabled, the parent button dynamically changes based on your pet's state: Revive Pet if dead, Mend Pet if hurt, Feed Pet if unhappy, or your default spell otherwise. When disabled, the parent always shows the 1st spell. Enabled by default.
- **zMounts / zCompanions — Random Parent**: A new "Random Parent" checkbox in Options → zButtons → zMounts and zCompanions. When enabled, a random child mount is picked as the parent icon on each login and after each use. Disabled by default.
- **zMounts — AQ Mount Filter**: A new "AQ Mount Filter" checkbox in Options → zButtons → zMounts. When enabled, Qiraji mounts are hidden outside AQ40, and only Qiraji mounts are shown inside AQ40. Enabled by default.
- **zAspect — NamPower support**: The Aspect button now listens to `NamPower` aura events when available, so aspect changes update faster and without the old polling delay.
- **MM Widget — NamPower support**: The widget now uses `NamPower` aura updates when available, which makes ammo proc tracking faster and more reliable. It still falls back to the old tracking method if NamPower is not installed.
- **MM Widget — keybind **: The MM Widget options panel now has a "Set Key" button. Press any key (with optional ALT/CTRL/SHIFT modifiers), or mouse button/wheel to bind the MM Widget action, turning it into a One-button rotation feature.
- **MM Widget — Quiver no-clip option**: New "Use Quiver no-clip casting" checkbox in the MM Widget options. When enabled and Quiver is installed, Aimed Shot, Steady Shot, and Multi-Shot of the Action bind are routed through Quiver's no-clip system to avoid clipping your auto-shot swing timer.
- **MM Widget — Anchor **: Since the widget is now clickable-through, a new anchor system was implemented to move the widget: toggling the anchor button in the options shows 4 drag handles (top, bottom, left, right) outside the widget edge. Any handle can be dragged to reposition the widget.
- **Hunter Book — Families stats modifiers**: The Families page now also shows `Health`, `Damage`, and `Armor` modifiers for every family. Those modifier values can be shown as raw multipliers or as `%` with a footer checkbox, and are color-coded for easier reading. Additionaly the footer recap now also shows the total number of families, and the Named/Coord counts are plugged on the new accurate server's data instead of old pfQuest data.
- **FOM — keybind**: The Feed-O-Matic "Set Key" button in options now also accepts modifiers and mouse.
- **/mth food command**: Added a new slash command to inspect Feed-O-Matic state, clear one item ban, or reset all food bans and quarantines.
- **AntiDaze — NamPower support**: Daze detection now uses NamPower's aura events when available, replacing chat-message parsing for faster and more reliable aspect cancellation.
- **Chronometer — NamPower support**: Timer bars now start and stop instantly via NamPower aura events when available. Bar durations are read directly from the NamPower aura array instead of guessed from spell data.
- **Feed-O-Matic — NamPower support**: Mount and buff detection (Shadowmeld, Feign Death, eating, etc.) now uses the NamPower aura array when available, replacing the old tooltip-scraping method.
### Fixed
- **Pet lifecycle — duplicate / ghost pets in Stable**: Reworked pet identity with GUID-based matching and fixed stable tracking so the same pet is no longer split into multiple rows after tame, rename, stable swap, or `/reload`. Old stale pets should also stop reappearing in the Stable tab as fake active/stabled entries.
- **Pet lifecycle — wrong taming info shown on old pets**: Fixed several cases where MetaHunt could invent tame data for pets that were only seen later through stable swaps or normal pet refreshes. Taming info is now only recorded from a real tame event, so old pets should no longer show fake tame location / tame date data.
- **Hunter Book — Stable tab now trusts real stable slots**: The Stable tab now shows the current pet plus the pets that are actually in your stable slots, instead of trusting stale saved slot data from older sessions.
- **Hunter Book — pet training kept on the correct pet**: Fixed cases where a pet could keep its row but lose its Training section after lifecycle repairs or row merges. Training/spellbook data now stays attached to the right pet.
- **Hunter Book — stable feed item icons**: Fixed broken food icons in the Stable tab feed summary, including cases where foods like `Roasted Quail` were showing as a red square or question mark.
- **Minimap markers moving with the player since 1.4.0**: Reworked minimap marker zone resolution and calibration so pins stay anchored to world locations again instead of sliding with player movement. The minimap path now uses exact current-zone lookup in pfQuest space, while the world map keeps the WMA-aware logic introduced for 1.18.1 zone support.
- **Chronometer — "Grow bars upward" forgotten after `/reload`**: Checking the "Grow bars upward" option and reloading the UI would revert the bars back to growing downward, even though the checkbox still appeared checked. Fixed.
- **Chronometer — bars appear below the anchor instead of above when "Grow bars upward" is enabled**: Even with "Grow bars upward" checked, the bars were still spawning a bit below the anchor and stacking upward from there, because of an existing issue in old `CandyBar` library. Fixed — bars now correctly start at the anchor and grow upward.
- **Chronometer — Aimed Shot tracker removed**: Chronometer no longer tracks Aimed Shot cooldowns. The MM Widget now owns that behavior, so Chronometer keeps its hunter timers focused on the remaining spell and proc bars.
- **MM Widget — Quiver no-clip covers Multi-Shot**: The Quiver no-clip option now explicitly includes Multi-Shot alongside Aimed Shot and Steady Shot.
- **MM Widget — stale NamPower proc state**: The widget now resyncs from the NamPower aura array after aura events, so it no longer keeps showing a proc after the debuff is gone.
- **Quiver no-clip macro — Steady Shot castbar not showing**: If you were using Quiver and its no-clip macro for your shots, the Quiver castbar for those shots would not appear with MetaHunt enabled. Fixed — the castbar now appears correctly when using the macro.
## [1.4.1] - 2026-03-24
### Added
- **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
- Added keybinding text overlay on zButtons: parent and child buttons now display their bound key for the 5 bindable sets (Aspect, Track, Trap, Ammo, Pet).
- Added "Expand on Hover" option for zButton children: when enabled, hovering over the parent button automatically expands children (instead of requiring right-click).
- Added "Auto-Hide Timer" option for zButton children: a configurable 010 second timer that automatically collapses expanded children after the mouse leaves the button group. Timer resets while hovering any button in the group.
### Fixed
- Fixed SavedVariables data loss on login caused by initialization race condition overwriting persisted data.
- Fixed SavedVariables bloat by adding a migration that strips redundant default-valued keys on first load.
- Fixed six performance issues including throttling high-frequency event handlers, caching repeated `getglobal` lookups, and reducing unnecessary table allocations in hot paths.
- Cleaned up ICU module global variable leaks by properly scoping locals.
- Standardized all remaining `SetScript` handler patterns to use the WoW 1.12 `this`-based convention consistently.
## [1.1.0] - 2026-03-09
### 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.1.0
## Version: 1.5.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
+23 -11
View File
@@ -1,14 +1,20 @@
# 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 usefull tools for all huntards, wheter they are still leveling, 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.
### Important notes
- Altough Nampower isn't mandatory for most of the features, it is a must-have for some.
- If you were using old versions of Feed-O-Matic, ICU, zHunterMod and HunterHelper, you dont need those with MetaHunt and you should disable them to avoid conflicts.
- MetaHunt is fully compatible with Quiver and is not a replacement for it.
## Twow Data
Metahunt ships with larges 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 +27,8 @@ Metahunt ships with larges up-to-date datastores for all Turtle WOW data a hunte
- 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 +37,7 @@ Metahunt ships with larges up-to-date datastores for all Turtle WOW data a hunte
- 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
@@ -37,8 +45,8 @@ Old zHunterMod addon on steroids. Fully compatible with Twow, and enhanced with
- zPet : Intelligent collapsible pet Bar regrouping all pet spells and providing an intelligent button that always show you the right spell to use depending on the situation
- zTrack: All your trackings in one collapsible bar
- zAspect: All your trackings in one collapsible bar
- zRanged : All the rnaged weampons currently in your bags to swap them fast
- But also zMounts, zToys and zCompanions.
- zRanged : All the ranged weapons currently in your bags to swap them fast
- But also zBar, zCrafts, zMounts, zToys and zCompanions.
## Feed-O-Matic
@@ -59,15 +67,19 @@ Define your own simple rules for auto buy ammo and pet food when you open a vend
## Auto-Quest
Just spam SHIFT-click on the NPC to validate quests Scorpok and Thorium arrows. Addionally you can have an enhanced tooltip that display your actual Scorpok-related items when you mouseover the mobs involved.
Just spam SHIFT-click on the NPC to validate quests Scorpok and Thorium arrows. Additionally you can have an enhanced tooltip that display your actual Scorpok-related items when you mouseover the mobs involved.
## Beast model Viewer
## The book of Hunters
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 book of Hunters 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.
## The Great Book of Huntards
- Browse the Metahunt datastores of NPCs (Beast, vendors, masters) and open the map to locate them like in pfQuest
The Book is the main GUI of MetaHunt, it allows to display the data constantly collected about you and your pet, and browse the addon's data to find anything a Hunter needs.
- Browse the MetaHunt datastores of NPCs (Beast, vendors, masters) and open the map to locate them like in pfQuest
- Track all info about the pet abilities that you know and don't know yet
- Track all info about your stabled pets
- Keep an history of all your pets after you abandon them or if they run away.
+49 -7
View File
@@ -25,16 +25,21 @@ local function AntiDaze_RegisterCombatEvents()
if not AntiDazeFrame then
return
end
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE")
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE")
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PARTY_DAMAGE")
if MTH and MTH.nampower then
AntiDazeFrame:RegisterEvent("DEBUFF_ADDED_SELF")
else
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE")
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE")
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PARTY_DAMAGE")
end
end
local function AntiDaze_UnregisterCombatEvents()
if not AntiDazeFrame then
return
end
AntiDazeFrame:UnregisterEvent("DEBUFF_ADDED_SELF")
AntiDazeFrame:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE")
AntiDazeFrame:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE")
AntiDazeFrame:UnregisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
@@ -87,10 +92,31 @@ local function AntiDaze_ResolvePlayerFromMessage(msg)
return nil
end
local AntiDaze_DazeSpellIdCache = {} -- [spellId] = true/false
local function AntiDaze_IsDazeSpellId(spellId)
local cached = AntiDaze_DazeSpellIdCache[spellId]
if cached ~= nil then return cached end
if type(GetSpellRecField) ~= "function" then return false end
local name = GetSpellRecField(spellId, "name")
if name and (name == "Daze" or name == "Dazed") then
AntiDaze_DazeSpellIdCache[spellId] = true
return true
end
AntiDaze_DazeSpellIdCache[spellId] = false
return false
end
local function AntiDaze_CancelRelevantAspect(isSelfDazed)
local packName = _G["ZHUNTER_ASPECT_PACK"] or "Aspect of the Pack"
local cheetahName = _G["ZHUNTER_ASPECT_CHEETAH"] or "Aspect of the Cheetah"
-- Always use GetPlayerBuff + tooltip scan for cancellation.
-- The aura-array slot index from GetUnitField does NOT map 1:1
-- to the sequential buff index that CancelPlayerBuff expects
-- (empty slots create gaps), so we cannot use the NamPower
-- array for the cancel call. NamPower is still used for daze
-- DETECTION via DEBUFF_ADDED_SELF in the event handler.
for i = 0, 32 do
if GetPlayerBuff(i, "HELPFUL") < 0 then
break
@@ -98,7 +124,7 @@ local function AntiDaze_CancelRelevantAspect(isSelfDazed)
AntiDazeTooltip:SetOwner(UIParent, "ANCHOR_NONE")
AntiDazeTooltip:SetPlayerBuff(i, "HELPFUL")
local text = AntiDazeTooltipTextLeft1 and AntiDazeTooltipTextLeft1:GetText()
local text = MTH_AntiDazeProbeTextLeft1 and MTH_AntiDazeProbeTextLeft1:GetText()
if text == packName or (isSelfDazed and text == cheetahName) then
CancelPlayerBuff(i)
return 1
@@ -112,12 +138,18 @@ local function AntiDaze_HandleSlash()
AntiDaze_SetEnabled(not AntiDaze_IsEnabled())
end
AntiDazeTooltip = CreateFrame("GameTooltip", "AntiDazeTooltip", nil, "GameTooltipTemplate")
AntiDazeFrame = CreateFrame("Frame", "AntiDazeFrame")
AntiDazeTooltip = CreateFrame("GameTooltip", "MTH_AntiDazeProbe", nil, "GameTooltipTemplate")
AntiDazeFrame = CreateFrame("Frame", "MTH_AntiDazeEvent")
if AntiDazeFrame then
AntiDazeFrame:RegisterEvent("VARIABLES_LOADED")
AntiDazeFrame:SetScript("OnEvent", function()
if event == "VARIABLES_LOADED" then
-- Class gate: only hunters use AntiDaze.
if MTH and MTH.IsClassGateBlocked and MTH:IsClassGateBlocked() then
AntiDazeFrame:UnregisterAllEvents()
AntiDazeFrame:SetScript("OnEvent", nil)
return
end
if AntiDaze_IsEnabled() then
AntiDaze_RegisterCombatEvents()
else
@@ -126,6 +158,16 @@ if AntiDazeFrame then
return
end
-- NamPower path: DEBUFF_ADDED_SELF fires with spellId in arg3
if event == "DEBUFF_ADDED_SELF" then
local spellId = tonumber(arg3)
if spellId and AntiDaze_IsDazeSpellId(spellId) then
AntiDaze_CancelRelevantAspect(true)
end
return
end
-- Legacy path: parse chat messages
local player = AntiDaze_ResolvePlayerFromMessage(arg1)
if player then
local you = _G["ZHUNTER_YOU"] or "You"
+26 -8
View File
@@ -112,7 +112,7 @@ end
local function AutoStrip_QueueDeferredRestore(reason)
if not AutoStrip_RestoreFrame then
AutoStrip_RestoreFrame = CreateFrame("Frame", "AutoStripRestoreFrame")
AutoStrip_RestoreFrame = CreateFrame("Frame", "MTH_AutoStripRestore")
if not AutoStrip_RestoreFrame then
return
end
@@ -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,11 +260,19 @@ 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
-- Class gate: only hunters use AutoStrip.
if MTH and MTH.IsClassGateBlocked and MTH:IsClassGateBlocked() then
AutoStrip_Frame:UnregisterAllEvents()
AutoStrip_Frame:SetScript("OnEvent", nil)
return
end
local saved = AutoStrip_GetSaved()
AutoStrip_Trace("VARIABLES_LOADED saved.autostrip=" .. tostring(saved["autostrip"] and 1 or 0)
.. " saved.display=" .. tostring(saved["display"] and 1 or 0))
@@ -316,11 +332,13 @@ local function AutoStrip_HandleSlash(msg)
end
end
AutoStrip_Frame = CreateFrame("Frame", "AutoStripFrame")
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)
+463
View File
@@ -0,0 +1,463 @@
-- ============================================================
-- Bag Ammo Labels — standalone overlay for bag & bank ammo slots
-- Uses globals from zButtonAmmo.lua:
-- zButtonAmmo_ShortArrowLabels, zButtonAmmo_ShortBulletLabels,
-- zButtonAmmo_GetQualityColor, zButtonAmmo_GetColorGradient,
-- zButtonAmmo_GetKnownAmmoTypes
-- Uses DPS data from MTH_DS_AmmoItems[id].dps
-- ============================================================
-- Saved variable cache (per-character, stored in "general" module)
local cache = {} -- bagAmmoLabels, bankAmmoLabels, bagAmmoDamage
local function GetStore()
return MTH and MTH.GetModuleCharSavedVariables and MTH:GetModuleCharSavedVariables("general")
end
local function GetCached(key, default)
if cache[key] == nil then
local store = GetStore()
if type(store) == "table" and store[key] ~= nil then
cache[key] = store[key] and true or false
else
cache[key] = default or false
end
end
return cache[key]
end
local function SetCached(key, enabled)
cache[key] = enabled and true or false
local store = GetStore()
if type(store) == "table" then
store[key] = cache[key]
end
end
-- Public getters/setters
function MTH_BagAmmoLabels_GetEnabled()
return GetCached("bagAmmoLabels")
end
function MTH_BagAmmoLabels_SetEnabled(enabled)
SetCached("bagAmmoLabels", enabled)
MTH_BagAmmoLabels_RefreshAll()
end
function MTH_BankAmmoLabels_GetEnabled()
return GetCached("bankAmmoLabels")
end
function MTH_BankAmmoLabels_SetEnabled(enabled)
SetCached("bankAmmoLabels", enabled)
MTH_BagAmmoLabels_RefreshAllBank()
end
function MTH_BagAmmoDamage_GetEnabled()
return GetCached("bagAmmoDamage")
end
function MTH_BagAmmoDamage_SetEnabled(enabled)
SetCached("bagAmmoDamage", enabled)
MTH_BagAmmoLabels_RefreshAll()
MTH_BagAmmoLabels_RefreshAllBank()
end
-- Heat color: red (7.5 DPS) → yellow → green (20.5 DPS); below 7.5 = grey
local DPS_MIN = 7.5
local DPS_MAX = 20.5
local function GetDpsHeatColor(dps)
if not dps then return 1, 1, 1 end
if dps < DPS_MIN then return 0.6, 0.6, 0.6 end
local perc = (dps - DPS_MIN) / (DPS_MAX - DPS_MIN)
return zButtonAmmo_GetColorGradient(perc)
end
-- Look up DPS from MTH_DS_AmmoItems by numeric item ID
local function GetAmmoDps(numericId)
if not MTH_DS_AmmoItems then return nil end
local entry = MTH_DS_AmmoItems[numericId]
return entry and entry.dps
end
-- Detect whether pfUI bags are active
local function HasPfUI()
return pfUI and pfUI.bags and pfUI.bag and true or false
end
-- Is this bagID a bank bag?
local BANK_BAGS = { [-1] = true, [5] = true, [6] = true, [7] = true, [8] = true, [9] = true, [10] = true, [11] = true }
local function IsBankBag(bagID)
return BANK_BAGS[bagID] or false
end
-- Apply or clear labels on a single bag-slot button
local function UpdateButton(button, bagID, slot, showName, showDmg)
if not button then return end
-- Lazy-create name label (TOPLEFT)
if not button._mthAmmoLabel then
button._mthAmmoLabel = button:CreateFontString(nil, "OVERLAY")
button._mthAmmoLabel:SetPoint("TOPLEFT", button, "TOPLEFT", 1, -1)
button._mthAmmoLabel:SetFont("Fonts\\ARIALN.ttf", 9, "OUTLINE")
button._mthAmmoLabel:Hide()
end
-- Lazy-create damage label (CENTER, large + bold)
if not button._mthAmmoDmgLabel then
button._mthAmmoDmgLabel = button:CreateFontString(nil, "OVERLAY")
button._mthAmmoDmgLabel:SetPoint("CENTER", button, "CENTER", 0, 0)
button._mthAmmoDmgLabel:SetFont("Fonts\\ARIALN.ttf", 10, "THICKOUTLINE")
button._mthAmmoDmgLabel:Hide()
end
local nameLabel = button._mthAmmoLabel
local dmgLabel = button._mthAmmoDmgLabel
if not showName and not showDmg then
nameLabel:Hide()
dmgLabel:Hide()
return
end
local link = GetContainerItemLink(bagID, slot)
if not link then
nameLabel:Hide()
dmgLabel:Hide()
return
end
local _, _, id = string.find(link, "Hitem:(%d+)")
if not id then
nameLabel:Hide()
dmgLabel:Hide()
return
end
local numericId = tonumber(id)
local itemName, _, itemQuality, _, _, _, _, _, itemSlot = GetItemInfo(id)
if not itemName then
nameLabel:Hide()
dmgLabel:Hide()
return
end
local isAmmo = (itemSlot == "INVTYPE_AMMO")
if not isAmmo then
local knownAmmo = zButtonAmmo_GetKnownAmmoTypes and zButtonAmmo_GetKnownAmmoTypes()
isAmmo = knownAmmo and knownAmmo[itemName]
end
if not isAmmo then
nameLabel:Hide()
dmgLabel:Hide()
return
end
-- Name label
if showName then
local shortText = zButtonAmmo_ShortArrowLabels[itemName]
or zButtonAmmo_ShortBulletLabels[itemName]
if shortText then
local cr, cg, cb = zButtonAmmo_GetQualityColor(itemQuality)
nameLabel:SetTextColor(cr, cg, cb, 1)
nameLabel:SetText(shortText)
nameLabel:Show()
else
nameLabel:Hide()
end
else
nameLabel:Hide()
end
-- Damage label
if showDmg then
local dps = GetAmmoDps(numericId)
if dps then
local cr, cg, cb = GetDpsHeatColor(dps)
dmgLabel:SetTextColor(cr, cg, cb, 1)
dmgLabel:SetText(dps)
dmgLabel:Show()
else
dmgLabel:Hide()
end
else
dmgLabel:Hide()
end
end
-- Clear both labels on a button
local function ClearButton(button)
if not button then return end
if button._mthAmmoLabel then button._mthAmmoLabel:Hide() end
if button._mthAmmoDmgLabel then button._mthAmmoDmgLabel:Hide() end
end
-- Forward declaration
local RefreshAllVisibleFrames
-- Refresh labels on a single bag (works for any bagID)
local function RefreshBag(bagID, showName, showDmg)
local numSlots = GetContainerNumSlots(bagID)
if numSlots == 0 then return end
-- pfUI path
if HasPfUI() then
local pfBag = pfUI.bags[bagID]
if not pfBag or not pfBag.slots then return end
for slot = 1, numSlots do
local slotData = pfBag.slots[slot]
local button = slotData and slotData.frame
if button then
if showName or showDmg then
UpdateButton(button, bagID, slot, showName, showDmg)
else
ClearButton(button)
end
end
end
return
end
-- Default UI path
-- Main bank (bagID -1): buttons are BankFrameItem1..28
if bagID == -1 then
for slot = 1, numSlots do
local button = getglobal("BankFrameItem" .. slot)
if button then
if showName or showDmg then
UpdateButton(button, -1, slot, showName, showDmg)
else
ClearButton(button)
end
end
end
return
end
-- All other bags (0-4 and 5-11): search all ContainerFrames
for ci = 1, 13 do
local frame = getglobal("ContainerFrame" .. ci)
if frame and frame:IsVisible() and frame:GetID() == bagID then
local frameName = frame:GetName()
-- Visual button i = API slot (numSlots - i + 1)
for i = 1, numSlots do
local button = getglobal(frameName .. "Item" .. i)
local apiSlot = numSlots - i + 1
if button then
if showName or showDmg then
UpdateButton(button, bagID, apiSlot, showName, showDmg)
else
ClearButton(button)
end
end
end
-- Clear stale labels on any buttons beyond numSlots
for slot = numSlots + 1, 24 do
local button = getglobal(frameName .. "Item" .. slot)
if not button then break end
ClearButton(button)
end
return
end
end
end
-- Public: refresh all inventory bags (0-4)
function MTH_BagAmmoLabels_RefreshAll()
RefreshAllVisibleFrames()
end
-- Public: refresh all bank bags (-1, 5-11)
function MTH_BagAmmoLabels_RefreshAllBank()
RefreshAllVisibleFrames()
end
-- Refresh all visible default UI ContainerFrames (frame-centric: no bagID lookup)
RefreshAllVisibleFrames = function()
local bagShowName = MTH_BagAmmoLabels_GetEnabled()
local bankShowName = MTH_BankAmmoLabels_GetEnabled()
local showDmg = MTH_BagAmmoDamage_GetEnabled()
if HasPfUI() then
-- pfUI: refresh via pfUI bag structure
for bagID = 0, 4 do
RefreshBag(bagID, bagShowName, showDmg)
end
local bankBags = { -1, 5, 6, 7, 8, 9, 10, 11 }
for _, bagID in ipairs(bankBags) do
RefreshBag(bagID, bankShowName, showDmg)
end
return
end
-- Default UI: iterate all visible ContainerFrames
for ci = 1, 13 do
local cf = getglobal("ContainerFrame" .. ci)
if cf and cf:IsVisible() then
local bagID = cf:GetID()
local showName = IsBankBag(bagID) and bankShowName or bagShowName
local numSlots = GetContainerNumSlots(bagID)
local fname = cf:GetName()
-- Visual button i = API slot (numSlots - i + 1)
for i = 1, numSlots do
local button = getglobal(fname .. "Item" .. i)
local apiSlot = numSlots - i + 1
if button then
if showName or showDmg then
UpdateButton(button, bagID, apiSlot, showName, showDmg)
else
ClearButton(button)
end
end
end
for slot = numSlots + 1, 24 do
local button = getglobal(fname .. "Item" .. slot)
if not button then break end
ClearButton(button)
end
end
end
-- Bank bag -1: BankFrameItem buttons
if bankShowName or showDmg then
local numSlots = GetContainerNumSlots(-1)
for slot = 1, numSlots do
local button = getglobal("BankFrameItem" .. slot)
if button then
UpdateButton(button, -1, slot, bankShowName, showDmg)
end
end
end
end
-- Event-driven refresh: coalesce with a short delay
do
local frame = CreateFrame("Frame", "MTH_BagAmmoLabelsFrame", UIParent)
local needsRefresh = false
local elapsed = 0
frame:RegisterEvent("BAG_UPDATE")
frame:RegisterEvent("PLAYERBANKSLOTS_CHANGED")
frame:RegisterEvent("PLAYERBANKBAGSLOTS_CHANGED")
frame:RegisterEvent("BANKFRAME_OPENED")
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:SetScript("OnEvent", function()
if event == "PLAYER_ENTERING_WORLD" then
-- After login/reload, delay 1s for item cache to populate
elapsed = -0.95
needsRefresh = true
frame:Show()
return
end
if event == "BANKFRAME_OPENED" then
if MTH_BankAmmoLabels_GetEnabled() or MTH_BagAmmoDamage_GetEnabled() then
needsRefresh = true
if not frame:IsShown() then
elapsed = 0
frame:Show()
end
end
return
end
if event == "PLAYERBANKSLOTS_CHANGED" or event == "PLAYERBANKBAGSLOTS_CHANGED" then
if MTH_BankAmmoLabels_GetEnabled() or MTH_BagAmmoDamage_GetEnabled() then
needsRefresh = true
if not frame:IsShown() then
elapsed = 0
frame:Show()
end
end
return
end
-- BAG_UPDATE
local bagID = arg1
if not bagID then return end
if IsBankBag(bagID) then
if not MTH_BankAmmoLabels_GetEnabled() and not MTH_BagAmmoDamage_GetEnabled() then return end
else
if not MTH_BagAmmoLabels_GetEnabled() and not MTH_BagAmmoDamage_GetEnabled() then return end
end
needsRefresh = true
if not frame:IsShown() then
elapsed = 0
frame:Show()
end
end)
frame:SetScript("OnUpdate", function()
elapsed = elapsed + (arg1 or 0.016)
if elapsed < 0.05 then return end
needsRefresh = false
frame:Hide()
RefreshAllVisibleFrames()
end)
frame:Hide()
-- Hook ContainerFrame OnShow: trigger a refresh when a bag becomes visible
if not HasPfUI() then
for ci = 1, 13 do
local cf = getglobal("ContainerFrame" .. ci)
if cf then
local origOnShow = cf:GetScript("OnShow")
cf:SetScript("OnShow", function()
if origOnShow then origOnShow() end
needsRefresh = true
if not frame:IsShown() then
elapsed = 0
frame:Show()
end
end)
end
end
end
-- Post-hook ContainerFrame_Update for default UI (synchronous update)
if type(ContainerFrame_Update) == "function" then
local origCFU = ContainerFrame_Update
ContainerFrame_Update = function(cf)
origCFU(cf)
if cf and cf:IsVisible() then
local bagID = cf:GetID()
local showDmg = MTH_BagAmmoDamage_GetEnabled()
local showName
if IsBankBag(bagID) then
showName = MTH_BankAmmoLabels_GetEnabled()
else
showName = MTH_BagAmmoLabels_GetEnabled()
end
if showName or showDmg then
local numSlots = GetContainerNumSlots(bagID)
local fname = cf:GetName()
-- Visual button i = API slot (numSlots - i + 1)
for i = 1, numSlots do
local button = getglobal(fname .. "Item" .. i)
local apiSlot = numSlots - i + 1
if button then
UpdateButton(button, bagID, apiSlot, showName, showDmg)
end
end
for slot = numSlots + 1, 24 do
local button = getglobal(fname .. "Item" .. slot)
if not button then break end
ClearButton(button)
end
end
else
-- Frame hidden: clear all labels
if cf then
local fname = cf:GetName()
if fname then
for slot = 1, 24 do
local button = getglobal(fname .. "Item" .. slot)
if not button then break end
ClearButton(button)
end
end
end
end
end
end
end
+8
View File
@@ -0,0 +1,8 @@
------------------------------------------------------
-- MetaHunt: Beast Lore Scanner (removed)
------------------------------------------------------
-- This file previously contained a feature that recorded
-- unknown beasts to SavedVariables when scanned with
-- Beast Lore. The feature has been removed; the static
-- beast database (ds-beasts) is now the sole data source.
------------------------------------------------------
+16 -4
View File
@@ -1,5 +1,5 @@
MTH_CONST = MTH_CONST or {}
MTH_CONST.version = MTH_CONST.version or "1.1.0"
MTH_CONST.version = MTH_CONST.version or "1.5.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,
+72 -9
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()
@@ -163,7 +167,7 @@ local function MTH_FEED_IndexBagFoods()
return true
end
local function MTH_FEED_HasPetFeedBuff()
function MTH_FEED_HasPetFeedBuff()
if type(UnitBuff) ~= "function" then
return false
end
@@ -460,6 +464,7 @@ local function MTH_FEED_CoreTrackerOnEvent()
end
elseif evt == "BAG_UPDATE" then
MTH_FEED_IndexBagFoods()
MTH_FEED_InvalidateDietMapCache()
elseif evt == "CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS" then
if MTH_FEED_ActiveCoreAttemptId and MTH_FEED_HasPetFeedBuff() then
MTH_FEED_Trace("Core tracker buff event confirms acceptance")
@@ -468,7 +473,32 @@ local function MTH_FEED_CoreTrackerOnEvent()
end
end
local MTH_FEED_CoreTrackerUpdateElapsed = 0
local MTH_FEED_LastStaleCleanupAt = 0
local function MTH_FEED_CoreTrackerOnUpdate()
MTH_FEED_CoreTrackerUpdateElapsed = MTH_FEED_CoreTrackerUpdateElapsed + (arg1 or 0)
if MTH_FEED_CoreTrackerUpdateElapsed < 0.10 then
return
end
MTH_FEED_CoreTrackerUpdateElapsed = 0
-- Periodic stale attempt cleanup (every ~30 seconds)
local now = GetTime() or 0
if (now - MTH_FEED_LastStaleCleanupAt) >= 30 then
MTH_FEED_LastStaleCleanupAt = now
if MTH_FEED_Runtime and type(MTH_FEED_Runtime.attempts) == "table" then
for id, row in pairs(MTH_FEED_Runtime.attempts) do
if type(row) == "table" then
local age = now - (tonumber(row.startedAt) or now)
if age > 10 then
MTH_FEED_Runtime.attempts[id] = nil
end
end
end
end
end
local attemptId = MTH_FEED_ActiveCoreAttemptId
if attemptId == nil then
return
@@ -499,7 +529,7 @@ local function MTH_FEED_EnsureTrackerFrame()
if MTH_FEED_TrackerFrame then
return MTH_FEED_TrackerFrame
end
local frame = CreateFrame("Frame", "MTH_FeedTrackingFrame")
local frame = CreateFrame("Frame", "MTH_FeedTracking")
if not frame then
return nil
end
@@ -610,6 +640,7 @@ end
function MTH_FEED_ReinstallCoreTracking()
local ok = MTH_FEED_InstallCoreHooks()
MTH_FEED_EnsureTrackerFrame()
return ok and true or false
end
@@ -617,19 +648,17 @@ local function MTH_FEED_EnsureBootstrapFrame()
if MTH_FEED_BootstrapFrame ~= nil then
return MTH_FEED_BootstrapFrame
end
local frame = CreateFrame("Frame", "MTH_FeedTrackingBootstrapFrame")
local frame = CreateFrame("Frame", "MTH_FeedTrackingBoot")
if not frame then
return nil
end
frame:RegisterEvent("VARIABLES_LOADED")
frame:RegisterEvent("PLAYER_LOGIN")
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:SetScript("OnEvent", function(self)
frame:SetScript("OnEvent", function()
MTH_FEED_ReinstallCoreTracking()
if self and self.UnregisterAllEvents then
self:UnregisterAllEvents()
self:SetScript("OnEvent", nil)
end
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
end)
MTH_FEED_BootstrapFrame = frame
return frame
@@ -826,7 +855,16 @@ local function MTH_FEED_NormalizeReason(reason, rawMessage)
return "unknown"
end
local MTH_FEED_DietMapCache = nil
MTH_FEED_InvalidateDietMapCache = function()
MTH_FEED_DietMapCache = nil
end
local function MTH_FEED_GetDietByItemIdMap()
if MTH_FEED_DietMapCache then
return MTH_FEED_DietMapCache
end
local map = {}
if type(FOM_Foods) ~= "table" then
return map
@@ -841,6 +879,7 @@ local function MTH_FEED_GetDietByItemIdMap()
end
end
end
MTH_FEED_DietMapCache = map
return map
end
@@ -1423,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
+87 -69
View File
@@ -1,5 +1,5 @@
MTH = MTH or {
version = "1.1.0",
version = "1.5.1",
name = "MetaHunt",
modules = {},
config = {},
@@ -44,7 +44,7 @@ local function MTH_ClassGateEnsureAnnounceFrame(self)
if not self or self._classGateAnnounceFrame then
return
end
local frame = CreateFrame("Frame", "MTH_ClassGateAnnounceFrame")
local frame = CreateFrame("Frame", "MTH_ClassGateAnnounce")
if not frame then
return
end
@@ -55,12 +55,12 @@ local function MTH_ClassGateEnsureAnnounceFrame(self)
end
end)
frame._mthElapsed = 0
frame:SetScript("OnUpdate", function(self, elapsed)
self._mthElapsed = (self._mthElapsed or 0) + (elapsed or arg1 or 0)
if self._mthElapsed < 0.5 then
frame:SetScript("OnUpdate", function()
this._mthElapsed = (this._mthElapsed or 0) + (arg1 or 0)
if this._mthElapsed < 0.5 then
return
end
self._mthElapsed = 0
this._mthElapsed = 0
if MTH and MTH._classGateAnnouncedChat then
MTH_ClassGateClearAnnounceFrame(MTH)
return
@@ -192,21 +192,19 @@ end
MTH:ApplyClassGate("startup")
if not MTH._classGateLifecycleFrame then
local classGateFrame = CreateFrame("Frame", "MTH_ClassGateLifecycleFrame")
local classGateFrame = CreateFrame("Frame", "MTH_ClassGateLifecycle")
if classGateFrame then
classGateFrame:RegisterEvent("PLAYER_LOGIN")
classGateFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
classGateFrame:SetScript("OnEvent", function(self, eventName)
self = self or this
eventName = eventName or event
if MTH and eventName == "PLAYER_LOGIN" then
classGateFrame:SetScript("OnEvent", function()
if MTH and event == "PLAYER_LOGIN" then
MTH._classGateAnnouncedChat = nil
MTH._classGateAnnounced = nil
MTH._classGateAnnouncePending = nil
end
if MTH and MTH.ApplyClassGate and MTH:ApplyClassGate(eventName) and MTH._classGateAnnouncedChat then
self:UnregisterAllEvents()
self:SetScript("OnEvent", nil)
if MTH and MTH.ApplyClassGate and MTH:ApplyClassGate(event) and MTH._classGateAnnouncedChat then
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
end
end)
MTH._classGateLifecycleFrame = classGateFrame
@@ -268,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
@@ -425,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
@@ -463,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
@@ -474,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
@@ -486,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] = {}
@@ -502,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] = {}
@@ -517,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] = {}
@@ -527,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
@@ -557,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()
@@ -565,10 +546,10 @@ function MTH:InitEventRouter()
return self._eventRouter.frame
end
local frame = CreateFrame("Frame", "MTH_EventRouterFrame")
frame:SetScript("OnEvent", function(self, evt, a1, a2, a3, a4, a5, a6, a7, a8, a9)
local frame = CreateFrame("Frame", "MTH_EventRouter")
frame:SetScript("OnEvent", function()
if MTH and MTH.DispatchEvent then
MTH:DispatchEvent(evt, a1, a2, a3, a4, a5, a6, a7, a8, a9)
MTH:DispatchEvent(event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
end
end)
@@ -628,7 +609,7 @@ function MTH:_SetModuleEventSubscriptions(name, enabled)
end
end
function MTH:DispatchEvent(evt, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
function MTH:DispatchEvent(evt, ea1, ea2, ea3, ea4, ea5, ea6, ea7, ea8, ea9)
for name, module in pairs(self.modules) do
if module.enabled and module.onEvent then
local allow = true
@@ -637,11 +618,10 @@ function MTH:DispatchEvent(evt, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
end
if allow then
local ok, err = pcall(function()
module:onEvent(evt, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
module:onEvent(evt, ea1, ea2, ea3, ea4, ea5, ea6, ea7, ea8, ea9)
end)
if not ok then
self:Print("Module '" .. name .. "' event error on '" .. tostring(evt) .. "': " .. tostring(err))
MTH:Print("[MTH] Module event error (" .. name .. ", " .. tostring(evt) .. "): " .. tostring(err), "error")
MTH:Print("Module '" .. name .. "' event error on '" .. tostring(evt) .. "': " .. tostring(err), "error")
end
end
end
@@ -672,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
@@ -687,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)
@@ -712,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
@@ -777,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
@@ -799,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
@@ -1038,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
@@ -1086,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)
@@ -1273,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)
@@ -1295,24 +1267,22 @@ function MTH:EnsureModuleStateBootstrap()
return self._moduleStateBootstrapFrame
end
local frame = CreateFrame("Frame", "MTH_ModuleStateBootstrapFrame")
local frame = CreateFrame("Frame", "MTH_ModuleBootstrap")
if not frame then
return nil
end
frame:RegisterEvent("VARIABLES_LOADED")
frame:SetScript("OnEvent", function(self, eventName)
self = self or this
eventName = eventName or event
if eventName ~= "VARIABLES_LOADED" then
frame:SetScript("OnEvent", function()
if event ~= "VARIABLES_LOADED" then
return
end
if MTH and MTH.ApplyPersistedModuleStates then
MTH:ApplyPersistedModuleStates("VARIABLES_LOADED")
end
if not self then return end
self:UnregisterAllEvents()
self:SetScript("OnEvent", nil)
if not this then return end
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
end)
self._moduleStateBootstrapFrame = frame
@@ -1341,7 +1311,17 @@ function MTH:AnnounceLoadComplete()
moduleList = table.concat(enabled, ", ")
end
self:Print("Check ignition: OK! Version " .. tostring(self.version or "unknown") .. " loaded.")
local namPowerStatus = ""
if type(GetNampowerVersion) == "function" then
local maj, min = GetNampowerVersion()
maj = tonumber(maj) or 0
min = tonumber(min) or 0
if maj > 2 or (maj == 2 and min >= 40) then
namPowerStatus = " | NamPower detected"
self.nampower = true
end
end
self:Print("Check Ignition.... OK! Version " .. tostring(self.version or "unknown") .. " loaded." .. namPowerStatus)
if self:IsMessageEnabled("initModulesLoaded", false) then
self:Print("Modules enabled : " .. moduleList .. ".")
end
@@ -1490,6 +1470,46 @@ function MTH:AnnounceLoadComplete()
"Addon synced. Your threat meter just sent a stress signal.",
"Loaded cleanly. Reputation with healers currently in freefall.",
"Mission start: hit things from far away and deny everything in chat.",
"Welcome back. Your pet missed you. Just kidding, it ran away.",
"MetaHunt loaded. You're still a Huntard, but now you're an organized one.",
"Aspects, traps, tracking... Don't worry, I'll handle the hard parts.",
"Oh good, another hunter who needs an addon to remember their own spells.",
"MetaHunt ready. Feign Death is not a personality trait.",
"Loaded successfully. No, I can't fix your DPS.",
"Welcome. Your pet's loyalty is higher than your raid attendance.",
"MetaHunt online. Please don't Multishot the sheep.",
"Greetings, hunter. I see you've chosen the 'easy' class and still need help.",
"All systems go. Try not to pull the entire dungeon this time.",
"MetaHunt activated. Reminder: Melee range is not your home.",
"Welcome back. Your ammo count is almost as low as your situational awareness.",
"Loaded. I'll manage your buttons since you clearly have too many.",
"MetaHunt ready. Aspect of the Cheetah in dungeons is a choice, not a strategy.",
"Oh look, a hunter that uses addons. There might be hope for you yet.",
"Welcome. No, you cannot tame that. Or that. Stop asking.",
"MetaHunt standing by. Unlike you during your last Feign Death.",
"Initialized. Fun fact: your pet has more utility than you do.",
"All buttons configured. Now if only I could configure your aim.",
"MetaHunt loaded. Growl is off, right? ...RIGHT?",
"MetaHunt loaded. Your Freezing Trap will still break early. That's on you.",
"Welcome. Aspect of the Pack is ready. Your group is not.",
"Initializing... unlike your pet, I actually come when called.",
"MetaHunt online. Remember: Hunter loot is everything. Everything.",
"Loaded. Concussive Shot the flag carrier, not the rogue vanishing.",
"Welcome back. Eyes of the Beast is not a valid boss strategy.",
"Ready. Your Aimed Shot takes longer to cast than this addon took to load.",
"MetaHunt armed. Scatter Shot into Freezing Trap — you'll get it right someday.",
"Online. Don't worry, I track your cooldowns since you clearly don't.",
"Welcome. Wing Clip and run. It's what you do best.",
"Loaded. Distracting Shot is for the boss, not for your ego.",
"MetaHunt ready. Your quiver is full but your skill bar is empty.",
"Initialized. Serpent Sting on every target doesn't make you a DoT class.",
"Welcome, hunter. Viper Sting the healer. No, the OTHER healer.",
"Ready to hunt. Mend Pet is not optional, it's a lifestyle.",
"MetaHunt loaded. Tranquilizing Shot the enrage, not the warrior.",
"Online. Rapid Fire is a cooldown, not your approach to pulling.",
"Welcome. You have 28 arrows left. Should've checked before the raid.",
"Loaded. Bestial Wrath does not make YOU immune. Learned that yet?",
"MetaHunt standing by. Dead zone? What dead zone? Oh... that dead zone.",
}
local welcomeIndex = 1
@@ -1520,24 +1540,22 @@ function MTH:EnsureLoadCompleteAnnouncement()
return self._loadCompleteFrame
end
local frame = CreateFrame("Frame", "MTH_LoadCompleteAnnounceFrame")
local frame = CreateFrame("Frame", "MTH_LoadAnnounce")
if not frame then
return nil
end
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:SetScript("OnEvent", function(self, eventName)
self = self or this
eventName = eventName or event
if eventName ~= "PLAYER_ENTERING_WORLD" then
frame:SetScript("OnEvent", function()
if event ~= "PLAYER_ENTERING_WORLD" then
return
end
if MTH and MTH.AnnounceLoadComplete then
MTH:AnnounceLoadComplete()
end
if not self then return end
self:UnregisterAllEvents()
self:SetScript("OnEvent", nil)
if not this then return end
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
end)
self._loadCompleteFrame = frame
+8 -14
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
@@ -802,7 +800,7 @@ function MTH_PT_InitService()
if MTH_PetTrainingFrame then
return
end
MTH_PetTrainingFrame = CreateFrame("Frame", "MTHPetTrainingScanFrame")
MTH_PetTrainingFrame = CreateFrame("Frame", "MTH_PetTrainingScan")
MTH_PetTrainingFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
MTH_PetTrainingFrame:RegisterEvent("LEARNED_SPELL_IN_TAB")
MTH_PetTrainingFrame:RegisterEvent("PET_TRAINING_SHOW")
@@ -813,9 +811,8 @@ function MTH_PT_InitService()
MTH_PetTrainingFrame:RegisterEvent("CRAFT_SHOW")
MTH_PetTrainingFrame:RegisterEvent("CRAFT_UPDATE")
MTH_PetTrainingFrame:SetScript("OnEvent", MTH_PT_OnEvent)
MTH_PetTrainingFrame:SetScript("OnUpdate", function(_, elapsed)
elapsed = elapsed or arg1
MTH_PT_PetBookPollElapsed = (MTH_PT_PetBookPollElapsed or 0) + (elapsed or 0)
MTH_PetTrainingFrame:SetScript("OnUpdate", function()
MTH_PT_PetBookPollElapsed = (MTH_PT_PetBookPollElapsed or 0) + (arg1 or 0)
if MTH_PT_PetBookPollElapsed < 0.20 then
return
end
@@ -861,19 +858,16 @@ function MTH_PT_InitBootstrap()
end
frame:RegisterEvent("PET_TRAINING_SHOW")
frame:RegisterEvent("CRAFT_SHOW")
frame:SetScript("OnEvent", function(self, evt)
evt = evt or event
if evt ~= "PET_TRAINING_SHOW" and evt ~= "CRAFT_SHOW" then
frame:SetScript("OnEvent", function()
if event ~= "PET_TRAINING_SHOW" and event ~= "CRAFT_SHOW" then
return
end
MTH_PT_InitService()
if type(MTH_PT_OnEvent) == "function" then
MTH_PT_OnEvent(MTH_PetTrainingFrame, evt)
end
if self and self.UnregisterAllEvents then
self:UnregisterAllEvents()
self:SetScript("OnEvent", nil)
MTH_PT_OnEvent(MTH_PetTrainingFrame, event)
end
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
MTH_PT_BootstrapFrame = nil
end)
MTH_PT_BootstrapFrame = frame
+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()
+1 -1
View File
@@ -331,7 +331,7 @@ function MTH_PS_InitService()
return
end
MTH_PS_Frame = CreateFrame("Frame", "MTHPetSpellbookEventFrame")
MTH_PS_Frame = CreateFrame("Frame", "MTH_PetSpellbookEvent")
MTH_PS_Frame:RegisterEvent("PLAYER_ENTERING_WORLD")
MTH_PS_Frame:RegisterEvent("UNIT_PET")
MTH_PS_Frame:RegisterEvent("PET_BAR_UPDATE")
+503 -145
View File
@@ -4,6 +4,7 @@ end
local MTH_PETS_SCHEMA_VERSION = 2
local MTH_PETS_CORE_HOOK_BOUNDARY_KEY = "core-pet-rename-hook"
local MTH_ST_FULL_DEBUG_TRACE = false
MTH_PETS_TRACE_CONSISTENCY = false
local MTH_PetLifecycleEventFrame = nil
@@ -24,8 +25,8 @@ local MTH_ST_Profile = {
runawayMaxMs = 0,
}
local MTH_ST_Trace = {
enabled = false,
verbose = false,
enabled = MTH_ST_FULL_DEBUG_TRACE and true or false,
verbose = MTH_ST_FULL_DEBUG_TRACE and true or false,
windowStart = 0,
windowSeconds = 5,
eventCount = 0,
@@ -36,7 +37,7 @@ local MTH_ST_Trace = {
local MTH_ST_Diag = {
lines = {},
maxLines = 200,
chatEcho = false,
chatEcho = MTH_ST_FULL_DEBUG_TRACE and true or false,
}
local MTH_ST_DiagCopyFrame = nil
local MTH_ST_PrintStableDiag
@@ -179,7 +180,7 @@ local function MTH_ST_EnsureDiagCopyFrame()
if MTH_ST_DiagCopyFrame then
return MTH_ST_DiagCopyFrame
end
local frame = CreateFrame("Frame", "MTHStableDiagCopyFrame", UIParent)
local frame = CreateFrame("Frame", "MTH_StableDiagCopy", UIParent)
if not frame then
return nil
end
@@ -219,11 +220,11 @@ local function MTH_ST_EnsureDiagCopyFrame()
copyButton:SetHeight(24)
copyButton:SetText("Select All")
local scrollFrame = CreateFrame("ScrollFrame", "MTHStableDiagCopyScroll", frame, "UIPanelScrollFrameTemplate")
local scrollFrame = CreateFrame("ScrollFrame", "MTH_StableDiagScroll", frame, "UIPanelScrollFrameTemplate")
scrollFrame:SetPoint("TOPLEFT", frame, "TOPLEFT", 16, -48)
scrollFrame:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -36, 44)
local editBox = CreateFrame("EditBox", "MTHStableDiagCopyText", scrollFrame)
local editBox = CreateFrame("EditBox", "MTH_StableDiagText", scrollFrame)
editBox:SetMultiLine(true)
editBox:SetMaxLetters(999999)
editBox:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
@@ -255,34 +256,40 @@ local function MTH_ST_EnsureDiagCopyFrame()
if UISpecialFrames then
local exists = false
for i = 1, table.getn(UISpecialFrames) do
if UISpecialFrames[i] == "MTHStableDiagCopyFrame" then
if UISpecialFrames[i] == "MTH_StableDiagCopy" then
exists = true
break
end
end
if not exists then
table.insert(UISpecialFrames, "MTHStableDiagCopyFrame")
table.insert(UISpecialFrames, "MTH_StableDiagCopy")
end
end
return frame
end
function MTH_CommandStableDiag(mode)
local lowerMode = string.lower(tostring(mode or "on"))
MTH_ST_Diag.lines = {}
MTH_ST_Diag.chatEcho = false
MTH_ST_Diag.chatEcho = (lowerMode ~= "off") and true or false
if MTH and MTH.Print then
MTH:Print("Stable diagnostics are disabled.")
MTH:Print("Stable diagnostics chat echo " .. (MTH_ST_Diag.chatEcho and "enabled" or "disabled") .. ".", "debug")
end
return false
return MTH_ST_Diag.chatEcho
end
function MTH_CommandStableTrace(mode)
MTH_ST_Trace.enabled = false
MTH_ST_Trace.verbose = false
if MTH and MTH.Print then
MTH:Print("Stable trace is disabled.")
local lowerMode = string.lower(tostring(mode or "verbose"))
MTH_ST_Trace.enabled = (lowerMode ~= "off") and true or false
MTH_ST_Trace.verbose = (lowerMode == "verbose" or lowerMode == "on") and MTH_ST_Trace.enabled or false
if MTH_ST_Trace.enabled then
MTH_ST_TraceResetCounters()
end
return false
if MTH and MTH.Print then
MTH:Print("Stable trace " .. (MTH_ST_Trace.enabled and "enabled" or "disabled")
.. " (verbose=" .. tostring(MTH_ST_Trace.verbose) .. ").", "debug")
end
return MTH_ST_Trace.enabled
end
local function MTH_ST_ProfileNow()
@@ -418,18 +425,27 @@ local function MTH_ST_ProfileFlushIfDue(force)
end
function MTH_CommandStableProfile(mode)
MTH_ST_Profile.enabled = false
if MTH and MTH.Print then
MTH:Print("Stable profiler is disabled.")
local lowerMode = string.lower(tostring(mode or "off"))
MTH_ST_Profile.enabled = (lowerMode ~= "off") and true or false
if MTH_ST_Profile.enabled then
MTH_ST_ProfileResetCounters()
end
return false
if MTH and MTH.Print then
MTH:Print("Stable profiler " .. (MTH_ST_Profile.enabled and "enabled" or "disabled") .. ".", "debug")
end
return MTH_ST_Profile.enabled
end
local function MTH_PETS_LogConsistency(line)
return
if not MTH_PETS_TRACE_CONSISTENCY then
return
end
if MTH and MTH.Print then
MTH:Print("[PETS] " .. tostring(line or ""), "debug")
end
end
local function MTH_PETS_LogTame(line)
local function MTH_PETS_LogTame(_)
return
end
@@ -816,6 +832,30 @@ local function MTH_PETS_NormalizeSystemMessageKey(rawMessage)
return message
end
local function MTH_PETS_GetUnitGuid(unitToken)
local token = tostring(unitToken or "")
if token == "" then
return nil
end
local getUnitGuid = (type(getglobal) == "function" and getglobal("GetUnitGUID")) or (_G and _G["GetUnitGUID"])
if type(getUnitGuid) == "function" then
local ok, guid = pcall(getUnitGuid, token)
if ok and type(guid) == "string" and guid ~= "" then
return guid
end
end
if type(UnitGUID) == "function" then
local ok, guid = pcall(UnitGUID, token)
if ok and type(guid) == "string" and guid ~= "" then
return guid
end
end
return nil
end
local function MTH_PETS_IsRunawaySystemMessage(rawMessage)
local message = MTH_PETS_NormalizeSystemMessage(rawMessage)
if message == "" then
@@ -856,11 +896,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
@@ -971,7 +1021,7 @@ local function MTH_PETS_CaptureTargetSnapshot()
if MTH_PETS_IsPlaceholderPetName(targetName) then
return nil
end
local targetGuid = (type(UnitGUID) == "function") and UnitGUID("target") or nil
local targetGuid = MTH_PETS_GetUnitGuid("target")
local beastId = MTH_PETS_ParseCreatureIdFromGuid(targetGuid)
local targetFamily = (type(UnitCreatureFamily) == "function") and UnitCreatureFamily("target") or nil
local targetLevel = (type(UnitLevel) == "function") and UnitLevel("target") or nil
@@ -1185,6 +1235,22 @@ local function MTH_PETS_ResolveTameBeastIdForRow(row, snapshot)
return nil
end
local function MTH_PETS_IsTameConfirmationSource(source)
local sourceText = tostring(source or "")
return sourceText == "unit-pet-acquire" or sourceText == "refresh-current-pet"
end
local function MTH_PETS_ResolveConfirmedTameBeastId(source, snapshot)
if not MTH_PETS_IsTameConfirmationSource(source) then
return nil
end
local resolved = MTH_PETS_GetPendingTameBeastId(snapshot)
if resolved and resolved > 0 then
return resolved
end
return nil
end
local function MTH_PETS_MakeSignature(name, family, level)
local cleanName = MTH_PETS_SafeLower(MTH_PETS_NormalizeText(name))
local cleanFamily = MTH_PETS_SafeLower(MTH_PETS_NormalizeText(family))
@@ -1192,7 +1258,7 @@ local function MTH_PETS_MakeSignature(name, family, level)
return cleanName .. "|" .. cleanFamily .. "|" .. tostring(numericLevel)
end
local function MTH_PETS_RowHasTameRecord(row)
local function MTH_PETS_RowHasAnyTameMetadata(row)
if type(row) ~= "table" then
return false
end
@@ -1211,11 +1277,33 @@ local function MTH_PETS_RowHasTameRecord(row)
return false
end
function MTH_PETS_HasVerifiedTameRecord(row)
if type(row) ~= "table" then
return false
end
if row.tameVerified == true then
return true
end
local legacyTameContext = type(row.tameContext) == "table" and row.tameContext or nil
if legacyTameContext and (legacyTameContext.name or legacyTameContext.zone or legacyTameContext.timestamp) then
return true
end
if type(row.events) == "table" then
for i = 1, table.getn(row.events) do
local ev = row.events[i]
if type(ev) == "table" and tostring(ev.type or "") == "pet-tamed" then
return true
end
end
end
return false
end
local function MTH_PETS_BackfillTameMetadataFromEvents(row)
if type(row) ~= "table" then
return false
end
if MTH_PETS_RowHasTameRecord(row) then
if MTH_PETS_HasVerifiedTameRecord(row) then
return false
end
if type(row.events) ~= "table" then
@@ -1226,7 +1314,7 @@ local function MTH_PETS_BackfillTameMetadataFromEvents(row)
local stableFirstSeenAt = tonumber(row.stableFirstSeenAt) or tonumber(row.stabledAt) or 0
for i = 1, table.getn(row.events) do
local ev = row.events[i]
if type(ev) == "table" and tostring(ev.type or "") == "pet-acquired" then
if type(ev) == "table" and tostring(ev.type or "") == "pet-tamed" then
local evAt = tonumber(ev.at) or 0
local timelineOk = true
if stableFirstSeenAt > 0 and evAt > 0 and evAt > stableFirstSeenAt then
@@ -1294,12 +1382,204 @@ local function MTH_PETS_BackfillTameMetadataFromEvents(row)
if changed then
row.tameRecorded = true
row.tameVerified = true
row.lastUpdated = time()
end
return changed
end
local function MTH_PETS_ParseNumericPetId(petId)
local _, _, numericId = string.find(tostring(petId or ""), "^pet%-(%d+)$")
return tonumber(numericId)
end
local function MTH_PETS_GetActiveRowRepairScore(pets, petId, row)
if type(row) ~= "table" then
return -1, 0
end
local score = 0
local resolvedPetId = tostring(petId or "")
local petStore = type(pets) == "table" and pets.petStore or nil
if resolvedPetId ~= "" and tostring((pets and pets.currentPetId) or "") == resolvedPetId then
score = score + 1000000
end
if resolvedPetId ~= "" and type(petStore) == "table" and tostring(petStore.activeCurrentId or "") == resolvedPetId then
score = score + 900000
end
if tonumber(row.stableSlot) and tonumber(row.stableSlot) > 0 then
score = score + 500000
end
if type(row.guid) == "string" and row.guid ~= "" then
score = score + 100000
end
if row.tameVerified == true then
score = score + 50000
end
if row.tameRecorded == true then
score = score + 10000
end
score = score + (tonumber(row.lastSeen) or tonumber(row.lastUpdated) or 0)
return score, tonumber(MTH_PETS_ParseNumericPetId(resolvedPetId)) or 0
end
local function MTH_PETS_MergeActiveRows(pets, winnerId, loserId)
if type(pets) ~= "table" or type(pets.petStore) ~= "table" then
return false
end
local petStore = pets.petStore
local winnerRow = type(petStore.activeById) == "table" and petStore.activeById[winnerId] or nil
local loserRow = type(petStore.activeById) == "table" and petStore.activeById[loserId] or nil
if type(winnerRow) ~= "table" or type(loserRow) ~= "table" or tostring(winnerId) == tostring(loserId) then
return false
end
local copyIfMissingFields = {
"guid", "signature", "beastId", "name", "family", "level",
"happiness", "loyalty", "loyaltyLevel", "xp", "xpMax", "xpPercent",
"origin", "originContext", "previousName", "stableRaw", "stableInfo",
"zone", "subZone", "x", "y", "hunterLevel", "lastSource",
"tameHunterLevel", "tameZone", "tameSubZone", "tameX", "tameY",
"tameBeastId", "tameContext",
}
for i = 1, table.getn(copyIfMissingFields) do
local field = copyIfMissingFields[i]
local winnerValue = winnerRow[field]
if winnerValue == nil or winnerValue == "" then
local loserValue = loserRow[field]
if loserValue ~= nil and loserValue ~= "" then
winnerRow[field] = loserValue
end
end
end
if type(winnerRow.petSpellbook) ~= "table" and type(loserRow.petSpellbook) == "table" then
winnerRow.petSpellbook = loserRow.petSpellbook
winnerRow.lastPetSpellbookSkipAt = winnerRow.lastPetSpellbookSkipAt or loserRow.lastPetSpellbookSkipAt
winnerRow.lastPetSpellbookSkipSource = winnerRow.lastPetSpellbookSkipSource or loserRow.lastPetSpellbookSkipSource
winnerRow.lastPetSpellbookSkipReason = winnerRow.lastPetSpellbookSkipReason or loserRow.lastPetSpellbookSkipReason
end
if type(winnerRow.abilities) ~= "table" then
winnerRow.abilities = {}
end
if type(loserRow.abilities) == "table" then
for token, ability in pairs(loserRow.abilities) do
if winnerRow.abilities[token] == nil then
winnerRow.abilities[token] = ability
end
end
end
if type(winnerRow.events) ~= "table" then
winnerRow.events = {}
end
if type(loserRow.events) == "table" then
for i = 1, table.getn(loserRow.events) do
table.insert(winnerRow.events, loserRow.events[i])
end
end
local winnerCreatedAt = tonumber(winnerRow.createdAt) or 0
local loserCreatedAt = tonumber(loserRow.createdAt) or 0
if winnerCreatedAt <= 0 or (loserCreatedAt > 0 and loserCreatedAt < winnerCreatedAt) then
winnerRow.createdAt = loserCreatedAt > 0 and loserCreatedAt or winnerRow.createdAt
end
local winnerFirstSeen = tonumber(winnerRow.firstSeen) or 0
local loserFirstSeen = tonumber(loserRow.firstSeen) or 0
if winnerFirstSeen <= 0 or (loserFirstSeen > 0 and loserFirstSeen < winnerFirstSeen) then
winnerRow.firstSeen = loserFirstSeen > 0 and loserFirstSeen or winnerRow.firstSeen
end
local winnerLastSeen = tonumber(winnerRow.lastSeen) or 0
local loserLastSeen = tonumber(loserRow.lastSeen) or 0
if loserLastSeen > winnerLastSeen then
winnerRow.lastSeen = loserLastSeen
end
local winnerLastUpdated = tonumber(winnerRow.lastUpdated) or 0
local loserLastUpdated = tonumber(loserRow.lastUpdated) or 0
if loserLastUpdated > winnerLastUpdated then
winnerRow.lastUpdated = loserLastUpdated
end
if winnerRow.tameVerified ~= true and loserRow.tameVerified == true then
winnerRow.tameVerified = true
winnerRow.tameRecorded = true
winnerRow.tamedAt = winnerRow.tamedAt or loserRow.tamedAt
winnerRow.tameHunterLevel = winnerRow.tameHunterLevel or loserRow.tameHunterLevel
winnerRow.tameZone = winnerRow.tameZone or loserRow.tameZone
winnerRow.tameSubZone = winnerRow.tameSubZone or loserRow.tameSubZone
winnerRow.tameX = winnerRow.tameX or loserRow.tameX
winnerRow.tameY = winnerRow.tameY or loserRow.tameY
winnerRow.tameBeastId = winnerRow.tameBeastId or loserRow.tameBeastId
end
if (winnerRow.stableSlot == nil or tonumber(winnerRow.stableSlot) == nil or tonumber(winnerRow.stableSlot) <= 0)
and tonumber(loserRow.stableSlot) and tonumber(loserRow.stableSlot) > 0 then
winnerRow.stableSlot = tonumber(loserRow.stableSlot)
end
if type(petStore.stableSlotIndex) == "table" then
for slotNumber, mappedPetId in pairs(petStore.stableSlotIndex) do
if tostring(mappedPetId or "") == tostring(loserId) then
petStore.stableSlotIndex[slotNumber] = tostring(winnerId)
end
end
end
if tostring(petStore.activeCurrentId or "") == tostring(loserId) then
petStore.activeCurrentId = tostring(winnerId)
end
if tostring(pets.currentPetId or "") == tostring(loserId) then
pets.currentPetId = tostring(winnerId)
end
if type(pets.currentPet) == "table" and tostring(pets.currentPet.id or "") == tostring(loserId) then
pets.currentPet.id = tostring(winnerId)
end
petStore.activeById[loserId] = nil
return true
end
local function MTH_PETS_RepairActiveRowsByGuid(pets)
if type(pets) ~= "table" or type(pets.petStore) ~= "table" or type(pets.petStore.activeById) ~= "table" then
return
end
local groupedByGuid = {}
for petId, row in pairs(pets.petStore.activeById) do
if type(row) == "table" and type(row.guid) == "string" and row.guid ~= "" then
local guid = row.guid
if type(groupedByGuid[guid]) ~= "table" then
groupedByGuid[guid] = {}
end
table.insert(groupedByGuid[guid], tostring(petId))
end
end
for _, petIds in pairs(groupedByGuid) do
if type(petIds) == "table" and table.getn(petIds) > 1 then
table.sort(petIds, function(a, b)
local rowA = pets.petStore.activeById[a]
local rowB = pets.petStore.activeById[b]
local scoreA, numericA = MTH_PETS_GetActiveRowRepairScore(pets, a, rowA)
local scoreB, numericB = MTH_PETS_GetActiveRowRepairScore(pets, b, rowB)
if scoreA ~= scoreB then
return scoreA > scoreB
end
if numericA ~= numericB then
return numericA < numericB
end
return tostring(a) < tostring(b)
end)
local winnerId = petIds[1]
for i = 2, table.getn(petIds) do
local loserId = petIds[i]
if pets.petStore.activeById[winnerId] and pets.petStore.activeById[loserId] then
MTH_PETS_MergeActiveRows(pets, winnerId, loserId)
end
end
end
end
end
local function MTH_PETS_EnsurePetStoreSchema(pets)
if type(pets.petStore) ~= "table" then
pets.petStore = {}
@@ -1326,9 +1606,36 @@ local function MTH_PETS_EnsurePetStoreSchema(pets)
if petStore.activeCurrentId == nil then
petStore.activeCurrentId = nil
end
MTH_PETS_RepairActiveRowsByGuid(pets)
petStore.signatureIndex = {}
petStore.guidIndex = {}
local highestPetNumericId = 0
for _, row in pairs(petStore.activeById) do
for petId, row in pairs(petStore.activeById) do
if type(row) == "table" then
local resolvedPetId = tostring(petId)
if row.id == nil or tostring(row.id) == "" then
row.id = resolvedPetId
end
local numericPetId = MTH_PETS_ParseNumericPetId(resolvedPetId)
if numericPetId and numericPetId > highestPetNumericId then
highestPetNumericId = numericPetId
end
if row.tameVerified ~= true then
local legacyTameContext = type(row.tameContext) == "table" and row.tameContext or nil
if legacyTameContext and (legacyTameContext.name or legacyTameContext.zone or legacyTameContext.timestamp) then
row.tameVerified = true
end
end
if type(row.signature) == "string" and row.signature ~= "" then
if type(petStore.signatureIndex[row.signature]) ~= "table" then
petStore.signatureIndex[row.signature] = {}
end
table.insert(petStore.signatureIndex[row.signature], resolvedPetId)
end
if type(row.guid) == "string" and row.guid ~= "" then
petStore.guidIndex[row.guid] = resolvedPetId
end
if row.loyaltyLevel == nil and type(row.loyalty) == "string" then
row.loyaltyLevel = MTH_PETS_ParseLoyaltyLevelFromText(row.loyalty)
end
@@ -1343,9 +1650,21 @@ local function MTH_PETS_EnsurePetStoreSchema(pets)
end
row.stableInfo.loyalty = nil
end
if row.tameVerified ~= true and MTH_PETS_RowHasAnyTameMetadata(row) then
row.tameRecorded = nil
end
MTH_PETS_BackfillTameMetadataFromEvents(row)
end
end
if highestPetNumericId >= tonumber(petStore.nextId) then
petStore.nextId = highestPetNumericId + 1
end
if petStore.activeCurrentId ~= nil and petStore.activeById[tostring(petStore.activeCurrentId)] == nil then
petStore.activeCurrentId = nil
end
if pets.currentPetId ~= nil and petStore.activeById[tostring(pets.currentPetId)] == nil then
pets.currentPetId = petStore.activeCurrentId
end
end
local function MTH_PETS_EnsureCurrentPetSchema(pets)
@@ -1719,10 +2038,10 @@ local function MTH_PETS_MakeSnapshotFromLivePet()
xpPercent = math.floor((xp / xpMax) * 1000 + 0.5) / 10
end
end
local petGuid = (type(UnitGUID) == "function") and UnitGUID("pet") or nil
local petGuid = MTH_PETS_GetUnitGuid("pet")
local beastId = MTH_PETS_ParseCreatureIdFromGuid(petGuid)
if beastId == nil and type(UnitGUID) == "function" then
beastId = MTH_PETS_ParseCreatureIdFromGuid(UnitGUID("target"))
if beastId == nil then
beastId = MTH_PETS_ParseCreatureIdFromGuid(MTH_PETS_GetUnitGuid("target"))
end
local signature = MTH_PETS_MakeSignature(petName, petFamily, petLevel)
return {
@@ -1751,15 +2070,28 @@ local function MTH_PETS_SelectPetIdBySnapshot(store, snapshot, previousPetId)
if previousRow.guid and snapshot.guid and previousRow.guid == snapshot.guid then
return previousPetId
end
if previousRow.signature and snapshot.signature and previousRow.signature == snapshot.signature then
return previousPetId
end
end
if snapshot.guid and store.guidIndex[snapshot.guid] and store.activeById[store.guidIndex[snapshot.guid]] then
return store.guidIndex[snapshot.guid]
end
if snapshot.guid and snapshot.guid ~= "" then
for petId, row in pairs(store.activeById or {}) do
if type(row) == "table" and row.guid == snapshot.guid then
store.guidIndex[snapshot.guid] = tostring(petId)
return tostring(petId)
end
end
end
if previousPetId and store.activeById[previousPetId] then
local previousRow = store.activeById[previousPetId]
if previousRow.signature and snapshot.signature and previousRow.signature == snapshot.signature then
return previousPetId
end
end
if snapshot.signature and snapshot.signature ~= "" then
local list = store.signatureIndex[snapshot.signature]
if type(list) == "table" then
@@ -1823,21 +2155,17 @@ local function MTH_PETS_ApplySnapshotToRow(row, snapshot, source, context)
end
row.guid = snapshot.guid or row.guid
row.beastId = snapshot.beastId or row.beastId
local hasPendingTame = type(MTH_PETS_LastTameAttempt) == "table"
if hasPendingTame and (row.tameBeastId == nil or tonumber(row.tameBeastId) == nil) then
local resolvedTameBeastId = MTH_PETS_ResolveTameBeastIdForRow(row, snapshot)
if resolvedTameBeastId and resolvedTameBeastId > 0 then
row.tameBeastId = resolvedTameBeastId
end
end
if hasPendingTame and (source == "unit-pet-acquire" or source == "refresh-current-pet") and type(context) == "table" then
local confirmedTameBeastId = MTH_PETS_ResolveConfirmedTameBeastId(source, snapshot)
if confirmedTameBeastId and type(context) == "table" then
row.tameRecorded = true
row.tameVerified = true
row.tamedAt = row.tamedAt or context.timestamp
row.tameHunterLevel = row.tameHunterLevel or context.hunterLevel
row.tameZone = row.tameZone or context.zone
row.tameSubZone = row.tameSubZone or context.subZone
row.tameX = row.tameX or context.x
row.tameY = row.tameY or context.y
row.tameBeastId = row.tameBeastId or confirmedTameBeastId
end
row.signature = snapshot.signature
row.lastUpdated = now
@@ -1862,30 +2190,6 @@ local function MTH_PETS_ApplySnapshotToRow(row, snapshot, source, context)
row.hunterLevel = context.hunterLevel or row.hunterLevel
end
end
if not isStableScan and type(context) == "table" then
local hasTameRecord = (row.tameRecorded == true)
or tonumber(row.tamedAt) ~= nil
or tonumber(row.tameBeastId) ~= nil
or (type(row.tameZone) == "string" and row.tameZone ~= "")
if not hasTameRecord and hasPendingTame then
local sourceText = tostring(source or "")
if sourceText == "unit-pet-acquire" or sourceText == "refresh-current-pet" then
row.tameRecorded = true
row.tamedAt = row.tamedAt or context.timestamp
row.tameHunterLevel = row.tameHunterLevel or context.hunterLevel
row.tameZone = row.tameZone or context.zone
row.tameSubZone = row.tameSubZone or context.subZone
row.tameX = row.tameX or context.x
row.tameY = row.tameY or context.y
if tonumber(row.tameBeastId) == nil then
local resolvedTameBeastId = MTH_PETS_ResolveTameBeastIdForRow(row, snapshot)
if resolvedTameBeastId and resolvedTameBeastId > 0 then
row.tameBeastId = resolvedTameBeastId
end
end
end
end
end
if type(row.abilities) ~= "table" then
row.abilities = {}
end
@@ -1954,18 +2258,6 @@ local function MTH_PETS_UpsertActivePetFromSnapshot(pets, snapshot, source, opti
if created then
row.origin = source or "unknown"
row.originContext = context
local hasPendingTame = type(MTH_PETS_LastTameAttempt) == "table"
local shouldRecordTame = hasPendingTame and (source == "unit-pet-acquire" or source == "refresh-current-pet")
if shouldRecordTame then
row.tamedAt = context.timestamp
row.tameHunterLevel = context.hunterLevel
row.tameZone = context.zone
row.tameSubZone = context.subZone
row.tameX = context.x
row.tameY = context.y
row.tameBeastId = snapshot.beastId
row.tameRecorded = true
end
if type(options) == "table" and options.stableSlot then
row.origin = "stable-slot"
end
@@ -2123,6 +2415,45 @@ local function MTH_PETS_RecordStableSlot(pets, slot, raw1, raw2, raw3, raw4, raw
pets.updatedAt = time()
end
local function MTH_PETS_ReconcileStableSlots(pets, slotCount)
if type(pets) ~= "table" or type(pets.petStore) ~= "table" then
return
end
local petStore = pets.petStore
if type(petStore.activeById) ~= "table" or type(petStore.stableSlotIndex) ~= "table" then
return
end
local authoritativeByPetId = {}
for slotNumber, petId in pairs(petStore.stableSlotIndex) do
local numericSlot = tonumber(slotNumber)
local mappedPetId = tostring(petId or "")
if numericSlot and numericSlot > 0 and mappedPetId ~= "" and type(petStore.activeById[mappedPetId]) == "table" then
if not slotCount or numericSlot <= slotCount then
authoritativeByPetId[mappedPetId] = numericSlot
else
petStore.stableSlotIndex[slotNumber] = nil
end
else
petStore.stableSlotIndex[slotNumber] = nil
end
end
for petId, row in pairs(petStore.activeById) do
if type(row) == "table" then
local resolvedPetId = tostring(petId)
local authoritativeSlot = authoritativeByPetId[resolvedPetId]
if authoritativeSlot then
row.stableSlot = authoritativeSlot
elseif tonumber(row.stableSlot) and tonumber(row.stableSlot) > 0 then
row.stableSlot = nil
end
end
end
pets.updatedAt = time()
end
function MTH_PETS_RecordCurrentPetLearnedAbility(abilityName, rankNumber, source)
local pets = MTH_PETS_GetRootStore()
if type(pets) ~= "table" then
@@ -2494,15 +2825,6 @@ function MTH_PETS_GetRootStore()
end
local pets = MTH_CharSavedVariables.MTH_Pets
if pets._betaFreshStoreApplied ~= 1 then
pets.petTraining = {}
pets.trainScan = {}
pets.stableScan = {}
pets.currentPet = {}
pets.petStore = {}
pets._betaFreshStoreApplied = 1
pets.updatedAt = time()
end
if type(pets.petTraining) ~= "table" then
pets.petTraining = {}
end
@@ -2519,14 +2841,52 @@ function MTH_PETS_GetRootStore()
MTH_PETS_EnsureSchema(pets)
MTH_PETS_EnsurePetTrainingCompatibility(pets)
MTH_CharSavedVariables.petTraining = pets.petTraining
MTH_CharSavedVariables.trainScan = pets.trainScan
MTH_CharSavedVariables.stableScan = pets.stableScan
MTH_CharSavedVariables.petStore = pets.petStore
-- Migration: remove root-level duplicates that bloated SavedVariables
if pets._migrationCleanup ~= 2 then
MTH_PETS_RunSavedVarCleanup(pets)
pets._migrationCleanup = 2
end
return pets
end
function MTH_PETS_RunSavedVarCleanup(pets)
-- 1. Remove root-level duplicates (data lives inside MTH_Pets only)
if MTH_CharSavedVariables then
MTH_CharSavedVariables.petTraining = nil
MTH_CharSavedVariables.trainScan = nil
MTH_CharSavedVariables.stableScan = nil
MTH_CharSavedVariables.petStore = nil
end
-- 2. Purge pet-updated events (unbounded bloat)
local purged = 0
local stores = { pets.petStore and pets.petStore.activeById, pets.petStore and pets.petStore.historyById }
for _, index in ipairs(stores) do
if type(index) == "table" then
for petId, row in pairs(index) do
if type(row) == "table" and type(row.events) == "table" then
local kept = {}
for _, evt in ipairs(row.events) do
if type(evt) == "table" and evt.type ~= "pet-updated" then
table.insert(kept, evt)
else
purged = purged + 1
end
end
row.events = kept
end
end
end
end
-- 3. Remove stale _betaFreshStoreApplied flag
pets._betaFreshStoreApplied = nil
if purged > 0 and MTH and MTH.Print then
end
end
function MTH_PETS_RefreshCurrentPet()
local pets = type(MTH_PETS_GetRootStore) == "function" and MTH_PETS_GetRootStore() or nil
if type(pets) ~= "table" then
@@ -2624,15 +2984,20 @@ function MTH_PETS_RefreshCurrentPet()
row.events = {}
end
local eventType = "pet-updated"
local eventType = nil
local confirmedTameBeastId = MTH_PETS_ResolveConfirmedTameBeastId("refresh-current-pet", snapshot)
if created and (not hadPetBeforeRefresh) then
eventType = "pet-acquired"
if confirmedTameBeastId then
eventType = "pet-tamed"
else
eventType = "pet-acquired"
end
elseif cp.id ~= nil and previousCurrentId ~= nil and cp.id ~= previousCurrentId then
eventType = "pet-swapped"
end
local shouldRecordLifecycleEvent = (eventType == "pet-acquired")
or (cp.id ~= nil and previousCurrentId ~= nil and cp.id ~= previousCurrentId)
local eventContext = nil
if shouldRecordLifecycleEvent then
if eventType then
eventContext = MTH_PETS_CaptureContext()
table.insert(row.events, {
type = eventType,
@@ -2642,26 +3007,6 @@ function MTH_PETS_RefreshCurrentPet()
})
end
if eventType == "pet-acquired" then
local hasTameRecord = (row.tameRecorded == true)
or (tonumber(row.tamedAt) and tonumber(row.tamedAt) > 0)
or (type(row.tameZone) == "string" and row.tameZone ~= "")
if not hasTameRecord and type(eventContext) == "table" then
row.tameRecorded = true
row.tamedAt = row.tamedAt or eventContext.timestamp or now
row.tameHunterLevel = row.tameHunterLevel or eventContext.hunterLevel
row.tameZone = row.tameZone or eventContext.zone
row.tameSubZone = row.tameSubZone or eventContext.subZone
row.tameX = row.tameX or eventContext.x
row.tameY = row.tameY or eventContext.y
if tonumber(row.tameBeastId) == nil then
local resolvedTameBeastId = MTH_PETS_ResolveTameBeastIdForRow(row, snapshot)
if resolvedTameBeastId and resolvedTameBeastId > 0 then
row.tameBeastId = resolvedTameBeastId
end
end
end
end
end
if hadPendingTame and cp.id and cp.id ~= previousCurrentId then
@@ -3078,6 +3423,10 @@ function MTH_ST_Scan(reason)
if currentPetId and type(petsRoot.petStore) == "table" and type(petsRoot.petStore.activeById) == "table" then
local currentRow = petsRoot.petStore.activeById[currentPetId]
if type(currentRow) == "table" then
MTH_ST_PrintStableDiag("scan currentRow petId=" .. tostring(currentPetId)
.. " guid='" .. tostring(currentRow.guid or "") .. "'"
.. " signature='" .. tostring(currentRow.signature or "") .. "'"
.. " beastId='" .. tostring(currentRow.beastId or "") .. "'")
local currentIcon = tostring(c1 or "")
if currentIcon ~= "" then
currentRow.icon = currentIcon
@@ -3102,6 +3451,8 @@ function MTH_ST_Scan(reason)
MTH_ST_PrintStableDiag("scan slots " .. table.concat(slotDiagParts, " | "))
end
MTH_PETS_ReconcileStableSlots(petsRoot, slotCount)
MTH_PETS_MarkStableVisited(petsRoot, tostring(reason or "stable-scan"))
MTH_PETS_RefreshCurrentPet()
@@ -3167,7 +3518,7 @@ end
local function MTH_ST_QueueDeferredStableScan(reason, delaySeconds)
if not MTH_ST_DeferredStableScanFrame then
MTH_ST_DeferredStableScanFrame = CreateFrame("Frame", "MTHDeferredStableScanFrame")
MTH_ST_DeferredStableScanFrame = CreateFrame("Frame", "MTH_DeferredStableScan")
if not MTH_ST_DeferredStableScanFrame then
MTH_ST_TraceRecord("MTH_ST_QueueDeferredStableScan", "scan", "frame-create-failed")
return false
@@ -3269,10 +3620,13 @@ local function MTH_PETS_HandleUnitPetTransition(source)
if type(row.events) ~= "table" then
row.events = {}
end
local confirmedTameBeastId = MTH_PETS_ResolveConfirmedTameBeastId(transitionSource, snapshot)
local eventType = (not hadPet) and "pet-acquired" or "pet-updated"
if eventType == "pet-updated" and source == "UNIT_PET" and MTH_ST_IsStableFrameOpen() then
MTH_ST_PrintStableDiag("swap history-skip reason=stable-open")
else
if not hadPet and confirmedTameBeastId then
eventType = "pet-tamed"
end
-- Only record lifecycle events (acquire, rename, abandon), not routine updates
if eventType ~= "pet-updated" then
table.insert(row.events, {
type = eventType,
source = source or transitionSource,
@@ -3369,7 +3723,6 @@ end
local function MTH_ST_DebugDumpLine(line)
if MTH and MTH.Print then
MTH:Print(tostring(line or ""), "debug")
end
end
@@ -3512,7 +3865,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()
@@ -3597,7 +3949,6 @@ function MTH_CommandPetsReset()
.. " preservedHistory=" .. tostring(historyCount)
.. " nextId=" .. tostring(store.nextId)
MTH:Print(line)
MTH:Print("[PETSRESET] " .. line, "debug")
return true
end
@@ -3742,8 +4093,20 @@ function MTH_ST_HandleSpellcastEvent(evt, eventArg1, eventArg2)
local isUnitSpellcast = (evt == "UNIT_SPELLCAST_START" or evt == "UNIT_SPELLCAST_STOP"
or evt == "UNIT_SPELLCAST_FAILED" or evt == "UNIT_SPELLCAST_INTERRUPTED"
or evt == "UNIT_SPELLCAST_CHANNEL_START" or evt == "UNIT_SPELLCAST_CHANNEL_STOP")
local isSpellcast = isUnitSpellcast
or evt == "SPELLCAST_START" or evt == "SPELLCAST_STOP"
or evt == "SPELLCAST_FAILED" or evt == "SPELLCAST_INTERRUPTED"
or evt == "SPELLCAST_CHANNEL_START" or evt == "SPELLCAST_CHANNEL_STOP"
if not isSpellcast then
return false
end
local castSpellName = eventArg1
local hasPendingTame = type(MTH_PETS_LastTameAttempt) == "table"
MTH_PETS_LogTame("Spellcast evt=" .. tostring(evt)
.. " arg1='" .. tostring(eventArg1 or "") .. "'"
.. " arg2='" .. tostring(eventArg2 or "") .. "'"
.. " unitCast=" .. tostring(isUnitSpellcast)
.. " pending=" .. tostring(hasPendingTame))
if isUnitSpellcast then
if eventArg1 ~= "player" then
return true
@@ -3753,9 +4116,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
@@ -4081,12 +4443,6 @@ local function MTH_ST_OnEvent(frame, evt, eventArg1, eventArg2)
if isStableUiEvt then
stableDiagStart = MTH_ST_ProfileNow()
MTH_ST_PrintStableDiag("evt=" .. tostring(evt) .. " phase=begin")
if MTH_ST_Trace and not MTH_ST_Trace.enabled then
MTH_ST_Trace.enabled = true
MTH_ST_Trace.verbose = false
MTH_ST_TraceResetCounters()
MTH_ST_PrintStableDiag("trace=auto-enabled")
end
end
MTH_ST_DispatchEvent(frame, evt, eventArg1, eventArg2)
@@ -4120,11 +4476,17 @@ function MTH_ST_InitService()
return
end
MTH_PetLifecycleEventFrame = CreateFrame("Frame", "MTHPetLifecycleEventFrame")
MTH_PetLifecycleEventFrame = CreateFrame("Frame", "MTH_PetLifecycleEvent")
MTH_PetLifecycleEventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
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")
@@ -4141,7 +4503,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
@@ -4153,29 +4514,26 @@ function MTH_ST_InitBootstrap()
return
end
local frame = CreateFrame("Frame", "MTHPetLifecycleBootstrapFrame")
local frame = CreateFrame("Frame", "MTH_PetLifecycleBoot")
if not frame then
return
end
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:RegisterEvent("PET_STABLE_SHOW")
frame:RegisterEvent("PET_STABLE_UPDATE")
frame:SetScript("OnEvent", function(self, evt)
evt = evt or event
if evt ~= "PLAYER_ENTERING_WORLD" and evt ~= "PET_STABLE_SHOW" and evt ~= "PET_STABLE_UPDATE" then
frame:SetScript("OnEvent", function()
if event ~= "PLAYER_ENTERING_WORLD" and event ~= "PET_STABLE_SHOW" and event ~= "PET_STABLE_UPDATE" then
return
end
if evt == "PET_STABLE_SHOW" or evt == "PET_STABLE_UPDATE" then
MTH_ST_PrintStableDiag("bootstrap evt=" .. tostring(evt) .. " phase=begin")
if event == "PET_STABLE_SHOW" or event == "PET_STABLE_UPDATE" then
MTH_ST_PrintStableDiag("bootstrap evt=" .. tostring(event) .. " phase=begin")
end
MTH_ST_InitService()
if evt == "PET_STABLE_SHOW" or evt == "PET_STABLE_UPDATE" then
MTH_ST_PrintStableDiag("bootstrap evt=" .. tostring(evt) .. " phase=end action=no-scan")
end
if self and self.UnregisterAllEvents then
self:UnregisterAllEvents()
self:SetScript("OnEvent", nil)
if event == "PET_STABLE_SHOW" or event == "PET_STABLE_UPDATE" then
MTH_ST_PrintStableDiag("bootstrap evt=" .. tostring(event) .. " phase=end action=no-scan")
end
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
MTH_PetLifecycleBootstrapFrame = nil
end)
MTH_PetLifecycleBootstrapFrame = frame
+231 -9
View File
@@ -12,7 +12,7 @@ local function MTH_PT_GetGlobal(name)
return nil
end
local MTH_ItemLinkCacheTooltip = nil
local MTH_ItemLinkCache = nil
local function MTH_GetQualityHexColor(quality)
local q = tonumber(quality)
@@ -34,14 +34,14 @@ function MTH_PrimeItemCache(itemId)
if not id or id <= 0 then return false end
if type(GetItemInfo) == "function" and GetItemInfo(id) then return true end
if not MTH_ItemLinkCacheTooltip then
MTH_ItemLinkCacheTooltip = CreateFrame("GameTooltip", "MTH_ItemLinkCacheTooltip", UIParent, "GameTooltipTemplate")
if not MTH_ItemLinkCacheTooltip then return false end
MTH_ItemLinkCacheTooltip:SetOwner(UIParent, "ANCHOR_NONE")
if not MTH_ItemLinkCache then
MTH_ItemLinkCache = CreateFrame("GameTooltip", "MTH_ItemLinkCache", UIParent, "GameTooltipTemplate")
if not MTH_ItemLinkCache then return false end
MTH_ItemLinkCache:SetOwner(UIParent, "ANCHOR_NONE")
end
MTH_ItemLinkCacheTooltip:ClearLines()
MTH_ItemLinkCacheTooltip:SetHyperlink("item:" .. tostring(id) .. ":0:0:0")
MTH_ItemLinkCache:ClearLines()
MTH_ItemLinkCache:SetHyperlink("item:" .. tostring(id) .. ":0:0:0")
return type(GetItemInfo) == "function" and (GetItemInfo(id) ~= nil) or false
end
@@ -312,16 +312,238 @@ function SlashCmdList.MTH(msg, editbox)
local lowerMsg = string.lower(msg)
local _, _, lowerCmd, lowerArg = string.find(lowerMsg, "^(%S+)%s*(.-)%s*$")
if msg == "" then
MTH:Print("Available: /mth options, /mth book")
MTH:Print("Available: /mth options, /mth book, /mth food")
elseif lowerMsg == "err" or lowerMsg == "errors" or lowerMsg == "debug" then
if MTH_DebugFrame and MTH_DebugFrame.Toggle then
MTH_DebugFrame:Toggle()
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 == "food" then
local sub = lowerArg
local function MTH_FoodItemLabel(itemId)
local id = tonumber(itemId)
if not id then return tostring(itemId) end
if type(GetItemInfo) == "function" then
local name = GetItemInfo(id)
if name and name ~= "" then
return "|cffffcc00" .. name .. "|r |cffaaaaaa(" .. id .. ")|r"
end
end
return "|cffaaaaaa" .. tostring(id) .. "|r"
end
if sub == "" or sub == "status" then
-- Show quarantine and exception state
local feedStore = type(MTH_CharSavedVariables) == "table" and MTH_CharSavedVariables.feedTracking or nil
local quarantine = feedStore and feedStore.fomQuarantine and feedStore.fomQuarantine.byFamily or nil
local exceptions = feedStore and feedStore.exceptions and feedStore.exceptions.byItemId or nil
MTH:Print("|cffffff00=== Feed-O-Matic Status ===|r")
-- Quarantine
local qCount = 0
if type(quarantine) == "table" then
for family, byItem in pairs(quarantine) do
if type(byItem) == "table" then
for itemId, _ in pairs(byItem) do
qCount = qCount + 1
MTH:Print(" |cffff8800Quarantine:|r " .. MTH_FoodItemLabel(itemId) .. " (family=" .. tostring(family) .. ")")
end
end
end
end
if qCount == 0 then MTH:Print(" Quarantine: |cff00ff00empty|r") end
-- Core exceptions
local eCount = 0
if type(exceptions) == "table" then
for itemId, _ in pairs(exceptions) do
if type(itemId) == "number" then -- skip string-key dupes
eCount = eCount + 1
MTH:Print(" |cffff4444Exception block:|r " .. MTH_FoodItemLabel(itemId))
end
end
end
if eCount == 0 then MTH:Print(" Exception blocks: |cff00ff00none|r") end
-- FOM_RemovedFoods
local rCount = 0
if type(FOM_RemovedFoods) == "table" then
for diet, list in pairs(FOM_RemovedFoods) do
if type(list) == "table" then
for _, itemId in ipairs(list) do
rCount = rCount + 1
MTH:Print(" |cffaaaaaaRemoved food:|r " .. MTH_FoodItemLabel(itemId) .. " (diet=" .. tostring(diet) .. ")")
end
end
end
end
if rCount == 0 then MTH:Print(" Removed foods: |cff00ff00none|r") end
MTH:Print("|cffaaaaaa/mth food unban <id>|r — unban one item |cffaaaaaa/mth food reset|r — clear all bans")
elseif string.find(sub, "^unban%s") or sub == "unban" then
local _, _, itemArg = string.find(lowerArg, "^unban%s+(%S+)")
local itemId = tonumber(itemArg)
if not itemId then
MTH:Print("|cffff4444Usage:|r /mth food unban <itemId>")
MTH:Print("Example: /mth food unban 8952")
elseif type(FOM_ClearItemBans) ~= "function" then
MTH:Print("|cffff4444Error:|r Feed-O-Matic not loaded.")
else
local label = MTH_FoodItemLabel(itemId)
FOM_ClearItemBans(itemId)
MTH:Print("|cff00ff00Unbanned " .. label .. "|r — removed from quarantine, exceptions and removed-foods list.")
end
elseif sub == "reset" then
local feedStore = type(MTH_CharSavedVariables) == "table" and MTH_CharSavedVariables.feedTracking or nil
if feedStore then
if feedStore.fomQuarantine then feedStore.fomQuarantine = { byFamily = {} } end
if feedStore.exceptions then feedStore.exceptions = { byItemId = {} } end
end
if type(FOM_RemovedFoods) == "table" then
for diet, _ in pairs(FOM_RemovedFoods) do FOM_RemovedFoods[diet] = {} end
end
if type(FOM_FEED_SCAN_CACHE) ~= "nil" then FOM_FEED_SCAN_CACHE = nil end
MTH:Print("|cff00ff00All Feed-O-Matic bans and quarantines cleared.|r")
else
MTH:Print("|cffffff00/mth food|r — show quarantine/ban status")
MTH:Print("|cffffff00/mth food unban <itemId>|r — remove one item from all bans")
MTH:Print("|cffffff00/mth food reset|r — clear ALL bans and quarantines")
end
elseif lowerCmd == "zbar" and lowerArg == "debug" then
-- Dump the live state of every zBar button to chat so we can see which one is the ghost.
local p = function(s) MTH:Print("[zBar] " .. tostring(s), "debug") end
local root = (type(MTH_ZH_GetSavedRoot) == "function") and MTH_ZH_GetSavedRoot() or ZHunterMod_Saved
local zs = root and root["_zbar"]
p("=== zBar debug ===")
p("enabled=" .. tostring(zs and zs.enabled) .. " anchor=" .. tostring(getglobal("MTH_ZBar_Anchor") ~= nil))
p("direction=" .. tostring(zs and zs.direction) .. " childexpand=" .. tostring(zs and zs.childexpand) .. " childarrange=" .. tostring(zs and zs.childarrange))
local allButtons = {
"zButtonAspect","zButtonAmmo","zButtonTrack","zButtonTrap",
"zButtonPet","zButtonRanged","zButtonMounts","zButtonCompanions","zButtonToys",
}
for _, bname in ipairs(allButtons) do
local btn = getglobal(bname)
local shown = btn and btn.IsShown and btn:IsShown()
local bsaved = root and type(root[bname]) == "table" and root[bname]
local enabled = bsaved and bsaved["enabled"]
local zvis = zs and zs.visible and zs.visible[bname]
local x, y, point = "?", "?", "?"
if btn and btn.GetPoint then
local pt, _, _, bx, by = btn:GetPoint()
point = tostring(pt)
x = tostring(bx and math.floor(bx + 0.5) or "?")
y = tostring(by and math.floor(by + 0.5) or "?")
end
local bmtname = btn and tostring(btn.name) or "nil"
p(bname .. ": shown=" .. tostring(shown) ..
" enabled=" .. tostring(enabled) ..
" zbarVisible=" .. tostring(zvis) ..
" btn.name=" .. bmtname ..
" anchor=(" .. point .. " " .. x .. "," .. y .. ")")
end
p("=== end ===")
else
MTH:Print("Unknown command: " .. tostring(msg))
MTH:Print("Available: /mth options, /mth book")
MTH:Print("Available: /mth options, /mth book, /mth npcid, /mth food")
end
end
+7 -10
View File
@@ -29,7 +29,7 @@ function MTH_DebugFrame:Initialize()
self.initialized = true
-- Create main frame
self.frame = CreateFrame("Frame", "MTH_DebugFrameWindow", UIParent)
self.frame = CreateFrame("Frame", "MTH_DebugWindow", UIParent)
self.frame:SetWidth(800)
self.frame:SetHeight(400)
self.frame:SetPoint("CENTER", UIParent, "CENTER")
@@ -51,13 +51,13 @@ function MTH_DebugFrame:Initialize()
if UISpecialFrames then
local already = false
for i = 1, table.getn(UISpecialFrames) do
if UISpecialFrames[i] == "MTH_DebugFrameWindow" then
if UISpecialFrames[i] == "MTH_DebugWindow" then
already = true
break
end
end
if not already then
table.insert(UISpecialFrames, "MTH_DebugFrameWindow")
table.insert(UISpecialFrames, "MTH_DebugWindow")
end
end
@@ -91,7 +91,7 @@ function MTH_DebugFrame:Initialize()
copyButton:SetScript("OnClick", function() MTH_DebugFrame:SelectAllText() end)
-- Scroll frame + text content with proper vertical scrollbar
self.scrollFrame = CreateFrame("ScrollFrame", "MTH_DebugScrollFrame", self.frame, "UIPanelScrollFrameTemplate")
self.scrollFrame = CreateFrame("ScrollFrame", "MTH_DebugScroll", self.frame, "UIPanelScrollFrameTemplate")
self.scrollFrame:SetPoint("TOPLEFT", self.frame, "TOPLEFT", 15, -50)
self.scrollFrame:SetPoint("BOTTOMRIGHT", self.frame, "BOTTOMRIGHT", -35, 45)
@@ -366,16 +366,13 @@ MTH_DebugFrame.UninstallHooks = MTH_DF_UninstallHooks
-- Event listener for addon load
local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("ADDON_LOADED")
eventFrame:SetScript("OnEvent", function(self, event, addonName)
if event == "ADDON_LOADED" and addonName == "MetaHunt" then
eventFrame:SetScript("OnEvent", function()
if event == "ADDON_LOADED" and arg1 == "MetaHunt" then
if MTH and MTH.GetConfig then
MTH_DF_SetGlobalCaptureEnabled(MTH:GetConfig("debug", "globalErrorCapture", false))
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
+174 -25
View File
@@ -2,18 +2,20 @@ if type(MTH_HUNTERBOOK_TABS) ~= "table" then MTH_HUNTERBOOK_TABS = {} end
MTH_HUNTERBOOK_TABS.families = {
headerLabel = "Families",
columnLabels = { "Family", "Named", "Coords", "Abilities", "Diet" },
columnLabels = { "Family", "Named", "Coords", "Abilities", "Diet", "HP", "dmg", "Arm" },
columnLayout = {
{ x = 10, width = 106, align = "LEFT" },
{ x = 122, width = 52, align = "LEFT" },
{ x = 176, width = 52, align = "LEFT" },
{ x = 232, width = 364, align = "LEFT" },
{ x = 532, width = 112, align = "LEFT" },
{ x = 10, width = 100, align = "LEFT" },
{ x = 114, width = 38, align = "LEFT" },
{ x = 154, width = 38, align = "LEFT" },
{ x = 196, width = 364, align = "LEFT" },
{ x = 496, width = 102, align = "LEFT" },
{ x = 602, width = 30, align = "CENTER" },
{ x = 636, width = 36, align = "CENTER" },
{ x = 674, width = 38, align = "CENTER" },
},
}
local function MTH_BOOKTAB_FamiliesTrace(message)
return
end
local function MTH_BOOKTAB_FamiliesSafeLower(value)
@@ -27,6 +29,28 @@ local function MTH_BOOKTAB_FamiliesTrim(value)
return text
end
local function MTH_BOOKTAB_FormatFamilyStat(value)
local numeric = tonumber(value)
if not numeric then
return "-"
end
local colorPrefix = "|cFF4DA6FF"
if numeric < 1 then
colorPrefix = "|cFFFF4040"
elseif numeric > 1 then
colorPrefix = "|cFF40FF40"
end
local text = nil
if MTH_BOOK_STATE and MTH_BOOK_STATE.familyStatsPercent == true then
text = tostring(math.floor((numeric * 100) + 0.5)) .. "%"
return colorPrefix .. text .. "|r"
end
text = string.format("%.3f", numeric)
text = string.gsub(text, "(%..-)0+$", "%1")
text = string.gsub(text, "%.$", "")
return colorPrefix .. text .. "|r"
end
local function MTH_BOOKTAB_ShouldDisplayAllDiet(familyName)
local token = MTH_BOOKTAB_FamiliesSafeLower(MTH_BOOKTAB_FamiliesTrim(familyName))
return token == "bears" or token == "boars"
@@ -52,12 +76,41 @@ end
function MTH_BOOKTAB_BuildFamiliesRows()
local results = {}
local families = MTH_DS_Families
local beasts = MTH_DS_Beasts
if type(families) ~= "table" then
return results
end
local abilityCounts = {}
local canonicalMap = {}
local familyCounts = {}
if type(beasts) == "table" then
for _, beastRow in pairs(beasts) do
if type(beastRow) == "table" then
local familyName = MTH_BOOKTAB_FamiliesTrim(beastRow.family)
if familyName ~= "" then
local counts = familyCounts[familyName]
if not counts then
counts = { named = 0, coords = 0 }
familyCounts[familyName] = counts
end
counts.named = counts.named + 1
if type(beastRow.coords) == "table" then
for coordIndex = 1, table.getn(beastRow.coords) do
local coord = beastRow.coords[coordIndex]
if type(coord) == "table" then
if coord[1] ~= nil and coord[2] ~= nil then
counts.coords = counts.coords + 1
end
end
end
end
end
end
end
end
for _, familyRow in pairs(families) do
if type(familyRow) == "table" and type(familyRow.abilities) == "table" then
@@ -83,6 +136,8 @@ function MTH_BOOKTAB_BuildFamiliesRows()
for familyName, familyRow in pairs(families) do
if type(familyRow) == "table" then
local beastCounts = familyCounts[familyName] or nil
MTH_BOOKTAB_FamiliesTrace("BUILD " .. tostring(familyName) .. " icon=" .. tostring(familyRow.icon))
local abilities = {}
local seen = {}
@@ -171,6 +226,10 @@ function MTH_BOOKTAB_BuildFamiliesRows()
end)
local dietText = ""
local stats = type(familyRow.stats) == "table" and familyRow.stats or nil
local healthStat = stats and tonumber(stats.health) or nil
local damageStat = stats and tonumber(stats.damage) or nil
local armorStat = stats and tonumber(stats.armor) or nil
if MTH_BOOKTAB_ShouldDisplayAllDiet(familyName) then
dietText = "ALL"
else
@@ -178,7 +237,8 @@ function MTH_BOOKTAB_BuildFamiliesRows()
if type(familyRow.food) == "table" then
for i = 1, table.getn(familyRow.food) do
local food = MTH_BOOKTAB_FamiliesTrim(familyRow.food[i])
if food ~= "" then
local token = MTH_BOOKTAB_FamiliesSafeLower(food)
if food ~= "" and token ~= "raw fish" and token ~= "raw meat" then
table.insert(dietItems, string.upper(string.sub(food, 1, 1)) .. string.sub(food, 2))
end
end
@@ -186,15 +246,22 @@ function MTH_BOOKTAB_BuildFamiliesRows()
return MTH_BOOKTAB_FamiliesSafeLower(a) < MTH_BOOKTAB_FamiliesSafeLower(b)
end)
end
dietText = table.concat(dietItems, ", ")
dietText = table.concat(dietItems, "|")
end
table.insert(results, {
family = tostring(familyName),
named = tonumber(familyRow.named) or 0,
coords = tonumber(familyRow.coords) or 0,
icon = familyRow.icon or nil,
named = beastCounts and (tonumber(beastCounts.named) or 0) or 0,
coords = beastCounts and (tonumber(beastCounts.coords) or 0) or 0,
abilities = abilities,
dietText = dietText,
healthStat = healthStat,
healthText = MTH_BOOKTAB_FormatFamilyStat(healthStat),
damageStat = damageStat,
damageText = MTH_BOOKTAB_FormatFamilyStat(damageStat),
armorStat = armorStat,
armorText = MTH_BOOKTAB_FormatFamilyStat(armorStat),
})
end
end
@@ -252,31 +319,76 @@ function MTH_BOOKTAB_EnsureFamiliesUI()
ui.frame:SetPoint("BOTTOMRIGHT", listParent, "BOTTOMRIGHT", -6, 6)
ui.frame:Hide()
local statsText = getglobal("MTH_BOOK_StatsText")
local statsParent = statsText and statsText:GetParent() or nil
if statsParent then
ui.percentCheckbox = CreateFrame("CheckButton", "MTH_BOOK_FamiliesPercentCheckbox", statsParent, "UICheckButtonTemplate")
ui.percentCheckbox:SetFrameStrata(statsParent:GetFrameStrata())
ui.percentCheckbox:SetFrameLevel((statsParent:GetFrameLevel() or 0) + 20)
ui.percentCheckbox:ClearAllPoints()
if statsText then
ui.percentCheckbox:SetPoint("LEFT", statsText, "RIGHT", 260, 0)
else
ui.percentCheckbox:SetPoint("TOPLEFT", statsParent, "TOPLEFT", 538, -526)
end
ui.percentCheckbox.label = ui.percentCheckbox:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.percentCheckbox.label:SetPoint("LEFT", ui.percentCheckbox, "RIGHT", 4, 0)
ui.percentCheckbox.label:SetTextColor(1.00, 0.82, 0.00)
ui.percentCheckbox.label:SetText("Show modifiers in %")
ui.percentCheckbox:SetScript("OnClick", function()
if not this then return end
MTH_BOOK_STATE.familyStatsPercent = this:GetChecked() == 1
MTH_BOOKTAB_RenderFamiliesList()
end)
ui.percentCheckbox:Hide()
end
ui.headerFamily = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerFamily:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 4, -2)
ui.headerFamily:SetTextColor(1.00, 0.82, 0.00)
ui.headerFamily:SetText("Family")
ui.headerNamed = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerNamed:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 122, -2)
ui.headerNamed:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 114, -2)
ui.headerNamed:SetTextColor(1.00, 0.82, 0.00)
ui.headerNamed:SetText("Named")
ui.headerCoords = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerCoords:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 176, -2)
ui.headerCoords:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 154, -2)
ui.headerCoords:SetTextColor(1.00, 0.82, 0.00)
ui.headerCoords:SetText("Coords")
ui.headerAbilities = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerAbilities:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 232, -2)
ui.headerAbilities:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 196, -2)
ui.headerAbilities:SetTextColor(1.00, 0.82, 0.00)
ui.headerAbilities:SetText("Abilities")
ui.headerDiet = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerDiet:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 532, -2)
ui.headerDiet:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 496, -2)
ui.headerDiet:SetTextColor(1.00, 0.82, 0.00)
ui.headerDiet:SetText("Diet")
ui.headerHP = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerHP:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 602, -2)
ui.headerHP:SetWidth(30)
ui.headerHP:SetJustifyH("CENTER")
ui.headerHP:SetTextColor(1.00, 0.82, 0.00)
ui.headerHP:SetText("HP")
ui.headerDmg = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerDmg:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 636, -2)
ui.headerDmg:SetWidth(36)
ui.headerDmg:SetJustifyH("CENTER")
ui.headerDmg:SetTextColor(1.00, 0.82, 0.00)
ui.headerDmg:SetText("dmg")
ui.headerArm = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerArm:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 674, -2)
ui.headerArm:SetWidth(38)
ui.headerArm:SetJustifyH("CENTER")
ui.headerArm:SetTextColor(1.00, 0.82, 0.00)
ui.headerArm:SetText("Arm")
ui.rows = {}
for i = 1, 28 do
local row = CreateFrame("Frame", nil, ui.frame)
@@ -289,26 +401,47 @@ 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(82)
row.family:SetJustifyH("LEFT")
row.named = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.named:SetPoint("LEFT", row, "LEFT", 122, 0)
row.named:SetWidth(52)
row.named:SetPoint("LEFT", row, "LEFT", 114, 0)
row.named:SetWidth(38)
row.named:SetJustifyH("LEFT")
row.coords = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.coords:SetPoint("LEFT", row, "LEFT", 176, 0)
row.coords:SetWidth(52)
row.coords:SetPoint("LEFT", row, "LEFT", 154, 0)
row.coords:SetWidth(38)
row.coords:SetJustifyH("LEFT")
row.diet = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.diet:SetPoint("LEFT", row, "LEFT", 532, 0)
row.diet:SetWidth(112)
row.diet:SetPoint("LEFT", row, "LEFT", 496, 0)
row.diet:SetWidth(102)
row.diet:SetJustifyH("LEFT")
row.hp = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.hp:SetPoint("LEFT", row, "LEFT", 602, 0)
row.hp:SetWidth(30)
row.hp:SetJustifyH("CENTER")
row.dmg = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.dmg:SetPoint("LEFT", row, "LEFT", 636, 0)
row.dmg:SetWidth(36)
row.dmg:SetJustifyH("CENTER")
row.arm = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.arm:SetPoint("LEFT", row, "LEFT", 674, 0)
row.arm:SetWidth(38)
row.arm:SetJustifyH("CENTER")
row.abilityButtons = {}
for b = 1, 16 do
local button = CreateFrame("Button", nil, row)
@@ -372,9 +505,14 @@ function MTH_BOOKTAB_SetFamiliesUIVisible(visible)
if detailParent then detailParent:Hide() end
if detailText then detailText:Hide() end
if openMapButton then openMapButton:Hide() end
if ui.percentCheckbox then
ui.percentCheckbox:SetChecked(MTH_BOOK_STATE and MTH_BOOK_STATE.familyStatsPercent == true and 1 or nil)
ui.percentCheckbox:Show()
end
ui.frame:Show()
else
ui.frame:Hide()
if ui.percentCheckbox then ui.percentCheckbox:Hide() end
if listParent then listParent:Show() end
if detailParent then detailParent:Show() end
if detailText then detailText:Show() end
@@ -393,12 +531,23 @@ 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 "-"))
rowFrame.hp:SetText(MTH_BOOKTAB_FormatFamilyStat(row.healthStat))
rowFrame.dmg:SetText(MTH_BOOKTAB_FormatFamilyStat(row.damageStat))
rowFrame.arm:SetText(MTH_BOOKTAB_FormatFamilyStat(row.armorStat))
local offsetX = 232
local abilityMaxRight = 526
local offsetX = 196
local abilityMaxRight = 490
for b = 1, table.getn(rowFrame.abilityButtons) do
local button = rowFrame.abilityButtons[b]
local ability = row.abilities and row.abilities[b] or nil
+100 -81
View File
@@ -140,7 +140,7 @@ function MTH_BOOK_GetResistanceShortName(nameText)
if string.find(lowerName, "arcane", 1, true) then return "AR" end
if string.find(lowerName, "nature", 1, true) then return "NR" end
if string.find(lowerName, "fire", 1, true) then return "FR" end
if string.find(lowerName, "frost", 1, true) then return "FR" end
if string.find(lowerName, "frost", 1, true) then return "FrR" end
if string.find(lowerName, "shadow", 1, true) then return "SR" end
return tostring(nameText or "-")
end
@@ -547,7 +547,75 @@ MTH_BOOK_NormalizeStableFoodLink = function(itemId, itemLink, itemName)
return nil
end
local function MTH_BOOK_FindStableFoodBagIcon(itemId, itemName)
local getContainerNumSlots = (type(getglobal) == "function" and getglobal("GetContainerNumSlots")) or (_G and _G["GetContainerNumSlots"])
local getContainerItemLink = (type(getglobal) == "function" and getglobal("GetContainerItemLink")) or (_G and _G["GetContainerItemLink"])
local getContainerItemInfo = (type(getglobal) == "function" and getglobal("GetContainerItemInfo")) or (_G and _G["GetContainerItemInfo"])
if type(getContainerNumSlots) ~= "function" or type(getContainerItemLink) ~= "function" or type(getContainerItemInfo) ~= "function" then
return nil
end
local wantedItemId = tonumber(itemId)
local wantedName = string.lower(tostring(itemName or ""))
for bag = 0, 4 do
local slotCount = tonumber(getContainerNumSlots(bag)) or 0
for slot = 1, slotCount do
local link = getContainerItemLink(bag, slot)
if link then
local matched = false
if wantedItemId then
local _, _, parsedId = string.find(tostring(link), "item:(%d+)")
if tonumber(parsedId) == wantedItemId then
matched = true
end
end
if not matched and wantedName ~= "" then
local _, _, parsedName = string.find(tostring(link), "|h%[([^%]]+)%]|h")
if parsedName and string.lower(tostring(parsedName)) == wantedName then
matched = true
end
end
if matched then
local bagTexture = getContainerItemInfo(bag, slot)
if bagTexture and bagTexture ~= "" then
if type(bagTexture) == "string" and string.find(bagTexture, "\\", 1, true) then
return bagTexture
end
end
end
end
end
end
return nil
end
local function MTH_BOOK_ResolveStableFoodIcon(itemId, iconHint, itemName, itemLink)
local function resolveTextureCandidate(value)
if value == nil or value == "" then
return nil
end
if type(value) == "number" then
return nil
end
local text = tostring(value)
if text == "" then
return nil
end
if string.find(text, "|Hitem:", 1, true) or string.find(text, "^item:%d+") then
return nil
end
if string.find(text, "^table:", 1, true) then
return nil
end
if string.find(text, "\\", 1, true) then
return text
end
local asNumber = tonumber(text)
if asNumber then
return nil
end
return "Interface\\Icons\\" .. text
end
local linkText = tostring(itemLink or "")
local linkItemId = nil
if linkText ~= "" then
@@ -559,104 +627,55 @@ local function MTH_BOOK_ResolveStableFoodIcon(itemId, iconHint, itemName, itemLi
local numericItemId = tonumber(itemId) or linkItemId
if linkText ~= "" and type(GetItemInfo) == "function" then
local _, _, _, _, _, _, _, _, _, linkTexture = GetItemInfo(linkText)
if linkTexture and linkTexture ~= "" then
if type(linkTexture) == "number" then
return linkTexture
end
if string.find(tostring(linkTexture), "\\", 1, true) then
return tostring(linkTexture)
end
local asNumber = tonumber(tostring(linkTexture))
if asNumber then
return asNumber
end
return "Interface\\Icons\\" .. tostring(linkTexture)
end
end
if type(iconHint) == "number" then
return iconHint
end
local iconHintText = tostring(iconHint or "")
if iconHintText ~= "" then
local numericHint = tonumber(iconHintText)
if numericHint then
return numericHint
end
end
local iconPath = iconHintText
if iconPath ~= "" then
if string.find(iconPath, "\\", 1, true) then
return iconPath
end
return "Interface\\Icons\\" .. iconPath
end
if numericItemId and type(GetItemInfo) == "function" then
local _, _, _, _, _, _, _, _, _, infoTexture = GetItemInfo(numericItemId)
if infoTexture and infoTexture ~= "" then
if type(infoTexture) == "number" then
return infoTexture
end
if string.find(tostring(infoTexture), "\\", 1, true) then
return tostring(infoTexture)
end
local asNumber = tonumber(tostring(infoTexture))
if asNumber then
return asNumber
end
return "Interface\\Icons\\" .. tostring(infoTexture)
local resolvedInfoTexture = resolveTextureCandidate(infoTexture)
if resolvedInfoTexture then
return resolvedInfoTexture
end
end
if numericItemId and type(GetItemIcon) == "function" then
local directIcon = GetItemIcon(numericItemId)
if directIcon and directIcon ~= "" then
if type(directIcon) == "number" then
return directIcon
end
if string.find(tostring(directIcon), "\\", 1, true) then
return tostring(directIcon)
end
local asNumber = tonumber(tostring(directIcon))
if asNumber then
return asNumber
end
return "Interface\\Icons\\" .. tostring(directIcon)
local resolvedDirectIcon = resolveTextureCandidate(directIcon)
if resolvedDirectIcon then
return resolvedDirectIcon
end
end
local resolvedHintIcon = resolveTextureCandidate(iconHint)
if resolvedHintIcon then
return resolvedHintIcon
end
if linkText ~= "" and type(GetItemInfo) == "function" then
local _, _, _, _, _, _, _, _, _, linkTexture = GetItemInfo(linkText)
local resolvedLinkTexture = resolveTextureCandidate(linkTexture)
if resolvedLinkTexture then
return resolvedLinkTexture
end
end
if itemName and itemName ~= "" and type(GetItemIcon) == "function" then
local namedIcon = GetItemIcon(itemName)
if namedIcon and namedIcon ~= "" then
if type(namedIcon) == "number" then
return namedIcon
end
if string.find(tostring(namedIcon), "\\", 1, true) then
return tostring(namedIcon)
end
local asNumber = tonumber(tostring(namedIcon))
if asNumber then
return asNumber
end
return "Interface\\Icons\\" .. tostring(namedIcon)
local resolvedNamedIcon = resolveTextureCandidate(namedIcon)
if resolvedNamedIcon then
return resolvedNamedIcon
end
end
if numericItemId and type(MTH_DS_Items) == "table" and type(MTH_DS_Items[numericItemId]) == "table" then
local dsIcon = tostring(MTH_DS_Items[numericItemId].icon or "")
if dsIcon ~= "" then
if string.find(dsIcon, "\\", 1, true) then
return dsIcon
end
return "Interface\\Icons\\" .. dsIcon
local resolvedDsIcon = resolveTextureCandidate(MTH_DS_Items[numericItemId].icon)
if resolvedDsIcon then
return resolvedDsIcon
end
end
local bagIcon = MTH_BOOK_FindStableFoodBagIcon(numericItemId, itemName)
if bagIcon then
return bagIcon
end
return nil
end
@@ -1566,7 +1585,7 @@ function MTH_BOOKTAB_RenderStableCards()
local abilitiesCount = MTH_BOOK_CountPetAbilitiesMap(row.abilities)
local abilitiesText = MTH_BOOK_GetStableAbilitySummary(row.abilities)
local legacyTameContext = type(row.tameContext) == "table" and row.tameContext or nil
local hasRecordedTame = row.tameRecorded == true
local hasRecordedTame = type(MTH_PETS_HasVerifiedTameRecord) == "function" and MTH_PETS_HasVerifiedTameRecord(row) or (row.tameRecorded == true)
if not hasRecordedTame and legacyTameContext then
if legacyTameContext.name or legacyTameContext.zone or legacyTameContext.timestamp then
hasRecordedTame = true
+510 -116
View File
File diff suppressed because it is too large Load Diff
+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>
+2 -1
View File
@@ -9,7 +9,6 @@ local MTH_LOCALE_ALIASES = {
enGB = "enUS",
esMX = "esES",
zhTW = "zhCN",
frFR = "enUS",
}
local MTH_SUPPORTED_LOCALES = {
@@ -57,6 +56,8 @@ BINDING_HEADER_ZTrackHeader = MTH_L("BINDING_HEADER_ZTrackHeader", "[MetaHunt] Z
BINDING_HEADER_ZTrapHeader = MTH_L("BINDING_HEADER_ZTrapHeader", "[MetaHunt] ZTrap Buttons")
BINDING_HEADER_ZPetHeader = MTH_L("BINDING_HEADER_ZPetHeader", "[MetaHunt] ZPet Buttons")
BINDING_HEADER_ZAmmoHeader = MTH_L("BINDING_HEADER_ZAmmoHeader", "[MetaHunt] ZAmmo Buttons")
BINDING_HEADER_ExpAmmoHeader = MTH_L("BINDING_HEADER_ExpAmmoHeader", "[MetaHunt] MM Widget")
BINDING_NAME_EXPAMMOACTION = MTH_L("BINDING_NAME_EXPAMMOACTION", "MM Widget Action")
function MTH:GetLocalization(key, default)
return MTH_L(key, default)
+144 -49
View File
@@ -5,6 +5,7 @@ MTH_Map = {
activeSource = "focus",
providers = {},
nodesByZone = {},
minimapNodesByZone = {},
worldPins = {},
minimapPins = {},
zoneNameToId = {},
@@ -12,6 +13,7 @@ MTH_Map = {
mapZoneCache = {},
focusNodes = {},
pendingZoneOpenId = nil,
pendingZoneOpenPfqId = nil,
pendingZoneOpenSourceId = nil,
pendingZoneOpenAttempts = 0,
pendingZoneOpenNextAttemptAt = 0,
@@ -23,7 +25,7 @@ MTH_Map = {
nodeRevision = 0,
minimapForceRefreshInterval = 1.0,
verboseMiniReasons = false,
minimapTickActive = 0.20,
minimapTickActive = 0.05,
minimapTickIdle = 0.75,
}
@@ -60,11 +62,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
@@ -126,16 +123,54 @@ local function MTH_Map_StripThePrefix(name)
return nil
end
local function MTH_Map_WmaToPfq(zoneId)
if type(MTH_MAP_WMA_TO_PFQ) == "table" then
return MTH_MAP_WMA_TO_PFQ[zoneId] or zoneId
end
return zoneId
end
local function MTH_Map_PfqToWma(pfqId)
if type(MTH_MAP_PFQ_TO_WMA) == "table" then
return MTH_MAP_PFQ_TO_WMA[pfqId] or pfqId
end
return pfqId
end
local function MTH_Map_GetMinimapSizes()
if pfDB and pfDB["minimap"] and MTH_DS_MinimapSizes then
if not MTH_Map._mergedMinimapSizes then
local merged = {}
for zoneId, size in pairs(pfDB["minimap"]) do
merged[zoneId] = size
end
for zoneId, size in pairs(MTH_DS_MinimapSizes) do
if merged[zoneId] == nil then
merged[zoneId] = size
end
end
MTH_Map._mergedMinimapSizes = merged
end
return MTH_Map._mergedMinimapSizes
elseif pfDB and pfDB["minimap"] then
return pfDB["minimap"]
end
return MTH_DS_MinimapSizes or {}
end
local function MTH_Map_GetZoneNameById(zoneId)
local normalizedId = tonumber(zoneId) or zoneId
if not normalizedId then return nil end
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 = MTH_Map_WmaToPfq(normalizedId)
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 +362,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)
@@ -363,6 +397,8 @@ end
function MTH_Map:ResolveZoneHierarchy(zoneId)
local inputId = tonumber(zoneId)
local pfqInputId = MTH_Map_WmaToPfq(inputId)
local inputIsWma = inputId and pfqInputId ~= inputId or false
local resolved = {
inputId = inputId,
zoneId = inputId,
@@ -376,9 +412,9 @@ function MTH_Map:ResolveZoneHierarchy(zoneId)
return resolved
end
local sizes = MTH_DS_MinimapSizes or (pfDB and pfDB["minimap"]) or {}
local sizes = MTH_Map_GetMinimapSizes()
local visited = {}
local cursor = inputId
local cursor = pfqInputId
local depth = 0
while cursor and not visited[cursor] and depth < 8 do
@@ -389,15 +425,17 @@ function MTH_Map:ResolveZoneHierarchy(zoneId)
end
local parent = tonumber(row.parent or row.continent)
local outputCursor = inputIsWma and MTH_Map_PfqToWma(cursor) or cursor
local outputParent = parent and (inputIsWma and MTH_Map_PfqToWma(parent) or parent) or nil
if parent and not resolved.continentId then
resolved.continentId = parent
resolved.continentId = outputParent
end
if parent and parent > 0 and parent ~= cursor then
resolved.subzoneId = resolved.subzoneId or cursor
resolved.parentId = parent
resolved.subzoneId = resolved.subzoneId or outputCursor
resolved.parentId = outputParent
cursor = parent
resolved.zoneId = cursor
resolved.zoneId = inputIsWma and MTH_Map_PfqToWma(cursor) or cursor
depth = depth + 1
else
break
@@ -405,11 +443,17 @@ function MTH_Map:ResolveZoneHierarchy(zoneId)
end
local renderZoneId = resolved.zoneId
local pfqRenderId = tonumber(cursor or pfqInputId)
local pfqParentId = resolved.parentId and MTH_Map_WmaToPfq(resolved.parentId) or nil
if not sizes[renderZoneId] then
if sizes[inputId] then
renderZoneId = inputId
elseif resolved.parentId and sizes[resolved.parentId] then
renderZoneId = resolved.parentId
elseif pfqRenderId and sizes[pfqRenderId] then
renderZoneId = pfqRenderId
elseif pfqParentId and sizes[pfqParentId] then
renderZoneId = pfqParentId
end
end
@@ -418,12 +462,13 @@ function MTH_Map:ResolveZoneHierarchy(zoneId)
return resolved
end
function MTH_Map:GetCurrentMapID()
function MTH_Map:GetCurrentMapID(space)
local continent = GetCurrentMapContinent and GetCurrentMapContinent() or nil
local zone = GetCurrentMapZone and GetCurrentMapZone() or nil
local zoneName = nil
local mapId = nil
local worldMapShown = WorldMapFrame and WorldMapFrame.IsShown and WorldMapFrame:IsShown() or false
local preferPfq = space == "pfq"
if continent and zone and continent > 0 and zone > 0 and GetMapZones then
if not self.mapZoneCache[continent] then
@@ -434,6 +479,9 @@ function MTH_Map:GetCurrentMapID()
if mapId then
local resolved = self:ResolveZoneHierarchy(mapId)
local normalized = resolved.zoneId or mapId
if not preferPfq then
normalized = MTH_Map_PfqToWma(normalized)
end
self.lastMapContext = string.format("continent=%s zone=%s mapZone='%s' real=nil mapId=%s normalized=%s", tostring(continent), tostring(zone), tostring(zoneName), tostring(mapId), tostring(normalized))
return normalized
end
@@ -449,6 +497,9 @@ function MTH_Map:GetCurrentMapID()
mapId = self:GetMapIDByName(real)
local resolved = self:ResolveZoneHierarchy(mapId)
local normalized = resolved.zoneId or mapId
if not preferPfq then
normalized = MTH_Map_PfqToWma(normalized)
end
self.lastMapContext = string.format("continent=%s zone=%s mapZone='%s' real='%s' mapId=%s normalized=%s", tostring(continent), tostring(zone), tostring(zoneName), tostring(real), tostring(mapId), tostring(normalized))
return normalized
end
@@ -458,6 +509,30 @@ function MTH_Map:GetCurrentMapID()
return nil
end
function MTH_Map:GetCurrentMinimapMapID()
local realZoneName = GetRealZoneText and GetRealZoneText() or nil
if realZoneName and realZoneName ~= "" then
local mapId = self:GetMapIDByName(realZoneName)
if mapId then
return mapId
end
end
local continent = GetCurrentMapContinent and GetCurrentMapContinent() or nil
local zone = GetCurrentMapZone and GetCurrentMapZone() or nil
if continent and zone and continent > 0 and zone > 0 and GetMapZones then
if not self.mapZoneCache[continent] then
self.mapZoneCache[continent] = { GetMapZones(continent) }
end
local zoneName = self.mapZoneCache[continent][zone]
if zoneName and zoneName ~= "" then
return self:GetMapIDByName(zoneName)
end
end
return nil
end
function MTH_Map:RegisterProvider(key, provider)
if not key or type(key) ~= "string" then return false end
if type(provider) ~= "table" or type(provider.buildNodes) ~= "function" then return false end
@@ -467,6 +542,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 +608,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 +626,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
@@ -864,6 +949,7 @@ end
function MTH_Map:RebuildNodes()
self:BuildZoneLookup()
self.nodesByZone = {}
self.minimapNodesByZone = {}
self.nodeRevision = (tonumber(self.nodeRevision) or 0) + 1
self._lastWorldRenderKey = nil
self._lastMiniState = nil
@@ -877,20 +963,26 @@ function MTH_Map:RebuildNodes()
local node = allNodes[i]
if node and node.zoneId then
local sourceZoneId = tonumber(node.zoneId)
local resolved = self:ResolveZoneHierarchy(sourceZoneId)
local zoneId = tonumber(resolved.zoneId or sourceZoneId)
if zoneId then
local worldResolved = self:ResolveZoneHierarchy(sourceZoneId)
local worldZoneId = tonumber(worldResolved.zoneId or sourceZoneId)
local pfqSourceZoneId = MTH_Map_WmaToPfq(sourceZoneId)
local minimapZoneId = tonumber(pfqSourceZoneId)
if worldZoneId then
node.sourceZoneId = sourceZoneId
node.zoneId = zoneId
node.parentZoneId = resolved.parentId
node.subzoneId = resolved.subzoneId
if not self.nodesByZone[zoneId] then self.nodesByZone[zoneId] = {} end
table.insert(self.nodesByZone[zoneId], node)
node.zoneId = worldZoneId
node.parentZoneId = worldResolved.parentId
node.subzoneId = worldResolved.subzoneId
node.minimapZoneId = minimapZoneId
if not self.nodesByZone[worldZoneId] then self.nodesByZone[worldZoneId] = {} end
table.insert(self.nodesByZone[worldZoneId], node)
if minimapZoneId then
if not self.minimapNodesByZone[minimapZoneId] then self.minimapNodesByZone[minimapZoneId] = {} end
table.insert(self.minimapNodesByZone[minimapZoneId], node)
end
end
end
end
MTH_Map_Log("Rebuilt nodes for source '" .. tostring(self.activeSource) .. "'")
end
function MTH_Map:UpdateWorldMap()
@@ -975,8 +1067,8 @@ function MTH_Map:UpdateMinimap()
return
end
local mapId = self:GetCurrentMapID()
local nodes = mapId and self.nodesByZone[mapId] or nil
local mapId = self:GetCurrentMinimapMapID()
local nodes = mapId and self.minimapNodesByZone[mapId] or nil
if not mapId or not nodes then
self.lastMiniReason = "no-mapid-or-nodes"
self._lastMiniState = nil
@@ -1009,16 +1101,18 @@ function MTH_Map:UpdateMinimap()
and miniState.zoom == zoom
and miniState.nodeRevision == (self.nodeRevision or 0)
then
local dx = math.abs((xPlayer or 0) - (miniState.xPlayer or 0))
local dy = math.abs((yPlayer or 0) - (miniState.yPlayer or 0))
if dx < 0.05 and dy < 0.05 and now < (miniState.nextForceAt or 0) then
self.lastMiniReason = self.verboseMiniReasons and "throttled-small-move" or "throttle"
if xPlayer == miniState.xPlayer and yPlayer == miniState.yPlayer and now < (miniState.nextForceAt or 0) then
self.lastMiniReason = self.verboseMiniReasons and "throttled-still" or "throttle"
return
end
end
local minimapSizes = MTH_DS_MinimapSizes or (pfDB and pfDB["minimap"])
local minimapSizes = MTH_Map_GetMinimapSizes()
local mapSize = minimapSizes and minimapSizes[mapId]
if not mapSize then
local wmaId = MTH_Map_PfqToWma(mapId)
mapSize = wmaId and minimapSizes and minimapSizes[wmaId]
end
if not mapSize then
self.lastMiniReason = self.verboseMiniReasons and "missing-minimap-size" or "missing-size"
self._lastMiniState = nil
@@ -1227,7 +1321,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
@@ -1244,7 +1337,7 @@ function MTH_Map:Init()
self.nodesByZone = self.nodesByZone or {}
if not self.controller then
self.controller = CreateFrame("Frame", "MTHMapController", UIParent)
self.controller = CreateFrame("Frame", "MTH_MapController", UIParent)
self.controller:RegisterEvent("PLAYER_ENTERING_WORLD")
self.controller:RegisterEvent("ZONE_CHANGED")
self.controller:RegisterEvent("ZONE_CHANGED_NEW_AREA")
@@ -1253,6 +1346,9 @@ function MTH_Map:Init()
self.controller:SetScript("OnEvent", function(frame, eventName)
eventName = eventName or event
if eventName == "WORLD_MAP_UPDATE" and MTH_Map._syncingMapContext then
return
end
if eventName == "PLAYER_ENTERING_WORLD" or eventName == "ZONE_CHANGED" or eventName == "ZONE_CHANGED_NEW_AREA" or eventName == "MINIMAP_ZONE_CHANGED" then
if SetMapToCurrentZone and (not WorldMapFrame or not WorldMapFrame:IsShown()) then
SetMapToCurrentZone()
@@ -1274,9 +1370,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()
@@ -1295,15 +1391,15 @@ function MTH_Map:Init()
end
if MTH_Map.enabled and MTH_Map.showMinimap then
local mapId = MTH_Map:GetCurrentMapID()
local mapId = MTH_Map:GetCurrentMinimapMapID()
if not mapId and (frame._mthMapRecoverAt or 0) <= now then
frame._mthMapRecoverAt = now + 2.0
if SetMapToCurrentZone and (not WorldMapFrame or not WorldMapFrame:IsShown()) then
SetMapToCurrentZone()
mapId = MTH_Map:GetCurrentMapID()
mapId = MTH_Map:GetCurrentMinimapMapID()
end
end
local nodes = mapId and MTH_Map.nodesByZone and MTH_Map.nodesByZone[mapId] or nil
local nodes = mapId and MTH_Map.minimapNodesByZone and MTH_Map.minimapNodesByZone[mapId] or nil
local hasNodes = nodes and table.getn(nodes) > 0
frame._mthTick = now + (hasNodes and (tonumber(MTH_Map.minimapTickActive) or 0.20) or (tonumber(MTH_Map.minimapTickIdle) or 0.75))
if hasNodes then
@@ -1318,5 +1414,4 @@ function MTH_Map:Init()
end)
end
MTH_Map_Log("Map system initialized")
end
+29 -26
View File
@@ -1,5 +1,13 @@
------------------------------------------------------
-- MetaHunt Minimap Button
--
-- NOTE: The namespace table is MTH_MinimapButton (a plain Lua table).
-- The actual UI frame is named "MTH_MinimapBtn" to avoid a global
-- collision — CreateFrame("...", "MTH_MinimapButton", ...) would
-- OVERWRITE the namespace table with the frame object, orphaning
-- all methods and .frame/.icon references. pfUI iterates Minimap
-- children by name and calls _G[name]:GetHeight(); if the global is
-- a plain {} table instead of a frame, that call crashes.
------------------------------------------------------
if not MTH_MinimapButton then
@@ -84,7 +92,9 @@ function MTH_MinimapButton:Initialize()
return
end
local button = CreateFrame("Button", "MTH_MinimapButtonFrame", Minimap)
-- Frame name is MTH_MinimapBtn (NOT "MTH_MinimapButton") to avoid
-- overwriting the namespace table in _G.
local button = CreateFrame("Button", "MTH_MinimapBtn", Minimap)
if not button then
return
end
@@ -114,10 +124,9 @@ function MTH_MinimapButton:Initialize()
highlight:SetHeight(31)
highlight:SetPoint("CENTER", button, "CENTER", 0, 0)
button:SetScript("OnEnter", function(self)
self = self or this
if not self then return end
GameTooltip:SetOwner(self, "ANCHOR_LEFT")
button:SetScript("OnEnter", function()
if not this then return end
GameTooltip:SetOwner(this, "ANCHOR_LEFT")
GameTooltip:SetText(MTH_MB_L("MINIMAP_TITLE", "MetaHunt"), 1, 0.82, 0)
GameTooltip:AddLine(MTH_MB_L("MINIMAP_HINT_LEFT_CLICK", "Left-Click: Open Hunter Book"), 1, 1, 1)
GameTooltip:AddLine(MTH_MB_L("MINIMAP_HINT_RIGHT_CLICK", "Right-Click: Open MetaHunt Options"), 1, 1, 1)
@@ -129,25 +138,23 @@ function MTH_MinimapButton:Initialize()
GameTooltip:Hide()
end)
button:SetScript("OnDragStart", function(self)
self = self or this
if not self then return end
self.dragging = 1
self:SetScript("OnUpdate", function()
button:SetScript("OnDragStart", function()
if not this then return end
this.dragging = 1
this:SetScript("OnUpdate", function()
MTH_MB_UpdateDragPosition()
end)
end)
button:SetScript("OnDragStop", function(self)
self = self or this
if not self then return end
self.dragging = nil
self:SetScript("OnUpdate", nil)
button:SetScript("OnDragStop", function()
if not this then return end
this.dragging = nil
this:SetScript("OnUpdate", nil)
MTH_MB_UpdateDragPosition()
end)
button:SetScript("OnClick", function(_, mouseButton)
mouseButton = mouseButton or arg1
button:SetScript("OnClick", function()
local mouseButton = arg1
if mouseButton == "RightButton" then
if type(MTH_OpenOptions) == "function" then
MTH_OpenOptions("General")
@@ -167,16 +174,12 @@ function MTH_MinimapButton:Initialize()
MTH_MB_UpdatePosition()
end
local loader = CreateFrame("Frame", "MTH_MinimapButtonLoader")
local loader = CreateFrame("Frame", "MTH_MinimapLoader")
loader:RegisterEvent("ADDON_LOADED")
loader:SetScript("OnEvent", function(self, eventName, addonName)
eventName = eventName or event
addonName = addonName or arg1
if eventName == "ADDON_LOADED" and addonName == "MetaHunt" then
loader:SetScript("OnEvent", function()
if event == "ADDON_LOADED" and arg1 == "MetaHunt" then
MTH_MinimapButton:Initialize()
if self and self.UnregisterAllEvents then
self:UnregisterAllEvents()
self:SetScript("OnEvent", nil)
end
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
end
end)
+30 -40
View File
@@ -657,28 +657,25 @@ local function MTH_AB_CreateRow(container, subtype, index, xBase, yOffset)
row.suffix:SetText("Stacks")
if row.check then
row.check:SetScript("OnClick", function(self)
self = self or this
if not self or MTH_AB_STATE.syncing then return end
row.check:SetScript("OnClick", function()
if not this or MTH_AB_STATE.syncing then return end
local store = MTH_AB_EnsureConfig()
MTH_AB_SetRuleEnabled(store, subtype, index, MTH_AB_IsChecked(self))
MTH_AB_SetRuleEnabled(store, subtype, index, MTH_AB_IsChecked(this))
MTH_AB_RefreshOptionsUI()
end)
end
row.qty:SetScript("OnEnterPressed", function(self)
self = self or this
if not self then return end
row.qty:SetScript("OnEnterPressed", function()
if not this then return end
local store = MTH_AB_EnsureConfig()
MTH_AB_SetRuleStacks(store, subtype, index, self:GetText())
MTH_AB_SetRuleStacks(store, subtype, index, this:GetText())
MTH_AB_RefreshOptionsUI()
self:ClearFocus()
this:ClearFocus()
end)
row.qty:SetScript("OnEditFocusLost", function(self)
self = self or this
if not self then return end
row.qty:SetScript("OnEditFocusLost", function()
if not this then return end
local store = MTH_AB_EnsureConfig()
MTH_AB_SetRuleStacks(store, subtype, index, self:GetText())
MTH_AB_SetRuleStacks(store, subtype, index, this:GetText())
MTH_AB_RefreshOptionsUI()
end)
@@ -715,11 +712,10 @@ local function MTH_AB_BuildUI(container)
controls.moduleEnabled = MTH_CreateCheckbox(container, "MetaHuntOptionsAutoBuyModuleEnabled", "Enable Auto Buy module", -30)
if controls.moduleEnabled then
controls.moduleEnabled:SetScript("OnClick", function(self)
self = self or this
if not self or MTH_AB_STATE.syncing then return end
controls.moduleEnabled:SetScript("OnClick", function()
if not this or MTH_AB_STATE.syncing then return end
if MTH and MTH.SetModuleEnabled then
local ok, err = MTH:SetModuleEnabled("autobuy", MTH_AB_IsChecked(self))
local ok, err = MTH:SetModuleEnabled("autobuy", MTH_AB_IsChecked(this))
if not ok and MTH and MTH.Print then
MTH:Print("Failed to change Auto Buy state: " .. tostring(err), "error")
end
@@ -730,11 +726,10 @@ local function MTH_AB_BuildUI(container)
controls.projectilesEnabled = MTH_CreateCheckbox(container, "MetaHuntOptionsAutoBuyProjectilesEnabled", "Enable for Projectiles", -74)
if controls.projectilesEnabled then
controls.projectilesEnabled:SetScript("OnClick", function(self)
self = self or this
if not self or MTH_AB_STATE.syncing then return end
controls.projectilesEnabled:SetScript("OnClick", function()
if not this or MTH_AB_STATE.syncing then return end
local store = MTH_AB_EnsureConfig()
store.projectiles.enabled = MTH_AB_IsChecked(self)
store.projectiles.enabled = MTH_AB_IsChecked(this)
MTH_AB_RefreshOptionsUI()
end)
end
@@ -769,11 +764,10 @@ local function MTH_AB_BuildUI(container)
controls.petFoodEnabled = MTH_CreateCheckbox(container, "MetaHuntOptionsAutoBuyPetFoodEnabled", "Enable for Pet Food", -322, MTH_AB_LAYOUT.LEFT_SECTION_X)
if controls.petFoodEnabled then
controls.petFoodEnabled:SetScript("OnClick", function(self)
self = self or this
if not self or MTH_AB_STATE.syncing then return end
controls.petFoodEnabled:SetScript("OnClick", function()
if not this or MTH_AB_STATE.syncing then return end
local store = MTH_AB_EnsureConfig()
store.petFood.enabled = MTH_AB_IsChecked(self)
store.petFood.enabled = MTH_AB_IsChecked(this)
MTH_AB_RefreshOptionsUI()
end)
end
@@ -791,19 +785,17 @@ local function MTH_AB_BuildUI(container)
controls.petFoodQty:SetHeight(20)
controls.petFoodQty:SetNumeric(true)
controls.petFoodQty:SetAutoFocus(false)
controls.petFoodQty:SetScript("OnEnterPressed", function(self)
self = self or this
if not self then return end
controls.petFoodQty:SetScript("OnEnterPressed", function()
if not this then return end
local store = MTH_AB_EnsureConfig()
MTH_AB_SetPetFoodStacks(store, self:GetText())
MTH_AB_SetPetFoodStacks(store, this:GetText())
MTH_AB_RefreshOptionsUI()
self:ClearFocus()
this:ClearFocus()
end)
controls.petFoodQty:SetScript("OnEditFocusLost", function(self)
self = self or this
if not self then return end
controls.petFoodQty:SetScript("OnEditFocusLost", function()
if not this then return end
local store = MTH_AB_EnsureConfig()
MTH_AB_SetPetFoodStacks(store, self:GetText())
MTH_AB_SetPetFoodStacks(store, this:GetText())
MTH_AB_RefreshOptionsUI()
end)
end
@@ -816,9 +808,8 @@ local function MTH_AB_BuildUI(container)
controls.petFoodScopeCurrent = MTH_CreateCheckbox(container, "MetaHuntOptionsAutoBuyPetFoodScopeCurrent", "Buy only for my current pet", -376, MTH_AB_LAYOUT.LEFT_SECTION_X + 28)
if controls.petFoodScopeCurrent then
controls.petFoodScopeCurrent:SetScript("OnClick", function(self)
self = self or this
if not self or MTH_AB_STATE.syncing then return end
controls.petFoodScopeCurrent:SetScript("OnClick", function()
if not this or MTH_AB_STATE.syncing then return end
local store = MTH_AB_EnsureConfig()
store.petFood.scope = "current"
MTH_AB_RefreshOptionsUI()
@@ -827,9 +818,8 @@ local function MTH_AB_BuildUI(container)
controls.petFoodScopeAll = MTH_CreateCheckbox(container, "MetaHuntOptionsAutoBuyPetFoodScopeAll", "Buy for all my pets", -402, MTH_AB_LAYOUT.LEFT_SECTION_X + 28)
if controls.petFoodScopeAll then
controls.petFoodScopeAll:SetScript("OnClick", function(self)
self = self or this
if not self or MTH_AB_STATE.syncing then return end
controls.petFoodScopeAll:SetScript("OnClick", function()
if not this or MTH_AB_STATE.syncing then return end
local store = MTH_AB_EnsureConfig()
store.petFood.scope = "all"
MTH_AB_RefreshOptionsUI()
+10 -14
View File
@@ -72,10 +72,9 @@ function MTH_SetupAutoQuestOptions()
local moduleToggle = MTH_CreateCheckbox(container, "MetaHuntOptionsAutoQuestModuleToggle", "Enable Auto Quest module", -70, 20)
if moduleToggle then
moduleToggle:SetChecked(moduleEnabled and true or false)
moduleToggle:SetScript("OnClick", function(self)
self = self or this
if not self then return end
local enabled = MTH_AQ_IsChecked(self)
moduleToggle:SetScript("OnClick", function()
if not this then return end
local enabled = MTH_AQ_IsChecked(this)
if MTH and MTH.SetModuleEnabled then
local ok, err = MTH:SetModuleEnabled("autoquest", enabled)
if not ok and MTH and MTH.Print then
@@ -84,7 +83,7 @@ function MTH_SetupAutoQuestOptions()
end
local module = MTH and MTH.GetModule and MTH:GetModule("autoquest") or nil
local actual = (type(module) == "table" and module.enabled ~= nil) and (module.enabled and true or false) or enabled
self:SetChecked(actual and true or false)
this:SetChecked(actual and true or false)
end)
end
@@ -151,9 +150,8 @@ function MTH_SetupAutoQuestOptions()
store.scorpokDrazial and true or false
)
if scorpokToggle then
scorpokToggle:SetScript("OnClick", function(self)
self = self or this
local enabled = MTH_AQ_IsChecked(self)
scorpokToggle:SetScript("OnClick", function()
local enabled = MTH_AQ_IsChecked(this)
store.scorpokDrazial = enabled and true or false
if MTH and MTH.GetModuleCharSavedVariables then
local moduleStore = MTH:GetModuleCharSavedVariables("autoquest")
@@ -176,9 +174,8 @@ function MTH_SetupAutoQuestOptions()
store.scorpokTooltip and true or false
)
if tooltipToggle then
tooltipToggle:SetScript("OnClick", function(self)
self = self or this
local enabled = MTH_AQ_IsChecked(self)
tooltipToggle:SetScript("OnClick", function()
local enabled = MTH_AQ_IsChecked(this)
store.scorpokTooltip = enabled and true or false
if MTH and MTH.GetModuleCharSavedVariables then
local moduleStore = MTH:GetModuleCharSavedVariables("autoquest")
@@ -212,9 +209,8 @@ function MTH_SetupAutoQuestOptions()
store.arrowsForSissies and true or false
)
if sissiesToggle then
sissiesToggle:SetScript("OnClick", function(self)
self = self or this
local enabled = MTH_AQ_IsChecked(self)
sissiesToggle:SetScript("OnClick", function()
local enabled = MTH_AQ_IsChecked(this)
store.arrowsForSissies = enabled and true or false
if MTH and MTH.GetModuleCharSavedVariables then
local moduleStore = MTH:GetModuleCharSavedVariables("autoquest")
+37 -40
View File
@@ -252,8 +252,14 @@ local function MTH_CHRON_ApplyLiveProfile(profile)
end
engine.profile = profile
if engine.SetCandyBarGroupGrowth then
engine:SetCandyBarGroupGrowth("MTHChronometer", profile.growup and true or false)
if engine.SetCandyBarGroupGrowth and engine.SetCandyBarGroupPoint and engine.anchor then
local growup = profile.growup and true or false
if growup then
engine:SetCandyBarGroupPoint("MTHChronometer", "BOTTOM", engine.anchor, "TOP", 0, 0)
else
engine:SetCandyBarGroupPoint("MTHChronometer", "TOP", engine.anchor, "BOTTOM", 0, 0)
end
engine:SetCandyBarGroupGrowth("MTHChronometer", growup)
end
if engine.SetCandyBarGroupVerticalSpacing then
engine:SetCandyBarGroupVerticalSpacing("MTHChronometer", profile.spacing or 0)
@@ -326,13 +332,12 @@ local function MTH_CHRON_MakeInput(parent, name, x, y, width, height, value, onA
edit:SetHeight(height or 20)
edit:SetAutoFocus(false)
edit:SetText(tostring(value or ""))
edit:SetScript("OnEnterPressed", function(self)
self = self or this
if not self then return end
edit:SetScript("OnEnterPressed", function()
if not this then return end
if type(onAccept) == "function" then
onAccept(self:GetText() or "")
onAccept(this:GetText() or "")
end
self:ClearFocus()
this:ClearFocus()
end)
return edit
end
@@ -385,16 +390,15 @@ local function MTH_CHRON_RenderTimerSection(container, profile, title, items, bu
local check = MTH_CreateCheckbox(container, controlName, label, timerY, timerX)
if check then
check:SetChecked(profile.disabledSpells[bucket][timerName] == nil and true or false)
check:SetScript("OnClick", function(self)
self = self or this
if not self then return end
check:SetScript("OnClick", function()
if not this then return end
local currentlyDisabled = profile.disabledSpells[bucket][timerName] ~= nil
if currentlyDisabled then
profile.disabledSpells[bucket][timerName] = nil
self:SetChecked(true)
this:SetChecked(true)
else
profile.disabledSpells[bucket][timerName] = {}
self:SetChecked(nil)
this:SetChecked(nil)
end
MTH_CHRON_ApplyLiveProfile(profile)
@@ -470,10 +474,9 @@ function MTH_SetupChronometerOptions(tabKey)
if moduleCheck then
local module = MTH and MTH:GetModule("chronometer")
moduleCheck:SetChecked(module and module.enabled and true or false)
moduleCheck:SetScript("OnClick", function(self)
self = self or this
if not self then return end
local enabled = MTH_CHRON_IsChecked(self)
moduleCheck:SetScript("OnClick", function()
if not this then return end
local enabled = MTH_CHRON_IsChecked(this)
if MTH and MTH.SetModuleEnabled then
MTH:SetModuleEnabled("chronometer", enabled)
end
@@ -495,40 +498,36 @@ function MTH_SetupChronometerOptions(tabKey)
local killCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerFadeOnKill", "Fade on kill", -154)
if killCheck then
killCheck:SetChecked(profile.fadeonkill and true or false)
killCheck:SetScript("OnClick", function(self)
self = self or this
if not self then return end
profile.fadeonkill = MTH_CHRON_IsChecked(self)
killCheck:SetScript("OnClick", function()
if not this then return end
profile.fadeonkill = MTH_CHRON_IsChecked(this)
end)
end
local fadeCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerFadeOnFade", "Fade on aura fade", -179)
if fadeCheck then
fadeCheck:SetChecked(profile.fadeonfade and true or false)
fadeCheck:SetScript("OnClick", function(self)
self = self or this
if not self then return end
profile.fadeonfade = MTH_CHRON_IsChecked(self)
fadeCheck:SetScript("OnClick", function()
if not this then return end
profile.fadeonfade = MTH_CHRON_IsChecked(this)
end)
end
local selfCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerSelfBars", "Show self bars", -204)
if selfCheck then
selfCheck:SetChecked(profile.selfbars and true or false)
selfCheck:SetScript("OnClick", function(self)
self = self or this
if not self then return end
profile.selfbars = MTH_CHRON_IsChecked(self)
selfCheck:SetScript("OnClick", function()
if not this then return end
profile.selfbars = MTH_CHRON_IsChecked(this)
end)
end
local onlySelfCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerOnlySelf", "Only self target", -229)
if onlySelfCheck then
onlySelfCheck:SetChecked(profile.onlyself and true or false)
onlySelfCheck:SetScript("OnClick", function(self)
self = self or this
if not self then return end
profile.onlyself = MTH_CHRON_IsChecked(self)
onlySelfCheck:SetScript("OnClick", function()
if not this then return end
profile.onlyself = MTH_CHRON_IsChecked(this)
end)
end
elseif section == "bar" then
@@ -610,10 +609,9 @@ function MTH_SetupChronometerOptions(tabKey)
local growCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerGrowUp", "Grow bars upward", -438)
if growCheck then
growCheck:SetChecked(profile.growup and true or false)
growCheck:SetScript("OnClick", function(self)
self = self or this
if not self then return end
profile.growup = MTH_CHRON_IsChecked(self)
growCheck:SetScript("OnClick", function()
if not this then return end
profile.growup = MTH_CHRON_IsChecked(this)
MTH_CHRON_ApplyLiveProfile(profile)
end)
end
@@ -621,10 +619,9 @@ function MTH_SetupChronometerOptions(tabKey)
local reverseCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerReverse", "Reverse bars", -463)
if reverseCheck then
reverseCheck:SetChecked(profile.reverse and true or false)
reverseCheck:SetScript("OnClick", function(self)
self = self or this
if not self then return end
profile.reverse = MTH_CHRON_IsChecked(self)
reverseCheck:SetScript("OnClick", function()
if not this then return end
profile.reverse = MTH_CHRON_IsChecked(this)
MTH_CHRON_ApplyLiveProfile(profile)
end)
end
+397
View File
@@ -0,0 +1,397 @@
------------------------------------------------------
-- 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
local MTH_EA_OPT_STATE = { bindStatus = nil, bindButton = nil, captureFrame = nil }
-- ── Layout constants ──────────────────────────────────────────
-- Two equal columns separated by a gutter. Values are offsets from the
-- container TOPLEFT corner (positive X right, negative Y down).
local OPT_L = 20 -- left column X
local OPT_R = 300 -- right column X
local OPT_CW = 255 -- column width (used for text wrapping and sliders)
local function MTH_EA_OPT_GetBindText()
if not GetBindingKey then return "Unbound" end
local k1, k2 = GetBindingKey("EXPAMMO ACTION")
if not k1 and not k2 then return "Unbound" end
if k1 and k2 then return k1 .. " / " .. k2 end
return k1 or k2 or "Unbound"
end
local function MTH_EA_OPT_UpdateBindStatus()
local bs = MTH_EA_OPT_STATE.bindStatus
if not bs then return end
bs:SetText("Key: " .. MTH_EA_OPT_GetBindText())
end
-- key must already be the full chord string (e.g. "ALT-F", "BUTTON3", "MOUSEWHEELUP")
local function MTH_EA_OPT_SaveBinding(key)
if not SetBinding or not SaveBindings or not GetBindingKey then return end
local old1, old2 = GetBindingKey("EXPAMMO ACTION")
if old1 then SetBinding(old1) end
if old2 then SetBinding(old2) end
if key and key ~= "" then
SetBinding(key, "EXPAMMO ACTION")
end
local bs = 1
if GetCurrentBindingSet then bs = GetCurrentBindingSet() or 1 end
SaveBindings(bs)
end
local function MTH_EA_OPT_GetPrefix()
return string.format("%s%s%s",
(IsAltKeyDown and IsAltKeyDown() and "ALT-" or ""),
(IsControlKeyDown and IsControlKeyDown() and "CTRL-" or ""),
(IsShiftKeyDown and IsShiftKeyDown() and "SHIFT-" or ""))
end
local function MTH_EA_OPT_StopCapture()
if MTH_EA_OPT_STATE.captureFrame then
MTH_EA_OPT_STATE.captureFrame:Hide()
end
local bb = MTH_EA_OPT_STATE.bindButton
if bb then bb:SetText("Set Key") end
MTH_EA_OPT_UpdateBindStatus()
end
local function MTH_EA_OPT_MakeCaptureFrame()
local f = CreateFrame("Frame", "MTH_ExpAmmoBindCapture", UIParent)
f:SetFrameStrata("FULLSCREEN_DIALOG")
f:SetAllPoints(UIParent)
f:EnableKeyboard(true)
f:EnableMouse(true)
f:EnableMouseWheel(true)
f:Hide()
local bg = f:CreateTexture(nil, "BACKGROUND")
bg:SetAllPoints(f)
bg:SetTexture(0, 0, 0, 0.5)
local lbl = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
lbl:SetPoint("CENTER", f, "CENTER", 0, 0)
lbl:SetText("|cffffff00Press a key, mouse button, or scroll wheel|r\n|cffaaaaaa(ESC to cancel)|r")
f:SetScript("OnKeyUp", function()
local key = arg1
if not key or key == "" then return end
if key == "ESCAPE" then MTH_EA_OPT_StopCapture() return end
if key == "UNKNOWN" or key == "PRINTSCREEN" then return end
if key == "ALT" or key == "CTRL" or key == "SHIFT" then return end
MTH_EA_OPT_SaveBinding(MTH_EA_OPT_GetPrefix() .. key)
MTH_EA_OPT_StopCapture()
end)
f:SetScript("OnMouseUp", function()
local btmap = { LeftButton="BUTTON1", RightButton="BUTTON2", MiddleButton="BUTTON3", Button4="BUTTON4", Button5="BUTTON5" }
local mapped = btmap[arg1]
if not mapped then return end
local prefix = MTH_EA_OPT_GetPrefix()
if prefix == "" and (mapped == "BUTTON1" or mapped == "BUTTON2") then return end
MTH_EA_OPT_SaveBinding(prefix .. mapped)
MTH_EA_OPT_StopCapture()
end)
f:SetScript("OnMouseWheel", function()
local wheelkey = (arg1 == 1 and "MOUSEWHEELUP") or (arg1 == -1 and "MOUSEWHEELDOWN") or nil
if not wheelkey then return end
MTH_EA_OPT_SaveBinding(MTH_EA_OPT_GetPrefix() .. wheelkey)
MTH_EA_OPT_StopCapture()
end)
return f
end
local function MTH_EA_OPT_StartCapture()
if not MTH_EA_OPT_STATE.captureFrame then
MTH_EA_OPT_STATE.captureFrame = MTH_EA_OPT_MakeCaptureFrame()
end
MTH_EA_OPT_STATE.captureFrame:Show()
local bb = MTH_EA_OPT_STATE.bindButton
if bb then bb:SetText("Press key...") end
end
-- Helper: checkbox anchored to a given X column
local function MTH_EA_OPT_CB(container, name, label, yOff, xOff, isChecked, onClick)
local cb = MTH_CreateCheckbox(container, name, label, yOff, xOff or OPT_L)
if cb then
cb:SetChecked(isChecked and 1 or 0)
cb:SetScript("OnClick", function()
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, xOff)
local fs = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
fs:SetPoint("TOPLEFT", container, "TOPLEFT", xOff or OPT_L, yOff)
fs:SetText("|cffff9900" .. text .. "|r")
return fs
end
-- Helper: small descriptive text
local function MTH_EA_OPT_Tip(container, text, yOff, xOff, r, g, b)
local fs = container:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
fs:SetPoint("TOPLEFT", container, "TOPLEFT", xOff or OPT_L, yOff)
fs:SetWidth(OPT_CW)
fs:SetJustifyH("LEFT")
fs:SetTextColor(r or 0.6, g or 0.6, b or 0.6)
fs:SetText(text)
return fs
end
-- Enable or disable a list of widgets (supports Frames/Buttons and FontStrings)
local function MTH_EA_OPT_SetGroupEnabled(widgets, enabled)
for _, w in ipairs(widgets) do
if w then
if w.Enable and w.Disable then
-- Frame / Button / CheckButton
if enabled then w:Enable() else w:Disable() end
end
-- Dim all (including FontStrings and frames) via alpha
if w.SetAlpha then
w:SetAlpha(enabled and 1.0 or 0.35)
end
end
end
end
local function MTH_EA_OPT_BuildUI(container)
if MTH_EA_OPT_built then return end
MTH_EA_OPT_built = true
local cfg = MTH_EA_OPT_GetCfg()
local showHint = cfg.showHint ~= false
---------------------------------------------------------------------------
-- Title
---------------------------------------------------------------------------
local title = container:CreateFontString(nil, "ARTWORK", "GameFontHighlightLarge")
title:SetPoint("TOPLEFT", container, "TOPLEFT", OPT_L, -16)
title:SetText("MM Widget — Experimental Ammunition Tracker")
---------------------------------------------------------------------------
-- LEFT COLUMN
---------------------------------------------------------------------------
-- ── Module ──────────────────────────────────────────────
MTH_EA_OPT_Header(container, "Module", -46)
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",
-66, OPT_L, moduleEnabled,
function(v)
if MTH and MTH.SetModuleEnabled then MTH:SetModuleEnabled("expammo", v) end
if MTH_ExpAmmo then MTH_ExpAmmo.SetEnabled(v) end
end)
-- ── Tracker Visibility ───────────────────────────────────
MTH_EA_OPT_Header(container, "Tracker Visibility", -100)
MTH_EA_OPT_CB(container, "MTH_ExpAmmoShowLnLCB",
"Show Lock and Load cell (top)",
-120, OPT_L, cfg.showLnL ~= false,
function(v) if MTH_ExpAmmo then MTH_ExpAmmo.SetShowLnL(v) end end)
-- "Show action hint cell" also gates the right column
local rightGroup = {} -- filled below; toggled when this CB changes
local showHintCB = MTH_EA_OPT_CB(container, "MTH_ExpAmmoShowHintCB",
"Show action hint cell (bottom)",
-144, OPT_L, showHint,
function(v)
if MTH_ExpAmmo then MTH_ExpAmmo.SetShowHint(v) end
MTH_EA_OPT_SetGroupEnabled(rightGroup, v)
end)
MTH_EA_OPT_CB(container, "MTH_ExpAmmoHideOOCCB",
"Hide widget when out of combat",
-168, OPT_L, cfg.hideOOC == true,
function(v) if MTH_ExpAmmo then MTH_ExpAmmo.SetHideOOC(v) end end)
-- ── Appearance ───────────────────────────────────────────
MTH_EA_OPT_Header(container, "Appearance", -202)
local sizeSlider = MTH_CreateSlider(container, "MTH_ExpAmmoCellSizeSlider",
"Cell Size (px)", 20, 80, 4, -222)
if sizeSlider then
sizeSlider:ClearAllPoints()
sizeSlider:SetPoint("TOPLEFT", container, "TOPLEFT", OPT_L + 5, -222)
sizeSlider:SetWidth(OPT_CW - 10)
sizeSlider:SetValue(math.max(20, math.min(80, cfg.cellSize or 40)))
sizeSlider.onChange = function(val)
if MTH_ExpAmmo then MTH_ExpAmmo.SetCellSize(val) end
end
end
local gapSlider = MTH_CreateSlider(container, "MTH_ExpAmmoCellGapSlider",
"Cell Spacing (px)", 0, 20, 1, -286)
if gapSlider then
gapSlider:ClearAllPoints()
gapSlider:SetPoint("TOPLEFT", container, "TOPLEFT", OPT_L + 5, -286)
gapSlider:SetWidth(OPT_CW - 10)
gapSlider:SetValue(math.max(0, math.min(20, cfg.cellGap or 2)))
gapSlider.onChange = function(val)
if MTH_ExpAmmo then MTH_ExpAmmo.SetCellGap(val) end
end
end
MTH_EA_OPT_CB(container, "MTH_ExpAmmoBigCDCB",
"Large cooldown numbers (centered in cell)",
-350, OPT_L, cfg.bigCD == true,
function(v) if MTH_ExpAmmo then MTH_ExpAmmo.SetBigCD(v) end end)
-- ── Position ─────────────────────────────────────────────
MTH_EA_OPT_Header(container, "Position", -390)
local anchorButton = MTH_CreateActionButton(container, "MTH_ExpAmmoToggleAnchor",
"Toggle Anchor", OPT_L, -410, 130, 22, function()
if MTH_ExpAmmo then MTH_ExpAmmo.ToggleAnchor() end
end)
if anchorButton then anchorButton:Show() end
MTH_EA_OPT_Tip(container,
"Shows 4 drag handles around the widget (top / bottom / left / right). Drag any one to reposition it.",
-436, OPT_L)
---------------------------------------------------------------------------
-- RIGHT COLUMN — Action Hint Cell (keybind + casting)
-- All widgets here are greyed out when the bottom cell is hidden.
---------------------------------------------------------------------------
-- Vertical divider line
local divider = container:CreateTexture(nil, "BACKGROUND")
divider:SetTexture(0.3, 0.3, 0.3, 0.6)
divider:SetPoint("TOPLEFT", container, "TOPLEFT", OPT_R - 10, -46)
divider:SetPoint("BOTTOMLEFT", container, "TOPLEFT", OPT_R - 10, -530)
divider:SetWidth(1)
-- Column title
local rTitle = container:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
rTitle:SetPoint("TOPLEFT", container, "TOPLEFT", OPT_R, -46)
rTitle:SetText("|cffff9900" .. "Action Hint Cell" .. "|r")
table.insert(rightGroup, rTitle)
local rSubtitle = MTH_EA_OPT_Tip(container,
"Options below apply to the bottom cell only.\n Disable \"Show action hint cell\" to hide the cell and these controls.",
-64, OPT_R, 1.00, 1.00, 1.00)
if rSubtitle then rSubtitle:SetWidth(290) end
table.insert(rightGroup, rSubtitle)
local rNamPowerTip = MTH_EA_OPT_Tip(container,
"Notes:\n - You need NAMPOWER for this to work reliably.\n - Aimed/Steady/Multi/Arcane Shots SHOULD BE on your action bars.",
-100, OPT_R, 0.35, 0.65, 1.00)
if rNamPowerTip then rNamPowerTip:SetWidth(290) end
table.insert(rightGroup, rNamPowerTip)
-- ── Keybind ──────────────────────────────────────────────
table.insert(rightGroup, MTH_EA_OPT_Header(container, "Keybind", -182, OPT_R))
local bindTip = MTH_EA_OPT_Tip(container,
"One key to rule them all.\n Aimed Shot when ready, Steady Shot while Aimed is on cooldown,\n consume-spell (Multi / Serpent / Arcane) when proc is up.",
-200, OPT_R, 0.65, 0.65, 0.65)
if bindTip then bindTip:SetWidth(290) end
table.insert(rightGroup, bindTip)
local bindStatus = container:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
bindStatus:SetPoint("TOPLEFT", container, "TOPLEFT", OPT_R, -248)
bindStatus:SetText("Key: " .. MTH_EA_OPT_GetBindText())
MTH_EA_OPT_STATE.bindStatus = bindStatus
table.insert(rightGroup, bindStatus)
local bindButton = CreateFrame("Button", "MTH_ExpAmmoBindButton", container, "UIPanelButtonTemplate")
bindButton:SetPoint("TOPLEFT", container, "TOPLEFT", OPT_R, -264)
bindButton:SetWidth(90)
bindButton:SetHeight(22)
bindButton:SetText("Set Key")
bindButton:EnableKeyboard(false)
bindButton:SetScript("OnClick", function()
if MTH_EA_OPT_STATE.captureFrame and MTH_EA_OPT_STATE.captureFrame:IsShown() then
MTH_EA_OPT_StopCapture()
else
MTH_EA_OPT_StartCapture()
end
end)
MTH_EA_OPT_STATE.bindButton = bindButton
table.insert(rightGroup, bindButton)
local clearButton = CreateFrame("Button", "MTH_ExpAmmoBindClear", container, "UIPanelButtonTemplate")
clearButton:SetPoint("LEFT", bindButton, "RIGHT", 6, 0)
clearButton:SetWidth(70)
clearButton:SetHeight(22)
clearButton:SetText("Clear")
clearButton:SetScript("OnClick", function()
MTH_EA_OPT_SaveBinding(nil)
end)
table.insert(rightGroup, clearButton)
-- ── Casting ──────────────────────────────────────────────
table.insert(rightGroup, MTH_EA_OPT_Header(container, "Casting", -304, OPT_R))
local quiverCB = MTH_EA_OPT_CB(container, "MTH_ExpAmmoQuiverNoClipCB",
"Use Quiver no-clip casting",
-324, OPT_R, cfg.useQuiverNoClip == true,
function(v) if MTH_ExpAmmo then MTH_ExpAmmo.SetUseQuiverNoClip(v) end end)
table.insert(rightGroup, quiverCB)
local quiverTip = MTH_EA_OPT_Tip(container,
"|cff4d9cffRequires the Quiver addon.|r \n Routes Aimed/Steady/Multi cast through Quiver.CastNoClip \n to avoid clipping your auto-shot swing timer",
-350, OPT_R, 1, 1, 1)
if quiverTip then quiverTip:SetWidth(290) end
table.insert(rightGroup, quiverTip)
-- ── Rotation ─────────────────────────────────────────────
table.insert(rightGroup, MTH_EA_OPT_Header(container, "Rotation", -400, OPT_R))
local rotTip = MTH_EA_OPT_Tip(container,
"Uncheck a shot to permanently skip it during its proc window.\n The keybind will cast Steady/Aimed instead.",
-418, OPT_R, 0.65, 0.65, 0.65)
if rotTip then rotTip:SetWidth(290) end
table.insert(rightGroup, rotTip)
local multiCB = MTH_EA_OPT_CB(container, "MTH_ExpAmmoBlockMultiCB",
"Multi-Shot (Explosive)",
-452, OPT_R, cfg.blockExplosive ~= true,
function(v) if MTH_ExpAmmo then MTH_ExpAmmo.SetBlockExplosive(not v) end end)
table.insert(rightGroup, multiCB)
local serpentCB = MTH_EA_OPT_CB(container, "MTH_ExpAmmoBlockSerpentCB",
"Serpent Sting (Poisonous)",
-476, OPT_R, cfg.blockPoisonous ~= true,
function(v) if MTH_ExpAmmo then MTH_ExpAmmo.SetBlockPoisonous(not v) end end)
table.insert(rightGroup, serpentCB)
local arcaneCB = MTH_EA_OPT_CB(container, "MTH_ExpAmmoBlockArcaneCB",
"Arcane Shot (Enchanted)",
-500, OPT_R, cfg.blockEnchanted ~= true,
function(v) if MTH_ExpAmmo then MTH_ExpAmmo.SetBlockEnchanted(not v) end end)
table.insert(rightGroup, arcaneCB)
-- Apply initial enabled state
MTH_EA_OPT_SetGroupEnabled(rightGroup, showHint)
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
+80 -64
View File
@@ -10,7 +10,7 @@ local MTH_FOM_STATE = {
bindStatus = nil,
bindButton = nil,
bindClearButton = nil,
bindingCapture = false,
captureFrame = nil,
ctrl = {},
}
@@ -93,43 +93,79 @@ local function MTH_FOM_UpdateBindingStatus()
if not bindStatus or not bindStatus.SetText then
return
end
if MTH_FOM_STATE.bindingCapture then
bindStatus:SetText(MTH_FOM_L("FOM_BIND_STATUS_PROMPT", "Feed key: press a key... (ESC to clear)"))
else
bindStatus:SetText(string.format(MTH_FOM_L("FOM_BIND_STATUS_VALUE", "Feed key: %s"), MTH_FOM_GetBindingDisplayText()))
end
bindStatus:SetText(string.format(MTH_FOM_L("FOM_BIND_STATUS_VALUE", "Feed key: %s"), MTH_FOM_GetBindingDisplayText()))
end
local function MTH_FOM_StopBindingCapture()
MTH_FOM_STATE.bindingCapture = false
if MTH_FOM_STATE.captureFrame then
MTH_FOM_STATE.captureFrame:Hide()
end
if MTH_FOM_STATE.bindButton then
if MTH_FOM_STATE.bindButton.SetPropagateKeyboardInput then
MTH_FOM_STATE.bindButton:SetPropagateKeyboardInput(true)
end
MTH_FOM_STATE.bindButton:EnableKeyboard(false)
MTH_FOM_STATE.bindButton:SetText(MTH_FOM_L("FOM_BIND_SET_KEY", "Set Key"))
end
MTH_FOM_UpdateBindingStatus()
end
local function MTH_FOM_StartBindingCapture()
MTH_FOM_STATE.bindingCapture = true
if MTH_FOM_STATE.bindButton then
MTH_FOM_STATE.bindButton:SetText(MTH_FOM_L("FOM_BIND_PRESS_KEY", "Press key..."))
MTH_FOM_STATE.bindButton:EnableKeyboard(true)
if MTH_FOM_STATE.bindButton.SetPropagateKeyboardInput then
MTH_FOM_STATE.bindButton:SetPropagateKeyboardInput(false)
end
end
MTH_FOM_UpdateBindingStatus()
local function MTH_FOM_BuildBindingChord(key)
return string.format("%s%s%s%s",
(IsAltKeyDown and IsAltKeyDown() and "ALT-" or ""),
(IsControlKeyDown and IsControlKeyDown() and "CTRL-" or ""),
(IsShiftKeyDown and IsShiftKeyDown() and "SHIFT-" or ""),
key)
end
local function MTH_FOM_BuildBindingChord(key)
local chord = ""
if IsControlKeyDown and IsControlKeyDown() and key ~= "LCTRL" and key ~= "RCTRL" then chord = chord .. "CTRL-" end
if IsAltKeyDown and IsAltKeyDown() and key ~= "LALT" and key ~= "RALT" then chord = chord .. "ALT-" end
if IsShiftKeyDown and IsShiftKeyDown() and key ~= "LSHIFT" and key ~= "RSHIFT" then chord = chord .. "SHIFT-" end
return chord .. key
local function MTH_FOM_MakeCaptureFrame()
local f = CreateFrame("Frame", "MTH_FOMBindCapture", UIParent)
f:SetFrameStrata("FULLSCREEN_DIALOG")
f:SetAllPoints(UIParent)
f:EnableKeyboard(true)
f:EnableMouse(true)
f:EnableMouseWheel(true)
f:Hide()
local bg = f:CreateTexture(nil, "BACKGROUND")
bg:SetAllPoints(f)
bg:SetTexture(0, 0, 0, 0.5)
local lbl = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
lbl:SetPoint("CENTER", f, "CENTER", 0, 0)
lbl:SetText("|cffffff00Press a key, mouse button, or scroll wheel|r\n|cffaaaaaa(ESC to cancel)|r")
f:SetScript("OnKeyUp", function()
local key = arg1
if not key or key == "" then return end
if key == "ESCAPE" then MTH_FOM_StopBindingCapture() return end
if key == "UNKNOWN" or key == "PRINTSCREEN" then return end
if key == "ALT" or key == "CTRL" or key == "SHIFT" then return end
MTH_FOM_SaveBinding(MTH_FOM_BuildBindingChord(key))
MTH_FOM_StopBindingCapture()
end)
f:SetScript("OnMouseUp", function()
local btmap = { LeftButton="BUTTON1", RightButton="BUTTON2", MiddleButton="BUTTON3", Button4="BUTTON4", Button5="BUTTON5" }
local mapped = btmap[arg1]
if not mapped then return end
local prefix = string.format("%s%s%s",
(IsAltKeyDown and IsAltKeyDown() and "ALT-" or ""),
(IsControlKeyDown and IsControlKeyDown() and "CTRL-" or ""),
(IsShiftKeyDown and IsShiftKeyDown() and "SHIFT-" or ""))
if prefix == "" and (mapped == "BUTTON1" or mapped == "BUTTON2") then return end
MTH_FOM_SaveBinding(prefix .. mapped)
MTH_FOM_StopBindingCapture()
end)
f:SetScript("OnMouseWheel", function()
local wheelkey = (arg1 == 1 and "MOUSEWHEELUP") or (arg1 == -1 and "MOUSEWHEELDOWN") or nil
if not wheelkey then return end
MTH_FOM_SaveBinding(MTH_FOM_BuildBindingChord(wheelkey))
MTH_FOM_StopBindingCapture()
end)
return f
end
local function MTH_FOM_StartBindingCapture()
if not MTH_FOM_STATE.captureFrame then
MTH_FOM_STATE.captureFrame = MTH_FOM_MakeCaptureFrame()
end
MTH_FOM_STATE.captureFrame:Show()
if MTH_FOM_STATE.bindButton then
MTH_FOM_STATE.bindButton:SetText(MTH_FOM_L("FOM_BIND_PRESS_KEY", "Press key..."))
end
end
local function MTH_FOM_SaveBinding(key)
@@ -226,10 +262,9 @@ function MTH_SetupFeedOMaticOptions()
local moduleCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsFeedOMaticEnabled", MTH_FOM_L("FOM_ENABLE_MODULE", "Enable FeedOMatic module"), -32 + yAdjust)
if moduleCheck then
moduleCheck:SetScript("OnClick", function(self)
self = self or this
if not self then return end
local enabled = self:GetChecked() == 1
moduleCheck:SetScript("OnClick", function()
if not this then return end
local enabled = this:GetChecked() == 1
if MTH and MTH.SetModuleEnabled then
local ok, err = MTH:SetModuleEnabled("feedomatic", enabled)
if not ok and MTH and MTH.Print then
@@ -255,31 +290,13 @@ function MTH_SetupFeedOMaticOptions()
bindButton:SetWidth(90)
bindButton:SetHeight(22)
bindButton:SetText(MTH_FOM_L("FOM_BIND_SET_KEY", "Set Key"))
bindButton:EnableKeyboard(false)
bindButton:SetScript("OnClick", function()
if MTH_FOM_STATE.bindingCapture then
if MTH_FOM_STATE.captureFrame and MTH_FOM_STATE.captureFrame:IsShown() then
MTH_FOM_StopBindingCapture()
else
MTH_FOM_StartBindingCapture()
end
end)
bindButton:SetScript("OnKeyDown", function(_, key)
if not MTH_FOM_STATE.bindingCapture then
return
end
key = key or arg1
if not key or key == "" then
return
end
if key == "ESCAPE" then
MTH_FOM_SaveBinding(nil)
return
end
if key == "UNKNOWN" or key == "PRINTSCREEN" then
return
end
MTH_FOM_SaveBinding(MTH_FOM_BuildBindingChord(key))
end)
MTH_FOM_STATE.bindButton = bindButton
local bindClearButton = CreateFrame("Button", "MetaHuntOptionsFOMBindClearButton", container, "UIPanelButtonTemplate")
@@ -305,10 +322,10 @@ function MTH_SetupFeedOMaticOptions()
MTH_FOM_STATE.ctrl.PreferHigherQuality = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMPreferQuality", FOM_OptionsButtonText and FOM_OptionsButtonText["PreferHigherQuality"] or MTH_FOM_L("FOM_LABEL_PREFER_HIGHER_QUALITY_FOOD", "Prefer higher quality food"), -220 + yAdjust)
MTH_FOM_STATE.ctrl.Fallback = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMFallback", FOM_OptionsButtonText and FOM_OptionsButtonText["Fallback"] or MTH_FOM_L("FOM_LABEL_FALLBACK_TO_AVOIDED_FOODS", "Fallback to avoided foods"), -245 + yAdjust)
if MTH_FOM_STATE.ctrl.AvoidQuestFood then MTH_FOM_STATE.ctrl.AvoidQuestFood:SetScript("OnClick", function(self) self = self or this if not self then return end FOM_Config.AvoidQuestFood = self:GetChecked() == 1 end) end
if MTH_FOM_STATE.ctrl.AvoidBonusFood then MTH_FOM_STATE.ctrl.AvoidBonusFood:SetScript("OnClick", function(self) self = self or this if not self then return end FOM_Config.AvoidBonusFood = self:GetChecked() == 1 end) end
if MTH_FOM_STATE.ctrl.PreferHigherQuality then MTH_FOM_STATE.ctrl.PreferHigherQuality:SetScript("OnClick", function(self) self = self or this if not self then return end FOM_Config.PreferHigherQuality = self:GetChecked() == 1 end) end
if MTH_FOM_STATE.ctrl.Fallback then MTH_FOM_STATE.ctrl.Fallback:SetScript("OnClick", function(self) self = self or this if not self then return end FOM_Config.Fallback = self:GetChecked() == 1 end) end
if MTH_FOM_STATE.ctrl.AvoidQuestFood then MTH_FOM_STATE.ctrl.AvoidQuestFood:SetScript("OnClick", function() if not this then return end FOM_Config.AvoidQuestFood = this:GetChecked() == 1 end) end
if MTH_FOM_STATE.ctrl.AvoidBonusFood then MTH_FOM_STATE.ctrl.AvoidBonusFood:SetScript("OnClick", function() if not this then return end FOM_Config.AvoidBonusFood = this:GetChecked() == 1 end) end
if MTH_FOM_STATE.ctrl.PreferHigherQuality then MTH_FOM_STATE.ctrl.PreferHigherQuality:SetScript("OnClick", function() if not this then return end FOM_Config.PreferHigherQuality = this:GetChecked() == 1 end) end
if MTH_FOM_STATE.ctrl.Fallback then MTH_FOM_STATE.ctrl.Fallback:SetScript("OnClick", function() if not this then return end FOM_Config.Fallback = this:GetChecked() == 1 end) end
local keepOpenLabel = container:CreateFontString("MetaHuntOptionsFOMKeepOpenLabel", "ARTWORK", "GameFontNormalSmall")
keepOpenLabel:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -302 + yAdjust)
@@ -320,14 +337,13 @@ function MTH_SetupFeedOMaticOptions()
keepOpen:SetHeight(20)
keepOpen:SetNumeric(true)
keepOpen:SetAutoFocus(false)
keepOpen:SetScript("OnTextChanged", function(self)
self = self or this
if not self then return end
local value = tonumber(self:GetText()) or 0
keepOpen:SetScript("OnTextChanged", function()
if not this then return end
local value = tonumber(this:GetText()) or 0
FOM_Config.KeepOpenSlots = value
end)
keepOpen:SetScript("OnEnterPressed", function(self) self = self or this if self then self:ClearFocus() end end)
keepOpen:SetScript("OnEscapePressed", function(self) self = self or this if self then self:ClearFocus() end end)
keepOpen:SetScript("OnEnterPressed", function() if this then this:ClearFocus() end end)
keepOpen:SetScript("OnEscapePressed", function() if this then this:ClearFocus() end end)
MTH_FOM_STATE.keepOpenEdit = keepOpen
local notifyHeader = container:CreateFontString("MetaHuntOptionsFOMNotifyHeader", "ARTWORK", "GameFontHighlight")
@@ -356,10 +372,10 @@ function MTH_SetupFeedOMaticOptions()
MTH_FOM_STATE.ctrl.AudioWarningBell = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMAudioWarningBell", FOM_OptionsButtonText and FOM_OptionsButtonText["AudioWarningBell"] or MTH_FOM_L("FOM_LABEL_USE_BELL_SOUND", "Use bell sound"), -403 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.TextWarning = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMTextWarning", FOM_OptionsButtonText and FOM_OptionsButtonText["TextWarning"] or MTH_FOM_L("FOM_LABEL_SHOW_TEXT", "Show text"), -428 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.IconWarning = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMIconWarning", FOM_OptionsButtonText and FOM_OptionsButtonText["IconWarning"] or MTH_FOM_L("FOM_LABEL_FLASH_ICON", "Flash icon"), -453 + yAdjust, rightColumnX)
if MTH_FOM_STATE.ctrl.AudioWarning then MTH_FOM_STATE.ctrl.AudioWarning:SetScript("OnClick", function(self) self = self or this if not self then return end if self:GetChecked() == 1 then if FOM_Config.AudioWarning ~= "bell" then FOM_Config.AudioWarning = true end else FOM_Config.AudioWarning = nil end MTH_RefreshFeedOMaticOptions() end) end
if MTH_FOM_STATE.ctrl.AudioWarningBell then MTH_FOM_STATE.ctrl.AudioWarningBell:SetScript("OnClick", function(self) self = self or this if not self then return end if self:GetChecked() == 1 then FOM_Config.AudioWarning = "bell" else FOM_Config.AudioWarning = true end MTH_RefreshFeedOMaticOptions() end) end
if MTH_FOM_STATE.ctrl.TextWarning then MTH_FOM_STATE.ctrl.TextWarning:SetScript("OnClick", function(self) self = self or this if not self then return end FOM_Config.TextWarning = self:GetChecked() == 1 end) end
if MTH_FOM_STATE.ctrl.IconWarning then MTH_FOM_STATE.ctrl.IconWarning:SetScript("OnClick", function(self) self = self or this if not self then return end FOM_Config.IconWarning = self:GetChecked() == 1 end) end
if MTH_FOM_STATE.ctrl.AudioWarning then MTH_FOM_STATE.ctrl.AudioWarning:SetScript("OnClick", function() if not this then return end if this:GetChecked() == 1 then if FOM_Config.AudioWarning ~= "bell" then FOM_Config.AudioWarning = true end else FOM_Config.AudioWarning = nil end MTH_RefreshFeedOMaticOptions() end) end
if MTH_FOM_STATE.ctrl.AudioWarningBell then MTH_FOM_STATE.ctrl.AudioWarningBell:SetScript("OnClick", function() if not this then return end if this:GetChecked() == 1 then FOM_Config.AudioWarning = "bell" else FOM_Config.AudioWarning = true end MTH_RefreshFeedOMaticOptions() end) end
if MTH_FOM_STATE.ctrl.TextWarning then MTH_FOM_STATE.ctrl.TextWarning:SetScript("OnClick", function() if not this then return end FOM_Config.TextWarning = this:GetChecked() == 1 end) end
if MTH_FOM_STATE.ctrl.IconWarning then MTH_FOM_STATE.ctrl.IconWarning:SetScript("OnClick", function() if not this then return end FOM_Config.IconWarning = this:GetChecked() == 1 end) end
local cookHeader = container:CreateFontString("MetaHuntOptionsFOMCookingHeader", "ARTWORK", "GameFontHighlight")
cookHeader:SetPoint("TOPLEFT", container, "TOPLEFT", rightColumnX, -62 + yAdjust)
+24 -32
View File
@@ -297,11 +297,10 @@ local function MTH_ICU_BuildUI(container)
local moduleEnabled = MTH_CreateCheckbox(container, "MetaHuntOptionsICUModuleEnabled", "Enable ICU module", -72, MTH_ICU_LAYOUT.LEFT_X)
if moduleEnabled then
moduleEnabled:SetScript("OnClick", function(self)
self = self or this
if not self then return end
moduleEnabled:SetScript("OnClick", function()
if not this then return end
if MTH and MTH.SetModuleEnabled then
local ok, err = MTH:SetModuleEnabled("icu", self:GetChecked() == 1)
local ok, err = MTH:SetModuleEnabled("icu", this:GetChecked() == 1)
if not ok and MTH and MTH.Print then
MTH:Print("Failed to change ICU module state: " .. tostring(err), "error")
end
@@ -313,11 +312,10 @@ local function MTH_ICU_BuildUI(container)
local mouseOver = MTH_CreateCheckbox(container, "MetaHuntOptionsICUMouseOver", "Enable CTRL mouseover scan", -98, MTH_ICU_LAYOUT.LEFT_X)
if mouseOver then
mouseOver:SetScript("OnClick", function(self)
self = self or this
if not self then return end
mouseOver:SetScript("OnClick", function()
if not this then return end
local store = MTH_ICU_GetStore()
store.mouseOver = self:GetChecked() == 1 and true or false
store.mouseOver = this:GetChecked() == 1 and true or false
end)
MTH_ICU_OPT_STATE.controls.mouseOver = mouseOver
end
@@ -346,11 +344,10 @@ local function MTH_ICU_BuildUI(container)
local expandUp = MTH_CreateCheckbox(container, "MetaHuntOptionsICUExpandUp", "Expand Up", -301, MTH_ICU_LAYOUT.LEFT_X + 132)
if expandUp then
expandUp:SetScript("OnClick", function(self)
self = self or this
if not self then return end
expandUp:SetScript("OnClick", function()
if not this then return end
local store = MTH_ICU_GetStore()
store.EXPAND_UP = self:GetChecked() == 1 and true or false
store.EXPAND_UP = this:GetChecked() == 1 and true or false
if type(ICU_SetPoints) == "function" then
ICU_SetPoints()
end
@@ -360,44 +357,40 @@ local function MTH_ICU_BuildUI(container)
local showGuild = MTH_CreateCheckbox(container, "MetaHuntOptionsICUShowGuild", "Show player guild names", -344, MTH_ICU_LAYOUT.LEFT_X)
if showGuild then
showGuild:SetScript("OnClick", function(self)
self = self or this
if not self then return end
showGuild:SetScript("OnClick", function()
if not this then return end
local store = MTH_ICU_GetStore()
store.SHOW_GUILD_NAME = self:GetChecked() == 1 and true or false
store.SHOW_GUILD_NAME = this:GetChecked() == 1 and true or false
end)
MTH_ICU_OPT_STATE.controls.showGuildName = showGuild
end
local showClass = MTH_CreateCheckbox(container, "MetaHuntOptionsICUShowClass", "Show player class", -368, MTH_ICU_LAYOUT.LEFT_X)
if showClass then
showClass:SetScript("OnClick", function(self)
self = self or this
if not self then return end
showClass:SetScript("OnClick", function()
if not this then return end
local store = MTH_ICU_GetStore()
store.SHOW_PLAYER_CLASS = self:GetChecked() == 1 and true or false
store.SHOW_PLAYER_CLASS = this:GetChecked() == 1 and true or false
end)
MTH_ICU_OPT_STATE.controls.showPlayerClass = showClass
end
local showRace = MTH_CreateCheckbox(container, "MetaHuntOptionsICUShowRace", "Show player race", -392, MTH_ICU_LAYOUT.LEFT_X)
if showRace then
showRace:SetScript("OnClick", function(self)
self = self or this
if not self then return end
showRace:SetScript("OnClick", function()
if not this then return end
local store = MTH_ICU_GetStore()
store.SHOW_PLAYER_RACE = self:GetChecked() == 1 and true or false
store.SHOW_PLAYER_RACE = this:GetChecked() == 1 and true or false
end)
MTH_ICU_OPT_STATE.controls.showPlayerRace = showRace
end
local showTitles = MTH_CreateCheckbox(container, "MetaHuntOptionsICUShowTitles", "Show custom titles in alerts", -416, MTH_ICU_LAYOUT.LEFT_X)
if showTitles then
showTitles:SetScript("OnClick", function(self)
self = self or this
if not self then return end
showTitles:SetScript("OnClick", function()
if not this then return end
local store = MTH_ICU_GetStore()
store.SHOW_CUSTOM_TITLES = self:GetChecked() == 1 and true or false
store.SHOW_CUSTOM_TITLES = this:GetChecked() == 1 and true or false
end)
MTH_ICU_OPT_STATE.controls.showCustomTitles = showTitles
end
@@ -410,11 +403,10 @@ local function MTH_ICU_BuildUI(container)
local reactionOnlyPvp = MTH_CreateCheckbox(container, "MetaHuntOptionsICUReactionOnlyPvp", "Reaction only when PvP-flagged", -200, MTH_ICU_LAYOUT.RIGHT_X)
if reactionOnlyPvp then
reactionOnlyPvp:SetScript("OnClick", function(self)
self = self or this
if not self then return end
reactionOnlyPvp:SetScript("OnClick", function()
if not this then return end
local store = MTH_ICU_GetStore()
store.REACTION_ONLY_PVP_PLAYERS = self:GetChecked() == 1 and true or false
store.REACTION_ONLY_PVP_PLAYERS = this:GetChecked() == 1 and true or false
end)
MTH_ICU_OPT_STATE.controls.reactionOnlyPvp = reactionOnlyPvp
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
+24 -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
@@ -128,6 +133,11 @@ function MTH_SelectOptionsTab(tabKey)
MTH_SetupMessagesOptions()
end
end
elseif tabKey == "ZBar" then
if type(MTH_SetupZBarOptions) == "function" then
MTH_SetupZBarOptions()
MTH_OPTIONS_SETUP["ZBar"] = true
end
elseif tabKey == "Pet" then
if not MTH_OPTIONS_SETUP["Pet"] then
MTH_SetupPetOptions()
@@ -177,6 +187,11 @@ function MTH_SelectOptionsTab(tabKey)
MTH_SetupToysOptions()
MTH_OPTIONS_SETUP["Toys"] = true
end
elseif tabKey == "Craft" then
if not MTH_OPTIONS_SETUP["Craft"] then
MTH_SetupCraftOptions()
MTH_OPTIONS_SETUP["Craft"] = true
end
elseif tabKey == "SmartAmmo" then
if not MTH_OPTIONS_SETUP["SmartAmmo"] then
MTH_SetupSmartAmmoOptions()
@@ -200,6 +215,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()
+321
View File
@@ -0,0 +1,321 @@
------------------------------------------------------
-- MetaHunt: SmartPet Options Panel
-- TWoW 1.18.1
------------------------------------------------------
local function MTH_SP_OPT_Ready()
return type(MTH_GetFrame) == "function"
and type(MTH_CreateCheckbox) == "function"
and type(MTH_CreateSlider) == "function"
end
local function MTH_SP_OPT_GetCfg()
if not MTH_SmartPet or not MTH_SmartPet.GetConfig then return {} end
return MTH_SmartPet.GetConfig()
end
local MTH_SP_OPT_built = false
-- Layout constants
local OPT_L = 16 -- left column X
local OPT_R = 290 -- right column X
local OPT_CW = 250 -- column width (for tip text wrapping)
-- Spacing constants
local GAP_SECTION = 34 -- between sections
local GAP_CB = 24 -- checkbox height (icon + text)
local GAP_TIP = 16 -- small tip text height
local GAP_BTN = 28 -- button height + margin
local GAP_SLIDER = 52 -- slider total height (label + track + value)
local GAP_CB_SLIDER = 40 -- checkbox to slider (checkbox + slider label above track)
-- Helper: checkbox with click handler
local function SP_CB(container, name, label, yOff, xOff, isChecked, onClick)
local cb = MTH_CreateCheckbox(container, name, label, yOff, xOff or OPT_L)
if cb then
cb:SetChecked(isChecked and 1 or 0)
cb:SetScript("OnClick", function()
local v = this:GetChecked()
onClick(v == 1 or v == true)
end)
end
return cb
end
-- Helper: section header label
local function SP_Header(container, text, yOff, xOff)
local fs = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
fs:SetPoint("TOPLEFT", container, "TOPLEFT", xOff or OPT_L, yOff)
fs:SetText("|cffff9900" .. text .. "|r")
return fs
end
-- Helper: small descriptive text
local function SP_Tip(container, text, yOff, xOff)
local fs = container:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
fs:SetPoint("TOPLEFT", container, "TOPLEFT", xOff or OPT_L, yOff)
fs:SetWidth(OPT_CW)
fs:SetJustifyH("LEFT")
fs:SetTextColor(0.4, 0.6, 1.0)
fs:SetText(text)
return fs
end
-- Taunt Mode cycle
local TAUNT_MODES = { "auto", "growl", "cower", "off" }
local TAUNT_LABELS = {
auto = "Auto (solo=Growl, tank=Cower)",
growl = "Always Growl",
cower = "Always Cower",
off = "Off (manual)",
}
local function SP_NextTauntMode(current)
for i = 1, 4 do
if TAUNT_MODES[i] == current then
return TAUNT_MODES[math.mod(i, 4) + 1]
end
end
return "auto"
end
-- Channel cycle
local CHANNELS = { "SAY", "PARTY", "RAID", "GUILD" }
local function SP_NextChannel(current)
for i = 1, 4 do
if CHANNELS[i] == current then
return CHANNELS[math.mod(i, 4) + 1]
end
end
return "SAY"
end
-- Build the UI
local function MTH_SP_OPT_BuildUI(container)
if MTH_SP_OPT_built then return end
MTH_SP_OPT_built = true
local cfg = MTH_SP_OPT_GetCfg()
local api = MTH_SmartPet or {}
---------------------------------------------------------------------------
-- Title (large, like ExpAmmo)
---------------------------------------------------------------------------
local title = container:CreateFontString(nil, "ARTWORK", "GameFontHighlightLarge")
title:SetPoint("TOPLEFT", container, "TOPLEFT", OPT_L, -16)
title:SetText("Smart Pet - Pet Combat Management")
---------------------------------------------------------------------------
-- Module enable checkbox
---------------------------------------------------------------------------
local moduleEnabled = true
if MTH and MTH.IsModuleEnabled then
moduleEnabled = MTH:IsModuleEnabled("smartpet", false) and true or false
end
SP_CB(container, "MTH_SP_OPT_ModuleEnable", "Enable Smart Pet module",
-46, OPT_L, moduleEnabled, function(v)
if MTH and MTH.SetModuleEnabled then MTH:SetModuleEnabled("smartpet", v) end
end)
-- Columns start below the module checkbox
local COL_TOP = -80
-- Vertical divider
local divider = container:CreateTexture(nil, "BACKGROUND")
divider:SetTexture(0.3, 0.3, 0.3, 0.6)
divider:SetPoint("TOPLEFT", container, "TOPLEFT", OPT_R - 14, COL_TOP)
divider:SetPoint("BOTTOMLEFT", container, "TOPLEFT", OPT_R - 14, -530)
divider:SetWidth(1)
---------------------------------------------------------------------------
-- LEFT COLUMN — Focus Management
---------------------------------------------------------------------------
local y = COL_TOP
SP_Header(container, "Focus Management", y)
y = y - 22
-- Smart Focus
SP_CB(container, "MTH_SP_OPT_SmartFocus", "Enable Smart Focus",
y, OPT_L, cfg.smartFocus, function(v)
if api.SetSmartFocus then api.SetSmartFocus(v) end
end)
y = y - GAP_CB
SP_Tip(container, "Budget pet focus across DPS abilities.", y)
y = y - GAP_TIP
SP_Tip(container, "|cff80ff80High focus|r — all DPS abilities ON.", y)
y = y - GAP_TIP
SP_Tip(container, "|cffffcc00Mid focus|r — expensive ability first, others wait.", y)
y = y - GAP_TIP
SP_Tip(container, "|cffff8080Low focus|r — cheapest ability always ON (never idle).", y)
y = y - GAP_TIP
SP_Tip(container, "Taunt cost is always reserved.", y)
y = y - GAP_SECTION
---------------------------------------------------------------------------
-- LEFT COLUMN — Taunt Management
---------------------------------------------------------------------------
SP_Header(container, "Taunt Management", y)
y = y - GAP_TIP
SP_Tip(container, "Controls Growl/Cower autocast based on party.", y)
y = y - 22
local tauntBtn = CreateFrame("Button", "MTH_SP_OPT_TauntBtn", container, "UIPanelButtonTemplate")
tauntBtn:SetPoint("TOPLEFT", container, "TOPLEFT", OPT_L, y)
tauntBtn:SetWidth(250)
tauntBtn:SetHeight(22)
tauntBtn:SetText("Mode: " .. (TAUNT_LABELS[cfg.tauntMode] or "Auto"))
tauntBtn:SetScript("OnClick", function()
local c = MTH_SP_OPT_GetCfg()
local newMode = SP_NextTauntMode(c.tauntMode)
if api.SetTauntMode then api.SetTauntMode(newMode) end
this:SetText("Mode: " .. (TAUNT_LABELS[newMode] or newMode))
end)
y = y - GAP_BTN
SP_CB(container, "MTH_SP_OPT_PvpDetaunt", "PVP Auto-Detaunt",
y, OPT_L, cfg.pvpDetaunt, function(v)
if api.SetPvpDetaunt then api.SetPvpDetaunt(v) end
end)
y = y - GAP_SECTION
---------------------------------------------------------------------------
-- LEFT COLUMN — CC-Break Prevention
---------------------------------------------------------------------------
SP_Header(container, "CC-Break Prevention", y)
y = y - 22
SP_CB(container, "MTH_SP_OPT_CcBreak", "Check for breakable CC",
y, OPT_L, cfg.ccBreakCheck, function(v)
if api.SetCcBreakCheck then api.SetCcBreakCheck(v) end
end)
y = y - GAP_CB
local ccLabels = { block = "Block attack", warn = "Warn only" }
local ccModeBtn = CreateFrame("Button", "MTH_SP_OPT_CcModeBtn", container, "UIPanelButtonTemplate")
ccModeBtn:SetPoint("TOPLEFT", container, "TOPLEFT", OPT_L + 20, y)
ccModeBtn:SetWidth(180)
ccModeBtn:SetHeight(20)
ccModeBtn:SetText("CC Mode: " .. (ccLabels[cfg.ccBreakMode] or "Block"))
ccModeBtn:SetScript("OnClick", function()
local c = MTH_SP_OPT_GetCfg()
local newMode = (c.ccBreakMode == "block") and "warn" or "block"
if api.SetCcBreakMode then api.SetCcBreakMode(newMode) end
this:SetText("CC Mode: " .. (ccLabels[newMode] or newMode))
end)
y = y - GAP_SECTION
---------------------------------------------------------------------------
-- RIGHT COLUMN — General
---------------------------------------------------------------------------
local ry = COL_TOP
-- The Button
SP_Header(container, "The Button", ry, OPT_R)
ry = ry - 22
SP_CB(container, "MTH_SP_OPT_TheButton", "Enable The Button keybind",
ry, OPT_R, cfg.theButton, function(v)
if api.SetTheButton then api.SetTheButton(v) end
end)
ry = ry - GAP_CB
SP_Tip(container, "Enemy=attack, friendly=assist, none=recall.", ry, OPT_R)
ry = ry - GAP_SECTION
-- Rush on Attack
SP_CB(container, "MTH_SP_OPT_Rush", "Rush on initial attack",
ry, OPT_R, cfg.rushOnAttack, function(v)
if api.SetRushOnAttack then api.SetRushOnAttack(v) end
end)
ry = ry - GAP_CB
SP_Tip(container, "Charge / Dash / Dive when pet is sent to attack.", ry, OPT_R)
ry = ry - GAP_SECTION
-- NoChase
SP_CB(container, "MTH_SP_OPT_NoChase", "NoChase (recall on mob flee)",
ry, OPT_R, cfg.noChase, function(v)
if api.SetNoChase then api.SetNoChase(v) end
end)
ry = ry - GAP_CB
SP_Tip(container, "Recall pet when target attempts to flee.", ry, OPT_R)
ry = ry - GAP_SECTION
-- AutoCower
SP_Header(container, "AutoCower", ry, OPT_R)
ry = ry - 22
SP_CB(container, "MTH_SP_OPT_AutoCower", "Enable AutoCower on low HP",
ry, OPT_R, cfg.autoCower, function(v)
if api.SetAutoCower then api.SetAutoCower(v) end
end)
ry = ry - GAP_CB_SLIDER
local acSlider = MTH_CreateSlider(container, "MTH_SP_OPT_AutoCowerSlider",
"HP Threshold (%)", 5, 80, 5, ry)
if acSlider then
acSlider:ClearAllPoints()
acSlider:SetPoint("TOPLEFT", container, "TOPLEFT", OPT_R, ry)
acSlider:SetWidth(200)
acSlider:SetValue(cfg.autoCowerPct or 30)
local valText = getglobal("MTH_SP_OPT_AutoCowerSliderValue")
if valText then valText:SetText(tostring(cfg.autoCowerPct or 30)) end
acSlider.onChange = function(val)
if api.SetAutoCowerPct then api.SetAutoCowerPct(val) end
end
end
ry = ry - GAP_SLIDER
-- AutoWarn
SP_Header(container, "AutoWarn", ry, OPT_R)
ry = ry - 22
SP_CB(container, "MTH_SP_OPT_AutoWarn", "Warn when pet HP is low",
ry, OPT_R, cfg.autoWarn, function(v)
if api.SetAutoWarn then api.SetAutoWarn(v) end
end)
ry = ry - GAP_CB_SLIDER
local awSlider = MTH_CreateSlider(container, "MTH_SP_OPT_AutoWarnSlider",
"HP Threshold (%)", 5, 60, 5, ry)
if awSlider then
awSlider:ClearAllPoints()
awSlider:SetPoint("TOPLEFT", container, "TOPLEFT", OPT_R, ry)
awSlider:SetWidth(200)
awSlider:SetValue(cfg.autoWarnPct or 20)
local valText = getglobal("MTH_SP_OPT_AutoWarnSliderValue")
if valText then valText:SetText(tostring(cfg.autoWarnPct or 20)) end
awSlider.onChange = function(val)
if api.SetAutoWarnPct then api.SetAutoWarnPct(val) end
end
end
ry = ry - GAP_SLIDER
local chanBtn = CreateFrame("Button", "MTH_SP_OPT_ChanBtn", container, "UIPanelButtonTemplate")
chanBtn:SetPoint("TOPLEFT", container, "TOPLEFT", OPT_R, ry)
chanBtn:SetWidth(180)
chanBtn:SetHeight(22)
chanBtn:SetText("Channel: " .. (cfg.autoWarnChannel or "SAY"))
chanBtn:SetScript("OnClick", function()
local c = MTH_SP_OPT_GetCfg()
local newChan = SP_NextChannel(c.autoWarnChannel or "SAY")
if api.SetAutoWarnChannel then api.SetAutoWarnChannel(newChan) end
this:SetText("Channel: " .. newChan)
end)
ry = ry - GAP_CB
SP_Tip(container, "Chat channel for low-HP warnings.", ry, OPT_R)
end
-- Public setup function (called by options-shell.lua)
function MTH_SetupSmartPetOptions()
if not MTH_SP_OPT_Ready() then return end
local container = MTH_GetFrame("MetaHuntOptionsSmartPet")
if not container then return end
local ok, err = pcall(MTH_SP_OPT_BuildUI, container)
if not ok then
if MTH and MTH.Print then
MTH:Print("[SmartPet Options] " .. tostring(err), "error")
end
MTH_SP_OPT_built = false
end
end
+13 -5
View File
@@ -1,4 +1,4 @@
MTH_OPTIONS_TREE_STATE = MTH_OPTIONS_TREE_STATE or { zButtons = true, chronometer = true }
MTH_OPTIONS_TREE_STATE = MTH_OPTIONS_TREE_STATE or { zButtons = false, chronometer = false }
MTH_OPTIONS_TREE_BUTTONS = MTH_OPTIONS_TREE_BUTTONS or {}
MTH_OPTIONS_STATE = MTH_OPTIONS_STATE or {}
if MTH_OPTIONS_STATE.activeTab == nil and MTH_OPTIONS_ACTIVE_TAB ~= nil then
@@ -27,9 +27,11 @@ 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 = "ZBar", label = "zBar", frame = "MetaHuntOptionsZBar" },
{ key = "Pet", label = "ZPet", frame = "MetaHuntOptionsPet" },
{ key = "Track", label = "ZTrack", frame = "MetaHuntOptionsTrack" },
{ key = "Aspect", label = "ZAspect", frame = "MetaHuntOptionsAspect" },
{ key = "Trap", label = "ZTrap", frame = "MetaHuntOptionsTrap" },
@@ -38,11 +40,13 @@ MTH_OPTIONS_TABS = MTH_OPTIONS_TABS or {
{ key = "Mounts", label = "ZMounts", frame = "MetaHuntOptionsMounts" },
{ key = "Companions", label = "ZCompanions", frame = "MetaHuntOptionsCompanions" },
{ key = "Toys", label = "ZToys", frame = "MetaHuntOptionsToys" },
{ key = "Craft", label = "ZCraft", frame = "MetaHuntOptionsCraft" },
{ key = "SmartAmmo", label = "Smart Ammo", frame = "MetaHuntOptionsSmartAmmo" },
{ key = "FeedOMatic", label = "FeedOMatic", frame = "MetaHuntOptionsFeedOMatic" },
{ key = "AutoBuy", label = "Auto Buy", frame = "MetaHuntOptionsAutoBuy" },
{ 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,17 +56,20 @@ 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",
children = {
{ label = "zAmmo", key = "Ammo" },
{ label = "zBar", key = "ZBar" },
{ label = "zAmmo", key = "Ammo" },
{ label = "zPet", key = "Pet" },
{ label = "zAspect", key = "Aspect" },
{ label = "zTrack", key = "Track" },
@@ -71,6 +78,7 @@ MTH_OPTIONS_TREE = MTH_OPTIONS_TREE or {
{ label = "zMounts", key = "Mounts" },
{ label = "zCompanions", key = "Companions" },
{ label = "zToys", key = "Toys" },
{ label = "zCraft", key = "Craft" },
},
},
{
+638 -140
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -68,9 +68,9 @@ function MTH_CreateSlider(parent, name, label, minVal, maxVal, step, yOffset)
if lowLabel then lowLabel:Hide() end
if highLabel then highLabel:Hide() end
slider:SetScript("OnValueChanged", function(self, newValue)
local activeSlider = self or this or slider
local current = tonumber(newValue)
slider:SetScript("OnValueChanged", function()
local activeSlider = this or this or slider
local current = tonumber(arg1)
if not current and activeSlider and activeSlider.GetValue then
current = tonumber(activeSlider:GetValue())
end
+6 -3
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>
@@ -64,8 +64,9 @@
</Backdrop>
</Frame>
<!-- Content Areas -->
<Frame name="$parentZBar" hidden="true" enableMouse="true"/>
<Frame name="$parentGeneral" hidden="true" enableMouse="true"/>
<Frame name="$parentProfiles" hidden="true" enableMouse="true"/>
<Frame name="$parentMessages" hidden="true" enableMouse="true"/>
<Frame name="$parentPet" hidden="true" enableMouse="true"/>
<Frame name="$parentTrack" hidden="true" enableMouse="true"/>
@@ -76,11 +77,13 @@
<Frame name="$parentMounts" hidden="true" enableMouse="true"/>
<Frame name="$parentCompanions" hidden="true" enableMouse="true"/>
<Frame name="$parentToys" hidden="true" enableMouse="true"/>
<Frame name="$parentCraft" hidden="true" enableMouse="true"/>
<Frame name="$parentSmartAmmo" hidden="true" enableMouse="true"/>
<Frame name="$parentFeedOMatic" hidden="true" enableMouse="true"/>
<Frame name="$parentAutoBuy" hidden="true" enableMouse="true"/>
<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)
+7 -8
View File
@@ -65,11 +65,10 @@ local function MTH_UIR_CreateControl(frame, key, option, context, x, y, width)
checkButton:SetPoint("TOPLEFT", frame, "TOPLEFT", x, y + 2)
checkButton:SetChecked(MTH_UI.ResolveValue(option.get, context) and 1 or nil)
MTH_UIR_SetCheckboxLabel(checkButton, optionName or "")
checkButton:SetScript("OnClick", function(self)
self = self or this
if not self then return end
checkButton:SetScript("OnClick", function()
if not this then return end
if option.set then
option.set(context, self:GetChecked() == 1)
option.set(context, this:GetChecked() == 1)
end
end)
if MTH_UI.ResolveValue(option.disabled, context) then
@@ -100,8 +99,8 @@ local function MTH_UIR_CreateControl(frame, key, option, context, x, y, width)
text:SetText(optionName or "Run")
text:SetTextColor(1.00, 0.82, 0.00)
button:SetScript("OnEnter", function(self) self = self or this if self then self:SetBackdropBorderColor(0.6, 0.6, 0.6) end end)
button:SetScript("OnLeave", function(self) self = self or this if self then self:SetBackdropBorderColor(0.4, 0.4, 0.4) end end)
button:SetScript("OnEnter", function() if this then this:SetBackdropBorderColor(0.6, 0.6, 0.6) end end)
button:SetScript("OnLeave", function() if this then this:SetBackdropBorderColor(0.4, 0.4, 0.4) end end)
button:SetScript("OnClick", function()
if option.func then
option.func(context)
@@ -139,8 +138,8 @@ local function MTH_UIR_CreateControl(frame, key, option, context, x, y, width)
text:SetText(tostring(currentValue or "Choose"))
text:SetTextColor(0.90, 0.90, 0.90)
button:SetScript("OnEnter", function(self) self = self or this if self then self:SetBackdropBorderColor(0.6, 0.6, 0.6) end end)
button:SetScript("OnLeave", function(self) self = self or this if self then self:SetBackdropBorderColor(0.4, 0.4, 0.4) end end)
button:SetScript("OnEnter", function() if this then this:SetBackdropBorderColor(0.6, 0.6, 0.6) end end)
button:SetScript("OnLeave", function() if this then this:SetBackdropBorderColor(0.4, 0.4, 0.4) end end)
button:SetScript("OnClick", function()
if option.onClick then
option.onClick(context, option)
+10 -4
View File
@@ -6,10 +6,11 @@ VC.abbrev = "MTH"
VC.channelName = "LFT"
VC.updateUrl = "https://github.com/DuvelCorp/MetaHunt"
VC.nextPublishAt = nil
VC.lastPublishedAt = nil
VC.joinAt = nil
VC.notified = false
VC._invalidVersionWarned = false
VC.maxPersistedPeers = 200
VC.maxPersistedPeers = 10000
VC.seenPeers = VC.seenPeers or {}
local function VC_GetTimeNow()
@@ -140,7 +141,7 @@ end
function VC:PrunePersistedPeers(limit)
local maxCount = tonumber(limit) or tonumber(self.maxPersistedPeers) or 200
if maxCount <= 0 then
maxCount = 200
maxCount = 10000
end
if type(self.seenPeers) ~= "table" then
return
@@ -219,7 +220,11 @@ function VC:GetTrackedPeers()
end
function VC:ResetPublishDelay()
self.nextPublishAt = VC_GetTimeNow() + math.random(10, 20)
local now = VC_GetTimeNow()
if self.lastPublishedAt and (now - self.lastPublishedAt) < 3600 then
return -- 1-hour cooldown between publishes
end
self.nextPublishAt = now + math.random(10, 20)
end
function VC:ShouldPublish()
@@ -247,6 +252,7 @@ function VC:TryPublish()
end
local payload = self.abbrev .. ":" .. tostring(localVersionNumber) .. ":v"
SendChatMessage(payload, "CHANNEL", nil, tostring(channelId))
self.lastPublishedAt = VC_GetTimeNow()
return true
end
@@ -305,7 +311,7 @@ function VC:HandleRemoteMessage(message, author, channelLabel)
end
if not VC.frame then
local frame = CreateFrame("Frame", "MTHVersionCheckFrame", UIParent)
local frame = CreateFrame("Frame", "MTH_VersionCheck", UIParent)
if frame then
VC.frame = frame
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
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
+77 -1
View File
@@ -6,8 +6,10 @@ if not MTH_DS then MTH_DS = {} end
MTH_DS_Families = {
["Bats"] = {
["icon"] = "Ability_Hunter_Pet_Bat",
["named"] = 20,
["coords"] = 18,
["stats"] = { ["health"] = 1, ["armor"] = 1, ["damage"] = 1.07 },
["food"] = {
"fruit",
"fungus",
@@ -21,8 +23,10 @@ MTH_DS_Families = {
},
},
["Bears"] = {
["icon"] = "Ability_Hunter_Pet_Bear",
["named"] = 45,
["coords"] = 45,
["stats"] = { ["health"] = 1.08, ["armor"] = 1.05, ["damage"] = 0.91 },
["food"] = {
"meat",
"fish",
@@ -30,6 +34,8 @@ MTH_DS_Families = {
"cheese",
"fruit",
"fungus",
"raw meat",
"raw fish",
},
["abilities"] = {
"Bite",
@@ -40,8 +46,10 @@ MTH_DS_Families = {
},
},
["Boars"] = {
["icon"] = "Ability_Hunter_Pet_Boar",
["named"] = 45,
["coords"] = 87,
["stats"] = { ["health"] = 1.04, ["armor"] = 1.09, ["damage"] = 0.9 },
["food"] = {
"meat",
"fish",
@@ -49,6 +57,8 @@ MTH_DS_Families = {
"cheese",
"fruit",
"fungus",
"raw meat",
"raw fish",
},
["abilities"] = {
"Bite",
@@ -59,10 +69,13 @@ MTH_DS_Families = {
},
},
["Carrion Birds"] = {
["icon"] = "Ability_Hunter_Pet_Vulture",
["named"] = 36,
["coords"] = 101,
["stats"] = { ["health"] = 1, ["armor"] = 1.05, ["damage"] = 1 },
["food"] = {
"meat",
"raw meat",
},
["abilities"] = {
"Bite",
@@ -74,11 +87,15 @@ MTH_DS_Families = {
},
},
["Cats"] = {
["icon"] = "Ability_Hunter_Pet_Cat",
["named"] = 85,
["coords"] = 88,
["stats"] = { ["health"] = 0.98, ["armor"] = 1, ["damage"] = 1.1 },
["food"] = {
"meat",
"fish",
"raw meat",
"raw fish",
},
["abilities"] = {
"Bite",
@@ -90,13 +107,16 @@ MTH_DS_Families = {
},
},
["Crabs"] = {
["icon"] = "Ability_Hunter_Pet_Crab",
["named"] = 32,
["coords"] = 74,
["stats"] = { ["health"] = 0.96, ["armor"] = 1.13, ["damage"] = 0.95 },
["food"] = {
"fruit",
"fish",
"fungus",
"bread",
"raw fish",
},
["abilities"] = {
"Bubble Barrier",
@@ -106,11 +126,15 @@ MTH_DS_Families = {
},
},
["Crocolisks"] = {
["icon"] = "Ability_Hunter_Pet_Crocolisk",
["named"] = 27,
["coords"] = 71,
["stats"] = { ["health"] = 0.95, ["armor"] = 1.1, ["damage"] = 1 },
["food"] = {
"meat",
"fish",
"raw meat",
"raw fish",
},
["abilities"] = {
"Bite",
@@ -120,11 +144,14 @@ MTH_DS_Families = {
},
},
["Foxes"] = {
["icon"] = "ability_hunter_aspectofthefox",
["named"] = 9,
["coords"] = 51,
["stats"] = { ["health"] = 1, ["armor"] = 1, ["damage"] = 1.07 },
["food"] = {
"meat",
"fruit",
"raw meat",
},
["abilities"] = {
"Bite",
@@ -135,8 +162,10 @@ MTH_DS_Families = {
},
},
["Gorillas"] = {
["icon"] = "Ability_Hunter_Pet_Gorilla",
["named"] = 24,
["coords"] = 117,
["stats"] = { ["health"] = 1.04, ["armor"] = 1, ["damage"] = 1.02 },
["food"] = {
"fruit",
"fungus",
@@ -149,11 +178,14 @@ MTH_DS_Families = {
},
},
["Hyenas"] = {
["icon"] = "Ability_Hunter_Pet_Hyena",
["named"] = 26,
["coords"] = 36,
["stats"] = { ["health"] = 1, ["armor"] = 1.05, ["damage"] = 1 },
["food"] = {
"meat",
"fruit",
"raw meat",
},
["abilities"] = {
"Bite",
@@ -164,10 +196,13 @@ MTH_DS_Families = {
},
},
["Owls"] = {
["icon"] = "Ability_Hunter_Pet_Owl",
["named"] = 16,
["coords"] = 59,
["stats"] = { ["health"] = 1, ["armor"] = 1, ["damage"] = 1.07 },
["food"] = {
"meat",
"raw meat",
},
["abilities"] = {
"Claw",
@@ -178,10 +213,13 @@ MTH_DS_Families = {
},
},
["Raptors"] = {
["icon"] = "Ability_Hunter_Pet_Raptor",
["named"] = 51,
["coords"] = 115,
["stats"] = { ["health"] = 0.95, ["armor"] = 1.03, ["damage"] = 1.1 },
["food"] = {
"meat",
"raw meat",
},
["abilities"] = {
"Bite",
@@ -193,10 +231,13 @@ MTH_DS_Families = {
},
},
["Scorpids"] = {
["icon"] = "Ability_Hunter_Pet_Scorpid",
["named"] = 38,
["coords"] = 36,
["stats"] = { ["health"] = 1, ["armor"] = 1.1, ["damage"] = 0.94 },
["food"] = {
"meat",
"raw meat",
},
["abilities"] = {
"Claw",
@@ -205,12 +246,16 @@ MTH_DS_Families = {
"Scorpid Poison",
},
},
["Serpents (Cobra)"] = {
["Serpents"] = {
["icon"] = "Ability_Hunter_Pet_Serpent1",
["named"] = 10,
["coords"] = 13,
["stats"] = { ["health"] = 1.03, ["armor"] = 1.03, ["damage"] = 1.135 },
["food"] = {
"meat",
"fish",
"raw meat",
"raw fish",
},
["abilities"] = {
"Bite",
@@ -220,10 +265,13 @@ MTH_DS_Families = {
},
},
["Spiders"] = {
["icon"] = "Ability_Hunter_Pet_Spider",
["named"] = 91,
["coords"] = 213,
["stats"] = { ["health"] = 1, ["armor"] = 1, ["damage"] = 1.07 },
["food"] = {
"meat",
"raw meat",
},
["abilities"] = {
"Bite",
@@ -233,8 +281,10 @@ MTH_DS_Families = {
},
},
["Tallstriders"] = {
["icon"] = "Ability_Hunter_Pet_TallStrider",
["named"] = 15,
["coords"] = 112,
["stats"] = { ["health"] = 1.05, ["armor"] = 1, ["damage"] = 1 },
["food"] = {
"fruit",
"fungus",
@@ -249,12 +299,15 @@ MTH_DS_Families = {
},
},
["Turtles"] = {
["icon"] = "Ability_Hunter_Pet_Turtle",
["named"] = 25,
["coords"] = 42,
["stats"] = { ["health"] = 1, ["armor"] = 1.13, ["damage"] = 0.9 },
["food"] = {
"fruit",
"fish",
"fungus",
"raw fish",
},
["abilities"] = {
"Bite",
@@ -264,12 +317,15 @@ MTH_DS_Families = {
},
},
["Wind Serpents"] = {
["icon"] = "Ability_Hunter_Pet_WindSerpent",
["named"] = 27,
["coords"] = 42,
["stats"] = { ["health"] = 1, ["armor"] = 1, ["damage"] = 1.07 },
["food"] = {
"fish",
"cheese",
"bread",
"raw fish",
},
["abilities"] = {
"Bite",
@@ -280,10 +336,13 @@ MTH_DS_Families = {
},
},
["Wolves"] = {
["icon"] = "Ability_Hunter_Pet_Wolf",
["named"] = 81,
["coords"] = 99,
["stats"] = { ["health"] = 1, ["armor"] = 1.05, ["damage"] = 1 },
["food"] = {
"meat",
"raw meat",
},
["abilities"] = {
"Bite",
@@ -293,4 +352,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,
},
},
},
},
+72
View File
@@ -10,6 +10,7 @@ MTH_DS_AmmoItems = {
["level"] = 5,
["reqlevel"] = 1,
["icon"] = "INV_Ammo_Arrow_02",
["dps"] = 1.5,
["subtype"] = "arrow",
["vendors"] = {
[150] = 0,
@@ -118,6 +119,7 @@ MTH_DS_AmmoItems = {
["level"] = 15,
["reqlevel"] = 10,
["icon"] = "INV_Ammo_Arrow_02",
["dps"] = 3.5,
["subtype"] = "arrow",
["vendors"] = {
[150] = 0,
@@ -275,6 +277,7 @@ MTH_DS_AmmoItems = {
["level"] = 5,
["reqlevel"] = 1,
["icon"] = "INV_Ammo_Bullet_02",
["dps"] = 2,
["subtype"] = "bullet",
["vendors"] = {
[151] = 0,
@@ -359,6 +362,7 @@ MTH_DS_AmmoItems = {
["level"] = 15,
["reqlevel"] = 10,
["icon"] = "INV_Ammo_Bullet_02",
["dps"] = 5.5,
["subtype"] = "bullet",
["vendors"] = {
[227] = 0,
@@ -494,6 +498,7 @@ MTH_DS_AmmoItems = {
["level"] = 30,
["reqlevel"] = 25,
["icon"] = "INV_Ammo_Arrow_02",
["dps"] = 5.5,
["subtype"] = "arrow",
["vendors"] = {
[227] = 0,
@@ -621,6 +626,7 @@ MTH_DS_AmmoItems = {
["level"] = 30,
["reqlevel"] = 25,
["icon"] = "INV_Ammo_Bullet_02",
["dps"] = 7.5,
["subtype"] = "bullet",
["vendors"] = {
[227] = 0,
@@ -736,18 +742,21 @@ MTH_DS_AmmoItems = {
["name"] = "Feathered Arrow",
["level"] = 35,
["icon"] = "INV_Ammo_Arrow_02",
["dps"] = 7.5,
["subtype"] = "arrow",
},
[3465] = {
["name"] = "Exploding Shot",
["level"] = 36,
["icon"] = "INV_Ammo_Bullet_02",
["dps"] = 7.5,
["subtype"] = "bullet",
},
[4960] = {
["name"] = "Flash Pellet",
["level"] = 7,
["icon"] = "INV_Ammo_Bullet_02",
["dps"] = 3.5,
["subtype"] = "bullet",
},
[5568] = {
@@ -755,6 +764,7 @@ MTH_DS_AmmoItems = {
["level"] = 18,
["reqlevel"] = 13,
["icon"] = "INV_Ammo_Bullet_02",
["dps"] = 3.5,
["subtype"] = "bullet",
["drops"] = {
[2156] = 30.97,
@@ -781,6 +791,7 @@ MTH_DS_AmmoItems = {
["level"] = 10,
["reqlevel"] = 5,
["icon"] = "INV_Ammo_Bullet_02",
["dps"] = 2,
["subtype"] = "bullet",
},
[8068] = {
@@ -788,6 +799,7 @@ MTH_DS_AmmoItems = {
["level"] = 20,
["reqlevel"] = 15,
["icon"] = "INV_Ammo_Bullet_02",
["dps"] = 5.5,
["subtype"] = "bullet",
},
[8069] = {
@@ -795,6 +807,7 @@ MTH_DS_AmmoItems = {
["level"] = 35,
["reqlevel"] = 30,
["icon"] = "INV_Ammo_Bullet_02",
["dps"] = 7.5,
["subtype"] = "bullet",
},
[9399] = {
@@ -802,6 +815,7 @@ MTH_DS_AmmoItems = {
["level"] = 40,
["reqlevel"] = 35,
["icon"] = "INV_Ammo_Arrow_01",
["dps"] = 7.5,
["subtype"] = "arrow",
["drops"] = {
[6906] = 100,
@@ -812,6 +826,7 @@ MTH_DS_AmmoItems = {
["level"] = 42,
["reqlevel"] = 37,
["icon"] = "INV_Ammo_Bullet_01",
["dps"] = 12.5,
["subtype"] = "bullet",
},
[10513] = {
@@ -819,6 +834,7 @@ MTH_DS_AmmoItems = {
["level"] = 49,
["reqlevel"] = 44,
["icon"] = "INV_Ammo_Bullet_01",
["dps"] = 12.5,
["subtype"] = "bullet",
},
[10579] = {
@@ -826,6 +842,7 @@ MTH_DS_AmmoItems = {
["level"] = 42,
["reqlevel"] = 37,
["icon"] = "INV_Ammo_Arrow_02",
["dps"] = 12.5,
["subtype"] = "arrow",
},
[11284] = {
@@ -833,6 +850,7 @@ MTH_DS_AmmoItems = {
["level"] = 45,
["reqlevel"] = 40,
["icon"] = "INV_Ammo_Bullet_01",
["dps"] = 10.5,
["subtype"] = "bullet",
["vendors"] = {
[734] = 0,
@@ -934,6 +952,7 @@ MTH_DS_AmmoItems = {
["level"] = 45,
["reqlevel"] = 40,
["icon"] = "INV_Weapon_ShortBlade_25",
["dps"] = 10.5,
["subtype"] = "arrow",
["vendors"] = {
[228] = 0,
@@ -1043,6 +1062,7 @@ MTH_DS_AmmoItems = {
["level"] = 52,
["reqlevel"] = 47,
["icon"] = "INV_Ammo_Bullet_02",
["dps"] = 10.5,
["subtype"] = "bullet",
["drops"] = {
[9025] = 16,
@@ -1053,6 +1073,7 @@ MTH_DS_AmmoItems = {
["level"] = 59,
["reqlevel"] = 54,
["icon"] = "Ability_Hunter_CriticalShot",
["dps"] = 20,
["subtype"] = "arrow",
["drops"] = {
[9236] = 100,
@@ -1063,6 +1084,7 @@ MTH_DS_AmmoItems = {
["level"] = 61,
["reqlevel"] = 56,
["icon"] = "INV_Ammo_Bullet_02",
["dps"] = 20.5,
["subtype"] = "bullet",
["drops"] = {
[10997] = 100,
@@ -1073,6 +1095,7 @@ MTH_DS_AmmoItems = {
["level"] = 57,
["reqlevel"] = 52,
["icon"] = "INV_Ammo_Bullet_03",
["dps"] = 17.5,
["subtype"] = "bullet",
["vendors"] = {
[17078] = 0,
@@ -1083,6 +1106,7 @@ MTH_DS_AmmoItems = {
["level"] = 57,
["reqlevel"] = 52,
["icon"] = "INV_Ammo_Arrow_02",
["dps"] = 17.5,
["subtype"] = "arrow",
["vendors"] = {
[17078] = 0,
@@ -1093,6 +1117,7 @@ MTH_DS_AmmoItems = {
["level"] = 1,
["reqlevel"] = 1,
["icon"] = "INV_Ammo_Arrow_02",
["dps"] = 1.5,
["subtype"] = "arrow",
},
[19316] = {
@@ -1100,6 +1125,7 @@ MTH_DS_AmmoItems = {
["level"] = 54,
["reqlevel"] = 51,
["icon"] = "Spell_Frost_IceShard",
["dps"] = 14.5,
["subtype"] = "arrow",
["vendors"] = {
[13216] = 0,
@@ -1113,6 +1139,7 @@ MTH_DS_AmmoItems = {
["level"] = 54,
["reqlevel"] = 51,
["icon"] = "Spell_Frost_FrostBlast",
["dps"] = 14.5,
["subtype"] = "bullet",
["vendors"] = {
[13216] = 0,
@@ -1121,6 +1148,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
+9
View File
@@ -54,22 +54,31 @@ MTH_DS_ZoneNamesFallback = {
[1519] = "Stormwind City",
[1537] = "Ironforge",
[1583] = "Blackrock Spire",
[1584] = "Blackrock Depths",
[1941] = "Caverns of Time",
[1977] = "Zul'Gurub",
[2040] = "Alah'Thalas",
[2100] = "Maraudon",
[2717] = "Molten Core",
[2597] = "Alterac Valley",
[3456] = "Naxxramas",
[3457] = "Tower of Karazhan",
[4012] = "Scarlet Enclave",
[5023] = "Sunnyglade",
[5024] = "Icepoint Rock",
[5077] = "Crescent Grove",
[5121] = "Tel'Abim",
[5130] = "Winter Veil Vale",
[5135] = "Scarlet Monastery Library",
[5179] = "Gilneas",
[5208] = "Gilneas City",
[5225] = "Thalassian Highlands",
[5536] = "Blackstone Island",
[5723] = "Karazhan",
[5561] = "Balor",
[5581] = "Northwind",
[5601] = "Dragonmaw Retreat",
[5602] = "Grim Reaches",
[5641] = "Windhorn Canyon",
[5642] = "Moonwhisper Coast",
}
File diff suppressed because it is too large Load Diff
+405
View File
@@ -0,0 +1,405 @@
-- 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
[615] = 1584, -- BlackrockDepths (interior)
[617] = 2717, -- MoltenCore
[619] = 1977, -- ZulGurub
[623] = 1584, -- BlackrockDepths
[625] = 3429, -- RuinsofAhnQiraj
[627] = 2159, -- OnyxiasLair
[629] = 1583, -- BlackrockSpire
[631] = 718, -- WailingCaverns (interior)
[633] = 2100, -- Maraudon (interior)
[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=3245.83, right=-1637.50, top=2916.67, bottom=-339.58, width=4883.33, height=3256.25 }, -- StonetalonMountains
[101] = { pfqAreaId=405, mapId=1, left=4233.33, right=-262.50, top=452.08, bottom=-2545.83, width=4495.83, height=2997.91 }, -- Desolace
[121] = { pfqAreaId=357, mapId=1, left=5441.67, right=-1508.33, top=-2366.67, bottom=-7000.00, width=6950.00, height=4633.33 }, -- Feralas
[141] = { pfqAreaId=15, mapId=1, left=-975.00, right=-6225.00, top=-2033.33, bottom=-5533.33, width=5250.00, height=3500.00 }, -- Dustwallow
[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
}
-1052
View File
File diff suppressed because it is too large Load Diff
+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
+8 -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,8 +16,12 @@
<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\bag-ammo-labels.lua"/>
<Include file="..\api\options-feedomatic.lua"/>
<Include file="..\api\hunterbook.xml"/>
<Include file="..\api\hunterbook.lua"/>
@@ -29,7 +32,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 +43,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"] = "震荡射击",
+2
View File
@@ -8,6 +8,8 @@ MTH_LocaleData.ui.enUS = {
BINDING_HEADER_ZTrapHeader = "[MetaHunt] ZTrap Buttons",
BINDING_HEADER_ZPetHeader = "[MetaHunt] ZPet Buttons",
BINDING_HEADER_ZAmmoHeader = "[MetaHunt] ZAmmo Buttons",
BINDING_HEADER_ExpAmmoHeader = "[MetaHunt] MM Widget",
BINDING_NAME_EXPAMMOACTION = "MM Widget Action",
FEEDOMATIC_NAME = "Feed-O-Matic",
FEEDOMATIC_DESC = "Helps a Hunter keep his pets well fed",
-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
+4 -33
View File
@@ -6,7 +6,6 @@
local MTH_AutoBuy = {
name = "autobuy",
enabled = false,
version = "1.1.0",
events = {
"VARIABLES_LOADED",
"MERCHANT_SHOW",
@@ -24,16 +23,13 @@ end
local AB_Engine
local function AB_Trace(message)
return
end
local function AB_EnsureBridgeFrame(moduleRef)
if MTH_AutoBuy._bridgeFrame then
return MTH_AutoBuy._bridgeFrame
end
local frame = CreateFrame("Frame", "MTH_AutoBuyBridgeFrame")
local frame = CreateFrame("Frame", "MTH_AutoBuyBridge")
if not frame then
AB_Log("failed to create bridge frame", "error")
return nil
@@ -42,19 +38,16 @@ local function AB_EnsureBridgeFrame(moduleRef)
frame:RegisterEvent("MERCHANT_SHOW")
frame:RegisterEvent("MERCHANT_UPDATE")
frame:RegisterEvent("MERCHANT_CLOSED")
frame:SetScript("OnEvent", function(_, eventName)
eventName = eventName or event
frame:SetScript("OnEvent", function()
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 +70,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 +82,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 +92,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 +107,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 +146,6 @@ function MTH_AutoBuy:onEvent(event)
end
function MTH_AutoBuy:cleanup()
AB_Log("module cleanup", "debug")
end
MTH:RegisterModule("autobuy", MTH_AutoBuy)
+26 -62
View File
@@ -6,7 +6,6 @@
local MTH_AutoQuest = {
name = "autoquest",
enabled = false,
version = "1.1.0",
events = {
"GOSSIP_SHOW",
"QUEST_GREETING",
@@ -34,7 +33,6 @@ local QA_ITEM_BLASTED_BOAR_LUNG_NAME = "Blasted Boar Lung"
local QA_ITEM_VULTURE_GIZZARD_NAME = "Vulture Gizzard"
local QA_ITEM_GROUND_SCORPOK_ASSAY_NAME = "Ground Scorpok Assay"
local QA_DirectEventFrame = nil
local QA_TooltipHooked = false
local QA_TooltipPollFrame = nil
local QA_TooltipRefreshFrame = nil
@@ -292,7 +290,8 @@ local function QA_CountItemInBags(expectedId, expectedName)
for slot = 1, slots do
local link = getContainerItemLink(bag, slot)
if type(link) == "string" and link ~= "" then
local itemId = tonumber(string.match(link, "item:(%d+)"))
local _, _, itemIdStr = string.find(link, "item:(%d+)")
local itemId = tonumber(itemIdStr)
local itemName = nil
if type(getItemInfo) == "function" then
itemName = getItemInfo(link)
@@ -456,8 +455,8 @@ local function QA_EnsureTooltipHook()
if hasHookScript and onTooltipSetUnitExists then
pcall(function()
GameTooltip:HookScript("OnTooltipSetUnit", function(self)
QA_AppendDrazialTooltip(self, "hook:setunit")
GameTooltip:HookScript("OnTooltipSetUnit", function()
QA_AppendDrazialTooltip(this, "hook:setunit")
end)
end)
elseif hasSetScript and hasGetScript and onTooltipSetUnitExists then
@@ -466,11 +465,11 @@ local function QA_EnsureTooltipHook()
end)
if okGetPrev then
pcall(function()
GameTooltip:SetScript("OnTooltipSetUnit", function(self)
GameTooltip:SetScript("OnTooltipSetUnit", function()
if type(previous) == "function" then
previous(self)
previous()
end
QA_AppendDrazialTooltip(self, "script:setunit")
QA_AppendDrazialTooltip(this, "script:setunit")
end)
end)
end
@@ -478,8 +477,8 @@ local function QA_EnsureTooltipHook()
if hasHookScript and onShowExists then
pcall(function()
GameTooltip:HookScript("OnShow", function(self)
QA_AppendDrazialTooltip(self, "hook:show")
GameTooltip:HookScript("OnShow", function()
QA_AppendDrazialTooltip(this, "hook:show")
end)
end)
elseif hasSetScript and hasGetScript and onShowExists then
@@ -488,11 +487,11 @@ local function QA_EnsureTooltipHook()
end)
if okGetPrevShow then
pcall(function()
GameTooltip:SetScript("OnShow", function(self)
GameTooltip:SetScript("OnShow", function()
if type(previousShow) == "function" then
previousShow(self)
previousShow()
end
QA_AppendDrazialTooltip(self, "script:show")
QA_AppendDrazialTooltip(this, "script:show")
end)
end)
end
@@ -505,17 +504,17 @@ local function QA_EnsureTooltipPoller()
if QA_TooltipPollFrame then
return
end
local frame = CreateFrame("Frame", "MTH_AutoQuestTooltipPollFrame")
local frame = CreateFrame("Frame", "MTH_AQTooltipPoll")
if not frame then
return
end
frame._mthElapsed = 0
frame:SetScript("OnUpdate", function(self, elapsed)
self._mthElapsed = (self._mthElapsed or 0) + (elapsed or arg1 or 0)
if self._mthElapsed < 0.20 then
frame:SetScript("OnUpdate", function()
this._mthElapsed = (this._mthElapsed or 0) + (arg1 or 0)
if this._mthElapsed < 0.20 then
return
end
self._mthElapsed = 0
this._mthElapsed = 0
if not (MTH_AutoQuest and MTH_AutoQuest.enabled) then
return
end
@@ -591,16 +590,16 @@ end
local function QA_RequestTooltipRefresh(delaySeconds)
delaySeconds = tonumber(delaySeconds) or 0.10
if not QA_TooltipRefreshFrame then
QA_TooltipRefreshFrame = CreateFrame("Frame", "MTH_AutoQuestTooltipRefreshFrame")
QA_TooltipRefreshFrame = CreateFrame("Frame", "MTH_AQTooltipRefresh")
if not QA_TooltipRefreshFrame then
return
end
QA_TooltipRefreshFrame:SetScript("OnUpdate", function(self, elapsed)
local frameRef = self or this or QA_TooltipRefreshFrame
QA_TooltipRefreshFrame:SetScript("OnUpdate", function()
local frameRef = this or QA_TooltipRefreshFrame
if not frameRef then
return
end
frameRef._elapsed = (frameRef._elapsed or 0) + (elapsed or arg1 or 0)
frameRef._elapsed = (frameRef._elapsed or 0) + (arg1 or 0)
if frameRef._elapsed < (frameRef._delay or 0.10) then
return
end
@@ -915,48 +914,14 @@ local function QA_RunProfileAutomation(eventName, profile)
end
end
local function QA_SetDirectEventFrameActive(active)
if not QA_DirectEventFrame then
return
end
if active then
QA_DirectEventFrame:RegisterEvent("GOSSIP_SHOW")
QA_DirectEventFrame:RegisterEvent("QUEST_GREETING")
QA_DirectEventFrame:RegisterEvent("QUEST_DETAIL")
QA_DirectEventFrame:RegisterEvent("QUEST_PROGRESS")
QA_DirectEventFrame:RegisterEvent("QUEST_COMPLETE")
else
QA_DirectEventFrame:UnregisterAllEvents()
end
end
local function QA_EnsureDirectEventFrame(moduleRef)
if QA_DirectEventFrame then
return QA_DirectEventFrame
end
local frame = CreateFrame("Frame", "MTH_AutoQuestEventFrame")
if not frame then
QA_Debug("direct frame create failed")
return nil
end
frame:SetScript("OnEvent", function(_, eventName)
eventName = eventName or event
if not (moduleRef and moduleRef.enabled) then
return
end
QA_Debug("directEvent=" .. tostring(eventName))
QA_RunProfileAutomation(eventName, QA_PROFILE_SCORPOK)
QA_RunProfileAutomation(eventName, QA_PROFILE_SISSIES)
end)
QA_DirectEventFrame = frame
return frame
end
-- NOTE: QA_DirectEventFrame removed (Phase 2 fix #22b)
-- The central event router already dispatches GOSSIP_SHOW, QUEST_GREETING,
-- QUEST_DETAIL, QUEST_PROGRESS, QUEST_COMPLETE to MTH_AutoQuest:onEvent().
-- Having a second frame register the same events caused double-dispatch.
function MTH_AutoQuest:init()
local store = QA_GetOptionsStore()
self.initialized = true
QA_EnsureDirectEventFrame(self)
QA_SetDirectEventFrameActive(self.enabled and true or false)
QA_Debug("init: module active=" .. tostring(self.enabled and true or false)
.. ", option scorpokDrazial=" .. tostring(store and store.scorpokDrazial and true or false)
.. ", option arrowsForSissies=" .. tostring(store and store.arrowsForSissies and true or false)
@@ -965,7 +930,6 @@ end
function MTH_AutoQuest:setEnabled(enabled)
self.enabled = enabled and true or false
QA_SetDirectEventFrameActive(self.enabled and true or false)
end
function MTH_AutoQuest:onEvent(eventName)
@@ -981,7 +945,7 @@ function MTH_AutoQuest:onEvent(eventName)
end
function MTH_AutoQuest:cleanup()
QA_SetDirectEventFrameActive(false)
-- no-op: direct event frame removed in Phase 2
end
MTH:RegisterModule("autoquest", MTH_AutoQuest)
+15 -4
View File
@@ -28,6 +28,7 @@ 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["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 +39,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 when consumed)
engine:AddTimer(engine.EVENT, BS["Scent of Blood"], 8, 0, 1, 1, { a = 1, cr = "RED" })
engine:AddTimer(engine.EVENT, BS["Piercing Shot"], 8, 1, 0, 1, { a = 1, cr = "PURPLE", tx = "Interface\\Icons\\Ability_Hunter_DisarmingShot" })
engine:AddTimer(engine.EVENT, BS["Entrapment"], 5, 1, 0, 1, { a = 1, cr = "MAROON", tx = "Interface\\Icons\\Spell_Nature_StrangleVines" })
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" })
+182 -71
View File
@@ -58,19 +58,20 @@ 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))
-- When growing up the group attachment point must flip so bars stack above the
-- anchor rather than below it. CandyBar uses the stored group point as-is when
-- positioning each bar, so SetCandyBarGroupGrowth alone is not enough.
local function applyGrowup(self, growup)
if growup then
self:SetCandyBarGroupPoint(BAR_GROUP, "BOTTOM", self.anchor, "TOP", 0, 0)
else
self:SetCandyBarGroupPoint(BAR_GROUP, "TOP", self.anchor, "BOTTOM", 0, 0)
end
self:SetCandyBarGroupGrowth(BAR_GROUP, growup)
end
local PARSER_OWNER = "MTHChronometerHunter"
local function MTH_CHRON_FormatBarPosition(profile)
local bp = profile and profile.barposition
@@ -195,13 +196,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 +210,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 +221,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 +236,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 +273,6 @@ local function ensureProfile()
profile.disabledSpells.HUNTER = {}
end
MTH_CHRON_Trace("ensureProfile result barposition=" .. MTH_CHRON_FormatBarPosition(profile))
return profile
end
@@ -309,8 +299,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 +318,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 +325,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
@@ -485,16 +469,22 @@ local function getActionSpell(slot)
end
if not Chronometer._scanTooltip then
Chronometer._scanTooltip = CreateFrame("GameTooltip", "MTH_ChronometerScanTooltip", UIParent, "GameTooltipTemplate")
Chronometer._scanTooltip:SetOwner(UIParent, "ANCHOR_NONE")
local tip = CreateFrame("GameTooltip", "MTH_ChrBarProbe", UIParent)
tip:SetOwner(UIParent, "ANCHOR_NONE")
for i = 1, 4 do
local L = tip:CreateFontString("MTH_ChrBarProbeTextLeft"..i, "ARTWORK", "GameTooltipText")
local R = tip:CreateFontString("MTH_ChrBarProbeTextRight"..i, "ARTWORK", "GameTooltipText")
tip:AddFontStrings(L, R)
end
Chronometer._scanTooltip = tip
end
local tooltip = Chronometer._scanTooltip
tooltip:ClearLines()
tooltip:SetAction(slot)
local left1 = getglobal("MTH_ChronometerScanTooltipTextLeft1")
local left2 = getglobal("MTH_ChronometerScanTooltipTextLeft2")
local left1 = getglobal("MTH_ChrBarProbeTextLeft1")
local left2 = getglobal("MTH_ChrBarProbeTextLeft2")
local spellName = left1 and left1:GetText() or nil
if not spellName or spellName == "" then
return nil, 0
@@ -521,7 +511,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 = {
@@ -532,7 +521,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)
@@ -541,19 +529,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
@@ -583,11 +568,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)
applyGrowup(self, 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")
@@ -609,14 +592,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
@@ -675,15 +662,34 @@ function Chronometer:OnEnable()
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_SELF_DAMAGE", function(event, info) self:SELF_DAMAGE(event, info) end)
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_DAMAGESHIELDS_ON_SELF", function(event, info) self:SELF_DAMAGE(event, info) end)
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_FAILED_LOCALPLAYER", function(event, info) self:SPELL_FAILED(event, info) end)
-- NamPower: register authoritative self-aura events for instant bar
-- kill and duration resync. The ParserLib chat paths remain active as
-- they handle creature/target debuffs that NamPower cannot see.
if MTH and MTH.nampower then
self._nampowerSpellIdCache = {}
self:RegisterEvent("BUFF_ADDED_SELF")
self:RegisterEvent("BUFF_REMOVED_SELF")
self:RegisterEvent("DEBUFF_ADDED_SELF")
self:RegisterEvent("DEBUFF_REMOVED_SELF")
end
end
function Chronometer:VARIABLES_LOADED()
self.profile = ensureProfile()
if self._mth_enabled then
applyGrowup(self, self.profile.growup and true or false)
self:SetCandyBarGroupVerticalSpacing(BAR_GROUP, self.profile.spacing or 0)
end
MTH_CHRON_RestoreAnchorFromProfile(self, "VARIABLES_LOADED")
end
function Chronometer:PLAYER_ENTERING_WORLD()
self.profile = ensureProfile()
if self._mth_enabled then
applyGrowup(self, self.profile.growup and true or false)
self:SetCandyBarGroupVerticalSpacing(BAR_GROUP, self.profile.spacing or 0)
end
MTH_CHRON_RestoreAnchorFromProfile(self, "PLAYER_ENTERING_WORLD")
end
@@ -695,9 +701,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
@@ -1191,7 +1195,7 @@ function Chronometer:ToggleAnchor()
end
function Chronometer:CreateAnchor(text, r, g, b)
local frame = CreateFrame("Button", "MTHChronometerAnchor", UIParent)
local frame = CreateFrame("Button", "MTH_ChronometerAnchor", UIParent)
frame:SetWidth(220)
frame:SetHeight(25)
frame.owner = self
@@ -1203,7 +1207,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"
@@ -1213,31 +1216,26 @@ 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(self)
self = self or this
if not self or not self.StartMoving then
frame:SetScript("OnDragStart", function()
if not this or not this.StartMoving then
return
end
MTH_CHRON_Trace("anchor drag-start point=" .. MTH_CHRON_DescribePoint(self))
self:StartMoving()
this:StartMoving()
end)
frame:SetScript("OnDragStop", function(self)
self = self or this
if not self then
frame:SetScript("OnDragStop", function()
if not this then
return
end
if self.StopMovingOrSizing then
self:StopMovingOrSizing()
if this.StopMovingOrSizing then
this:StopMovingOrSizing()
end
local point, _, _, x, y = self:GetPoint()
local point, _, _, x, y = this:GetPoint()
x = tonumber(x)
y = tonumber(y)
if not x or not y then
@@ -1246,16 +1244,14 @@ function Chronometer:CreateAnchor(text, r, g, b)
x = math.floor(x + 0.5)
y = math.floor(y + 0.5)
self:ClearAllPoints()
self:SetPoint(point or "TOPLEFT", UIParent, point or "TOPLEFT", x, y)
this:ClearAllPoints()
this:SetPoint(point or "TOPLEFT", UIParent, point or "TOPLEFT", x, y)
if self.owner and self.owner.profile and type(self.owner.profile.barposition) == "table" then
self.owner.profile.barposition.point = point or "TOPLEFT"
self.owner.profile.barposition.relativePoint = point or "TOPLEFT"
self.owner.profile.barposition.x = x
self.owner.profile.barposition.y = y
MTH_CHRON_Trace("anchor drag-stop saved profile=" .. MTH_CHRON_FormatBarPosition(self.owner.profile)
.. " frame=" .. MTH_CHRON_DescribePoint(self))
if this.owner and this.owner.profile and type(this.owner.profile.barposition) == "table" then
this.owner.profile.barposition.point = point or "TOPLEFT"
this.owner.profile.barposition.relativePoint = point or "TOPLEFT"
this.owner.profile.barposition.x = x
this.owner.profile.barposition.y = y
end
end)
@@ -1284,6 +1280,85 @@ function Chronometer:CreateAnchor(text, r, g, b)
return frame
end
-- ────────────────────────────────────────────────────────────
-- NamPower self-aura handlers
-- ────────────────────────────────────────────────────────────
-- Helper: resolve spellId → spell name, with cache
local function NP_ResolveName(cache, spellId)
if not spellId or spellId == 0 then return nil end
local name = cache[spellId]
if name then return name end
name = GetSpellRecField(spellId, "name")
if name and name ~= "" then
cache[spellId] = name
return name
end
return nil
end
-- BUFF / DEBUFF removed from the player → kill matching self-bar instantly
function Chronometer:BUFF_REMOVED_SELF()
if not self.profile.fadeonfade then return end
local spellId = arg3
local name = NP_ResolveName(self._nampowerSpellIdCache, spellId)
if not name then return end
self:KillBar(name)
end
Chronometer.DEBUFF_REMOVED_SELF = Chronometer.BUFF_REMOVED_SELF
-- BUFF / DEBUFF added to the player → resync running bar duration
function Chronometer:BUFF_ADDED_SELF()
local spellId = arg3
local name = NP_ResolveName(self._nampowerSpellIdCache, spellId)
if not name then return end
-- Find the matching self-bar
local bar = nil
for i = 1, 20 do
if self.bars[i].id and self.bars[i].name == name then
local tgt = self.bars[i].target
if tgt == "none" or tgt == UnitName("player") then
bar = self.bars[i]
break
end
end
end
if not bar then return end
-- Get authoritative remaining duration from NamPower aura array
local remaining = nil
local auras = GetUnitField("player", "aura")
if auras then
for slot = 1, 48 do
if auras[slot] == spellId then
local _, ms = GetPlayerAuraDuration(slot - 1)
if ms and ms > 0 then
remaining = ms / 1000
end
break
end
end
end
if not remaining then
-- Fallback: use base DBC duration
local dms = GetSpellDuration(spellId)
if dms and dms > 0 then
remaining = dms / 1000
end
end
if not remaining then return end
-- Resync: update total first (allows SetTimeLeft to extend), then set remaining
self:SetCandyBarTime(bar.id, remaining)
self:SetCandyBarTimeLeft(bar.id, remaining)
end
Chronometer.DEBUFF_ADDED_SELF = Chronometer.BUFF_ADDED_SELF
-- ────────────────────────────────────────────────────────────
function Chronometer:COMBAT_DEATH(event, info)
if not self.profile.fadeonkill then return end
if info.type == "experience" then
@@ -1301,9 +1376,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
@@ -1311,8 +1388,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
@@ -1340,7 +1429,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
@@ -1400,7 +1509,9 @@ function Chronometer:UNIT_AURA(unit)
end
local hasFeedAura = false
if type(UnitBuff) == "function" then
if type(MTH_FEED_HasPetFeedBuff) == "function" then
hasFeedAura = MTH_FEED_HasPetFeedBuff()
elseif type(UnitBuff) == "function" then
for i = 1, 32 do
local texture = UnitBuff("pet", i)
if not texture then
@@ -8,7 +8,6 @@ Documentation: http://web.archive.org/web/20070314234510/http://wowace.com/wiki/
Description: A timer bars library.
Dependencies: AceLibrary, AceOO-2.0, PaintChips-2.0
]]
local match = string.match
local getn,setn,tinsert = table.getn, table.setn,table.insert
local vmajor, vminor = "CandyBar-2.0", "$Revision: 16011 $"
-19
View File
@@ -1,28 +1,14 @@
local MTH_ChronometerModule = {
name = "chronometer",
enabled = true,
version = "1.1.0",
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 +23,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 +38,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>
+150 -273
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,15 @@ 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
-- [DISABLED] NO-BUFF quarantine: set to true to re-enable.
-- When false, failed feeds are still logged but items are never
-- blacklisted and the quarantine store is never consulted.
local FOM_QUARANTINE_ENABLED = false;
local function FOM_GetServerProfile()
return FOM_ServerProfiles[FOM_SERVER_PROFILE] or {};
@@ -260,6 +191,7 @@ local function FOM_GetFamilyKeyFromInfo(petInfo)
end
local function FOM_RegisterNoBuffQuarantine(itemID, petInfo)
if (not FOM_QUARANTINE_ENABLED) then return; end
local numericItem = tonumber(itemID);
if (numericItem == nil) then
return;
@@ -288,12 +220,10 @@ 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)
if (not FOM_QUARANTINE_ENABLED) then return false; end
local numericItem = tonumber(itemID);
local numericPet = tonumber(petLevel);
if (numericItem == nil or numericPet == nil or numericPet <= 0) then
@@ -346,29 +276,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 +361,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 +546,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 +591,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
@@ -780,7 +685,7 @@ local function FOM_DebugLogMerchantFoods(sourceEvent)
end
end
function FOM_FeedButton_OnClick()
function MTH_FOM_FeedButton_OnClick()
if (arg1 == "RightButton") then
if (MTH_OpenOptions) then
MTH_OpenOptions("FeedOMatic");
@@ -790,7 +695,7 @@ function FOM_FeedButton_OnClick()
end
end
function FOM_FeedButton_OnEnter()
function MTH_FOM_FeedButton_OnEnter()
if ( PetFrameHappiness.tooltip ) then
GameTooltip:SetOwner(PetFrameHappiness, "ANCHOR_RIGHT");
GameTooltip:SetText(PetFrameHappiness.tooltip);
@@ -807,7 +712,7 @@ function FOM_FeedButton_OnEnter()
end
end
function FOM_FeedButton_OnLeave()
function MTH_FOM_FeedButton_OnLeave()
GameTooltip:Hide();
end
@@ -824,10 +729,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
@@ -985,13 +886,13 @@ function FOM_OnEvent(event, arg1)
this:RegisterEvent("PLAYER_REGEN_ENABLED");
end
if (FOM_FeedButton == nil) then
FOM_FeedButton = CreateFrame("Button", "FOM_FeedButton", PetFrameHappiness);
FOM_FeedButton:SetAllPoints(PetFrameHappiness);
FOM_FeedButton:RegisterForClicks("LeftButtonUp", "RightButtonUp");
FOM_FeedButton:SetScript("OnClick", FOM_FeedButton_OnClick);
FOM_FeedButton:SetScript("OnEnter", FOM_FeedButton_OnEnter);
FOM_FeedButton:SetScript("OnLeave", FOM_FeedButton_OnLeave);
if (MTH_FOM_FeedButton == nil) then
MTH_FOM_FeedButton = CreateFrame("Button", "MTH_FOM_FeedButton", PetFrameHappiness);
MTH_FOM_FeedButton:SetAllPoints(PetFrameHappiness);
MTH_FOM_FeedButton:RegisterForClicks("LeftButtonUp", "RightButtonUp");
MTH_FOM_FeedButton:SetScript("OnClick", MTH_FOM_FeedButton_OnClick);
MTH_FOM_FeedButton:SetScript("OnEnter", MTH_FOM_FeedButton_OnEnter);
MTH_FOM_FeedButton:SetScript("OnLeave", MTH_FOM_FeedButton_OnLeave);
end
if (FOM_Config.Level == "happy") then
@@ -1032,7 +933,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 +973,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 +982,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 +1001,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 +1009,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));
@@ -1236,16 +1126,27 @@ function FOM_DifficultyToNum(level)
end
end
local FOM_CachedPlayerClass = nil
local FOM_OnUpdateElapsed = 0
function FOM_OnUpdate(elapsed)
_, realClass = UnitClass("player");
if (realClass ~= "HUNTER") then return; end
FOM_OnUpdateElapsed = FOM_OnUpdateElapsed + (elapsed or 0)
if FOM_OnUpdateElapsed < 0.05 then
return
end
local dt = FOM_OnUpdateElapsed
FOM_OnUpdateElapsed = 0
if not FOM_CachedPlayerClass then
_, FOM_CachedPlayerClass = UnitClass("player")
end
if (FOM_CachedPlayerClass ~= "HUNTER") then return; end
-- If it's been more than a second since our last tradeskill update,
-- we can allow the event to process again.
FOM_TradeSkillLock.EventTimer = FOM_TradeSkillLock.EventTimer + elapsed;
FOM_TradeSkillLock.EventTimer = FOM_TradeSkillLock.EventTimer + dt;
if (FOM_TradeSkillLock.Locked) then
FOM_TradeSkillLock.EventCooldown = FOM_TradeSkillLock.EventCooldown + elapsed;
FOM_TradeSkillLock.EventCooldown = FOM_TradeSkillLock.EventCooldown + dt;
if (FOM_TradeSkillLock.EventCooldown > FOM_TradeSkillLock.EventCooldownTime) then
FOM_TradeSkillLock.EventCooldown = 0;
@@ -1267,10 +1168,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
@@ -1286,10 +1183,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
@@ -1299,16 +1194,12 @@ function FOM_OnUpdate(elapsed)
FOM_CoreFinalizeAttempt(FOM_LastFeedAttempt.coreAttemptId, "rejected", "no-buff");
local currentPetInfo = MTH_FOM_GetCorePetInfo();
FOM_RegisterNoBuffQuarantine(FOM_LastFeedAttempt.itemId, currentPetInfo);
if (type(MTH_FEED_BlockItemForPetLevel) == "function") then
if (FOM_QUARANTINE_ENABLED and type(MTH_FEED_BlockItemForPetLevel) == "function") then
pcall(MTH_FEED_BlockItemForPetLevel,
FOM_LastFeedAttempt.itemId,
tonumber(currentPetInfo and currentPetInfo.level) or nil,
"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;
@@ -1335,8 +1226,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,
@@ -1363,35 +1252,72 @@ 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
-- NamPower path: scan aura array by spellId → name, no tooltip needed
if MTH and MTH.nampower and type(GetUnitField) == "function" and type(GetSpellRecField) == "function" then
local auras = GetUnitField("player", "aura")
if type(auras) == "table" then
local dontFeedNames = {
["Shadowmeld"] = true,
["Feign Death"] = true,
["Food"] = true,
["Drink"] = true,
["Refreshment"] = true,
}
for i = 1, 32 do
local sid = tonumber(auras[i])
if sid and sid > 0 then
local name = GetSpellRecField(sid, "name")
if name then
if dontFeedNames[name] then
FOM_DebugLog("Can't feed; buff detected: " .. name)
return false
end
if UnitLevel("player") >= 40 then
local lname = string.lower(name)
for _, mountSub in FOM_MOUNT_NAME_SUBSTRINGS do
if string.find(lname, mountSub) then
FOM_DebugLog("Can't feed; mounted (" .. name .. ").")
return false
end
end
end
end
end
end
end
return true
end
-- Legacy path: texture + tooltip scanning
local buff, buffIndex;
local dontFeedBuffTextures = {
"Interface\\Icons\\Ability_Ambush", -- NE Shadowmeld (maybe not unique buff icon?)
@@ -1408,20 +1334,20 @@ 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
if ( UnitLevel("player") >= 40 ) then
for _, buffTexture in mountTextureSubStrings do
if ( string.find(buff, buffTexture) ) then
FOMTooltip:SetUnitBuff("player", buffIndex+1);
local msg = FOMTooltipTextLeft1:GetText();
MTH_FOM_Tooltip:SetUnitBuff("player", buffIndex+1);
local msg = MTH_FOM_TooltipTextLeft1:GetText();
if (msg ~= nil) then
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
@@ -1941,6 +1867,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
@@ -1973,13 +1900,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
@@ -2000,6 +1927,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
@@ -2007,15 +1940,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
@@ -2046,6 +1979,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)
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;
@@ -2151,6 +2120,9 @@ end
-- Check Feed Effect
function FOM_HasFeedEffect()
if type(MTH_FEED_HasPetFeedBuff) == "function" then
return MTH_FEED_HasPetFeedBuff()
end
local i = 1;
local buff;
@@ -2275,7 +2247,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);
@@ -2284,7 +2255,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
@@ -2310,19 +2280,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
@@ -2346,24 +2306,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"
@@ -2371,8 +2325,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();
@@ -2382,32 +2334,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
@@ -2422,12 +2365,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)
@@ -2446,26 +2387,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);
@@ -2473,26 +2403,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
@@ -2510,10 +2433,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
@@ -2567,9 +2486,9 @@ function FOM_IsTemporaryFood(itemLink)
end
return false;
end
FOMTooltip:ClearLines();
FOMTooltip:SetHyperlink(link);
if (FOMTooltipTextLeft2:GetText() == ITEM_CONJURED) then
MTH_FOM_Tooltip:ClearLines();
MTH_FOM_Tooltip:SetHyperlink(link);
if (MTH_FOM_TooltipTextLeft2:GetText() == ITEM_CONJURED) then
if (numericItemId ~= nil) then
FOM_TEMP_FOOD_CACHE[numericItemId] = true;
end
@@ -2598,7 +2517,6 @@ local function FOM_EnsureQuestScanFresh(maxAgeSeconds)
end
function FOM_FlatFoodList()
local flatStartMs = FOM_PerfNowMs();
local foodList = {};
local overflowFoodList = {};
local scanned = 0;
@@ -2688,27 +2606,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);
@@ -2719,12 +2620,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();
@@ -2811,18 +2706,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
@@ -2836,15 +2719,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
@@ -2878,12 +2755,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
@@ -2895,19 +2772,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
@@ -2945,7 +2822,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;
+1 -127
View File
@@ -4,7 +4,7 @@
------------------------------------------------------
-- Key bindings
BINDING_HEADER_FEEDOMATIC = "Fizzwidget Feed-O-Matic";
-- NOTE: BINDING_HEADER_FEEDOMATIC is defined in api/localization.lua via the MTH_L() system.
BINDING_NAME_FEEDOMATIC = "Feed Pet";
-- Diet names. These should be the all-lowercase versions of the six diets returned from GetPetFoodTypes(). (Want to get them all nice and quick for your localization? Go tame a bear... they eat anything.)
@@ -111,110 +111,6 @@ FOM_OptionsButtonText = {
};
------------------------------------------------------
if (GetLocale() == "frFR") then
-- Diet names. These should be the all-lowercase versions of the six diets returned from GetPetFoodTypes(). (Want to get them all nice and quick for your localization? Go tame a bear... they eat anything.)
-- THESE STRINGS MUST BE LOCALIZED for Feed-O-Matic to work properly in other locales.
FOM_DIET_MEAT = "viande";
FOM_DIET_FISH = "poisson";
FOM_DIET_BREAD = "pain";
FOM_DIET_CHEESE = "fromage";
FOM_DIET_FRUIT = "fruit";
FOM_DIET_FUNGUS = "champignon";
-- If the player has a buff whose icon name contains "Ability_Mount" and whose name contains one of these substrings, we assume the player is mounted. We have to use this rather oblique way to check for mounted because some other buffs use the Ability_Mount_* icons. If these aren't localized, we may not recognize when the player is mounted and still try to feed the pet.
FOM_MOUNT_NAME_SUBSTRINGS = { "kodo", "loup", "raptor", "steed", "cheval", "bélier", "tigre", "mécanotrotteur", };
-- It's not clear whether the 5th and 6th returns from GetItemInfo() are supposed to be localized... so we have localized keys for them just in case. If these aren't localized, we'll just needlessly search your ammo bags for food.
FOM_AMMO_POUCH = "Giberne";
FOM_QUIVER = "Carquois";
-- From here on down, the localized strings are just for readability... they don't affect whether Feed-O-Matic works.
-- Two special diets; one is used for tracking food that provides a bonus (or at least, that you don't want to have fed to your pet when the 'savebonus' option is on), and another that's used in the chat-line interface for listing all diets.
FOM_DIET_BONUS = "bonus";
FOM_DIET_ALL = "tous";
-- Used in tooltips to indicate food quality.
FOM_QUALITY_UNKNOWN = "%s peut le manger, mais n'a jamais essayé.";
FOM_QUALITY_UNDER = "%s n'en mange plus désormais.";
FOM_QUALITY_MIGHT = "%s en mangera peut-être.";
FOM_QUALITY_WILL = "%s en mangera.";
FOM_QUALITY_LIKE = "%s aime en manger.";
FOM_QUALITY_LOVE = "%s adore en manger.";
-- User-visible errors
FOM_ERROR_NO_PET = "Vous n'avez pas de familier.";
FOM_ERROR_PET_DEAD = "Votre familier est mort.";
FOM_ERROR_NO_FEEDABLE_PET = "Vous n'avez pas de familier capable de manger.";
FOM_ERROR_FOOD_NOT_FOUND = "%s n'a pas trouvé de %s dans votre sac.";
FOM_ERROR_NO_FOOD = "%s n'a pas trouvé de nourriture dans votre sac.";
-- Feeding status messages
FOM_FEEDING_EAT_ANOTHER = "%s n'aime pas cette nourriture et va essayer autre chose..."
FOM_FEEDING_FEED_ANOTHER = "cherche autre chose à donner à %s...";
FOM_FEEDING_EAT = "%s mange un(e) %s.";
FOM_FEEDING_FEED = "donne à %s à manger un(e) %s. ";
-- Beast family names
BAT = "Chauve-souris";
BEAR = "Ours";
BOAR = "Sanglier";
CARRION_BIRD = "Charognard";
CAT = "Félin";
CRAB = "Crabe";
CROCOLISK = "Crocilisque";
GORILLA = "Gorille";
HYENA = "Hyène";
OWL = "Chouette";
RAPTOR = "Raptor";
SCORPID = "Scorpide";
SPIDER = "Araignée";
TALLSTRIDER = "Haut-trotteur";
TURTLE = "Tortue";
WIND_SERPENT = "Serpent des vents";
WOLF = "Loup";
FOM_PET_HUNGRY = "%s a faim!";
FOM_PET_VERY_HUNGRY = "%s a très faim!";
FOM_OPTIONS_HEADER = "Feed-O-Matic Options";
FOM_OPTIONS_GENERAL = "Options générales:";
FOM_OPTIONS_FOOD_CHOICE = "Eviter la nourriture utilisée en cuisine:";
FOM_OPTIONS_FEED_NOTIFY = "Prévenir lorsque votre familier est nourrit:";
FOM_OPTIONS_WARNING = "Avertir quand votre familier a besoin d'être nourrit:";
FOM_KEEPOPEN = "Essayer de garder au moins";
FOM_KEEPOPEN_SLOTS = "emplacements de sac libres";
FOM_KEEPOPEN_INFO = "(Le familier choisira la nourriture des piles les plus petites, sans tenir compte de ses préférences.)";
FOM_OptionsButtonText = {
["Tooltip"] = "Afficher l'avis de votre familier concernant la nourriture",
["AvoidQuestFood"] = "Eviter la nourriture utile dans les quêtes",
["AvoidBonusFood"] = "Eviter la nourriture donnant un bonus",
["PreferHigherQuality"] = "Privilégier la nourriture que votre familier préfère",
["Fallback"] = "Utiliser la nourriture évitée s'il n'y en a plus d'autres",
["SaveForCook_All"] = "Toutes nourritures utilisées pour la cuisine",
["SaveForCook_Green"] = "si elle est utilisée dans les recettes |cff3fbf3f\"faciles\"|r ou supérieures",
["SaveForCook_Yellow"] = "si elle est utilisée dans les recettes |cffffff00\"moyennes\"|r ou supérieures",
["SaveForCook_Orange"] = "si elle est utilisée dans les recettes |cffff7f3f\"difficiles\"|r",
["SaveForCook_None"] = "Ne pas éviter la nourriture utilisée en cuisine",
["IconWarning"] = "Clignoter l'icône",
["TextWarning"] = "Afficher un texte",
["AudioWarning"] = "Jouer un son",
["AudioWarningBell"] = "Un son de cloche au lieu du cri du familier",
["AlertEmote"] = "Via emote (visible par les autres)",
["AlertChat"] = "Dans la fenêtre de discussion (visible uniquement par vous)",
["AlertNone"] = "Ne pas prévenir",
["LevelContent"] = "quand \"content\"",
["LevelUnhappy"] = "quand \"mécontent\"",
["LevelOff"] = "Ne pas avertir",
};
end
------------------------------------------------------
if (GetLocale() == "deDE") then
-- Diet names. These should be the all-lowercase versions of the six diets returned from GetPetFoodTypes(). (Want to get them all nice and quick for your localization? Go tame a bear... they eat anything.)
@@ -279,26 +175,4 @@ if (GetLocale() == "deDE") then
WIND_SERPENT = "Windnatter";
-- WOLF = "Wolf"; -- same as enUS
end
------------------------------------------------------
if (GetLocale() == "koKR") then
-- Diet names. These should be the all-lowercase versions of the six diets returned from GetPetFoodTypes(). (Want to get them all nice and quick for your localization? Go tame a bear... they eat anything.)
-- THESE STRINGS MUST BE LOCALIZED for Feed-O-Matic to work properly in other locales.
-- FOM_DIET_MEAT = "meat";
-- FOM_DIET_FISH = "fish";
-- FOM_DIET_BREAD = "bread";
-- FOM_DIET_CHEESE = "cheese";
-- FOM_DIET_FRUIT = "fruit";
-- FOM_DIET_FUNGUS = "fungus";
-- If the player has a buff whose icon name contains "Ability_Mount" and whose name contains one of these substrings, we assume the player is mounted. We have to use this rather oblique way to check for mounted because some other buffs use the Ability_Mount_* icons.
-- FOM_MOUNT_NAME_SUBSTRINGS = { "kodo", "wolf", "raptor", "steed", "horse", "ram", "saber", "mechanostrider" };
-- It's not clear whether the 5th and 6th returns from GetItemInfo() are supposed to be localized... so we have localized keys for them just in case.
-- FOM_AMMO_POUCH = "Ammo Pouch";
FOM_QUIVER = "화살통";
end
+17 -28
View File
@@ -6,7 +6,6 @@
local MTH_FeedOMatic = {
name = "feedomatic",
enabled = false,
version = "1.1.0",
events = {
"VARIABLES_LOADED",
"MERCHANT_SHOW",
@@ -32,7 +31,7 @@ local MTH_FeedOMatic = {
}
local MTH_PETL_HOOK_BOUNDARY_KEY = "pet-lifecycle-hooks"
local MTH_FeedOMatic_MerchantProbeFrame = nil
local MTH_FOM_MerchantProbe = nil
function MTH_FeedOMatic_OnUpdate(elapsed)
if not elapsed then
@@ -47,7 +46,7 @@ function MTH_FeedOMatic_OnUpdate(elapsed)
end
local function MTH_FeedOMatic_GetRuntimeFrame()
return getglobal("Frame_GFW_FeedOMatic")
return getglobal("MTH_FOM_OnUpdateFrame")
end
local function MTH_FeedOMatic_CaptureHookBoundary()
@@ -112,10 +111,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 +161,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 +192,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,34 +201,31 @@ 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
local function MTH_FeedOMatic_SetMerchantProbeEnabled(enabled)
if not enabled then
if MTH_FeedOMatic_MerchantProbeFrame then
MTH_FeedOMatic_MerchantProbeFrame:UnregisterEvent("MERCHANT_SHOW")
MTH_FeedOMatic_MerchantProbeFrame:UnregisterEvent("MERCHANT_UPDATE")
if MTH_FOM_MerchantProbe then
MTH_FOM_MerchantProbe:UnregisterEvent("MERCHANT_SHOW")
MTH_FOM_MerchantProbe:UnregisterEvent("MERCHANT_UPDATE")
end
return
end
if not MTH_FeedOMatic_MerchantProbeFrame then
MTH_FeedOMatic_MerchantProbeFrame = CreateFrame("Frame", "MTH_FeedOMatic_MerchantProbeFrame")
MTH_FeedOMatic_MerchantProbeFrame:SetScript("OnEvent", function()
if not MTH_FOM_MerchantProbe then
MTH_FOM_MerchantProbe = CreateFrame("Frame", "MTH_FOM_MerchantProbe")
MTH_FOM_MerchantProbe:SetScript("OnEvent", function()
local evt = event
if evt == "MERCHANT_SHOW" or evt == "MERCHANT_UPDATE" then
MTH_FeedOMatic_Log("probe event received: " .. tostring(evt), "debug")
MTH_FeedOMatic_DebugMerchantScan(evt)
end
end)
end
MTH_FeedOMatic_MerchantProbeFrame:RegisterEvent("MERCHANT_SHOW")
MTH_FeedOMatic_MerchantProbeFrame:RegisterEvent("MERCHANT_UPDATE")
MTH_FeedOMatic_Log("merchant probe enabled", "debug")
MTH_FOM_MerchantProbe:RegisterEvent("MERCHANT_SHOW")
MTH_FOM_MerchantProbe:RegisterEvent("MERCHANT_UPDATE")
end
function MTH_FeedOMatic:init()
@@ -277,8 +266,8 @@ function MTH_FeedOMatic:setEnabled(enabled)
MTH_FeedOMatic_SyncSavedVariables()
MTH_FeedOMatic_EnsureVariablesLoaded(self)
MTH_FeedOMatic_SetMerchantProbeEnabled(false)
if FOM_FeedButton then
FOM_FeedButton:Show()
if MTH_FOM_FeedButton then
MTH_FOM_FeedButton:Show()
end
else
-- Disable FeedOMatic functionality
@@ -287,8 +276,8 @@ function MTH_FeedOMatic:setEnabled(enabled)
FOM_State.ShouldFeed = false
end
MTH_FeedOMatic_SetMerchantProbeEnabled(false)
if FOM_FeedButton then
FOM_FeedButton:Hide()
if MTH_FOM_FeedButton then
MTH_FOM_FeedButton:Hide()
end
end
end
@@ -316,8 +305,8 @@ function MTH_FeedOMatic:cleanup()
FOM_State.ShouldFeed = false
end
MTH_FeedOMatic_SetMerchantProbeEnabled(false)
if FOM_FeedButton then
FOM_FeedButton:Hide();
if MTH_FOM_FeedButton then
MTH_FOM_FeedButton:Hide();
end
end
+2 -2
View File
@@ -3,7 +3,7 @@ FeedOMatic Runtime Frame Loader (MetaHunt)
Minimal runtime XML without legacy options window definitions.
-->
<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">
<Frame name="Frame_GFW_FeedOMatic">
<Frame name="MTH_FOM_OnUpdateFrame">
<Scripts>
<OnUpdate>
if (MTH_FOM_OnUpdate) then
@@ -15,7 +15,7 @@ Minimal runtime XML without legacy options window definitions.
</Scripts>
</Frame>
<GameTooltip name="FOMTooltip" frameStrata="TOOLTIP" hidden="true" parent="UIParent" inherits="GameTooltipTemplate">
<GameTooltip name="MTH_FOM_Tooltip" frameStrata="TOOLTIP" hidden="true" parent="UIParent" inherits="GameTooltipTemplate">
<Scripts>
<OnLoad>
this:SetOwner(UIParent, "ANCHOR_NONE");
+1127 -1127
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -6,7 +6,6 @@
local MTH_ICU = {
name = "icu",
enabled = false,
version = "1.1.0",
events = {
"VARIABLES_LOADED",
},
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -7,10 +7,9 @@ MTH_SA_MANAGED_HOOKS = true
local MTH_SmartAmmo = {
name = "smartammo",
enabled = true,
version = "1.1.0",
events = {
"VARIABLES_LOADED",
"PLAYER_ENTERING_WORLD",
-- PLAYER_ENTERING_WORLD handled by engine's own frame
},
}
File diff suppressed because it is too large Load Diff
+6
View File
@@ -0,0 +1,6 @@
<!--
MetaHunt: SmartPet Module Loader
-->
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<Include file="engine.lua"/>
</Ui>

Some files were not shown because too many files have changed in this diff Show More