18 Commits

Author SHA1 Message Date
Duvel c81ce85950 Merge pull request #13 from DuvelCorp/V1.4.1
v1.4.1 event leak fixes, chronometer trap fix, profile memory, autost…
2026-03-24 14:24:08 +01:00
DuvelCorp 680ddcba8a v1.4.1 event leak fixes, chronometer trap fix, profile memory, autostrip alloc fix 2026-03-24 14:21:58 +01:00
Duvel 5c08656532 Merge pull request #12 from DuvelCorp/V1.4.1
V1.4.1 performance fixes
2026-03-24 14:15:25 +01:00
DuvelCorp 8d80d0663c V1.4.1 performance fixes 2026-03-24 14:14:33 +01:00
Duvel 43e23e7616 Merge pull request #10 from DuvelCorp/v1.4.0
v1.4.0 Twow 1.18.1
2026-03-23 22:28:09 +01:00
DuvelCorp 8479607a13 v1.4.0 Twow 1.18.1 2026-03-23 22:25:10 +01:00
Duvel f7b0318179 Add model viewer feature to README
Added model viewer feature for all beasts in the game.
2026-03-19 16:36:01 +01:00
Duvel 23bb69d094 Merge pull request #7 from DuvelCorp/v1.3.0
V1.3.0 - Last before Twow upgrade
2026-03-19 16:32:36 +01:00
DuvelCorp b2060e0aab V1.3.0 2026-03-19 16:27:21 +01:00
Duvel 609a6bdd83 Merge pull request #6 from DuvelCorp/v1.2.0
v1.2.0
2026-03-11 00:41:30 +01:00
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
Duvel d41703949d Merge pull request #4 from DuvelCorp/1.1.0
v1.1.0
2026-03-09 13:42:21 +01:00
DuvelCorp 27d37564ce v1.1.0 2026-03-09 13:41:30 +01:00
Duvel 4aed1174dd Merge pull request #3 from DuvelCorp/v1.0.6
v1.0.6
2026-03-05 12:31:27 +01:00
DuvelCorp 233899b2a0 v1.0.6 2026-03-05 12:30:24 +01:00
Duvel 89fd8e079c Merge pull request #2 from DuvelCorp/v1.0.5
## [1.0.5] - 2026-03-01

### Fixed
- Multiple changes to the addon to reduce its memory and CPU time footprint to the strict minimum possible.
- Restored FeedOMatic pet hunger notifications so hungry/very hungry warnings are emitted reliably again when enabled.
- Fixed Chronometer `Wing Clip` tracking by adding a safe fallback timer path for the missing trigger case.
- Fixed Chronometer `Quick Shots` bar icon by mapping the event to the correct icon texture.
- Fixed module enabled/disabled persistence to be truly per-character (`MTH_CharSavedVariables.moduleStates`) instead of account-shared.
- Fixed AutoBuy persistence fallback by removing undeclared `MTH_AutoBuy_Saved` paths and using module stores/transient runtime fallback only.
- Consolidated FeedOMatic legacy persistence to a single module-backed store (`modules.feedomatic.legacy`) with globals bound to that source.
- Switched Chronometer profile persistence to per-character module storage with one-time migration from existing account profile data.
- Switched Tooltips module options persistence to per-character module storage with one-time migration from existing account settings.
2026-03-01 09:12:06 +01:00
DuvelCorp 1a28d95b87 v1.0.5 2026-03-01 09:10:22 +01:00
118 changed files with 90250 additions and 64992 deletions
+4 -1
View File
@@ -9,4 +9,7 @@
tools/
LOC*.md
LOCALIZATION*.md
CHANGELOG.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>
+208
View File
@@ -0,0 +1,208 @@
# Changelog
All notable changes to MetaHunt will be documented in this file.
## [1.4.1] - 2026-03-24
### Added
- **zTrack**: added `Find Trees` in tracking spells.
### Changed
- **Performance — MM Widget / ExpAmmo**: Replaced tooltip-based aura scanning (which triggered hidden tooltip rendering on every aura check) with direct texture/icon comparison via `UnitBuff`/`UnitDebuff`. Added a 150 ms throttle on `PLAYER_AURAS_CHANGED`, which fires 1050+ times per second in combat. Lock and Load detection and ammo cycle recovery after `/reload` both updated to use the new zero-cost texture path.
- **Performance — event frame leaks**: Some addon features were registering WoW game events at load time and never unregistering them when the feature was disabled from the options, causing their `OnEvent` handlers to fire even with the option turned off. Fixed for: Auto-Strip (`PLAYER_REGEN_*`), Smart Ammo (`SPELLCAST_*`, `START/STOP_AUTOREPEAT_SPELL`), and some zButton frames (Pet, Mounts, Track, Trap, Toys). Events are now properly registered on enable and unregistered on disable.
- **Chronometer — trap effects from other hunters**: Trap effect bars (`Immolation Trap Effect`, `Explosive Trap Effect`, `Freezing Trap Effect`, `Frost Trap Aura`) were incorrectly appearing when another hunter's trap triggered on a mob you were also targeting. .
- **Version check broadcast interval**: Increased update notification broadcast cooldown from 10 minutes to 1 hour to reduce channel traffic now that the user base has grown.
- **Profile snapshot memory**: The auto `BuildSnapshot` (called on every `/reload` via `PLAYER_LOGOUT`) was deep-copying `feedomatic.legacy`, a large migration table mirroring `FOM_Cooking`, `FOM_QuestFood`, `FOM_AddedFoods` etc. These are runtime/operational blobs, not user configuration settings, and should never be included in a profile. Introduced `MTH_Profile_CopyModules` which skips `legacy` and `history` keys, reducing the snapshot allocation from ~1 MB to a few KB.
## [1.4.0] - 2026-03-23
### TWoW 1.18.1 "Nightmares of Ursol" support
### Added
- **Complete beast database overhaul**: The MTH beast datastore was reconstructed from scratch, based on a full (beasts) 1.18.1 extract from live DB, gladly provided by Twow staff (thanks @Haaxor!). The beast data should now be 100% accurate, and notably all spawn coordinates, respawn times and pet abilities learnable on all beasts in the world.
- The **Beast Lore** page of the Book now allows to filter the list by Zone. Additionally, the beast inspector has been reformatted and all meaningful beast's data is displayed nicely.
- All new and missing **Beasts Models** have been (re)scanned from MPQ game files and are viewable in the Beast Lore page, and browsable in the Family Model viewer.
- Added **MM widget** module: tracks the new 3-state of Experimental ammo cycle (Fire → Nature → Arcane), the Lock and Load procs, and has a dynamic cell that shows either Aimed Shot availability or the right shot to use following the current Experimental ammo proc if any. Lets Make MM great again.
- Added new **Moth** pet family.
- Added new **Pollen Burst** pet ability.
- Added new **Aspect of the Viper** in zAspect.
- Added new **Chronometer** timer entries for 1.18.1 spells:
- Aimed Shot cooldown tracker
- Kill Command cooldown tracker
- Lock and Load proc timer
- Scent of Blood proc timer
- Added new **Chronometer** target debuff entries for the Experimental Ammo states:
- Poisonous Ammunition: 15s armor-reduction debuff (triggered when Serpent Sting consumes the Poisonous state)
- Enchanted Ammunition: 6s magic-resist debuff (triggered when Arcane Shot consumes the Enchanted state)
- Added the **5 new craftable ammo** to the Projectiles book's page, SmartAmmo and zAmmo button:
- **Bright Wood Arrows** (Survival: Journeyman, req. 30) — 10.5 DPS
- **Shade Wood Arrows** (Survival: Expert, req. 40) — 14.5 DPS
- **Smooth Ironfeather Arrows** (Survival: Artisan, req. 50) — 17.5 DPS
- **Starfeather Arrows** (Survival: Artisan, req. 55) — 19 DPS
- **Enchanted Thorium Shells** (Engineering: Artisan, req. 55) — 19 DPS
- Added new hunter-friends NPC of **Moonwhisper Coast** to the NPC Finder Book's page:
- **Hula Swiftmane** — Horde Stable Master (66.1, 39.1)
- **Ornala** — Horde Ammo Vendor (67.3, 37.1) — sells Arrows and Slugs
- **Badel Wildlance** — Alliance Stable Master (59.0, 24.9)
- **Elendon Truebough** — Alliance Ammo Vendor (58.8, 24.5) — sells Arrows
### Fixed
- Fixed **Chronometer** bars appearing from nearby players' combat events (e.g. another hunter's traps or DoTs triggering your own bars).
- Fixed the list in Families Book's page, that wasn't tall enough to display all families (wolves were hidden).
## [1.3.0] - 2026-03-19
### Added
- Added a **3D Model Viewer** to the Hunter Book Beast Lore inspector panel. Clicking a beast in the list shows its in-game model.
- Added a standalone **3D Model Viewer** that is shown when cliking the model of a beast in the Beast lore. The model is rotatable with the mouse, and you can browse all models of the same pet family and shows all other beasts that share the same skin, with one-click to jump to any of them in the Beast Lore.
- Added a **Family icon** column to the Beast Lore list, replacing the text family name with the pet family icon for a more compact view.
- Added an **Attack Speed (AS)** column to the Beast Lore list.
- Added `displayId` and `skinId` fields to **all ~1000 beasts** in the MetaHunt datastore. `displayId` was scraped from the Turtle WoW DB, and `skinId` derived from it following an extraction on MPQ game files. This is what powers the model viewer and skin-grouping.
- Added per-family icons to all 19 pet families in the family data table.
- Added missing food types (`raw meat`, `raw fish`) to all pet families that accept `meat` or `fish` respectively (sourced from MPQ data).
- Added **Profile Management** panel (Options → Profiles): save the current addon configuration as a named profile and instantly load it on any character. Profiles cover all module settings (ICU, Chronometer, AutoBuy, AutoQuest, SmartAmmo, Feed-O-Matic, zButtons layout) and per-character module enabled/disabled states.
- Added automatic per-character **config snapshots** saved on every logout (`CharName-RealmName`), visible in the "Copy from Character" section — import any character's snapshot as a loadable profile in one click.
### Fixed
- Fixed the pet feeding counter never incrementing: `MTH_FEED_Trace` was a no-op stub and `FOM_CORE_ATTEMPT_TRACKING_ENABLED` was `false`; both are now active.
- Fixed `FOM_AddFood` not clearing a food from `FOM_RemovedFoods` when the item belongs to the base diet list, which permanently banned it even after an explicit `/fom add`.
- Wired `FOM_ClearItemBans` into the `/fom add` command path: explicitly re-adding a food now clears it from `FOM_RemovedFoods`, FOM quarantine, and core exceptions in one step.
- Removed debug messages forgotten when releasing 1.2.
- Fixed SmartAmmo + **Quiver castbar conflict**: Quiver's castbar was disappearing when both addons were active. Root cause: MetaHunt SmartAmmo was re-installing its hooks every 0.5 seconds, constantly bumping Quiver off the top of the hook chain. Fixed by removing the repeated re-hook and adding a guard that only installs once (or when no other addon is on top).
## [1.2.0] - 2026-03-10
### Added
- 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
- Added new `ICU` module with dedicated options panel.
- Added ICU custom popup anchor mode with draggable anchor and direction toggle.
- Added ICU popup hide delay options (`INSTANT`, 1..10 seconds).
- Added new `Auto Quest` module for :
- `Salt of the Scorpok` (`Bloodmage Drazial`)
- `Arrows Are For Sissies` (`Artilleryman Sheldonore`)
- Added a Food Feed learning feature to Feed-O-Matic.
- Added Chronometer `Entrapment` effect tracking support.
### Changed
- Loads of Frame/Handlers/Processes adjustments to optimize addon's CPU time and Memory usage.
- Updated Chronometer Improved Wing Clip effect color to `MAROON` for clearer effect-bar distinction.
- Reworked Stable Master processing to minimize swap-time overhead:
- No auto-scan on `PET_STABLE_SHOW`.
- Deferred stable scan moved to `PET_STABLE_CLOSED`.
- Optional heavy workloads are skipped while stable UI is open.
- Changed Hunter Book page order, and default opening page to `Beast Lore`.
- Optimized `Pet History` result build/sort path using cached precomputed sort keys.
- Modules Auto-Buy, Auto-Quest, ICU, Feed-O-Matic are by default disabled on first use on a character.
### Fixed
- Fixed issues with zBouttons,Autostrp and Chronometer bar anchor not retaining their configured spawn position after the disabling/renabling of the Addon.
- Fixed issues with options checkbox-state not always retaining their state on startup.
- Fixed multiple issues in Feeed-O-Matic with some foods that were accepted by lvl 60 pets in Vanilla and are not anymore on Turtle.
- Fixed Chronometer spell/event disable toggles in options so disabled entries now reliably block bar creation.
- Fixed Chronometer `Feed Pet` bar missing trigger by adding `UNIT_AURA` fallback detection.
- Fixed stable swap stutter/freezes by removing heavy processing from repeated `UNIT_PET` bursts during stable interactions.
## [1.0.6] - 2026-03-05
### Fixed
- Added missing abilities to all pet families.
- Rescraped from Twow DB the previously in correct `Roar of Fortitude` pet-ability.
- Fixed an issue with Tooltip on pet action bar abilities, that were falsely displayed as not learned yet for rankless abilities.
- Restored visible Stable Master auto-scan feedback (`Stable scan complete: X slot(s).`) when opening the stable window.
- Fixed stable scan icon persistence for active pets that were never stabled by capturing current-pet icon data during stable scans.
- Hardened stable-scan bootstrap to initialize on both `PET_STABLE_SHOW` and `PET_STABLE_UPDATE` event paths.
- Fixed issues with taming pet metadata that were not properly recorded since the last update.
- Fixed issues with pet runaway interception that was broken since the last update.
- Updated version-update notification text to include clearer upgrade guidance and plain GitHub URL text for updates.
- Fixed an issue with `zTrack` buttons spells being incorrect after having respec the talents using brainwashing device.
## [1.0.5] - 2026-03-01
### Fixed
- Multiple changes to the addon to reduce its memory and CPU time footprint to the strict minimum possible.
- Restored FeedOMatic pet hunger notifications so hungry/very hungry warnings are emitted reliably again when enabled.
- Fixed Chronometer `Wing Clip` tracking by adding a safe fallback timer path for the missing trigger case.
- Fixed Chronometer `Quick Shots` bar icon by mapping the event to the correct icon texture.
- Fixed module enabled/disabled persistence to be truly per-character (`MTH_CharSavedVariables.moduleStates`) instead of account-shared.
- Fixed AutoBuy persistence fallback by removing undeclared `MTH_AutoBuy_Saved` paths and using module stores/transient runtime fallback only.
- Consolidated FeedOMatic legacy persistence to a single module-backed store (`modules.feedomatic.legacy`) with globals bound to that source.
- Switched Chronometer profile persistence to per-character module storage with one-time migration from existing account profile data.
- Switched Tooltips module options persistence to per-character module storage with one-time migration from existing account settings.
## [1.0.4] - 2026-02-27
### Added
- Added new zButton bar `zRanged` that tracks all ranged weapons in bags and allows to quickly swap them.
- Added structured localization architecture with dedicated locale packs under `locales/` and loader wiring through `init/localization.xml`.
- Added a dedicated `Messages` options panel with per-message toggles.
- Added Smart Ammo option `Enable Weapon-Swap Auto Ammo` to instantly equip best bullets/arrows when switching ranged weapon type.
- Added two new Hunter Book pages in the main tab bar: `Projectiles` and `Ammo Bags`
### Changed
- Reworked core localization runtime to support explicit locale fallback rules (`enUS`, `deDE`, `esES`, `ptBR`, `ruRU`, `zhCN`; no `frFR` target).
- Updated Hunter Book NPC finder and map vendor markers to use localized NPC names by NPC ID.
- Updated Hunter Book, item search/sort, AutoBuy item labels, and drop map markers to use localized item names by item ID.
### Fixed
- Fixed version metadata consistency so all first-party module descriptors report the same release version as the main addon.
- Fixed SmartAmmo junk-shot detection by including `Baited Shot` and `Tranquilizing Shot` in `MTH_AMMO_JUNKSHOT_SET`.
- Fixed zButtons options side effect where opening Mounts/Companions/Toys options could implicitly enable those buttons.
- Normalized zButtons checkbox defaults (`enabled`, `tooltip`, `hideonclick`, `parent.hide`, `parent.circle`, `showammoname`) so toggle states are always explicit and never nil.
- Fixed Beast Lore ability dropdown to exclude trainer-only abilities and list beast-learned abilities only.
- Removed junk `TBD` ability value from Wind Serpent dataset entry (`Venomflayer Serpent`) so it no longer appears in Beast Lore filters.
- Fixed zhunter child-button visibility restore so collapsed bars no longer hard-hide children (`ztrack` / `ztrap` expansion regression).
- Fixed pet tame metadata fallback so existing pets are not assigned current zone/time unless a real tame attempt is pending.
- Fixed Smart Ammo options copy mismatch by clarifying junk-shot swap behavior and restoring ammo target as previous equipped ammo.
- Fixed Smart Ammo default state so junk-shot swaps are enabled when no saved value exists.
- Fixed Beast Training startup prompt behavior so low-level hunters no longer receive training-scan warnings before level threshold.
- Restored hidden `/mth err` slash command behavior for debug-frame access.
- Fixed `zammo` children refresh on bag/inventory updates so newly acquired ammo types appear without `/reload`.
- Preserved `zammo` out-of-stock behavior so previously shown ammo entries still remain visible with zero/out state.
- Fixed intermittent `ztrack`/`ztrap` right-click expand/collapse issue after fresh login by preventing duplicate child-frame recreation during startup.
- Fixed `zpet` spell resolution after trainer updates by using robust spellbook lookup and immediate refresh on `LEARNED_SPELL_IN_TAB`.
## [1.0.3] - 2026-02-24
### Added
- Initial published release.
+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.0.4
## Version: 1.4.1
## Title: MetaHunt - |cff00ff00Hunter
## Author: Metasploit and his Copilot ;)
## Notes: Unified addon suite for huntards making old addons compatible with TurtleWoW, and adding an arsenal of never seen Hunter's utilities.
@@ -20,8 +20,6 @@ init\skins.xml
## Load database files (DB files must come before data\init.lua)
data\ds-beasts.lua
data\ds-beasts-extra.lua
data\ds-beasts-html-enrichment.lua
data\ds-vendors.lua
data\ds-vendors-extra.lua
data\ds-items.lua
@@ -31,12 +29,17 @@ data\ds-bags.lua
data\ds-items-atlas-sources.lua
data\ds-zones.lua
data\ds-zones-fallback.lua
data\ds-zones-wma.lua
data\ds-minimap-sizes.lua
data\ds-unit-coords.lua
data\ds-creature-coords.lua
data\ds-beasts-families.lua
data\ds-beasts-families-dbc.lua
data\ds-beasts-skins.lua
data\ds-pet-spells.lua
data\ds-pet-spells-trainer.lua
data\ds-hunter-spells.lua
data\ds-hunter-spells-dbc.lua
data\ds-racial.lua
data\init.lua
+27 -10
View File
@@ -1,14 +1,14 @@
# MetaHunt
MetaHunt is a Turtle WoW Hunter toolkit.
It combines never-seen pet-centric and beast tracking features, and revives old broken hunter add-ons.
It provides usefull tools for all hunters, 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.
## 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 +21,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 +31,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,7 +39,7 @@ 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
- zRanged : All the ranged weampons currently in your bags to swap them fast
- But also zMounts, zToys and zCompanions.
## Feed-O-Matic
@@ -48,16 +50,31 @@ Now compatible with Turtle, feed your pet with one key bind, it will always pick
The good old Chronometer addon, purged of non-necessary things and reconfigured for a pure hunter use.
## Autobuy
## ICU (I see you)
Old addon used to display Targets' enhanced information when you click on your minimap tracking things. MetaHunt offers many more customization options for it.
## Auto-Buy
Define your own simple rules for auto buy ammo and pet food when you open a vendor.
## The book of Hunters
## Auto-Quest
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.
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.
- Browse the Metahunt datastores of NPCs (Beast, vendors, masters) and open the map to locate them like in pfQuest
## Beast model Viewer
Display the model of all beasts in the world. Always know what you are going to tame exactly before running there ! You can also browse all skin models of the same pet family to find the best-looking pet according to your tastes.
## The Great Book of Huntards
The Book is the main GUI of MetaHunt, it allows to display the data constantly collected about you and your pet, and browse the addon's data to find anything a Hunter needs.
- Browse the MetaHunt datastores of NPCs (Beast, vendors, masters) and open the map to locate them like in pfQuest
- Model viewer of all beasts in the game.
- Track all info about the pet abilities that you know and don't know yet
- Track all info about your stabled pets
- Keep an history of all your pets after you abandon them or if they run away.
+3 -3
View File
@@ -98,7 +98,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,8 +112,8 @@ 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()
+167 -3
View File
@@ -6,6 +6,29 @@ local function AutoStrip_Print(msg)
end
end
local AutoStrip_TraceEnabled = false
local function AutoStrip_Trace(msg)
if not AutoStrip_TraceEnabled then
return
end
AutoStrip_Print("[ASTRACE] " .. tostring(msg))
end
function AutoStrip_CommandTrace(mode)
local action = string.lower(tostring(mode or "status"))
if action == "on" or action == "1" or action == "true" then
AutoStrip_TraceEnabled = true
AutoStrip_Trace("trace enabled")
return
elseif action == "off" or action == "0" or action == "false" then
AutoStrip_Trace("trace disabled")
AutoStrip_TraceEnabled = false
return
end
AutoStrip_Print("[ASTRACE] status=" .. tostring(AutoStrip_TraceEnabled and true or false))
end
function AutoStrip_GetSaved()
if type(MTH_CharSavedVariables) ~= "table" then
MTH_CharSavedVariables = {}
@@ -20,6 +43,90 @@ local AUTO_STRIP_ORDER = {16, 17, 18, 5, 7, 1, 3, 10, 8, 6, 9}
AutoStrip_On = nil
AutoStrip_WasInCombat = nil
AutoStrip_Frame = nil
local AutoStrip_RestoreFrame = nil
local function AutoStrip_DescribePoint(frame)
if not (frame and frame.GetPoint) then
return "<no-frame>"
end
local point, relTo, relPoint, x, y = frame:GetPoint()
local relName = "nil"
if type(relTo) == "table" and relTo.GetName then
relName = tostring(relTo:GetName() or "<anon>")
elseif relTo then
relName = tostring(relTo)
end
return tostring(point or "nil")
.. "->" .. relName
.. ":" .. tostring(relPoint or "nil")
.. " @(" .. tostring(x) .. "," .. tostring(y) .. ")"
end
function AutoStrip_SaveDisplayPosition(reason)
if not (AutoStripDisplay and AutoStripDisplay.GetPoint) then
return
end
local saved = AutoStrip_GetSaved()
local point, _, relPoint, x, y = AutoStripDisplay:GetPoint()
x = tonumber(x)
y = tonumber(y)
if not (point and relPoint and x and y) then
return
end
saved["point"] = tostring(point)
saved["relativePoint"] = tostring(relPoint)
saved["x"] = math.floor(x + 0.5)
saved["y"] = math.floor(y + 0.5)
AutoStrip_Trace("persist-display-position reason=" .. tostring(reason or "")
.. " point=" .. tostring(saved["point"])
.. " rel=" .. tostring(saved["relativePoint"])
.. " x=" .. tostring(saved["x"])
.. " y=" .. tostring(saved["y"]))
end
local function AutoStrip_RestoreDisplayPosition(reason)
if not (AutoStripDisplay and AutoStripDisplay.ClearAllPoints and AutoStripDisplay.SetPoint) then
return false
end
local saved = AutoStrip_GetSaved()
local point = tostring(saved["point"] or "")
local relPoint = tostring(saved["relativePoint"] or "")
local x = tonumber(saved["x"])
local y = tonumber(saved["y"])
if point == "" or not x or not y then
AutoStrip_Trace("restore-display-position skipped reason=" .. tostring(reason or "")
.. " savedPoint=" .. tostring(saved["point"])
.. " savedX=" .. tostring(saved["x"])
.. " savedY=" .. tostring(saved["y"]))
return false
end
if relPoint == "" then
relPoint = point
end
AutoStripDisplay:ClearAllPoints()
AutoStripDisplay:SetPoint(point, UIParent, relPoint, x, y)
AutoStrip_Trace("restore-display-position reason=" .. tostring(reason or "")
.. " frame=" .. AutoStrip_DescribePoint(AutoStripDisplay))
return true
end
local function AutoStrip_QueueDeferredRestore(reason)
if not AutoStrip_RestoreFrame then
AutoStrip_RestoreFrame = CreateFrame("Frame", "MTH_AutoStripRestore")
if not AutoStrip_RestoreFrame then
return
end
end
AutoStrip_RestoreFrame._elapsed = 0
AutoStrip_RestoreFrame:SetScript("OnUpdate", function()
this._elapsed = (this._elapsed or 0) + (arg1 or 0)
if this._elapsed < 0.2 then
return
end
this:SetScript("OnUpdate", nil)
AutoStrip_RestoreDisplayPosition("deferred:" .. tostring(reason or ""))
end)
end
function AutoStrip_SetDisplayIcon()
local icon = getglobal("AutoStripDisplayIcon") or getglobal("AutoStripDisplayIconTexture")
@@ -35,9 +142,18 @@ end
function AutoStrip_SetAutoStripToggle(enabled, silent)
local saved = AutoStrip_GetSaved()
AutoStrip_Trace("SetAutoStripToggle enabled=" .. tostring(enabled and true or false)
.. " silent=" .. tostring(silent and true or false)
.. " savedBefore=" .. tostring(saved["autostrip"] and 1 or 0)
.. " runtimeBefore=" .. tostring(AutoStrip_On and 1 or 0)
.. " castShownBefore=" .. tostring((AutoStripDisplayAutoCast and AutoStripDisplayAutoCast:IsVisible()) and 1 or 0))
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
@@ -47,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
@@ -54,10 +174,17 @@ function AutoStrip_SetAutoStripToggle(enabled, silent)
AutoStrip_Print("Auto-Strip disabled.")
end
end
AutoStrip_Trace("SetAutoStripToggle savedAfter=" .. tostring(saved["autostrip"] and 1 or 0)
.. " runtimeAfter=" .. tostring(AutoStrip_On and 1 or 0)
.. " castShownAfter=" .. tostring((AutoStripDisplayAutoCast and AutoStripDisplayAutoCast:IsVisible()) and 1 or 0))
end
function AutoStrip_SetDisplayToggle(enabled, silent)
local saved = AutoStrip_GetSaved()
AutoStrip_Trace("SetDisplayToggle enabled=" .. tostring(enabled and true or false)
.. " silent=" .. tostring(silent and true or false)
.. " savedDisplayBefore=" .. tostring(saved["display"] and 1 or 0)
.. " frameShownBefore=" .. tostring((AutoStripDisplay and AutoStripDisplay:IsVisible()) and 1 or 0))
if enabled then
saved["display"] = 1
if AutoStripDisplay then
@@ -79,9 +206,15 @@ function AutoStrip_SetDisplayToggle(enabled, silent)
AutoStrip_Print("AutoStrip button is now hidden.")
end
end
AutoStrip_Trace("SetDisplayToggle savedDisplayAfter=" .. tostring(saved["display"] and 1 or 0)
.. " frameShownAfter=" .. tostring((AutoStripDisplay and AutoStripDisplay:IsVisible()) and 1 or 0)
.. " castShownAfter=" .. tostring((AutoStripDisplayAutoCast and AutoStripDisplayAutoCast:IsVisible()) and 1 or 0))
end
function AutoStrip_UnequipAll(weaponsOnly)
AutoStrip_Trace("UnequipAll called weaponsOnly=" .. tostring(weaponsOnly and 1 or 0)
.. " runtimeBefore=" .. tostring(AutoStrip_On and 1 or 0)
.. " wasInCombat=" .. tostring(AutoStrip_WasInCombat and 1 or 0))
AutoStrip_On = nil
if AutoStripDisplayAutoCast then
AutoStripDisplayAutoCast:Hide()
@@ -102,6 +235,7 @@ function AutoStrip_UnequipAll(weaponsOnly)
if not hasEmptySlot then
AutoStrip_Print("You need at least one empty bag slot to auto-strip.")
AutoStrip_Trace("UnequipAll aborted no-empty-slot")
return
end
@@ -121,19 +255,38 @@ function AutoStrip_UnequipAll(weaponsOnly)
end
end
end
AutoStrip_Trace("UnequipAll completed runtimeAfter=" .. tostring(AutoStrip_On and 1 or 0)
.. " castShownAfter=" .. tostring((AutoStripDisplayAutoCast and AutoStripDisplayAutoCast:IsVisible()) and 1 or 0))
end
local function AutoStrip_OnEvent()
if AutoStrip_TraceEnabled then
AutoStrip_Trace("OnEvent evt=" .. tostring(event or "")
.. " runtime=" .. tostring(AutoStrip_On and 1 or 0)
.. " wasInCombat=" .. tostring(AutoStrip_WasInCombat and 1 or 0)
.. " playerInCombat=" .. tostring((type(UnitAffectingCombat) == "function" and UnitAffectingCombat("player")) and 1 or 0))
end
if event == "VARIABLES_LOADED" then
local saved = AutoStrip_GetSaved()
AutoStrip_Trace("VARIABLES_LOADED saved.autostrip=" .. tostring(saved["autostrip"] and 1 or 0)
.. " saved.display=" .. tostring(saved["display"] and 1 or 0))
AutoStrip_WasInCombat = UnitAffectingCombat("player") and 1 or nil
AutoStrip_RestoreDisplayPosition("VARIABLES_LOADED")
AutoStrip_SetDisplayToggle(saved["display"] and true or false, 1)
AutoStrip_SetAutoStripToggle(saved["autostrip"] and true or false, 1)
AutoStrip_QueueDeferredRestore("VARIABLES_LOADED")
return
end
if event == "PLAYER_ENTERING_WORLD" then
AutoStrip_WasInCombat = UnitAffectingCombat("player") and 1 or nil
AutoStrip_RestoreDisplayPosition("PLAYER_ENTERING_WORLD")
AutoStrip_QueueDeferredRestore("PLAYER_ENTERING_WORLD")
return
end
if event == "PLAYER_LOGOUT" then
AutoStrip_SaveDisplayPosition("PLAYER_LOGOUT")
return
end
@@ -144,6 +297,7 @@ local function AutoStrip_OnEvent()
if event == "PLAYER_REGEN_ENABLED" then
if AutoStrip_On and AutoStrip_WasInCombat then
AutoStrip_Trace("PLAYER_REGEN_ENABLED triggering auto strip")
AutoStrip_UnequipAll()
end
AutoStrip_WasInCombat = nil
@@ -152,6 +306,8 @@ end
local function AutoStrip_HandleSlash(msg)
local saved = AutoStrip_GetSaved()
AutoStrip_Trace("Slash msg='" .. tostring(msg or "") .. "' saved.autostrip=" .. tostring(saved["autostrip"] and 1 or 0)
.. " runtime=" .. tostring(AutoStrip_On and 1 or 0))
if msg == "display" then
AutoStrip_SetDisplayToggle(not (saved["display"] and true or false))
elseif msg == "toggle" then
@@ -170,12 +326,15 @@ 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)
end
@@ -187,3 +346,8 @@ SLASH_AUTOSTRIP1 = "/autostrip"
SlashCmdList["AUTOSTRIP"] = AutoStrip_HandleSlash
SLASH_ZSTRIP1 = "/zstrip"
SlashCmdList["ZSTRIP"] = AutoStrip_HandleSlash
SLASH_AUTOSTRIPTRACE1 = "/autostriptrace"
SLASH_AUTOSTRIPTRACE2 = "/astrace"
SlashCmdList["AUTOSTRIPTRACE"] = function(msg)
AutoStrip_CommandTrace(msg)
end
+6
View File
@@ -68,6 +68,9 @@
if ( this.isMoving ) then
this:StopMovingOrSizing()
this.isMoving = false
if AutoStrip_SaveDisplayPosition then
AutoStrip_SaveDisplayPosition("mouse-up-stop")
end
end
</OnMouseUp>
<OnDragStart>
@@ -79,6 +82,9 @@
<OnDragStop>
this:StopMovingOrSizing()
this.isMoving = false
if AutoStrip_SaveDisplayPosition then
AutoStrip_SaveDisplayPosition("drag-stop")
end
</OnDragStop>
</Scripts>
<NormalTexture name="$parentNormalTexture" file="Interface\Buttons\UI-Quickslot2">
+543
View File
@@ -0,0 +1,543 @@
------------------------------------------------------
-- MetaHunt: Beast Lore Scanner
-- TWoW 1.18.1 — "Nightmares of Ursol"
------------------------------------------------------
-- Fires when the player channels Beast Lore on a target.
-- Captures all available data and stores it in
-- MTH_SavedVariables.beastLoreScan.entries (account-wide).
--
-- Dedup rule: skips recording if the same creature name
-- already exists in this zone within 30 yards of current
-- player position (uses ds-minimap-sizes for yard scale).
-- A chat message is always printed either way.
------------------------------------------------------
local SPELL_BEAST_LORE = "Beast Lore"
local DEDUP_YARDS = 30
local FALLBACK_DEDUP_PCT = 0.015 -- ~1.5% of map width if zone size unknown
-- ── Tooltip probe ────────────────────────────────────────────
local MTH_BLS_Probe = nil
local function MTH_BLS_EnsureProbe()
if not MTH_BLS_Probe then
MTH_BLS_Probe = CreateFrame("GameTooltip", "MTH_BLSProbe", UIParent, "GameTooltipTemplate")
MTH_BLS_Probe:SetOwner(UIParent, "ANCHOR_NONE")
end
return MTH_BLS_Probe
end
-- Read left/right text lines from the probe (Lua 5.0 compatible: getglobal)
local function MTH_BLS_GetProbeLines()
local lines = {}
for i = 1, 24 do
local L = getglobal("MTH_BLSProbeTextLeft" .. i)
local R = getglobal("MTH_BLSProbeTextRight" .. i)
local lText = L and L:GetText() or nil
local rText = R and R:GetText() or nil
if (not lText or lText == "") and (not rText or rText == "") then
break
end
table.insert(lines, { left = lText or "", right = rText or "" })
end
return lines
end
-- Parse Beast Lore tooltip lines into structured fields.
-- Uses string.find with captures (Lua 5.0, no string.match).
local function MTH_BLS_ParseTooltip(lines)
local data = {
tameable = nil, -- true / false / nil (unknown)
diet = nil,
abilities = nil,
armor = nil,
health = nil,
minDmg = nil,
maxDmg = nil,
}
for _, line in ipairs(lines) do
local L = line.left or ""
local R = line.right or ""
-- Tameability ──────────────────────────────────────────
if string.find(L, "Cannot Be Tamed", 1, true) or string.find(R, "Cannot Be Tamed", 1, true) then
data.tameable = false
elseif string.find(L, "Can Be Tamed", 1, true) or string.find(R, "Can Be Tamed", 1, true)
or string.find(L, "Tameable", 1, true) or string.find(R, "Tameable", 1, true) then
data.tameable = true
end
-- Diet ─────────────────────────────────────────────────
if not data.diet then
local _, _, v = string.find(L, "^Diet:%s*(.*)")
if not v then _, _, v = string.find(R, "^Diet:%s*(.*)") end
if v and v ~= "" then data.diet = v end
end
-- Tamed Abilities ──────────────────────────────────────
if not data.abilities then
local _, _, v = string.find(L, "^Tamed Abilities:%s+(.*)")
if not v then _, _, v = string.find(R, "^Tamed Abilities:%s+(.*)") end
if v and v ~= "" then data.abilities = v end
end
-- Damage range ("107 - 133 Damage") ────────────────────
if not data.minDmg then
local _, _, lo, hi = string.find(L, "(%d+) %- (%d+) Damage")
if not lo then _, _, lo, hi = string.find(R, "(%d+) %- (%d+) Damage") end
if lo and hi then
data.minDmg = tonumber(lo)
data.maxDmg = tonumber(hi)
end
end
-- Armor ────────────────────────────────────────────────
if not data.armor then
local _, _, v = string.find(L, "Armor:%s*(%d+)")
if not v then _, _, v = string.find(R, "Armor:%s*(%d+)") end
if v then data.armor = tonumber(v) end
end
-- Health ───────────────────────────────────────────────
if not data.health then
local _, _, v = string.find(L, "Health:%s*(%d+)")
if not v then _, _, v = string.find(R, "Health:%s*(%d+)") end
if v then data.health = tonumber(v) end
end
end
return data
end
-- ── Location helpers ─────────────────────────────────────────
-- pfQuest AreaTable ID → WMA ID bridge (GetCurrentMapAreaID returns pfQ IDs)
local MTH_PFQ_TO_WMA = {
[1]=27,[3]=17,[4]=19,[8]=38,[10]=34,[11]=40,[12]=30,[14]=4,[15]=141,[16]=181,
[17]=11,[25]=519,[28]=22,[33]=37,[36]=15,[38]=35,[40]=39,[41]=32,[44]=36,
[45]=16,[46]=29,[47]=26,[51]=28,[85]=20,[130]=21,[139]=23,[141]=41,[148]=42,
[209]=650,[215]=9,[267]=24,[331]=43,[357]=121,[361]=182,[400]=61,[405]=101,
[406]=81,[408]=505,[409]=504,[440]=161,[490]=201,[491]=641,[493]=241,
[616]=501,[618]=281,[717]=611,[718]=516,[719]=609,[721]=518,[722]=639,
[796]=520,[876]=500,[1176]=605,[1337]=521,[1377]=261,[1477]=607,[1497]=382,
[1519]=301,[1537]=341,[1581]=514,[1583]=629,[1584]=623,[1637]=321,[1638]=362,
[1657]=381,[1769]=700,[1941]=523,[1977]=619,[2017]=652,[2040]=509,[2057]=648,
[2100]=517,[2159]=627,[2257]=659,[2366]=663,[2437]=603,[2557]=689,[2597]=401,
[2677]=635,[2717]=617,[3277]=443,[3358]=461,[3428]=654,[3429]=625,[3456]=667,
[3457]=682,[3478]=522,[4012]=502,[5023]=503,[5024]=511,[5053]=680,[5077]=670,
[5086]=674,[5087]=676,[5097]=678,[5103]=672,[5121]=507,[5130]=601,[5135]=644,
[5136]=643,[5138]=637,[5147]=655,[5148]=668,[5153]=645,[5163]=646,[5179]=510,
[5204]=662,[5208]=665,[5225]=513,[5536]=512,[5557]=683,[5561]=685,[5581]=686,
[5601]=691,[5602]=684,[5628]=693,[5640]=701,[5641]=698,[5642]=699,[5722]=702,
[5723]=657,[5731]=705,[5734]=704,
}
local function MTH_BLS_GetZoneId()
if type(SetMapToCurrentZone) == "function" then
pcall(SetMapToCurrentZone)
end
if type(GetCurrentMapAreaID) == "function" then
local ok, id = pcall(GetCurrentMapAreaID)
if ok and tonumber(id) and tonumber(id) > 0 then
local pfq = tonumber(id)
return MTH_PFQ_TO_WMA[pfq] or pfq
end
end
return 0
end
local function MTH_BLS_GetPlayerCoords()
if type(SetMapToCurrentZone) == "function" then
pcall(SetMapToCurrentZone)
end
if type(GetPlayerMapPosition) ~= "function" then return nil, nil end
local ok, x, y = pcall(GetPlayerMapPosition, "player")
if ok and x and y and (x ~= 0 or y ~= 0) then return x, y end
return nil, nil
end
-- Returns true if a beast with this name is already in the static MTH_DS_Beasts database.
-- We skip SavedVariables recording for beasts already catalogued in the DB.
local function MTH_BLS_IsInStaticDB(name)
if type(MTH_DS_Beasts) ~= "table" or not name or name == "" then return false end
for _, row in pairs(MTH_DS_Beasts) do
if type(row) == "table" and row.name == name then
return true
end
end
return false
end
-- ── Storage ──────────────────────────────────────────────────
local function MTH_BLS_EnsureStore()
if type(MTH_SavedVariables) ~= "table" then
MTH_SavedVariables = {}
end
if type(MTH_SavedVariables.beastLoreScan) ~= "table" then
MTH_SavedVariables.beastLoreScan = {}
end
if type(MTH_SavedVariables.beastLoreScan.entries) ~= "table" then
MTH_SavedVariables.beastLoreScan.entries = {}
end
return MTH_SavedVariables.beastLoreScan
end
-- ── Dedup ────────────────────────────────────────────────────
-- Returns the existing entry for a same-named beast in the same zone,
-- plus whether any of its recorded coords is within DEDUP_YARDS of (x, y).
local function MTH_BLS_FindEntry(name, zoneId, x, y)
local store = MTH_BLS_EnsureStore()
-- Normalize: treat 0 as unknown (entries are stored with nil for unknown zones)
if not zoneId or zoneId == 0 then zoneId = nil end
local mapW, mapH = nil, nil
if type(MTH_DS_MinimapSizes) == "table" and zoneId then
local sz = MTH_DS_MinimapSizes[zoneId]
if type(sz) == "table" then
mapW = tonumber(sz[1])
mapH = tonumber(sz[2])
end
end
local function IsNearby(ex, ey)
if not ex or not ey or not x or not y then return false end
local dx = ex - x
local dy = ey - y
if mapW and mapH then
return math.sqrt((dx * mapW)^2 + (dy * mapH)^2) < DEDUP_YARDS
else
-- No map size data: use a generous fallback (~5% of map width)
return math.sqrt(dx * dx + dy * dy) < 0.05
end
end
local entries = store.entries
for i = 1, table.getn(entries) do
local e = entries[i]
-- Match by name and zone. When either side has no zone, match on name alone
-- (coords will still be checked below; cross-zone false positives are
-- unlikely given players don't share coordinate spaces across zones).
local zoneMatch = (e.zoneId == zoneId) or (not e.zoneId) or (not zoneId)
if type(e) == "table"
and e.name == name
and zoneMatch
then
-- Check all recorded coords for this beast.
if type(e.coords) == "table" then
for j = 1, table.getn(e.coords) do
local c = e.coords[j]
if type(c) == "table" and IsNearby(c.x, c.y) then
return e, true -- entry found, nearby coord found
end
end
elseif IsNearby(e.x, e.y) then
-- Legacy single-coord entry
return e, true
end
return e, false -- entry found, but no coord nearby
end
end
return nil, false -- no entry for this beast in this zone
end
-- ── Main scan ────────────────────────────────────────────────
-- Per-session cooldown: prevents re-recording the same beast in the
-- same zone within SCAN_COOLDOWN seconds, as a safety net on top of
-- the coordinate-based dedup.
local MTH_BLS_ScanCooldowns = {}
local SCAN_COOLDOWN = 120 -- 2 minutes
local function MTH_BLS_Scan()
-- Basic target guards
if type(UnitExists) == "function" and not UnitExists("target") then return end
if type(UnitIsPlayer) == "function" and UnitIsPlayer("target") then return end
local name = (type(UnitName) == "function") and UnitName("target") or nil
if not name or name == "" then return end
-- ── Unit data ───────────────────────────────────────────
local level = (type(UnitLevel) == "function") and UnitLevel("target") or nil
local family = (type(UnitCreatureFamily) == "function") and UnitCreatureFamily("target") or nil
local healthMax = (type(UnitHealthMax) == "function") and UnitHealthMax("target") or nil
local minDmg, maxDmg, mainSpeed = nil, nil, nil
if type(UnitDamage) == "function" then
local ok, a, b, _, _, spd = pcall(UnitDamage, "target")
if ok then minDmg, maxDmg, mainSpeed = a, b, spd end
end
if not mainSpeed and type(UnitAttackSpeed) == "function" then
local ok, spd = pcall(UnitAttackSpeed, "target")
if ok then mainSpeed = spd end
end
local guid = nil
if type(UnitGUID) == "function" then
local ok, g = pcall(UnitGUID, "target")
if ok and g then guid = g end
end
-- ── Location ────────────────────────────────────────────
local zoneId = MTH_BLS_GetZoneId()
local x, y = MTH_BLS_GetPlayerCoords()
local zone = (type(GetZoneText) == "function") and GetZoneText() or ""
local subzone = (type(GetSubZoneText) == "function") and GetSubZoneText() or ""
-- Session cooldown: hard block on re-scanning the same beast in same zone
local cooldownKey = tostring(name) .. "|" .. tostring(zone)
local lastScan = MTH_BLS_ScanCooldowns[cooldownKey]
if lastScan and (GetTime() - lastScan) < SCAN_COOLDOWN then
MTH:Log("|cff888888Beast Lore: " .. tostring(name) .. " scanned recently — skipped|r")
return
end
-- ── Tooltip probe ───────────────────────────────────────
local probe = MTH_BLS_EnsureProbe()
probe:ClearLines()
local probeOk = pcall(function() probe:SetUnit("target") end)
local parsed = { tameable = nil, diet = nil, abilities = nil, armor = nil, health = nil }
if probeOk then
local lines = MTH_BLS_GetProbeLines()
parsed = MTH_BLS_ParseTooltip(lines)
end
-- Tooltip values override unit API (Beast Lore reveals more accurate data)
if parsed.minDmg then minDmg = parsed.minDmg end
if parsed.maxDmg then maxDmg = parsed.maxDmg end
if parsed.health then healthMax = parsed.health end
-- Fallback tameability: family present = tameable
if parsed.tameable == nil then
parsed.tameable = (family and family ~= "") and true or false
end
-- ── Dedup / upsert ──────────────────────────────────────
local function R3(v)
if type(v) ~= "number" then return nil end
return math.floor(v * 1000 + 0.5) / 1000
end
local function R2(v)
if type(v) ~= "number" then return nil end
return math.floor(v * 100 + 0.5) / 100
end
local rx, ry = R3(x), R3(y)
-- Mark scanned now (before storing) so any rapid re-trigger is blocked
MTH_BLS_ScanCooldowns[cooldownKey] = GetTime()
local existing, nearbyFound = MTH_BLS_FindEntry(name, zoneId, x, y)
if nearbyFound then
-- Already recorded at this location — do nothing.
MTH:Log("|cff888888Beast Lore: " .. tostring(name)
.. " already recorded nearby — skipped|r")
return
end
local store = MTH_BLS_EnsureStore()
if existing then
-- Known beast, new location — add coord to its list.
if type(existing.coords) ~= "table" then
-- Migrate legacy single coord into coords list.
existing.coords = {}
if existing.x or existing.y then
table.insert(existing.coords, { x = existing.x, y = existing.y, timestamp = existing.timestamp })
end
existing.x = nil
existing.y = nil
end
table.insert(existing.coords, { x = rx, y = ry, timestamp = (type(time) == "function") and time() or nil })
existing.lastUpdated = (type(time) == "function") and time() or nil
-- Update abilities/data if newly revealed by this cast.
if parsed.abilities and parsed.abilities ~= "" then existing.abilities = parsed.abilities end
local coordStr = (rx and ry)
and string.format(" (%.1f, %.1f)", rx * 100, ry * 100)
or ""
local abilitiesStr = (existing.abilities and existing.abilities ~= "")
and (" |cff88ddff[" .. existing.abilities .. "]|r")
or ""
MTH:Log("Beast Lore: |cffffaa00" .. tostring(name) .. "|r lv" .. (existing.level and tostring(existing.level) or "?")
.. " — new coord added" .. coordStr
.. abilitiesStr
.. " |cff888888[#" .. tostring(table.getn(store.entries)) .. " +" .. tostring(table.getn(existing.coords)) .. " loc]|r")
return
end
-- Skip recording if this beast is already in the static database.
if MTH_BLS_IsInStaticDB(name) then
MTH:Log("|cff888888Beast Lore: " .. tostring(name) .. " is already in the MetaHunt database — skipped|r")
return
end
-- New beast entirely.
local entry = {
name = name,
guid = guid,
level = level,
family = family,
tameable = parsed.tameable,
minDmg = minDmg and math.floor(minDmg + 0.5) or nil,
maxDmg = maxDmg and math.floor(maxDmg + 0.5) or nil,
attackSpeed = mainSpeed and R2(mainSpeed) or nil,
armor = parsed.armor or nil,
health = healthMax or nil,
diet = parsed.diet or nil,
abilities = parsed.abilities or nil,
coords = { { x = rx, y = ry, timestamp = (type(time) == "function") and time() or nil } },
zoneId = (zoneId and zoneId > 0) and zoneId or nil,
zone = (zone and zone ~= "") and zone or nil,
subzone = (subzone and subzone ~= "") and subzone or nil,
timestamp = (type(time) == "function") and time() or nil,
}
table.insert(store.entries, entry)
-- ── Chat confirmation ────────────────────────────────────
local tameStr = parsed.tameable
and "|cff33ff55Tameable|r"
or "|cffff4444Cannot be Tamed|r"
local famStr = (family and family ~= "")
and ("|cffffcc00" .. family .. "|r")
or "|cff888888unknown family|r"
local lvlStr = level and tostring(level) or "?"
local coordStr = (x and y)
and string.format(" (%.1f, %.1f)", x * 100, y * 100)
or ""
local zoneStr = (zone and zone ~= "") and (", " .. zone .. coordStr) or coordStr
local idx = table.getn(store.entries)
local abilitiesStr = ""
if parsed.abilities and parsed.abilities ~= "" then
abilitiesStr = " |cff88ddff[" .. parsed.abilities .. "]|r"
end
MTH:Log("|cff00ff88YOU FOUND AND RECORDED A NEW BEAST!|r |cffffaa00" .. tostring(name) .. "|r lv" .. lvlStr
.. "" .. tameStr .. "" .. famStr
.. zoneStr
.. abilitiesStr
.. " |cff888888[#" .. tostring(idx) .. "]|r")
end
-- ── Detection: event-gated tooltip poll ─────────────────────
-- SPELLCAST_CHANNEL_* events do not reliably fire in TurtleWoW 1.18.1.
-- Strategy: spellcast events open a short window (MTH_BLS_WindowEnd).
-- OnUpdate only runs during that window, checking the target tooltip
-- for Beast Lore-specific lines ("Diet:", "Tamed Abilities:").
-- If no event fires at all, the window stays closed and nothing runs.
local MTH_BLS_Frame = CreateFrame("Frame", "MTH_BeastLoreScanFrame")
local MTH_BLS_PollAccum = 0
local MTH_BLS_WindowEnd = 0 -- GetTime() deadline; 0 = window closed
local MTH_BLS_PollLast = nil -- target name already scanned this window
local WINDOW_DURATION = 6 -- seconds to probe after Beast Lore cast
local function MTH_BLS_OpenWindow()
MTH_BLS_WindowEnd = GetTime() + WINDOW_DURATION
MTH_BLS_PollLast = nil
end
-- [DISABLED] Beast Lore scan activation — not needed while full beast DB is shipped.
-- Re-enable by removing the `if false then` / `end` wrapper below.
if false then
-- Register all known spellcast event variants.
MTH_BLS_Frame:RegisterEvent("SPELLCAST_CHANNEL_START")
MTH_BLS_Frame:RegisterEvent("SPELLCAST_CHANNEL_STOP")
MTH_BLS_Frame:RegisterEvent("SPELLCAST_START")
MTH_BLS_Frame:RegisterEvent("SPELLCAST_STOP")
MTH_BLS_Frame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
MTH_BLS_Frame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
MTH_BLS_Frame:RegisterEvent("UNIT_SPELLCAST_START")
MTH_BLS_Frame:RegisterEvent("UNIT_SPELLCAST_STOP")
MTH_BLS_Frame:RegisterEvent("PLAYER_TARGET_CHANGED")
MTH_BLS_Frame:SetScript("OnEvent", function()
local evt = event or ""
if evt == "PLAYER_TARGET_CHANGED" then
-- New target: reset so re-targeting after a cast works.
MTH_BLS_PollLast = nil
return
end
-- Determine spell name from event args.
local isUnitEvt = string.sub(evt, 1, 15) == "UNIT_SPELLCAST_"
local spellName
if isUnitEvt then
if (arg1 or "") ~= "player" then return end
spellName = arg2 or ""
else
spellName = arg1 or ""
end
if string.find(spellName, SPELL_BEAST_LORE, 1, true) then
MTH_BLS_OpenWindow()
end
end)
MTH_BLS_Frame:SetScript("OnUpdate", function()
MTH_BLS_PollAccum = MTH_BLS_PollAccum + (arg1 or 0)
if MTH_BLS_PollAccum < 2.0 then return end
MTH_BLS_PollAccum = 0
if not (type(UnitExists) == "function" and UnitExists("target")) then return end
if type(UnitIsPlayer) == "function" and UnitIsPlayer("target") then return end
local name = (type(UnitName) == "function") and UnitName("target") or nil
if not name or name == "" then return end
if name == MTH_BLS_PollLast then return end
-- Probe the target tooltip for Beast Lore-specific lines.
local probe = MTH_BLS_EnsureProbe()
probe:ClearLines()
local probeOk = pcall(function() probe:SetUnit("target") end)
if not probeOk then return end
local hasData = false
for i = 1, 20 do
local L = getglobal("MTH_BLSProbeTextLeft" .. i)
if not L then break end
local txt = (L.GetText and L:GetText()) or ""
if string.find(txt, "Diet:", 1, true)
or string.find(txt, "Tamed Abilities:", 1, true)
or string.find(txt, "Cannot Be Tamed", 1, true)
then
hasData = true
break
end
end
if not hasData then return end
-- Beast Lore data confirmed — record and close window.
MTH_BLS_PollLast = name
MTH_BLS_WindowEnd = 0
local ok, err = pcall(MTH_BLS_Scan)
if not ok and MTH and MTH.Print then
MTH:Print("[BeastLore Scan] error: " .. tostring(err), "error")
end
end)
end -- [DISABLED] if false
-- ── Public accessors ─────────────────────────────────────────
-- Returns the first saved entry whose name matches (case-insensitive),
-- or nil if none found.
function MTH_BLS_FindSavedBeastByName(name)
local store = MTH_BLS_EnsureStore()
if not store or type(store.entries) ~= "table" then return nil end
local wantLower = string.lower(name or "")
if wantLower == "" then return nil end
for i = 1, table.getn(store.entries) do
local e = store.entries[i]
if type(e) == "table" and string.lower(e.name or "") == wantLower then
return e
end
end
return nil
end
+16 -4
View File
@@ -1,5 +1,5 @@
MTH_CONST = MTH_CONST or {}
MTH_CONST.version = MTH_CONST.version or "1.0.4"
MTH_CONST.version = MTH_CONST.version or "1.4.1"
MTH_CONST.WEAPON_TYPES = {
BOWS = "Bows",
@@ -24,6 +24,7 @@ MTH_CONST.AMMO = {
ICETHREADED = "Ice Threaded Bullet",
THORIUM = "Thorium Shells",
ROCKSHARD = "Rockshard Pellets",
CRAFTEDTHORIUM = "Enchanted Thorium Shells",
CANNONBALL = "Miniature Cannon Balls",
},
ARROWS = {
@@ -32,16 +33,19 @@ MTH_CONST.AMMO = {
RAZOR = "Razor Arrow",
FEATHER = "Feathered Arrow",
PRECISION = "Precision Arrow",
BRIGHT = "Bright Wood Arrows",
JAGGED = "Jagged Arrow",
SHADE = "Shade Wood Arrows",
ICETHREADED = "Ice Threaded Arrow",
SMOOTH = "Smooth Ironfeather Arrows",
THORIUM = "Thorium Headed Arrow",
STAR = "Starfeather Arrows",
DOOMSHOT = "Doomshot",
},
JUNKSHOTS = {
SERPENT = "Serpent Sting",
SCORPID = "Scorpid Sting",
VIPER = "Viper Sting",
ARCANE = "Arcane Shot",
CONCUSSIVE = "Concussive Shot",
DISTRACTING = "Distracting Shot",
BAITED = "Baited Shot",
@@ -69,6 +73,7 @@ BULLETS_ICETHREADED = MTH_CONST.AMMO.BULLETS.ICETHREADED
BULLETS_THORIUM = MTH_CONST.AMMO.BULLETS.THORIUM
BULLETS_ROCKSHARD = MTH_CONST.AMMO.BULLETS.ROCKSHARD
BULLETS_CANNONBALL = MTH_CONST.AMMO.BULLETS.CANNONBALL
BULLETS_CRAFTEDTHORIUM = MTH_CONST.AMMO.BULLETS.CRAFTEDTHORIUM
ARROWS_ROUGH = MTH_CONST.AMMO.ARROWS.ROUGH
ARROWS_SHARP = MTH_CONST.AMMO.ARROWS.SHARP
@@ -77,13 +82,16 @@ ARROWS_FEATHER = MTH_CONST.AMMO.ARROWS.FEATHER
ARROWS_PRECISION = MTH_CONST.AMMO.ARROWS.PRECISION
ARROWS_JAGGED = MTH_CONST.AMMO.ARROWS.JAGGED
ARROWS_ICETHREADED = MTH_CONST.AMMO.ARROWS.ICETHREADED
ARROWS_BRIGHT = MTH_CONST.AMMO.ARROWS.BRIGHT
ARROWS_SHADE = MTH_CONST.AMMO.ARROWS.SHADE
ARROWS_SMOOTH = MTH_CONST.AMMO.ARROWS.SMOOTH
ARROWS_THORIUM = MTH_CONST.AMMO.ARROWS.THORIUM
ARROWS_STAR = MTH_CONST.AMMO.ARROWS.STAR
ARROWS_DOOMSHOT = MTH_CONST.AMMO.ARROWS.DOOMSHOT
JUNKSHOT_SERPENT = MTH_CONST.AMMO.JUNKSHOTS.SERPENT
JUNKSHOT_SCORPID = MTH_CONST.AMMO.JUNKSHOTS.SCORPID
JUNKSHOT_VIPER = MTH_CONST.AMMO.JUNKSHOTS.VIPER
JUNKSHOT_ARCANE = MTH_CONST.AMMO.JUNKSHOTS.ARCANE
JUNKSHOT_CONCUSSIVE = MTH_CONST.AMMO.JUNKSHOTS.CONCUSSIVE
JUNKSHOT_DISTRACTING = MTH_CONST.AMMO.JUNKSHOTS.DISTRACTING
JUNKSHOT_BAITED = MTH_CONST.AMMO.JUNKSHOTS.BAITED
@@ -105,6 +113,7 @@ MTH_AMMO_BULLETS = {
BULLETS_ICETHREADED,
BULLETS_THORIUM,
BULLETS_ROCKSHARD,
BULLETS_CRAFTEDTHORIUM,
BULLETS_CANNONBALL,
}
@@ -114,9 +123,13 @@ MTH_AMMO_ARROWS = {
ARROWS_RAZOR,
ARROWS_FEATHER,
ARROWS_PRECISION,
ARROWS_BRIGHT,
ARROWS_JAGGED,
ARROWS_SHADE,
ARROWS_ICETHREADED,
ARROWS_SMOOTH,
ARROWS_THORIUM,
ARROWS_STAR,
ARROWS_DOOMSHOT,
}
@@ -124,7 +137,6 @@ MTH_AMMO_JUNKSHOTS = {
JUNKSHOT_SERPENT,
JUNKSHOT_SCORPID,
JUNKSHOT_VIPER,
JUNKSHOT_ARCANE,
JUNKSHOT_CONCUSSIVE,
JUNKSHOT_DISTRACTING,
JUNKSHOT_BAITED,
+174 -14
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()
@@ -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
@@ -526,6 +556,7 @@ local function MTH_FEED_CoreDropItemOnUnitHook(unit)
end
if target == "pet" and CursorHasItem and CursorHasItem() then
MTH_FEED_EnsureTrackerFrame()
local itemId, itemName, itemLink, itemIcon = MTH_FEED_GetCursorItemSnapshot()
local pet = MTH_FEED_GetCurrentPetContext()
if not itemId then
@@ -608,8 +639,8 @@ function MTH_FEED_DebugStatus()
end
function MTH_FEED_ReinstallCoreTracking()
MTH_FEED_EnsureTrackerFrame()
local ok = MTH_FEED_InstallCoreHooks()
MTH_FEED_EnsureTrackerFrame()
return ok and true or false
end
@@ -617,7 +648,7 @@ 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
@@ -626,6 +657,8 @@ local function MTH_FEED_EnsureBootstrapFrame()
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:SetScript("OnEvent", function()
MTH_FEED_ReinstallCoreTracking()
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
end)
MTH_FEED_BootstrapFrame = frame
return frame
@@ -822,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
@@ -837,6 +879,7 @@ local function MTH_FEED_GetDietByItemIdMap()
end
end
end
MTH_FEED_DietMapCache = map
return map
end
@@ -940,9 +983,6 @@ function MTH_FEED_BeginAttempt(payload)
itemIcon = infoTexture
end
end
if (not itemIcon or itemIcon == "") then
itemIcon = MTH_FEED_FindItemIconInBags(itemId, payload and payload.itemName)
end
MTH_FEED_Runtime.attempts[attemptId] = {
id = attemptId,
@@ -1113,7 +1153,7 @@ function MTH_FEED_IsFoodCompatible(petLevel, foodLevel)
return true
end
function MTH_FEED_IsExceptionReject(itemId)
function MTH_FEED_IsExceptionReject(itemId, petLevel)
local key = tonumber(itemId)
if not key then
return false
@@ -1123,6 +1163,11 @@ function MTH_FEED_IsExceptionReject(itemId)
if type(row) ~= "table" then
return false
end
local numericPet = tonumber(petLevel)
local blockedAtOrAbove = tonumber(row.blockAtOrAbovePetLevel)
if blockedAtOrAbove and numericPet and numericPet >= blockedAtOrAbove then
return true
end
if row.anomalyReject ~= true then
return false
end
@@ -1137,6 +1182,17 @@ function MTH_FEED_IsExceptionReject(itemId)
if not reasons then
return false
end
local lowLevelCount = tonumber(reasons.lowLevel) or 0
if lowLevelCount >= 1 then
local maxPetLevel = tonumber(row.maxPetLevel)
if numericPet and maxPetLevel then
if numericPet > maxPetLevel then
return true
end
else
return true
end
end
local wrongFoodCount = tonumber(reasons.wrongFood) or 0
if wrongFoodCount < 2 then
return false
@@ -1144,6 +1200,49 @@ function MTH_FEED_IsExceptionReject(itemId)
return true
end
function MTH_FEED_BlockItemForPetLevel(itemId, petLevel, reason)
local key = tonumber(itemId)
local numericPet = tonumber(petLevel)
if not key or not numericPet then
return false
end
if numericPet < 1 then
numericPet = 1
end
local store = MTH_FEED_EnsureStore()
if type(store.exceptions.byItemId[key]) ~= "table" then
store.exceptions.byItemId[key] = {
anomalyReject = true,
reasons = { noBuff = 0, wrongFood = 0, lowLevel = 0, unknown = 0 },
lastObservedAt = 0,
}
end
local row = store.exceptions.byItemId[key]
if type(row.reasons) ~= "table" then
row.reasons = { noBuff = 0, wrongFood = 0, lowLevel = 0, unknown = 0 }
end
row.anomalyReject = true
row.lastObservedAt = MTH_FEED_Now()
if tonumber(row.blockAtOrAbovePetLevel) == nil or numericPet < tonumber(row.blockAtOrAbovePetLevel) then
row.blockAtOrAbovePetLevel = numericPet
end
local normalizedReason = MTH_FEED_NormalizeReason(reason, nil)
if normalizedReason == "no-buff" then
row.reasons.noBuff = (tonumber(row.reasons.noBuff) or 0) + 1
elseif normalizedReason == "low-level" then
row.reasons.lowLevel = (tonumber(row.reasons.lowLevel) or 0) + 1
elseif normalizedReason == "wrong-food" then
row.reasons.wrongFood = (tonumber(row.reasons.wrongFood) or 0) + 1
else
row.reasons.unknown = (tonumber(row.reasons.unknown) or 0) + 1
end
MTH_FEED_TouchStore(store)
MTH_FEED_Trace("BlockItemForPetLevel itemId=" .. tostring(key)
.. " blockAtOrAbove=" .. tostring(row.blockAtOrAbovePetLevel)
.. " reason=" .. tostring(normalizedReason))
return true
end
function MTH_FEED_GetPetFeedStats(petId)
local key = tostring(petId or "")
if key == "" then
@@ -1153,7 +1252,7 @@ function MTH_FEED_GetPetFeedStats(petId)
return store.byPetId[key]
end
local function MTH_FEED_UpsertException(store, itemId, reason)
local function MTH_FEED_UpsertException(store, itemId, reason, petLevel)
if not itemId then
return
end
@@ -1175,6 +1274,16 @@ local function MTH_FEED_UpsertException(store, itemId, reason)
row.reasons.wrongFood = (tonumber(row.reasons.wrongFood) or 0) + 1
elseif reason == "low-level" then
row.reasons.lowLevel = (tonumber(row.reasons.lowLevel) or 0) + 1
local numericPetLevel = tonumber(petLevel)
if numericPetLevel and numericPetLevel > 1 then
local derivedMaxPetLevel = numericPetLevel - 1
if derivedMaxPetLevel < 1 then
derivedMaxPetLevel = 1
end
if tonumber(row.maxPetLevel) == nil or derivedMaxPetLevel < tonumber(row.maxPetLevel) then
row.maxPetLevel = derivedMaxPetLevel
end
end
else
row.reasons.unknown = (tonumber(row.reasons.unknown) or 0) + 1
end
@@ -1182,6 +1291,36 @@ local function MTH_FEED_UpsertException(store, itemId, reason)
row.lastObservedAt = MTH_FEED_Now()
end
local function MTH_FEED_ClearStaleExceptionOnAccept(store, itemId)
if not itemId or type(store) ~= "table" or type(store.exceptions) ~= "table" or type(store.exceptions.byItemId) ~= "table" then
return
end
local key = tonumber(itemId) or itemId
local row = store.exceptions.byItemId[key]
if type(row) ~= "table" then
return
end
if type(row.reasons) ~= "table" then
store.exceptions.byItemId[key] = nil
return
end
row.reasons.noBuff = 0
row.lastObservedAt = MTH_FEED_Now()
local noBuff = tonumber(row.reasons.noBuff) or 0
local lowLevel = tonumber(row.reasons.lowLevel) or 0
local wrongFood = tonumber(row.reasons.wrongFood) or 0
local unknown = tonumber(row.reasons.unknown) or 0
if noBuff <= 0
and lowLevel <= 0
and wrongFood <= 0
and unknown <= 0
and tonumber(row.blockAtOrAbovePetLevel) == nil
and tonumber(row.maxPetLevel) == nil then
store.exceptions.byItemId[key] = nil
end
end
function MTH_FEED_FinalizeAttempt(attemptId, payload)
local row = MTH_FEED_Runtime.attempts[attemptId]
if type(row) ~= "table" then
@@ -1241,6 +1380,7 @@ function MTH_FEED_FinalizeAttempt(attemptId, payload)
if itemRow then
itemRow.accepted = itemRow.accepted + 1
end
MTH_FEED_ClearStaleExceptionOnAccept(store, row.itemId)
local petKey = tostring((petRow and petRow.petId) or row.petId or "")
if petKey ~= "" then
MTH_FEED_Runtime.sessionFeedsByPetId[petKey] = (tonumber(MTH_FEED_Runtime.sessionFeedsByPetId[petKey]) or 0) + 1
@@ -1257,7 +1397,7 @@ function MTH_FEED_FinalizeAttempt(attemptId, payload)
if itemRow then
itemRow.rejected = itemRow.rejected + 1
end
MTH_FEED_UpsertException(store, row.itemId, reason)
MTH_FEED_UpsertException(store, row.itemId, reason, row.petLevel)
end
if itemRow then
@@ -1280,9 +1420,6 @@ function MTH_FEED_FinalizeAttempt(attemptId, payload)
persistedIcon = directTexture
end
end
if (not persistedIcon or persistedIcon == "") then
persistedIcon = MTH_FEED_FindItemIconInBags(row.itemId, row.itemName)
end
if persistedIcon and persistedIcon ~= "" then
itemRow.itemIcon = persistedIcon
end
@@ -1325,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
@@ -1542,6 +1703,5 @@ function MTH_FEED_DebugDumpCurrentPet(limit)
return true
end
MTH_FEED_EnsureTrackerFrame()
MTH_FEED_InstallCoreHooks()
MTH_FEED_EnsureBootstrapFrame()
+147 -109
View File
@@ -1,5 +1,5 @@
MTH = MTH or {
version = "1.0.4",
version = "1.4.1",
name = "MetaHunt",
modules = {},
config = {},
@@ -16,6 +16,14 @@ local MTH_MESSAGE_DEFAULTS = {
spellbookScan = false,
petRanAway = true,
mapMarkers = true,
stableScan = true,
}
local MTH_MODULE_DEFAULT_STATES = {
autoquest = false,
autobuy = false,
feedomatic = false,
icu = false,
}
local function MTH_ClassGateTrace(_step, _detail)
@@ -36,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
@@ -47,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
@@ -184,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
@@ -260,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
@@ -417,16 +414,57 @@ 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
local resolvedName = MTH_NormalizeModuleName(name)
local candidates = MTH_ModuleNameCandidates(name)
if type(MTH_CharSavedVariables) ~= "table" then
MTH_CharSavedVariables = {}
end
if type(MTH_CharSavedVariables.moduleStates) ~= "table" then
MTH_CharSavedVariables.moduleStates = {}
end
if type(MTH_CharSavedVariables.modules) ~= "table" then
MTH_CharSavedVariables.modules = {}
end
if type(MTH_CharSavedVariables.modules[resolvedName]) ~= "table" then
MTH_CharSavedVariables.modules[resolvedName] = {}
end
if type(MTH_SavedVariables) ~= "table" then
MTH_ModuleStateDebug("read " .. tostring(name) .. " no-savedvars fallback-default=" .. tostring(defaultEnabled and true or false))
return defaultEnabled and true or false
MTH_SavedVariables = {}
end
if type(MTH_SavedVariables.moduleStates) ~= "table" then
MTH_SavedVariables.moduleStates = {}
end
if type(MTH_SavedVariables.modules) ~= "table" then
MTH_SavedVariables.modules = {}
end
local hardDefault = MTH_MODULE_DEFAULT_STATES[resolvedName]
if hardDefault ~= nil then
local hasCharState = MTH_CharSavedVariables.moduleStates[resolvedName] ~= nil
or MTH_CharSavedVariables.modules[resolvedName].enabled ~= nil
if not hasCharState then
local resolved = hardDefault and true or false
MTH_CharSavedVariables.moduleStates[resolvedName] = resolved
MTH_CharSavedVariables.modules[resolvedName].enabled = resolved
return resolved
end
end
if type(MTH_CharSavedVariables.moduleStates) == "table"
then
for i = 1, table.getn(candidates) do
local key = candidates[i]
if MTH_CharSavedVariables.moduleStates[key] ~= nil then
local resolved = MTH_CharSavedVariables.moduleStates[key] and true or false
MTH_CharSavedVariables.moduleStates[resolvedName] = resolved
return resolved
end
end
end
if type(MTH_SavedVariables.moduleStates) == "table" then
@@ -434,26 +472,11 @@ 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_SavedVariables.moduleStates[resolvedName] = resolved
return resolved
end
end
end
if type(MTH_CharSavedVariables) == "table"
and type(MTH_CharSavedVariables.moduleStates) == "table"
then
for i = 1, table.getn(candidates) do
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))
if type(MTH_SavedVariables.moduleStates) ~= "table" then
MTH_SavedVariables.moduleStates = {}
end
MTH_SavedVariables.moduleStates[resolvedName] = resolved
MTH_CharSavedVariables.moduleStates[resolvedName] = resolved
if type(MTH_CharSavedVariables.modules[resolvedName]) ~= "table" then
MTH_CharSavedVariables.modules[resolvedName] = {}
end
MTH_CharSavedVariables.modules[resolvedName].enabled = resolved
return resolved
end
end
@@ -464,10 +487,11 @@ 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))
if type(MTH_SavedVariables.modules[resolvedName]) ~= "table" then
MTH_SavedVariables.modules[resolvedName] = MTH_SavedVariables.modules[key]
MTH_CharSavedVariables.moduleStates[resolvedName] = resolved
if type(MTH_CharSavedVariables.modules[resolvedName]) ~= "table" then
MTH_CharSavedVariables.modules[resolvedName] = {}
end
MTH_CharSavedVariables.modules[resolvedName].enabled = resolved
return resolved
end
end
@@ -477,22 +501,15 @@ 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))
if type(MTH_SavedVariables.modules) ~= "table" then
MTH_SavedVariables.modules = {}
MTH_CharSavedVariables.moduleStates[resolvedName] = resolved
if type(MTH_CharSavedVariables.modules[resolvedName]) ~= "table" then
MTH_CharSavedVariables.modules[resolvedName] = {}
end
if type(MTH_SavedVariables.modules[resolvedName]) ~= "table" then
MTH_SavedVariables.modules[resolvedName] = MTH_SavedVariables[key]
end
if type(MTH_SavedVariables.moduleStates) ~= "table" then
MTH_SavedVariables.moduleStates = {}
end
MTH_SavedVariables.moduleStates[resolvedName] = resolved
MTH_CharSavedVariables.modules[resolvedName].enabled = resolved
return resolved
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
@@ -506,28 +523,22 @@ local function MTH_SetPersistedModuleEnabled(name, enabled)
if type(MTH_SavedVariables) ~= "table" then
MTH_SavedVariables = {}
end
if type(MTH_SavedVariables.moduleStates) ~= "table" then
MTH_SavedVariables.moduleStates = {}
end
if type(MTH_SavedVariables.modules) ~= "table" then
MTH_SavedVariables.modules = {}
end
if type(MTH_SavedVariables.modules[name]) ~= "table" then
MTH_SavedVariables.modules[name] = {}
end
if type(MTH_CharSavedVariables) ~= "table" then
MTH_CharSavedVariables = {}
end
if type(MTH_CharSavedVariables.moduleStates) ~= "table" then
MTH_CharSavedVariables.moduleStates = {}
end
if type(MTH_CharSavedVariables.modules) ~= "table" then
MTH_CharSavedVariables.modules = {}
end
if type(MTH_CharSavedVariables.modules[name]) ~= "table" then
MTH_CharSavedVariables.modules[name] = {}
end
enabled = enabled and true or false
MTH_SavedVariables.moduleStates[name] = enabled
MTH_SavedVariables.modules[name].enabled = enabled
MTH_SavedVariables[name] = MTH_SavedVariables.modules[name]
MTH_CharSavedVariables.moduleStates[name] = enabled
MTH_ModuleStateDebug("write " .. tostring(name) .. " enabled=" .. tostring(enabled))
MTH_CharSavedVariables.modules[name].enabled = enabled
end
function MTH:InitEventRouter()
@@ -535,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)
@@ -598,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
@@ -607,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
@@ -642,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
@@ -657,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)
@@ -682,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
@@ -747,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
@@ -769,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
@@ -1008,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
@@ -1056,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)
@@ -1243,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)
@@ -1265,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
@@ -1460,6 +1460,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
@@ -1490,24 +1530,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
+34 -6
View File
@@ -20,6 +20,7 @@ end
local MTH_PT_ApplyScan
local MTH_PT_AbilityHasPositiveRanks
local MTH_PT_PromoteUnrankedKnownAliases
local MTH_PT_BootstrapFrame = nil
local MTH_PT_VERBOSE_SCAN_LOGS = false
@@ -622,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
@@ -801,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")
@@ -812,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
@@ -846,6 +844,35 @@ function MTH_PT_InitService()
end)
end
function MTH_PT_InitBootstrap()
if MTH and MTH.ApplyClassGate and MTH:ApplyClassGate("trainscan-bootstrap") then
return
end
if MTH_PetTrainingFrame or MTH_PT_BootstrapFrame then
return
end
local frame = CreateFrame("Frame")
if not frame then
return
end
frame:RegisterEvent("PET_TRAINING_SHOW")
frame:RegisterEvent("CRAFT_SHOW")
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, event)
end
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
MTH_PT_BootstrapFrame = nil
end)
MTH_PT_BootstrapFrame = frame
end
function MTH_PT_ShutdownService(_reason)
if not MTH_PetTrainingFrame then
return
@@ -929,3 +956,4 @@ end
MTH_TR_InitService = MTH_PT_InitService
MTH_TR_ShutdownService = MTH_PT_ShutdownService
MTH_TR_InitBootstrap = MTH_PT_InitBootstrap
+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()
+85 -2
View File
@@ -7,6 +7,25 @@ local MTH_PS_LastScanAt = 0
local MTH_PS_VERBOSE_LOGS = false
local MTH_PS_ForceScanUntil = 0
local MTH_PS_ForceScanBudget = 0
local MTH_PS_EventThrottle = {}
local MTH_PS_LastNoPetScanAt = 0
local function MTH_PS_IsPlayerDeadOrGhost()
if type(UnitIsDeadOrGhost) == "function" then
return UnitIsDeadOrGhost("player") and true or false
end
if type(UnitIsDead) == "function" then
return UnitIsDead("player") and true or false
end
return false
end
local function MTH_PS_HasLivePet()
if type(UnitExists) ~= "function" then
return false
end
return UnitExists("pet") and true or false
end
local function MTH_PS_ClearForceWindow()
MTH_PS_ForceScanUntil = 0
@@ -26,9 +45,25 @@ end
local function MTH_PS_IsFollowupEventTrigger(trigger)
local t = tostring(trigger or "")
return t == "event:UNIT_PET"
or t == "UNIT_PET"
or t == "event:PET_BAR_UPDATE"
or t == "PET_BAR_UPDATE"
or t == "event:SPELLS_CHANGED"
or t == "SPELLS_CHANGED"
or t == "event:LEARNED_SPELL_IN_TAB"
or t == "LEARNED_SPELL_IN_TAB"
end
local function MTH_PS_ShouldHandleEvent(evt, minIntervalSeconds)
local now = tonumber(GetTime and GetTime() or time()) or 0
local key = tostring(evt or "")
local minInterval = tonumber(minIntervalSeconds) or 0
local last = tonumber(MTH_PS_EventThrottle[key] or 0) or 0
if minInterval > 0 and (now - last) < minInterval then
return false
end
MTH_PS_EventThrottle[key] = now
return true
end
local function MTH_PS_IsPriorityTrigger(trigger)
@@ -190,6 +225,41 @@ function MTH_PSP_RequestScan(trigger, minIntervalSeconds)
local minInterval = tonumber(minIntervalSeconds) or 1
local bypassThrottle = false
local bypassReason = ""
local deadOrGhost = MTH_PS_IsPlayerDeadOrGhost()
local hasLivePet = MTH_PS_HasLivePet()
if deadOrGhost then
if triggerText == "PET_BAR_UPDATE" or triggerText == "event:PET_BAR_UPDATE"
or triggerText == "SPELLS_CHANGED" or triggerText == "event:SPELLS_CHANGED"
or triggerText == "LEARNED_SPELL_IN_TAB" or triggerText == "event:LEARNED_SPELL_IN_TAB"
then
MTH_PS_TraceTrigger(triggerText, false, "dead-skip")
return false
end
end
if not hasLivePet then
if MTH_PS_IsFollowupEventTrigger(triggerText) then
MTH_PS_TraceTrigger(triggerText, false, "no-pet-followup")
return false
end
if triggerText ~= "manual"
and triggerText ~= "PLAYER_ENTERING_WORLD"
and triggerText ~= "event:PLAYER_ENTERING_WORLD"
and triggerText ~= "UNIT_PET"
and triggerText ~= "event:UNIT_PET"
then
MTH_PS_TraceTrigger(triggerText, false, "no-pet")
return false
end
if (now - (tonumber(MTH_PS_LastNoPetScanAt) or 0)) < 5 then
MTH_PS_TraceTrigger(triggerText, false, "no-pet-throttled")
return false
end
MTH_PS_LastNoPetScanAt = now
else
MTH_PS_LastNoPetScanAt = 0
end
if MTH_PS_IsPriorityTrigger(triggerText) then
bypassThrottle = true
@@ -237,7 +307,19 @@ local function MTH_PS_OnEvent(_, evt, eventArg1)
end
if evt == "PLAYER_ENTERING_WORLD" or evt == "UNIT_PET" or evt == "PET_BAR_UPDATE" or evt == "SPELLS_CHANGED" or evt == "LEARNED_SPELL_IN_TAB" then
MTH_PSP_RequestScan("event:" .. tostring(evt), 1)
local minInterval = 1
if evt == "PET_BAR_UPDATE" then
minInterval = 4
elseif evt == "SPELLS_CHANGED" then
minInterval = 4
elseif evt == "LEARNED_SPELL_IN_TAB" then
minInterval = 1.5
elseif evt == "UNIT_PET" then
minInterval = 1
end
if MTH_PS_ShouldHandleEvent(evt, minInterval) then
MTH_PSP_RequestScan(evt, minInterval)
end
end
end
@@ -249,7 +331,7 @@ function MTH_PS_InitService()
return
end
MTH_PS_Frame = CreateFrame("Frame", "MTHPetSpellScanFrame")
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")
@@ -266,6 +348,7 @@ function MTH_PS_ShutdownService(_reason)
MTH_PS_Frame:SetScript("OnEvent", nil)
MTH_PS_Frame = nil
MTH_PS_LastScanAt = 0
MTH_PS_LastNoPetScanAt = 0
MTH_PS_ClearForceWindow()
end
File diff suppressed because it is too large Load Diff
+198 -18
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
@@ -129,6 +129,49 @@ local function MTH_CommandOptions()
end
end
local function MTH_FormatAgeSeconds(seconds)
local value = tonumber(seconds) or 0
if value < 0 then value = 0 end
if value < 60 then
return tostring(math.floor(value)) .. "s"
end
if value < 3600 then
return tostring(math.floor(value / 60)) .. "m"
end
if value < 86400 then
return tostring(math.floor(value / 3600)) .. "h"
end
return tostring(math.floor(value / 86400)) .. "d"
end
local function MTH_CommandPeers()
if not (MTH and MTH.VersionCheck and type(MTH.VersionCheck.GetTrackedPeers) == "function") then
MTH:Print("Peer tracker is not available yet")
return
end
local peers = MTH.VersionCheck:GetTrackedPeers()
local count = table.getn(peers)
if count <= 0 then
MTH:Print("No MetaHunt broadcasters seen yet on LFT")
return
end
MTH:Print("MetaHunt broadcasters seen on LFT: " .. tostring(count))
local now = (type(time) == "function" and time()) or (type(GetTime) == "function" and math.floor(GetTime())) or 0
local maxRows = math.min(count, 20)
for i = 1, maxRows do
local peer = peers[i]
if type(peer) == "table" then
local age = MTH_FormatAgeSeconds((now or 0) - (tonumber(peer.lastSeenAt) or 0))
MTH:Print("- " .. tostring(peer.name or "?") .. " v" .. tostring(peer.versionText or "?") .. " (seen " .. tostring(age) .. " ago)")
end
end
if count > maxRows then
MTH:Print("... and " .. tostring(count - maxRows) .. " more")
end
end
local function MTH_CommandPetSpellScanFallback()
local getSpellName = (type(getglobal) == "function" and getglobal("GetSpellName")) or (_G and _G["GetSpellName"])
local getSpellTexture = (type(getglobal) == "function" and getglobal("GetSpellTexture")) or (_G and _G["GetSpellTexture"])
@@ -197,6 +240,34 @@ local function MTH_CommandPetSpellScanFallback()
end
end
local function MTH_CommandFoodData(limitText)
if type(FOM_PrintCollectedFoodData) ~= "function" then
MTH:Print("Food data command is not available yet")
return
end
local trimmed = string.gsub(tostring(limitText or ""), "^%s+", "")
trimmed = string.gsub(trimmed, "%s+$", "")
if string.lower(trimmed) == "purge" then
if type(FOM_PruneCollectedFoodData) ~= "function" then
MTH:Print("Food data purge is not available yet")
return
end
local removed = tonumber(FOM_PruneCollectedFoodData()) or 0
MTH:Print("Food data purge removed rows: " .. tostring(removed))
return
end
if trimmed == "" then
FOM_PrintCollectedFoodData(nil)
return
end
local limit = tonumber(trimmed)
if not limit then
MTH:Print("Usage: /mth food [limit|purge]")
return
end
FOM_PrintCollectedFoodData(limit)
end
if type(MTH_PS_ScanNow) ~= "function" then
function MTH_PS_ScanNow(trigger)
local ok, count = MTH_CommandPetSpellScanFallback()
@@ -239,33 +310,142 @@ function SlashCmdList.MTH(msg, editbox)
msg = string.gsub(tostring(msg), "^%s+", "")
msg = string.gsub(msg, "%s+$", "")
local lowerMsg = string.lower(msg)
local _, _, lowerCmd, lowerArg = string.find(lowerMsg, "^(%S+)%s*(.-)%s*$")
if msg == "" then
MTH:Print("Available: /mth options, /mth book")
elseif lowerMsg == "err" or lowerMsg == "errors" or lowerMsg == "debug" then
if MTH_DebugFrame and MTH_DebugFrame.Toggle then
MTH_DebugFrame:Toggle()
else
MTH:Print("Debug frame is not available.")
end
elseif lowerMsg == "options" then
MTH_CommandOptions()
elseif lowerMsg == "book" or lowerMsg == "hunterbook" then
MTH_CommandBook()
elseif lowerMsg == "err" then
if MTH_DebugFrame and type(MTH_DebugFrame.Toggle) == "function" then
MTH_DebugFrame:Toggle()
elseif lowerMsg == "peers" or lowerMsg == "who" then
MTH_CommandPeers()
elseif lowerMsg == "zoneinfo" then
-- Dumps all available zone data for the current zone, for adding to ds-zones / ds-minimap-sizes
pcall(SetMapToCurrentZone)
local zoneName = (type(GetRealZoneText) == "function" and GetRealZoneText()) or ""
local subZone = (type(GetSubZoneText) == "function" and GetSubZoneText()) or ""
local zoneText = (type(GetZoneText) == "function" and GetZoneText()) or ""
local areaId = nil
if type(GetCurrentMapAreaID) == "function" then
local ok, v = pcall(GetCurrentMapAreaID)
if ok then areaId = v end
end
local cx, cy = nil, nil
if type(GetPlayerMapPosition) == "function" then
local ok, x, y = pcall(GetPlayerMapPosition, "player")
if ok then cx, cy = x, y end
end
local contId = nil
if type(GetCurrentMapContinent) == "function" then
local ok, v = pcall(GetCurrentMapContinent)
if ok then contId = v end
end
MTH:Print("|cffffff00=== Zone Info ===|r")
MTH:Print("GetRealZoneText: |cffffaa00" .. tostring(zoneName) .. "|r")
MTH:Print("GetZoneText: |cffffaa00" .. tostring(zoneText) .. "|r")
MTH:Print("GetSubZoneText: |cffffaa00" .. tostring(subZone) .. "|r")
MTH:Print("AreaID (GetCurrentMapAreaID): |cff88ffff" .. tostring(areaId) .. "|r")
MTH:Print("ContinentID: |cff88ffff" .. tostring(contId) .. "|r")
if cx and cy then
MTH:Print(string.format("Player map pos: |cff88ffff%.4f, %.4f|r (display: %.1f, %.1f)", cx, cy, cx*100, cy*100))
end
-- Also check if zone is in our tables
local inZones = MTH_DS_Zones and MTH_DS_Zones[tonumber(areaId)] and "YES" or "not in MTH_DS_Zones"
local inSizes = MTH_DS_MinimapSizes and MTH_DS_MinimapSizes[tonumber(areaId)] and "YES" or "not in ds-minimap-sizes"
MTH:Print("MTH_DS_Zones entry: " .. inZones)
MTH:Print("MTH_DS_MinimapSizes entry: " .. inSizes)
elseif lowerMsg == "npcid" then
-- Print the creature entry ID of the current target, or arm a listener for next NPC interaction
local function MTH_ParseCreatureEntryFromAnyGuid(guid)
if type(guid) ~= "string" or guid == "" then return nil end
-- Format 1: TBC-style "Creature-realm-map-inst-?-ENTRY-spawn"
local _, _, id1 = string.find(guid, "^Creature%-%d+%-%d+%-%d+%-%d+%-(%d+)%-%d+$")
if id1 then return tonumber(id1) end
-- Format 2: vanilla hex "0xF130EEEEEECCCCCC" — entry is bits 24-47 = hex chars 7-12
if string.sub(guid, 1, 2) == "0x" and string.len(guid) >= 14 then
local entryHex = string.sub(guid, 7, 12) -- 24 bits = up to 16M
local parsed = tonumber(entryHex, 16)
if parsed and parsed > 0 then return parsed end
end
return nil
end
local function MTH_PrintNpcId(unitToken)
unitToken = unitToken or "target"
local name = (type(UnitName) == "function") and UnitName(unitToken) or "?"
local level = (type(UnitLevel) == "function") and UnitLevel(unitToken) or "?"
-- Try UnitGUID first, then GetUnitGUID (TurtleWoW nameplate API)
local guid = nil
if type(UnitGUID) == "function" then
local ok, g = pcall(UnitGUID, unitToken)
if ok and g and g ~= "" then guid = g end
end
if (not guid or guid == "") and type(GetUnitGUID) == "function" then
local ok, g = pcall(GetUnitGUID, unitToken)
if ok and g and g ~= "" then guid = g end
end
local creatureId = MTH_ParseCreatureEntryFromAnyGuid(guid)
if creatureId then
MTH:Print(string.format("|cffffff00NPC:|r %s |cffffff00ID:|r |cff88ffff%s|r lvl: %s", tostring(name), tostring(creatureId), tostring(level)))
else
MTH:Print(string.format("|cffffff00NPC:|r %s lvl: %s |cffff8800GUID: %s|r", tostring(name), tostring(level), tostring(guid or "nil")))
MTH:Print("|cffaaaaaa(Could not parse creature ID from GUID)|r")
end
end
-- Try current target first
local hasTarget = (type(UnitExists) == "function") and UnitExists("target")
if hasTarget then
MTH_PrintNpcId("target")
end
-- Arm a one-shot frame for MERCHANT_SHOW / TRAINER_LIST_UPDATE / GOSSIP_SHOW
if not _G["MTH_NpcIdListenerFrame"] then
local f = CreateFrame("Frame", "MTH_NpcIdListenerFrame")
f:RegisterEvent("MERCHANT_SHOW")
f:RegisterEvent("TRAINER_LIST_UPDATE")
f:RegisterEvent("GOSSIP_SHOW")
f:SetScript("OnEvent", function()
MTH_PrintNpcId("target")
this:UnregisterEvent("MERCHANT_SHOW")
this:UnregisterEvent("TRAINER_LIST_UPDATE")
this:UnregisterEvent("GOSSIP_SHOW")
_G["MTH_NpcIdListenerFrame"] = nil
end)
if not hasTarget then
MTH:Print("|cffaaffaaArmed.|r Open any vendor, trainer, or stable master to capture their ID.")
end
end
elseif lowerCmd == "bls" then
if lowerArg == "reset" then
if type(MTH_SavedVariables) == "table" then
MTH_SavedVariables.beastLoreScan = { entries = {} }
end
MTH:Print("|cffff4444Beast Lore scan data wiped.|r All recorded beasts cleared.")
else
MTH:Print("Debug frame is not available yet")
MTH:Print("Beast Lore Scan: |cffffff00/mth bls reset|r — wipe all recorded beast data")
end
else
MTH:Print("Unknown command: " .. tostring(msg))
MTH:Print("Available: /mth options, /mth book")
MTH:Print("Available: /mth options, /mth book, /mth npcid, /mth bls reset")
end
end
-- Initialize on load
MTH:InitSavedVariables()
if type(MTH_ST_InitService) == "function" then
MTH_ST_InitService()
if type(MTH_ST_InitBootstrap) == "function" then
MTH_ST_InitBootstrap()
end
if type(MTH_TR_InitService) == "function" then
MTH_TR_InitService()
elseif type(MTH_PT_InitService) == "function" then
MTH_PT_InitService()
if type(MTH_TR_InitBootstrap) == "function" then
MTH_TR_InitBootstrap()
elseif type(MTH_PT_InitBootstrap) == "function" then
MTH_PT_InitBootstrap()
end
if type(MTH_PS_InitService) == "function" then
MTH_PS_InitService()
+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
+95 -38
View File
@@ -7,13 +7,12 @@ MTH_HUNTERBOOK_TABS.families = {
{ x = 10, width = 106, align = "LEFT" },
{ x = 122, width = 52, align = "LEFT" },
{ x = 176, width = 52, align = "LEFT" },
{ x = 232, width = 292, align = "LEFT" },
{ x = 460, width = 184, align = "LEFT" },
{ x = 232, width = 364, align = "LEFT" },
{ x = 532, width = 112, align = "LEFT" },
},
}
local function MTH_BOOKTAB_FamiliesTrace(message)
return
end
local function MTH_BOOKTAB_FamiliesSafeLower(value)
@@ -27,6 +26,11 @@ local function MTH_BOOKTAB_FamiliesTrim(value)
return text
end
local function MTH_BOOKTAB_ShouldDisplayAllDiet(familyName)
local token = MTH_BOOKTAB_FamiliesSafeLower(MTH_BOOKTAB_FamiliesTrim(familyName))
return token == "bears" or token == "boars"
end
local function MTH_BOOKTAB_FindAbilityBundle(abilityName)
if not (MTH_DS_PetSpells and type(MTH_DS_PetSpells.byAbility) == "table") then
return nil
@@ -78,6 +82,7 @@ function MTH_BOOKTAB_BuildFamiliesRows()
for familyName, familyRow in pairs(families) do
if type(familyRow) == "table" then
MTH_BOOKTAB_FamiliesTrace("BUILD " .. tostring(familyName) .. " icon=" .. tostring(familyRow.icon))
local abilities = {}
local seen = {}
@@ -95,25 +100,49 @@ function MTH_BOOKTAB_BuildFamiliesRows()
if token ~= "" and not seen[token] then
seen[token] = true
local spellRows = (type(bundle) == "table" and type(bundle.spells) == "table") and bundle.spells or {}
local firstAnySpell = nil
local firstBeastSpell = nil
local rankMin = nil
local rankMax = nil
local rankMap = {}
local beastRankMin = nil
local beastRankMax = nil
local beastRankMap = {}
local anyRankMin = nil
local anyRankMax = nil
local anyRankMap = {}
for s = 1, table.getn(spellRows) do
local spell = spellRows[s]
if type(spell) == "table" and MTH_BOOKTAB_FamiliesSafeLower(spell.learnMethod or "beast") ~= "trainer" then
if not firstBeastSpell then
firstBeastSpell = spell
if type(spell) == "table" then
if not firstAnySpell then
firstAnySpell = spell
end
local rank = tonumber(spell.rankNumber)
if rank and rank > 0 then
rankMap[rank] = true
if not rankMin or rank < rankMin then rankMin = rank end
if not rankMax or rank > rankMax then rankMax = rank end
anyRankMap[rank] = true
if not anyRankMin or rank < anyRankMin then anyRankMin = rank end
if not anyRankMax or rank > anyRankMax then anyRankMax = rank end
end
if MTH_BOOKTAB_FamiliesSafeLower(spell.learnMethod or "beast") ~= "trainer" then
if not firstBeastSpell then
firstBeastSpell = spell
end
if rank and rank > 0 then
beastRankMap[rank] = true
if not beastRankMin or rank < beastRankMin then beastRankMin = rank end
if not beastRankMax or rank > beastRankMax then beastRankMax = rank end
end
end
end
end
local displaySpell = firstBeastSpell or firstAnySpell
local rankMap = beastRankMap
local rankMin = beastRankMin
local rankMax = beastRankMax
if not firstBeastSpell then
rankMap = anyRankMap
rankMin = anyRankMin
rankMax = anyRankMax
end
local rankCount = 0
for _ in pairs(rankMap) do
rankCount = rankCount + 1
@@ -123,8 +152,8 @@ function MTH_BOOKTAB_BuildFamiliesRows()
name = canonicalMap[token] or canonical,
token = token,
isUnique = (abilityCounts[token] or 0) == 1,
icon = firstBeastSpell and firstBeastSpell.icon or nil,
description = firstBeastSpell and firstBeastSpell.description or "",
icon = displaySpell and displaySpell.icon or nil,
description = displaySpell and displaySpell.description or "",
rankMin = rankMin,
rankMax = rankMax,
rankCount = rankCount,
@@ -141,25 +170,32 @@ function MTH_BOOKTAB_BuildFamiliesRows()
return MTH_BOOKTAB_FamiliesSafeLower(a.name) < MTH_BOOKTAB_FamiliesSafeLower(b.name)
end)
local dietItems = {}
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
table.insert(dietItems, string.upper(string.sub(food, 1, 1)) .. string.sub(food, 2))
local dietText = ""
if MTH_BOOKTAB_ShouldDisplayAllDiet(familyName) then
dietText = "ALL"
else
local dietItems = {}
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
table.insert(dietItems, string.upper(string.sub(food, 1, 1)) .. string.sub(food, 2))
end
end
table.sort(dietItems, function(a, b)
return MTH_BOOKTAB_FamiliesSafeLower(a) < MTH_BOOKTAB_FamiliesSafeLower(b)
end)
end
table.sort(dietItems, function(a, b)
return MTH_BOOKTAB_FamiliesSafeLower(a) < MTH_BOOKTAB_FamiliesSafeLower(b)
end)
dietText = table.concat(dietItems, ", ")
end
table.insert(results, {
family = tostring(familyName),
icon = familyRow.icon or nil,
named = tonumber(familyRow.named) or 0,
coords = tonumber(familyRow.coords) or 0,
abilities = abilities,
dietText = table.concat(dietItems, ", "),
dietText = dietText,
})
end
end
@@ -238,7 +274,7 @@ function MTH_BOOKTAB_EnsureFamiliesUI()
ui.headerAbilities:SetText("Abilities")
ui.headerDiet = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerDiet:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 460, -2)
ui.headerDiet:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 532, -2)
ui.headerDiet:SetTextColor(1.00, 0.82, 0.00)
ui.headerDiet:SetText("Diet")
@@ -254,9 +290,15 @@ function MTH_BOOKTAB_EnsureFamiliesUI()
row:SetPoint("TOPRIGHT", ui.rows[i - 1], "BOTTOMRIGHT", 0, -1)
end
row.familyIcon = row:CreateTexture(nil, "ARTWORK")
row.familyIcon:SetPoint("LEFT", row, "LEFT", 4, 0)
row.familyIcon:SetWidth(16)
row.familyIcon:SetHeight(16)
row.familyIcon:Hide()
row.family = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.family:SetPoint("LEFT", row, "LEFT", 4, 0)
row.family:SetWidth(106)
row.family:SetPoint("LEFT", row, "LEFT", 24, 0)
row.family:SetWidth(88)
row.family:SetJustifyH("LEFT")
row.named = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
@@ -270,8 +312,8 @@ function MTH_BOOKTAB_EnsureFamiliesUI()
row.coords:SetJustifyH("LEFT")
row.diet = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.diet:SetPoint("LEFT", row, "LEFT", 460, 0)
row.diet:SetWidth(184)
row.diet:SetPoint("LEFT", row, "LEFT", 532, 0)
row.diet:SetWidth(112)
row.diet:SetJustifyH("LEFT")
row.abilityButtons = {}
@@ -358,11 +400,20 @@ 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 "-"))
local offsetX = 232
local abilityMaxRight = 526
for b = 1, table.getn(rowFrame.abilityButtons) do
local button = rowFrame.abilityButtons[b]
local ability = row.abilities and row.abilities[b] or nil
@@ -375,17 +426,23 @@ function MTH_BOOKTAB_RenderFamiliesList()
button.text:SetText(nameText)
local textW = button.text:GetStringWidth() or 40
if textW < 24 then textW = 24 end
button:SetWidth(14 + textW)
button:ClearAllPoints()
button:SetPoint("LEFT", rowFrame, "LEFT", offsetX, 0)
local iconPath = ability.icon and MTH_BOOK_ResolveIconPath and MTH_BOOK_ResolveIconPath(ability.icon) or nil
if iconPath then
button.icon:SetTexture(iconPath)
local buttonWidth = 14 + textW
if (offsetX + buttonWidth) > abilityMaxRight then
button.entry = nil
button:Hide()
else
button.icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
button:SetWidth(buttonWidth)
button:ClearAllPoints()
button:SetPoint("LEFT", rowFrame, "LEFT", offsetX, 0)
local iconPath = ability.icon and MTH_BOOK_ResolveIconPath and MTH_BOOK_ResolveIconPath(ability.icon) or nil
if iconPath then
button.icon:SetTexture(iconPath)
else
button.icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
end
button:Show()
offsetX = offsetX + button:GetWidth() + 6
end
button:Show()
offsetX = offsetX + button:GetWidth() + 6
else
button.entry = nil
button:Hide()
+1 -1
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
+507 -133
View File
@@ -55,7 +55,7 @@ local function MTH_BOOK_FamiliesTrace(message)
end
local MTH_BOOK_STATE = {
mode = "stable",
mode = "pets",
search = "",
quick = "all",
minLevel = nil,
@@ -69,6 +69,8 @@ local MTH_BOOK_STATE = {
petOnlyMyLevel = true,
itemOnlyMyLevel = false,
petInZoneOnly = false,
petContinent = "all",
petZoneFilter = "all",
itemSubtype = "all",
npcFunction = "all",
npcZone = "all",
@@ -150,7 +152,7 @@ local function MTH_HB_RequireOpenDeps()
return true
end
local MTH_BOOK_MAX_COLS = 9
local MTH_BOOK_MAX_COLS = 10
local MTH_BOOK_MAX_ROWS = 20
local MTH_BOOK_PetFamilyOptions = {
@@ -222,7 +224,8 @@ local MTH_BOOK_CONTENT_LAYOUT_DEFAULT = {
detailX = 608,
detailY = -166,
detailW = 136,
detailH = 324,
detailH = 208,
detailTextH = 192, -- inspector text only; model viewer sits below in its own frame
}
local MTH_BOOK_CONTENT_LAYOUT_ITEMS = {
@@ -273,7 +276,7 @@ local MTH_BOOK_CONTENT_LAYOUT_FAMILIES = {
listX = 16,
listY = -74,
listW = 728,
listH = 376,
listH = 456,
detailX = 748,
detailY = -74,
detailW = 1,
@@ -328,7 +331,7 @@ local function MTH_BOOK_ApplyContentLayoutForMode()
detailText:ClearAllPoints()
detailText:SetPoint("TOPLEFT", detailParent, "TOPLEFT", 6, -6)
detailText:SetWidth(layout.detailW - 12)
detailText:SetHeight(layout.detailH - 12)
detailText:SetHeight(layout.detailTextH or (layout.detailH - 12))
end
local sliderHeight = layout.listH - 32
@@ -363,7 +366,7 @@ local function MTH_BOOK_UpdateCheckboxLayoutByMode()
getglobal("MTH_BOOK_HideNoAbilities"),
getglobal("MTH_BOOK_HideUnknown"),
}
local xOffsets = { 18, 86, 154, 232, 372 }
local xOffsets = { 18, 86, 154, 232, 345 }
local yOffset = -140
if MTH_BOOK_STATE.mode == "npcs" then
yOffset = -116
@@ -753,12 +756,15 @@ local function MTH_BOOK_ShowAllFilteredBeastsOnMap()
end
local nodes = {}
local firstZoneId = nil
for i = 1, table.getn(MTH_BOOK_STATE.results or {}) do
local beastId = tonumber(MTH_BOOK_STATE.results[i])
local beast = beastId and MTH_DS_Beasts and MTH_DS_Beasts[beastId]
if beast and beast.coords then
local beastDisplayName = (MTH and MTH.GetLocalizedBeastName and MTH:GetLocalizedBeastName(beastId, beast.name)) or beast.name or "Unknown"
local cr, cg, cb = 0.95, 0.82, 0.10
if MTH_Map and MTH_Map.GetFamilyColor then
cr, cg, cb = MTH_Map:GetFamilyColor(beast.family)
end
for j = 1, table.getn(beast.coords) do
local c = beast.coords[j]
if c and c[1] and c[2] and c[3] then
@@ -766,9 +772,6 @@ local function MTH_BOOK_ShowAllFilteredBeastsOnMap()
local y = tonumber(c[2])
local zoneId = tonumber(c[3])
if x and y and zoneId then
if not firstZoneId then
firstZoneId = zoneId
end
local details = string.format("Family: %s\nLevel: %s\nAbilities: %s", beast.family or "?", beast.lvl or "?", beast.abilities or "None")
table.insert(nodes, {
zoneId = zoneId,
@@ -777,7 +780,7 @@ local function MTH_BOOK_ShowAllFilteredBeastsOnMap()
title = string.format("%s (%d)", beastDisplayName, beastId),
detail = details,
beastId = beastId,
color = { 0.95, 0.82, 0.10 },
color = { cr, cg, cb },
})
end
end
@@ -793,8 +796,11 @@ local function MTH_BOOK_ShowAllFilteredBeastsOnMap()
if not MTH_Map:SetSource("focus") then
return
end
if firstZoneId and MTH_Map.OpenWorldMapForZone then
MTH_Map:OpenWorldMapForZone(firstZoneId)
-- Navigate to the player's current (filter) zone, not the first coord's zone ID
-- which may point to a different zone due to multi-zone beast spawn data.
local navZoneId = MTH_BOOK_STATE._cachedZoneId
if navZoneId and MTH_Map.OpenWorldMapForZone then
MTH_Map:OpenWorldMapForZone(navZoneId)
end
MTH_Map:UpdateWorldMap()
MTH_Map:UpdateMinimap()
@@ -1002,9 +1008,9 @@ end
local function MTH_BOOK_ParseProjectileDPSFromText(text)
if type(text) ~= "string" or text == "" then return nil end
local lowered = string.lower(text)
local raw = string.match(lowered, "adds%s+([%d]+[%.,]?[%d]*)%s+damage%s+per%s+seconds?")
local _, _, raw = string.find(lowered, "adds%s+([%d]+[%.,]?[%d]*)%s+damage%s+per%s+seconds?")
if not raw then
raw = string.match(lowered, "([%d]+[%.,]?[%d]*)%s+damage%s+per%s+seconds?")
_, _, raw = string.find(lowered, "([%d]+[%.,]?[%d]*)%s+damage%s+per%s+seconds?")
end
if not raw then return nil end
local normalized = string.gsub(raw, ",", ".")
@@ -1075,11 +1081,17 @@ end
MTH_BOOK_GetZoneName = function(zoneId)
local normalizedId = tonumber(zoneId) or zoneId
if not normalizedId then return "Zone " .. tostring(zoneId or "?") end
if MTH_DS_ZoneNamesFallback and MTH_DS_ZoneNamesFallback[normalizedId] then
return MTH_DS_ZoneNamesFallback[normalizedId]
-- Beast coords use WMA IDs. Always translate WMA→pfQ first so that e.g.
-- WMA 4 → pfQ 14 (Durotar) instead of hitting pfQ 4 (Blasted Lands) by accident.
local pfqId = normalizedId
if type(MTH_MAP_WMA_TO_PFQ) == "table" then
pfqId = MTH_MAP_WMA_TO_PFQ[normalizedId] or normalizedId
end
if not MTH_DS_Zones then return "Zone " .. tostring(zoneId or "?") end
local row = MTH_DS_Zones[normalizedId] or MTH_DS_Zones[tostring(normalizedId)]
if MTH_DS_ZoneNamesFallback and MTH_DS_ZoneNamesFallback[pfqId] then
return MTH_DS_ZoneNamesFallback[pfqId]
end
if not MTH_DS_Zones then return "Zone " .. tostring(normalizedId) end
local row = MTH_DS_Zones[pfqId] or MTH_DS_Zones[tostring(pfqId)]
if not row then return "Zone " .. tostring(normalizedId) end
local names = row.names
if not names then return "Zone " .. tostring(normalizedId) end
@@ -1108,7 +1120,12 @@ function MTH_BOOK_GetCurrentZoneId()
if MTH_Map and type(MTH_Map.GetMapIDByName) == "function" then
local mapId = MTH_Map:GetMapIDByName(zoneName)
if mapId then
return tonumber(mapId) or mapId
-- Translate pfQ → WMA so zone IDs match coord tuples in MTH_DS_Beasts.
local pfqId = tonumber(mapId)
if pfqId and type(MTH_MAP_PFQ_TO_WMA) == "table" then
return MTH_MAP_PFQ_TO_WMA[pfqId] or pfqId
end
return pfqId or mapId
end
end
@@ -1122,7 +1139,11 @@ function MTH_BOOK_GetCurrentZoneId()
if row and row.names then
for _, value in pairs(row.names) do
if MTH_BOOK_NormalizeZoneLookupName(value) == normalized then
return tonumber(zoneId) or zoneId
local pfqId = tonumber(zoneId) or zoneId
if type(pfqId) == "number" and type(MTH_MAP_PFQ_TO_WMA) == "table" then
return MTH_MAP_PFQ_TO_WMA[pfqId] or pfqId
end
return pfqId
end
end
end
@@ -1281,6 +1302,7 @@ local function MTH_BOOK_GetSortKey(entry, col)
if col == 7 then return traits.rare and 1 or 0 end
if col == 8 then return traits.elite and 1 or 0 end
if col == 9 then return traits.unique and 1 or 0 end
if col == 10 then return tonumber(beast.attackSpeed) or 0 end
end
if MTH_BOOK_STATE.mode == "abilities" then
@@ -1430,12 +1452,25 @@ local function MTH_BOOK_BuildResults()
local results = {}
if MTH_BOOK_STATE.mode == "pets" then
if not MTH_DS_Beasts then return results end
for beastId, beast in pairs(MTH_DS_Beasts) do
if MTH_BOOK_BeastMatches(beastId, beast) then
table.insert(results, beastId)
-- Cache zone info once for this build pass (avoids calling GetCurrentZoneId
-- once per beast, which is expensive and causes freezes on large datasets).
if MTH_BOOK_STATE.petInZoneOnly then
MTH_BOOK_STATE._cachedZoneId = MTH_BOOK_GetCurrentZoneId()
MTH_BOOK_STATE._cachedZoneName = (type(GetRealZoneText) == "function" and GetRealZoneText())
or (type(GetZoneText) == "function" and GetZoneText()) or nil
else
MTH_BOOK_STATE._cachedZoneId = nil
MTH_BOOK_STATE._cachedZoneName = nil
end
if MTH_DS_Beasts then
for beastId, beast in pairs(MTH_DS_Beasts) do
if MTH_BOOK_BeastMatches(beastId, beast) then
table.insert(results, beastId)
end
end
end
table.sort(results, MTH_BOOK_BeastSort)
MTH_BOOK_ApplyActiveSort(results)
return results
@@ -1537,13 +1572,75 @@ local function MTH_BOOK_BuildResults()
if MTH_BOOK_STATE.mode == "pethistory" then
local store = MTH_BOOK_GetPetDatastore()
if type(store) ~= "table" or type(store.historyById) ~= "table" then return results end
local cached = {}
for petId, row in pairs(store.historyById) do
if type(row) == "table" then
table.insert(results, petId)
local idText = tostring(petId)
table.insert(cached, {
id = petId,
idText = idText,
name = MTH_BOOK_SafeLower(row.name),
family = MTH_BOOK_SafeLower(row.family),
level = tonumber(row.level) or 0,
abandonedAt = tonumber(row.abandonedAt) or 0,
reason = MTH_BOOK_SafeLower(tostring(row.abandonReason or "")),
})
end
end
table.sort(results, MTH_BOOK_PetHistorySort)
MTH_BOOK_ApplyActiveSort(results)
local function defaultHistoryCompare(a, b)
if a.abandonedAt ~= b.abandonedAt then
return a.abandonedAt > b.abandonedAt
end
if a.name ~= b.name then
return a.name < b.name
end
return a.idText < b.idText
end
local state = MTH_BOOK_GetSortState()
if state and state.col then
table.sort(cached, function(a, b)
local ka = nil
local kb = nil
if state.col == 1 then
ka = a.idText
kb = b.idText
elseif state.col == 2 then
ka = a.name
kb = b.name
elseif state.col == 3 then
ka = a.family
kb = b.family
elseif state.col == 4 then
ka = a.level
kb = b.level
elseif state.col == 5 then
ka = a.abandonedAt
kb = b.abandonedAt
elseif state.col == 6 then
ka = a.reason
kb = b.reason
end
if ka ~= kb then
if ka == nil then return not state.asc end
if kb == nil then return state.asc end
if state.asc then
return ka < kb
end
return ka > kb
end
return defaultHistoryCompare(a, b)
end)
else
table.sort(cached, defaultHistoryCompare)
end
for i = 1, table.getn(cached) do
table.insert(results, cached[i].id)
end
return results
end
@@ -1990,6 +2087,77 @@ local function MTH_BOOK_UpdatePetLearnSourceDropdownText()
MTH_BOOK_DropdownSetText(sourceDropdown, MTH_BOOK_GetPetLearnSourceLabel(MTH_BOOK_STATE.petLearnSource))
end
local MTH_BOOK_BeastContinentOptions = {
{ value = "all", label = "" },
{ value = 1, label = "Kalimdor" },
{ value = 0, label = "E. Kingdoms" },
}
local MTH_BOOK_BeastZoneOptions = {
{ value = "all", label = "All Zones" },
}
local function MTH_BOOK_BuildBeastZoneOptions(continentMapId)
local seen = {}
local rows = {}
if MTH_DS_Beasts and MTH_DS_WMAZones then
for _, beast in pairs(MTH_DS_Beasts) do
if beast and beast.coords then
for i = 1, table.getn(beast.coords) do
local c = beast.coords[i]
if c and c[3] and not seen[c[3]] then
local wmaEntry = MTH_DS_WMAZones[c[3]]
if wmaEntry and wmaEntry.mapId == continentMapId then
seen[c[3]] = true
local zoneName = MTH_BOOK_GetZoneName and MTH_BOOK_GetZoneName(c[3])
if zoneName and string.sub(zoneName, 1, 5) ~= "Zone " then
table.insert(rows, { value = c[3], label = zoneName })
end
end
end
end
end
end
end
table.sort(rows, function(a, b)
return MTH_BOOK_SafeLower(a.label) < MTH_BOOK_SafeLower(b.label)
end)
MTH_BOOK_BeastZoneOptions = { { value = "all", label = "All Zones" } }
for i = 1, table.getn(rows) do
table.insert(MTH_BOOK_BeastZoneOptions, rows[i])
end
end
local function MTH_BOOK_GetBeastContinentLabel(value)
for i = 1, table.getn(MTH_BOOK_BeastContinentOptions) do
if MTH_BOOK_BeastContinentOptions[i].value == value then
return MTH_BOOK_BeastContinentOptions[i].label
end
end
return ""
end
local function MTH_BOOK_GetBeastZoneLabel(value)
for i = 1, table.getn(MTH_BOOK_BeastZoneOptions) do
if MTH_BOOK_BeastZoneOptions[i].value == value then
return MTH_BOOK_BeastZoneOptions[i].label
end
end
return "All Zones"
end
local function MTH_BOOK_UpdateBeastContinentDropdownText()
local dd = getglobal("MTH_BOOK_BeastContinentDropdown")
if not dd then return end
MTH_BOOK_DropdownSetText(dd, MTH_BOOK_GetBeastContinentLabel(MTH_BOOK_STATE.petContinent))
end
local function MTH_BOOK_UpdateBeastZoneDropdownText()
local dd = getglobal("MTH_BOOK_BeastZoneDropdown")
if not dd then return end
MTH_BOOK_DropdownSetText(dd, MTH_BOOK_GetBeastZoneLabel(MTH_BOOK_STATE.petZoneFilter))
end
local function MTH_BOOK_GetItemSubtypeLabelByValue(value)
local options = MTH_BOOK_GetItemSubtypeOptions()
for i = 1, table.getn(options) do
@@ -2025,7 +2193,7 @@ local function MTH_BOOK_UpdateQuickFilterControls()
if MTH_BOOK_IsItemMode() then
MTH_BOOK_STATE.pageSize = 18
elseif MTH_BOOK_STATE.mode == "families" then
MTH_BOOK_STATE.pageSize = 16
MTH_BOOK_STATE.pageSize = MTH_BOOK_MAX_ROWS
elseif MTH_BOOK_STATE.mode == "stable" then
MTH_BOOK_STATE.pageSize = 5
elseif MTH_BOOK_STATE.mode == "pethistory" then
@@ -2038,6 +2206,9 @@ local function MTH_BOOK_UpdateQuickFilterControls()
local abilityDropdown = getglobal("MTH_BOOK_AbilityDropdown")
local rankDropdown = getglobal("MTH_BOOK_RankDropdown")
local petLearnSourceDropdown = getglobal("MTH_BOOK_PetLearnSourceDropdown")
local beastContinentDropdown = getglobal("MTH_BOOK_BeastContinentDropdown")
local beastZoneDropdown = getglobal("MTH_BOOK_BeastZoneDropdown")
local beastZoneLabel = getglobal("MTH_BOOK_BeastZoneLabel")
local itemSubtypeDropdown = getglobal("MTH_BOOK_ItemSubtypeDropdown")
local npcFunctionDropdown = getglobal("MTH_BOOK_NPCFunctionDropdown")
local npcZoneDropdown = getglobal("MTH_BOOK_NPCZoneDropdown")
@@ -2158,6 +2329,19 @@ local function MTH_BOOK_UpdateQuickFilterControls()
end
end
if petLearnSourceDropdown then petLearnSourceDropdown:Hide() end
if beastZoneLabel then beastZoneLabel:Show() end
if beastContinentDropdown then
beastContinentDropdown:Show()
MTH_BOOK_UpdateBeastContinentDropdownText()
end
if beastZoneDropdown then
if MTH_BOOK_STATE.petContinent ~= "all" then
beastZoneDropdown:Show()
MTH_BOOK_UpdateBeastZoneDropdownText()
else
beastZoneDropdown:Hide()
end
end
if itemSubtypeDropdown then itemSubtypeDropdown:Hide() end
if npcFunctionDropdown then npcFunctionDropdown:Hide() end
if npcZoneDropdown then npcZoneDropdown:Hide() end
@@ -2193,6 +2377,9 @@ local function MTH_BOOK_UpdateQuickFilterControls()
petLearnSourceDropdown:Show()
MTH_BOOK_UpdatePetLearnSourceDropdownText()
end
if beastZoneLabel then beastZoneLabel:Hide() end
if beastContinentDropdown then beastContinentDropdown:Hide() end
if beastZoneDropdown then beastZoneDropdown:Hide() end
if itemSubtypeDropdown then itemSubtypeDropdown:Hide() end
if petOnlyMyLevel then
petOnlyMyLevel:ClearAllPoints()
@@ -2249,6 +2436,9 @@ local function MTH_BOOK_UpdateQuickFilterControls()
if abilityDropdown then abilityDropdown:Hide() end
if rankDropdown then rankDropdown:Hide() end
if petLearnSourceDropdown then petLearnSourceDropdown:Hide() end
if beastZoneLabel then beastZoneLabel:Hide() end
if beastContinentDropdown then beastContinentDropdown:Hide() end
if beastZoneDropdown then beastZoneDropdown:Hide() end
if itemSubtypeDropdown then itemSubtypeDropdown:Hide() end
if npcFunctionDropdown then npcFunctionDropdown:Show() end
if npcZoneDropdown then npcZoneDropdown:Hide() end
@@ -2303,6 +2493,9 @@ local function MTH_BOOK_UpdateQuickFilterControls()
if abilityDropdown then abilityDropdown:Hide() end
if rankDropdown then rankDropdown:Hide() end
if petLearnSourceDropdown then petLearnSourceDropdown:Hide() end
if beastZoneLabel then beastZoneLabel:Hide() end
if beastContinentDropdown then beastContinentDropdown:Hide() end
if beastZoneDropdown then beastZoneDropdown:Hide() end
if itemSubtypeDropdown then
itemSubtypeDropdown:ClearAllPoints()
itemSubtypeDropdown:SetPoint("TOPLEFT", itemSubtypeDropdown:GetParent(), "TOPLEFT", 238, -80)
@@ -2440,6 +2633,15 @@ local function MTH_BOOK_UpdateQuickFilterControls()
if MTH_BOOK_STATE.mode ~= "pets" and petInZoneOnly then
petInZoneOnly:Hide()
end
if MTH_BOOK_STATE.mode ~= "pets" and beastContinentDropdown then
beastContinentDropdown:Hide()
end
if MTH_BOOK_STATE.mode ~= "pets" and beastZoneDropdown then
beastZoneDropdown:Hide()
end
if MTH_BOOK_STATE.mode ~= "pets" and beastZoneLabel then
beastZoneLabel:Hide()
end
if MTH_BOOK_STATE.mode ~= "npcs" and npcInZoneOnly then
npcInZoneOnly:Hide()
end
@@ -2545,6 +2747,7 @@ local function MTH_BOOK_InitRankDropdown()
MTH_BOOK_UpdateRankDropdownText()
end
local function MTH_BOOK_InitPetLearnSourceDropdown()
local sourceDropdown = getglobal("MTH_BOOK_PetLearnSourceDropdown")
if not sourceDropdown then return end
@@ -2639,6 +2842,56 @@ local function MTH_BOOK_AddStableStyleKV(lines, label, value)
table.insert(lines, "|cff9a9a9a" .. tostring(label or "") .. ":|r |cffffffff" .. tostring(value or "-") .. "|r")
end
local function MTH_BOOK_GetOrCreateDetailTitleLabel()
local lbl = getglobal("MTH_BOOK_DetailTitleLabel")
if lbl then return lbl end
local parent = getglobal("MTH_BOOK_DetailBackdrop")
if not parent then return nil end
lbl = parent:CreateFontString("MTH_BOOK_DetailTitleLabel", "OVERLAY")
lbl:SetFontObject(GameFontNormal)
lbl:SetJustifyH("LEFT")
lbl:SetJustifyV("TOP")
lbl:SetPoint("TOPLEFT", parent, "TOPLEFT", 8, -8)
lbl:SetWidth(120)
lbl:Hide()
return lbl
end
local function MTH_BOOK_ShowDetailTitle(text)
local lbl = MTH_BOOK_GetOrCreateDetailTitleLabel()
if lbl then
lbl:SetText(tostring(text or ""))
lbl:Show()
end
local editBox = getglobal("MTH_BOOK_DetailBackdropDetailText")
local parent = getglobal("MTH_BOOK_DetailBackdrop")
if editBox and parent then
editBox:ClearAllPoints()
editBox:SetPoint("TOPLEFT", parent, "TOPLEFT", 6, -28)
end
end
local function MTH_BOOK_HideDetailTitle()
local lbl = getglobal("MTH_BOOK_DetailTitleLabel")
if lbl then lbl:Hide() end
local editBox = getglobal("MTH_BOOK_DetailBackdropDetailText")
local parent = getglobal("MTH_BOOK_DetailBackdrop")
if editBox and parent then
editBox:ClearAllPoints()
editBox:SetPoint("TOPLEFT", parent, "TOPLEFT", 6, -6)
end
end
local function MTH_BOOK_FormatBeastReact(reactA, reactH)
local function reactColor(r)
local rl = r and string.lower(r) or ""
if rl == "friendly" then return "|cFF44DD44" end
if rl == "neutral" then return "|cFFFFCC00" end
return "|cFFDD4444"
end
return reactColor(reactA) .. "A|r " .. reactColor(reactH) .. "H|r"
end
local function MTH_BOOK_FormatDateTimeValue(value)
local ts = tonumber(value)
if not ts or ts <= 0 then
@@ -2879,7 +3132,7 @@ end
function MTH_BOOK_SetDetailText(detail, text)
if not detail then return end
local wrapped = tostring(text or "")
if MTH_BOOK_STATE.mode ~= "pethistory" then
if MTH_BOOK_STATE.mode ~= "pethistory" and MTH_BOOK_STATE.mode ~= "pets" and MTH_BOOK_STATE.mode ~= "abilities" then
wrapped = MTH_BOOK_WrapDetailText(wrapped)
end
detail._mthLocking = true
@@ -2932,9 +3185,15 @@ end
function MTH_BOOK_UpdateDetail()
local detail = getglobal("MTH_BOOK_DetailBackdropDetailText")
if not detail then return end
MTH_BOOK_HideDetailTitle()
-- Hide model skin preview when not in a beast-list mode
if MTH_BOOK_STATE.mode ~= "pets" and MTH_BOOK_STATE.mode ~= "abilities" then
if MTH_BOOK_ModelViewer_Clear then MTH_BOOK_ModelViewer_Clear() end
end
local petTop = MTH_BOOK_STATE.petUI and MTH_BOOK_STATE.petUI.detailTop
local petBottom = MTH_BOOK_STATE.petUI and MTH_BOOK_STATE.petUI.detailBottom
if not MTH_BOOK_STATE.selectedEntry then
if MTH_BOOK_ModelViewer_Clear then MTH_BOOK_ModelViewer_Clear() end
if MTH_BOOK_STATE.mode == "petabilities" then
if petTop and petBottom then
MTH_BOOK_SetDetailText(petTop, "|cFFFFD100Baseline|r\n\nSelect a spell baseline from the left list.")
@@ -3005,49 +3264,52 @@ function MTH_BOOK_UpdateDetail()
return
end
local beastId = selected.beastId
local beast = MTH_DS_Beasts and MTH_DS_Beasts[beastId]
if not beast then
MTH_BOOK_SetDetailText(detail, "|cFFFFD100Inspector|r\n\nSelect an entry from the list to view identity, stats, sources and locations.")
MTH_BOOK_UpdateOpenMapButton()
return
end
local lines = {}
local beastDisplayName = (MTH and MTH.GetLocalizedBeastName and MTH:GetLocalizedBeastName(beastId, beast.name)) or beast.name
table.insert(lines, "|cFFFFFF00" .. (beastDisplayName or "Unknown") .. "|r")
MTH_BOOK_AddDetailSection(lines, "Identity")
MTH_BOOK_AddDetailKV(lines, "ID", beastId)
MTH_BOOK_AddDetailKV(lines, "Family", beast.family or "?")
MTH_BOOK_ShowDetailTitle("|cFFFFFF00" .. (beastDisplayName or "Unknown") .. "|r")
MTH_BOOK_AddDetailSection(lines, "Stats")
MTH_BOOK_AddDetailKV(lines, "Level", beast.lvl or "?")
MTH_BOOK_AddDetailKV(lines, "Attack Speed", beast.attackSpeed or "?")
local lines = {}
-- Identity block
MTH_BOOK_AddStableStyleKV(lines, "ID", beastId)
MTH_BOOK_AddStableStyleKV(lines, "Family", beast.family or "?")
local rankLabel = beast.rank
if rankLabel == "normal" or rankLabel == nil or rankLabel == "" then rankLabel = nil end
if rankLabel then MTH_BOOK_AddStableStyleKV(lines, "Rank", rankLabel) end
local spawnCount = (beast.coords and table.getn(beast.coords)) or 0
MTH_BOOK_AddStableStyleKV(lines, "Spawns", spawnCount > 0 and tostring(spawnCount) or "?")
if beast.reactA or beast.reactH then
table.insert(lines, "|cff9a9a9aReaction:|r " .. MTH_BOOK_FormatBeastReact(beast.reactA, beast.reactH))
end
-- Stats block
MTH_BOOK_AddStableStyleKV(lines, "Level", beast.lvl or "?")
MTH_BOOK_AddStableStyleKV(lines, "Atk Speed", beast.attackSpeed or "?")
local respawnWindow = MTH_BOOK_FormatRespawnWindow(beast)
if respawnWindow then
MTH_BOOK_AddDetailKV(lines, "Respawn", respawnWindow)
if respawnWindow then MTH_BOOK_AddStableStyleKV(lines, "Respawn", respawnWindow) end
if beast.health then MTH_BOOK_AddStableStyleKV(lines, "Health", beast.health) end
if beast.armor then MTH_BOOK_AddStableStyleKV(lines, "Armor", beast.armor) end
if beast.dmgMin and beast.dmgMax then
MTH_BOOK_AddStableStyleKV(lines, "Damage", string.format("%.1f - %.1f", tonumber(beast.dmgMin) or 0, tonumber(beast.dmgMax) or 0))
end
MTH_BOOK_AddDetailKV(lines, "Rare", beast.rare and "Yes" or "No")
MTH_BOOK_AddDetailKV(lines, "Abilities", beast.abilities or "None")
local fam = MTH_DS_Families and beast.family and MTH_DS_Families[beast.family]
if fam and fam.abilities and table.getn(fam.abilities) > 0 then
MTH_BOOK_AddDetailSection(lines, "Family Pool")
table.insert(lines, " " .. table.concat(fam.abilities, ", "))
end
if beast.coords and table.getn(beast.coords) > 0 then
MTH_BOOK_AddDetailSection(lines, "Locations")
for i = 1, table.getn(beast.coords) do
local c = beast.coords[i]
if c then
local x = tonumber(c[1] or 0) or 0
local y = tonumber(c[2] or 0) or 0
local zoneId = c[3]
table.insert(lines, string.format(" - %s (%.1f, %.1f)", MTH_BOOK_GetZoneName(zoneId), x, y))
end
if i >= 8 then break end
end
if beast.abilities and beast.abilities ~= "" and beast.abilities ~= "None" then
MTH_BOOK_AddStableStyleKV(lines, "Abilities", beast.abilities)
end
local beastZone = MTH_BOOK_GetBeastZoneSummary and MTH_BOOK_GetBeastZoneSummary(beast)
if beastZone and beastZone ~= "-" then
MTH_BOOK_AddStableStyleKV(lines, "Zone", beastZone)
end
local subzone = MTH_BOOK_GetBeastSubZoneSummary and MTH_BOOK_GetBeastSubZoneSummary(beast)
if subzone then
MTH_BOOK_AddStableStyleKV(lines, "Subzone", subzone)
end
if MTH_BOOK_ModelViewer_SetFamily then MTH_BOOK_ModelViewer_SetFamily(beast.family, beast.skinId) end
MTH_BOOK_SetDetailText(detail, table.concat(lines, "\n"))
MTH_BOOK_UpdateOpenMapButton()
return
@@ -3066,37 +3328,38 @@ function MTH_BOOK_UpdateDetail()
MTH_BOOK_UpdateOpenMapButton()
return
end
MTH_BOOK_ShowDetailTitle("|cFFFFFF00" .. tostring(entry.abilityToken) .. "|r")
local lines = {}
table.insert(lines, "|cFFFFFF00" .. tostring(entry.abilityToken) .. "|r")
local beastDisplayName = (MTH and MTH.GetLocalizedBeastName and MTH:GetLocalizedBeastName(entry.beastId, beast.name)) or beast.name
MTH_BOOK_AddStableStyleKV(lines, "Beast", tostring(beastDisplayName or "Unknown") .. " (#" .. tostring(entry.beastId) .. ")")
MTH_BOOK_AddStableStyleKV(lines, "Family", beast.family or "?")
local rankLabel = beast.rank
if rankLabel == "normal" or rankLabel == nil or rankLabel == "" then rankLabel = nil end
if rankLabel then MTH_BOOK_AddStableStyleKV(lines, "Rank", rankLabel) end
MTH_BOOK_AddDetailSection(lines, "Identity")
MTH_BOOK_AddDetailKV(lines, "Beast", tostring(beastDisplayName or "Unknown") .. " (ID " .. tostring(entry.beastId) .. ")")
MTH_BOOK_AddDetailKV(lines, "Family", beast.family or "?")
MTH_BOOK_AddDetailSection(lines, "Stats")
MTH_BOOK_AddDetailKV(lines, "Level", beast.lvl or "?")
MTH_BOOK_AddDetailKV(lines, "Attack Speed", beast.attackSpeed or "?")
MTH_BOOK_AddStableStyleKV(lines, "Level", beast.lvl or "?")
MTH_BOOK_AddStableStyleKV(lines, "Atk Speed", beast.attackSpeed or "?")
local respawnWindow = MTH_BOOK_FormatRespawnWindow(beast)
if respawnWindow then
MTH_BOOK_AddDetailKV(lines, "Respawn", respawnWindow)
if respawnWindow then MTH_BOOK_AddStableStyleKV(lines, "Respawn", respawnWindow) end
if beast.health then MTH_BOOK_AddStableStyleKV(lines, "Health", beast.health) end
if beast.armor then MTH_BOOK_AddStableStyleKV(lines, "Armor", beast.armor) end
if beast.dmgMin and beast.dmgMax then
MTH_BOOK_AddStableStyleKV(lines, "Damage", string.format("%.1f - %.1f", tonumber(beast.dmgMin) or 0, tonumber(beast.dmgMax) or 0))
end
MTH_BOOK_AddDetailKV(lines, "Rare", beast.rare and "Yes" or "No")
if beast.coords and table.getn(beast.coords) > 0 then
MTH_BOOK_AddDetailSection(lines, "Locations")
for i = 1, table.getn(beast.coords) do
local c = beast.coords[i]
if c then
local x = tonumber(c[1] or 0) or 0
local y = tonumber(c[2] or 0) or 0
local zoneId = c[3]
table.insert(lines, string.format(" - %s (%.1f, %.1f)", MTH_BOOK_GetZoneName(zoneId), x, y))
end
if i >= 8 then break end
end
if beast.reactA or beast.reactH then
table.insert(lines, "|cff9a9a9aReaction:|r " .. MTH_BOOK_FormatBeastReact(beast.reactA, beast.reactH))
end
local beastZone = MTH_BOOK_GetBeastZoneSummary and MTH_BOOK_GetBeastZoneSummary(beast)
if beastZone and beastZone ~= "-" then
MTH_BOOK_AddStableStyleKV(lines, "Zone", beastZone)
end
local subzone = MTH_BOOK_GetBeastSubZoneSummary and MTH_BOOK_GetBeastSubZoneSummary(beast)
if subzone then
MTH_BOOK_AddStableStyleKV(lines, "Subzone", subzone)
end
if MTH_BOOK_ModelViewer_SetFamily then MTH_BOOK_ModelViewer_SetFamily(beast.family, beast.skinId) end
MTH_BOOK_SetDetailText(detail, table.concat(lines, "\n"))
MTH_BOOK_UpdateOpenMapButton()
return
@@ -3405,7 +3668,7 @@ local function MTH_BOOK_GetColumnLabels()
return tabDef.columnLabels
end
if MTH_BOOK_STATE.mode == "pets" then
return { "ID", "Lvl", "Family", "Name", "Abilities", "Zone", "R", "E", "U" }
return { "ID", "Lvl", "Fam", "Name", "Abilities", "Zone", "R", "E", "U", "AS" }
elseif MTH_BOOK_STATE.mode == "petabilities" then
return { "Ability Name", "Ranks", "Families" }
elseif MTH_BOOK_STATE.mode == "stable" then
@@ -3431,13 +3694,14 @@ local function MTH_BOOK_GetColumnLayout()
return {
{ x = 8, width = 28, align = "LEFT" },
{ x = 38, width = 30, align = "LEFT" },
{ x = 68, width = 74, align = "LEFT" },
{ x = 142, width = 130, align = "LEFT" },
{ x = 272, width = 180, align = "LEFT" },
{ x = 452, width = 68, align = "LEFT" },
{ x = 520, width = 10, align = "CENTER" },
{ x = 532, width = 10, align = "CENTER" },
{ x = 544, width = 10, align = "CENTER" },
{ x = 68, width = 18, align = "LEFT" },
{ x = 88, width = 130, align = "LEFT" },
{ x = 220, width = 148, align = "LEFT" },
{ x = 370, width = 80, align = "LEFT" },
{ x = 452, width = 14, align = "CENTER" },
{ x = 468, width = 14, align = "CENTER" },
{ x = 484, width = 14, align = "CENTER" },
{ x = 500, width = 54, align = "LEFT" },
}
elseif MTH_BOOK_STATE.mode == "petabilities" then
return {
@@ -3598,6 +3862,19 @@ local function MTH_BOOK_ApplyColumnLayout(parent)
btn.cols[itemNameColumn]:SetPoint("LEFT", btn, "LEFT", itemNameLayout.x + 12, 0)
btn.cols[itemNameColumn]:SetWidth(itemNameLayout.width - 16)
end
elseif MTH_BOOK_STATE.mode == "families" and layout[1] then
local familyCol = layout[1]
btn.itemIcon:ClearAllPoints()
btn.itemIcon:SetPoint("LEFT", btn, "LEFT", familyCol.x, 0)
if btn.cols and btn.cols[1] then
btn.cols[1]:ClearAllPoints()
btn.cols[1]:SetPoint("LEFT", btn, "LEFT", familyCol.x + 18, 0)
btn.cols[1]:SetWidth(familyCol.width - 18)
end
elseif MTH_BOOK_STATE.mode == "pets" and layout[3] then
local famCol = layout[3]
btn.itemIcon:ClearAllPoints()
btn.itemIcon:SetPoint("LEFT", btn, "LEFT", famCol.x, 0)
else
btn.itemIcon:Hide()
end
@@ -3608,8 +3885,9 @@ end
local function MTH_BOOK_GetRowValues(entry)
if MTH_BOOK_STATE.mode == "pets" then
-- MetaHunt DB entry
local beast = MTH_DS_Beasts and MTH_DS_Beasts[entry]
if not beast then return { "", "", "", "", "", "", "", "", "" } end
if not beast then return { "", "", "", "", "", "", "", "", "", "" } end
local beastDisplayName = (MTH and MTH.GetLocalizedBeastName and MTH:GetLocalizedBeastName(entry, beast.name)) or beast.name
local traits = MTH_BOOK_ParseBeastTraits(beast)
local abilities = MTH_BOOK_GetBeastAbilitiesSummary(beast)
@@ -3617,13 +3895,14 @@ local function MTH_BOOK_GetRowValues(entry)
return {
"|cFF33CCFF" .. tostring(entry) .. "|r",
tostring(beast.lvl or "?"),
tostring(beast.family or "?"),
"", -- family icon rendered separately
tostring(beastDisplayName or "Unknown"),
abilities,
zone,
(traits.rare and "R" or ""),
(traits.elite and "E" or ""),
(traits.unique and "U" or ""),
tostring(beast.attackSpeed or ""),
}
end
@@ -4028,6 +4307,31 @@ MTH_BOOK_UpdateResults = function()
btn.itemIcon:SetTexture(nil)
btn.itemIcon:Hide()
end
elseif MTH_BOOK_STATE.mode == "families" and type(entry) == "table" and entry.icon then
local iconPath = MTH_BOOK_ResolveIconPath(entry.icon)
if iconPath then
btn.itemIcon:SetTexture(iconPath)
btn.itemIcon:Show()
else
btn.itemIcon:SetTexture(nil)
btn.itemIcon:Hide()
end
elseif MTH_BOOK_STATE.mode == "pets" then
local beast = MTH_DS_Beasts and MTH_DS_Beasts[entry]
local famData = beast and beast.family and MTH_DS_Families and MTH_DS_Families[beast.family]
if famData and famData.icon and famData.icon ~= "" then
local iconPath = MTH_BOOK_ResolveIconPath(famData.icon)
if iconPath then
btn.itemIcon:SetTexture(iconPath)
btn.itemIcon:Show()
else
btn.itemIcon:SetTexture(nil)
btn.itemIcon:Hide()
end
else
btn.itemIcon:SetTexture(nil)
btn.itemIcon:Hide()
end
else
btn.itemIcon:SetTexture(nil)
btn.itemIcon:Hide()
@@ -4379,23 +4683,14 @@ local function MTH_BOOK_LayoutSectionTabs()
local sectionPetHistory = getglobal("MTH_BOOK_SectionPetHistory")
if not (tabBar and sectionPets and sectionFamilies and sectionPetAbilities and sectionItems and sectionNPCs and sectionProjectiles and sectionAmmoBags) then return end
if sectionStable then
sectionStable:ClearAllPoints()
sectionStable:SetPoint("TOPLEFT", tabBar, "TOPLEFT", 0, 0)
end
sectionPets:ClearAllPoints()
sectionPets:SetPoint("TOPLEFT", tabBar, "TOPLEFT", 0, 0)
sectionPetAbilities:ClearAllPoints()
if sectionStable then
sectionPetAbilities:SetPoint("LEFT", sectionStable, "RIGHT", 2, 0)
else
sectionPetAbilities:SetPoint("TOPLEFT", tabBar, "TOPLEFT", 0, 0)
end
sectionPets:ClearAllPoints()
sectionPets:SetPoint("LEFT", sectionPetAbilities, "RIGHT", 2, 0)
sectionPetAbilities:SetPoint("LEFT", sectionPets, "RIGHT", 2, 0)
sectionFamilies:ClearAllPoints()
sectionFamilies:SetPoint("LEFT", sectionPets, "RIGHT", 2, 0)
sectionFamilies:SetPoint("LEFT", sectionPetAbilities, "RIGHT", 2, 0)
sectionNPCs:ClearAllPoints()
sectionNPCs:SetPoint("LEFT", sectionFamilies, "RIGHT", 2, 0)
@@ -4409,9 +4704,18 @@ local function MTH_BOOK_LayoutSectionTabs()
sectionAmmoBags:ClearAllPoints()
sectionAmmoBags:SetPoint("LEFT", sectionProjectiles, "RIGHT", 2, 0)
if sectionStable then
sectionStable:ClearAllPoints()
sectionStable:SetPoint("LEFT", sectionAmmoBags, "RIGHT", 2, 0)
end
if sectionPetHistory then
sectionPetHistory:ClearAllPoints()
sectionPetHistory:SetPoint("LEFT", sectionAmmoBags, "RIGHT", 2, 0)
if sectionStable then
sectionPetHistory:SetPoint("LEFT", sectionStable, "RIGHT", 2, 0)
else
sectionPetHistory:SetPoint("LEFT", sectionAmmoBags, "RIGHT", 2, 0)
end
end
end
@@ -4430,6 +4734,8 @@ local function MTH_BOOK_GetFilterWidgets()
npcFunctionDropdown = getglobal("MTH_BOOK_NPCFunctionDropdown"),
npcZoneDropdown = getglobal("MTH_BOOK_NPCZoneDropdown"),
petLearnSourceDropdown = getglobal("MTH_BOOK_PetLearnSourceDropdown"),
beastContinentDropdown = getglobal("MTH_BOOK_BeastContinentDropdown"),
beastZoneDropdown = getglobal("MTH_BOOK_BeastZoneDropdown"),
itemSubtypeDropdown = getglobal("MTH_BOOK_ItemSubtypeDropdown"),
petOnlyMyLevel = getglobal("MTH_BOOK_PetOnlyMyLevel"),
petInZoneOnly = getglobal("MTH_BOOK_PetInZoneOnly"),
@@ -4451,6 +4757,8 @@ local function MTH_BOOK_ResetStateForMode(mode)
MTH_BOOK_STATE.petOnlyMyLevel = false
MTH_BOOK_STATE.itemOnlyMyLevel = false
MTH_BOOK_STATE.petInZoneOnly = false
MTH_BOOK_STATE.petContinent = "all"
MTH_BOOK_STATE.petZoneFilter = "all"
MTH_BOOK_STATE.itemSubtype = "all"
MTH_BOOK_STATE.npcFunction = "all"
MTH_BOOK_STATE.npcZone = "all"
@@ -4514,6 +4822,8 @@ local function MTH_BOOK_ApplyStateToWidgets(widgets)
if widgets.petInZoneOnly then
widgets.petInZoneOnly:SetChecked(MTH_BOOK_STATE.mode == "pets" and MTH_BOOK_STATE.petInZoneOnly and 1 or nil)
end
if widgets.beastContinentDropdown then MTH_BOOK_UpdateBeastContinentDropdownText() end
if widgets.beastZoneDropdown then MTH_BOOK_UpdateBeastZoneDropdownText() end
if widgets.npcInZoneOnly then
widgets.npcInZoneOnly:SetChecked(MTH_BOOK_STATE.mode == "npcs" and MTH_BOOK_STATE.npcInZoneOnly and 1 or nil)
end
@@ -4552,6 +4862,8 @@ function MTH_BOOK_JumpToBeastById(beastId)
MTH_BOOK_STATE.mode = "pets"
local widgets = MTH_BOOK_GetFilterWidgets()
MTH_BOOK_ResetStateForMode("pets")
MTH_BOOK_STATE.petHideNoAbilities = false
MTH_BOOK_STATE.petHideUnknown = false
MTH_BOOK_STATE.search = tostring(id)
MTH_BOOK_STATE.forcedBeastId = id
MTH_BOOK_ApplyStateToWidgets(widgets)
@@ -4761,6 +5073,8 @@ local function MTH_BOOK_ResetAllFiltersAndRefresh()
if widgets.abilityDropdown then MTH_BOOK_UpdateAbilityDropdownText() end
if widgets.rankDropdown then MTH_BOOK_UpdateRankDropdownText() end
if widgets.petLearnSourceDropdown then MTH_BOOK_UpdatePetLearnSourceDropdownText() end
if widgets.beastContinentDropdown then MTH_BOOK_UpdateBeastContinentDropdownText() end
if widgets.beastZoneDropdown then MTH_BOOK_UpdateBeastZoneDropdownText() end
if widgets.itemSubtypeDropdown then MTH_BOOK_UpdateItemSubtypeDropdownText() end
if widgets.npcFunctionDropdown or widgets.npcZoneDropdown then MTH_BOOK_UpdateNPCDropdownTexts() end
@@ -4768,6 +5082,71 @@ local function MTH_BOOK_ResetAllFiltersAndRefresh()
MTH_BOOK_RefreshFilter()
end
local function MTH_BOOK_WireBeastDropdowns()
local continentDD = getglobal("MTH_BOOK_BeastContinentDropdown")
if continentDD then
UIDropDownMenu_Initialize(continentDD, function()
for i = 1, table.getn(MTH_BOOK_BeastContinentOptions) do
local option = MTH_BOOK_BeastContinentOptions[i]
local info = MTH_BOOK_DropdownCreateInfo()
info.text = (option.label ~= "") and option.label or "(any)"
info.func = function()
MTH_BOOK_STATE.petContinent = option.value
MTH_BOOK_STATE.petZoneFilter = "all"
MTH_BOOK_STATE.page = 1
MTH_BOOK_STATE.selectedEntry = nil
MTH_BOOK_DropdownSetText(continentDD, info.text)
if option.value ~= "all" then
MTH_BOOK_BuildBeastZoneOptions(option.value)
local zoneDD = getglobal("MTH_BOOK_BeastZoneDropdown")
if zoneDD then
UIDropDownMenu_Initialize(zoneDD, MTH_BOOK_STATE._beastZoneInitFunc)
zoneDD:Show()
MTH_BOOK_UpdateBeastZoneDropdownText()
end
else
local zoneDD = getglobal("MTH_BOOK_BeastZoneDropdown")
if zoneDD then zoneDD:Hide() end
end
MTH_BOOK_RefreshFilter()
end
info.checked = (MTH_BOOK_STATE.petContinent == option.value)
UIDropDownMenu_AddButton(info)
end
end)
UIDropDownMenu_SetWidth(80, continentDD)
UIDropDownMenu_JustifyText("LEFT", continentDD)
MTH_BOOK_UpdateBeastContinentDropdownText()
end
local zoneInitFunc = function()
local zoneDD = getglobal("MTH_BOOK_BeastZoneDropdown")
for i = 1, table.getn(MTH_BOOK_BeastZoneOptions) do
local option = MTH_BOOK_BeastZoneOptions[i]
local info = MTH_BOOK_DropdownCreateInfo()
info.text = option.label
info.func = function()
MTH_BOOK_STATE.petZoneFilter = option.value
MTH_BOOK_STATE.page = 1
MTH_BOOK_STATE.selectedEntry = nil
if zoneDD then MTH_BOOK_DropdownSetText(zoneDD, option.label) end
MTH_BOOK_RefreshFilter()
end
info.checked = (MTH_BOOK_STATE.petZoneFilter == option.value)
UIDropDownMenu_AddButton(info)
end
end
MTH_BOOK_STATE._beastZoneInitFunc = zoneInitFunc
local zoneDD = getglobal("MTH_BOOK_BeastZoneDropdown")
if zoneDD then
UIDropDownMenu_Initialize(zoneDD, zoneInitFunc)
UIDropDownMenu_SetWidth(120, zoneDD)
UIDropDownMenu_JustifyText("LEFT", zoneDD)
MTH_BOOK_UpdateBeastZoneDropdownText()
end
end
local function MTH_BOOK_WireUI(frame)
if frame and frame.SetBackdropColor then
frame:SetBackdropColor(0.05, 0.05, 0.05, 1.00)
@@ -4789,12 +5168,6 @@ local function MTH_BOOK_WireUI(frame)
local requireVendor = getglobal("MTH_BOOK_RequireVendor")
local requireDrop = getglobal("MTH_BOOK_RequireDrop")
local requireObject = getglobal("MTH_BOOK_RequireObject")
local abilityDropdown = getglobal("MTH_BOOK_AbilityDropdown")
local rankDropdown = getglobal("MTH_BOOK_RankDropdown")
local petLearnSourceDropdown = getglobal("MTH_BOOK_PetLearnSourceDropdown")
local itemSubtypeDropdown = getglobal("MTH_BOOK_ItemSubtypeDropdown")
local npcFunctionDropdown = getglobal("MTH_BOOK_NPCFunctionDropdown")
local npcZoneDropdown = getglobal("MTH_BOOK_NPCZoneDropdown")
local petBookScanButton = getglobal("MTH_BOOK_PetBookScanButton")
local hideNoAbilities = getglobal("MTH_BOOK_HideNoAbilities")
local hideUnknown = getglobal("MTH_BOOK_HideUnknown")
@@ -4855,6 +5228,7 @@ local function MTH_BOOK_WireUI(frame)
MTH_BOOK_InitFamilyDropdown()
MTH_BOOK_InitAbilityDropdown()
MTH_BOOK_InitRankDropdown()
MTH_BOOK_WireBeastDropdowns()
MTH_BOOK_InitPetLearnSourceDropdown()
MTH_BOOK_InitItemSubtypeDropdown()
MTH_BOOK_InitNPCFunctionDropdown()
@@ -5004,19 +5378,19 @@ local function MTH_BOOK_WireUI(frame)
listSlider:SetScript("OnMouseDown", function()
MTH_BOOK_STATE.sliderDragging = true
MTH_BOOK_SetSliderFromCursor(this)
this:SetScript("OnUpdate", function()
if IsMouseButtonDown and not IsMouseButtonDown("LeftButton") then
MTH_BOOK_STATE.sliderDragging = false
this:SetScript("OnUpdate", nil)
else
MTH_BOOK_SetSliderFromCursor(this)
end
end)
end)
listSlider:SetScript("OnMouseUp", function()
MTH_BOOK_SetSliderFromCursor(this)
MTH_BOOK_STATE.sliderDragging = false
end)
listSlider:SetScript("OnUpdate", function()
if MTH_BOOK_STATE.sliderDragging then
if IsMouseButtonDown and not IsMouseButtonDown("LeftButton") then
MTH_BOOK_STATE.sliderDragging = false
else
MTH_BOOK_SetSliderFromCursor(this)
end
end
this:SetScript("OnUpdate", nil)
end)
listSlider:SetScript("OnValueChanged", function()
local value = this:GetValue()
@@ -5119,19 +5493,19 @@ local function MTH_BOOK_WireUI(frame)
sliderBackdrop:SetScript("OnMouseDown", function()
MTH_BOOK_STATE.sliderDragging = true
MTH_BOOK_SetSliderFromCursor(listSlider)
listSlider:SetScript("OnUpdate", function()
if IsMouseButtonDown and not IsMouseButtonDown("LeftButton") then
MTH_BOOK_STATE.sliderDragging = false
this:SetScript("OnUpdate", nil)
else
MTH_BOOK_SetSliderFromCursor(listSlider)
end
end)
end)
sliderBackdrop:SetScript("OnMouseUp", function()
MTH_BOOK_SetSliderFromCursor(listSlider)
MTH_BOOK_STATE.sliderDragging = false
end)
sliderBackdrop:SetScript("OnUpdate", function()
if MTH_BOOK_STATE.sliderDragging then
if IsMouseButtonDown and not IsMouseButtonDown("LeftButton") then
MTH_BOOK_STATE.sliderDragging = false
else
MTH_BOOK_SetSliderFromCursor(listSlider)
end
end
listSlider:SetScript("OnUpdate", nil)
end)
end
end
+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>
+3 -1
View File
@@ -9,7 +9,6 @@ local MTH_LOCALE_ALIASES = {
enGB = "enUS",
esMX = "esES",
zhTW = "zhCN",
frFR = "enUS",
}
local MTH_SUPPORTED_LOCALES = {
@@ -198,6 +197,9 @@ local MTH_SPELL_ID_BY_TOKEN = {
["Counterattack"] = 19306,
["Clever Traps"] = 19239,
["Deterrence"] = 19263,
["Entrapment"] = 19184,
["Entrapment (Rank 2)"] = 19387,
["Entrapment (Rank 3)"] = 19388,
["Explosive Trap"] = 13813,
["Explosive Trap Effect"] = 13812,
["Feed Pet"] = 6991,
+140 -64
View File
@@ -2,7 +2,7 @@ MTH_Map = {
enabled = true,
showWorld = true,
showMinimap = true,
activeSource = "beasts",
activeSource = "focus",
providers = {},
nodesByZone = {},
worldPins = {},
@@ -12,6 +12,7 @@ MTH_Map = {
mapZoneCache = {},
focusNodes = {},
pendingZoneOpenId = nil,
pendingZoneOpenPfqId = nil,
pendingZoneOpenSourceId = nil,
pendingZoneOpenAttempts = 0,
pendingZoneOpenNextAttemptAt = 0,
@@ -21,6 +22,10 @@ MTH_Map = {
lastWorldReason = "n/a",
lastMiniReason = "n/a",
nodeRevision = 0,
minimapForceRefreshInterval = 1.0,
verboseMiniReasons = false,
minimapTickActive = 0.20,
minimapTickIdle = 0.75,
}
local MTH_MAP_MINIMAP_ZOOM = {
@@ -56,11 +61,6 @@ local function MTH_Map_Mod(a, b)
return 0
end
local function MTH_Map_Log(msg)
if MTH and MTH.debug and MTH.Print then
MTH:Print("[MAP] " .. tostring(msg), "debug")
end
end
local function MTH_Map_NormalizeName(name)
if not name then return "" end
@@ -126,12 +126,18 @@ local function MTH_Map_GetZoneNameById(zoneId)
local normalizedId = tonumber(zoneId) or zoneId
if not normalizedId then return nil end
if MTH_DS_ZoneNamesFallback and MTH_DS_ZoneNamesFallback[normalizedId] then
return MTH_DS_ZoneNamesFallback[normalizedId]
-- Translate WMA→pfQ first so WMA IDs don't accidentally hit wrong pfQ entries.
local pfqId = normalizedId
if type(MTH_MAP_WMA_TO_PFQ) == "table" then
pfqId = MTH_MAP_WMA_TO_PFQ[normalizedId] or normalizedId
end
if MTH_DS_ZoneNamesFallback and MTH_DS_ZoneNamesFallback[pfqId] then
return MTH_DS_ZoneNamesFallback[pfqId]
end
if not MTH_DS_Zones then return nil end
local row = MTH_DS_Zones[normalizedId] or MTH_DS_Zones[tostring(normalizedId)]
local row = MTH_DS_Zones[pfqId] or MTH_DS_Zones[tostring(pfqId)]
if not row or not row.names then return nil end
return row.names.enUS or row.names[GetLocale()] or row.names.deDE or row.names.frFR
end
@@ -323,7 +329,6 @@ function MTH_Map:BuildZoneLookup()
for _ in pairs(self.zoneNameNormToId) do
count = count + 1
end
MTH_Map_Log("Zone lookup rebuilt: " .. tostring(count) .. " entries")
end
function MTH_Map:GetMapIDByName(name)
@@ -414,6 +419,15 @@ function MTH_Map:ResolveZoneHierarchy(zoneId)
return resolved
end
-- Translate a pfQ AreaTable ID to a WMA ID (for zone matching against coord tuples).
-- Falls back to the pfQ ID itself if no mapping exists (sub-zones, custom zones).
local function MTH_Map_PfqToWma(pfqId)
if type(MTH_MAP_PFQ_TO_WMA) == "table" then
return MTH_MAP_PFQ_TO_WMA[pfqId] or pfqId
end
return pfqId
end
function MTH_Map:GetCurrentMapID()
local continent = GetCurrentMapContinent and GetCurrentMapContinent() or nil
local zone = GetCurrentMapZone and GetCurrentMapZone() or nil
@@ -430,6 +444,7 @@ function MTH_Map:GetCurrentMapID()
if mapId then
local resolved = self:ResolveZoneHierarchy(mapId)
local normalized = resolved.zoneId or mapId
normalized = MTH_Map_PfqToWma(normalized)
self.lastMapContext = string.format("continent=%s zone=%s mapZone='%s' real=nil mapId=%s normalized=%s", tostring(continent), tostring(zone), tostring(zoneName), tostring(mapId), tostring(normalized))
return normalized
end
@@ -445,6 +460,7 @@ function MTH_Map:GetCurrentMapID()
mapId = self:GetMapIDByName(real)
local resolved = self:ResolveZoneHierarchy(mapId)
local normalized = resolved.zoneId or mapId
normalized = MTH_Map_PfqToWma(normalized)
self.lastMapContext = string.format("continent=%s zone=%s mapZone='%s' real='%s' mapId=%s normalized=%s", tostring(continent), tostring(zone), tostring(zoneName), tostring(real), tostring(mapId), tostring(normalized))
return normalized
end
@@ -463,12 +479,24 @@ 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
end
function MTH_Map:EnsureInitialized()
if self.controller then
return true
end
self:Init()
return self.controller ~= nil
end
function MTH_Map:SetSource(key)
if not self:EnsureInitialized() then
return false
end
if not self.providers[key] then
return false
end
@@ -517,8 +545,12 @@ end
function MTH_Map:OpenWorldMapForZone(zoneId)
local zid = tonumber(zoneId)
if not zid then return false end
local resolved = self:ResolveZoneHierarchy(zid)
local targetZoneId = tonumber(resolved.zoneId or zid) or zid
-- Translate WMA ID → pfQ so game map APIs (pfMap, SetMapZoom) work correctly.
local pfqZid = (type(MTH_MAP_WMA_TO_PFQ) == "table" and MTH_MAP_WMA_TO_PFQ[zid]) or zid
local resolved = self:ResolveZoneHierarchy(pfqZid)
local targetZoneId = tonumber(resolved.zoneId or pfqZid) or pfqZid
-- WMA ID of this zone, for comparing against GetCurrentMapID() which returns WMA IDs.
local targetWmaId = (type(MTH_MAP_PFQ_TO_WMA) == "table" and MTH_MAP_PFQ_TO_WMA[targetZoneId]) or zid
if ToggleWorldMap then
if not WorldMapFrame or not WorldMapFrame:IsShown() then
@@ -531,34 +563,39 @@ function MTH_Map:OpenWorldMapForZone(zoneId)
if pfMap and pfMap.SetMapByID then
pfMap:SetMapByID(targetZoneId)
local currentAfterPF = self:GetCurrentMapID()
if tonumber(currentAfterPF) == tonumber(targetZoneId) then
if tonumber(currentAfterPF) == tonumber(targetWmaId) then
MTH_Map_ClearPendingZoneOpen()
return true
end
end
local zoomApplied = MTH_Map_ApplyWorldMapZone(targetZoneId, zid)
local zoomApplied = MTH_Map_ApplyWorldMapZone(zid, zid)
if zoomApplied then
local currentAfterZoom = self:GetCurrentMapID()
if tonumber(currentAfterZoom) == tonumber(targetZoneId) then
if tonumber(currentAfterZoom) == tonumber(targetWmaId) then
MTH_Map_ClearPendingZoneOpen()
return true
end
end
local nowMapId = self:GetCurrentMapID()
if tonumber(nowMapId) == tonumber(targetZoneId) then
if tonumber(nowMapId) == tonumber(targetWmaId) then
MTH_Map_ClearPendingZoneOpen()
return true
end
self.pendingZoneOpenId = targetZoneId
self.pendingZoneOpenSourceId = zid
self.pendingZoneOpenId = targetWmaId
self.pendingZoneOpenPfqId = targetZoneId
self.pendingZoneOpenSourceId = pfqZid
self.pendingZoneOpenAttempts = 8
self.pendingZoneOpenNextAttemptAt = (GetTime and GetTime() or 0) + 0.25
return true
end
function MTH_Map:GetFamilyColor(family)
return MTH_Map_GetFamilyColor(family)
end
function MTH_Map:FocusBeast(beastId, beast)
local id = tonumber(beastId)
if not id then return false end
@@ -672,6 +709,9 @@ function MTH_Map:SetEnabled(enabled)
if not self.enabled then
self:HideAllPins()
else
if not self:EnsureInitialized() then
return
end
self:UpdateWorldMap()
self:UpdateMinimap()
end
@@ -682,6 +722,9 @@ function MTH_Map:SetWorldEnabled(enabled)
if not self.showWorld then
self:HideWorldPins()
else
if not self:EnsureInitialized() then
return
end
self:UpdateWorldMap()
end
end
@@ -691,6 +734,9 @@ function MTH_Map:SetMinimapEnabled(enabled)
if not self.showMinimap then
self:HideMinimapPins()
else
if not self:EnsureInitialized() then
return
end
self:UpdateMinimap()
end
end
@@ -866,7 +912,6 @@ function MTH_Map:RebuildNodes()
end
end
MTH_Map_Log("Rebuilt nodes for source '" .. tostring(self.activeSource) .. "'")
end
function MTH_Map:UpdateWorldMap()
@@ -952,22 +997,21 @@ function MTH_Map:UpdateMinimap()
end
local mapId = self:GetCurrentMapID()
if not mapId or not self.nodesByZone[mapId] then
local nodes = mapId and self.nodesByZone[mapId] or nil
if not mapId or not nodes then
self.lastMiniReason = "no-mapid-or-nodes"
self._lastMiniState = nil
self:HideMinimapPins()
return
end
local minimapSizes = MTH_DS_MinimapSizes or (pfDB and pfDB["minimap"])
local mapSize = minimapSizes and minimapSizes[mapId]
if not mapSize then
self.lastMiniReason = "missing-minimap-size"
if table.getn(nodes) <= 0 then
self.lastMiniReason = "no-nodes-in-zone"
self._lastMiniState = nil
self:HideMinimapPins()
return
end
local now = GetTime() or 0
local xPlayer, yPlayer = GetPlayerMapPosition("player")
if not xPlayer or not yPlayer or (xPlayer == 0 and yPlayer == 0) then
self.lastMiniReason = "player-position-unavailable"
@@ -980,10 +1024,38 @@ function MTH_Map:UpdateMinimap()
yPlayer = yPlayer * 100
local zoom = Minimap:GetZoom()
local miniState = self._lastMiniState
if miniState
and miniState.mapId == mapId
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"
return
end
end
local minimapSizes = MTH_DS_MinimapSizes or (pfDB and pfDB["minimap"])
local mapSize = minimapSizes and minimapSizes[mapId]
if not mapSize and type(MTH_MAP_WMA_TO_PFQ) == "table" then
-- Fallback: try the pfQ key (for sub-zones not yet mapped to WMA)
local pfqId = MTH_MAP_WMA_TO_PFQ[mapId]
mapSize = pfqId and minimapSizes and minimapSizes[pfqId]
end
if not mapSize then
self.lastMiniReason = self.verboseMiniReasons and "missing-minimap-size" or "missing-size"
self._lastMiniState = nil
self:HideMinimapPins()
return
end
local indoor = MTH_Map_MinimapIndoorState()
local zoomSet = MTH_MAP_MINIMAP_ZOOM[indoor]
if not zoomSet then
self.lastMiniReason = "missing-zoomset"
self.lastMiniReason = self.verboseMiniReasons and "missing-zoomset" or "missing-zoomset"
self._lastMiniState = nil
self:HideMinimapPins()
return
@@ -991,7 +1063,7 @@ function MTH_Map:UpdateMinimap()
local mapZoom = zoomSet[zoom]
if not mapZoom then
self.lastMiniReason = "missing-mapzoom"
self.lastMiniReason = self.verboseMiniReasons and "missing-mapzoom" or "missing-zoom"
self._lastMiniState = nil
self:HideMinimapPins()
return
@@ -1000,33 +1072,16 @@ function MTH_Map:UpdateMinimap()
local mapWidth = mapSize[1]
local mapHeight = mapSize[2]
if not mapWidth or not mapHeight or mapWidth == 0 or mapHeight == 0 then
self.lastMiniReason = "invalid-map-size"
self.lastMiniReason = self.verboseMiniReasons and "invalid-map-size" or "invalid-size"
self._lastMiniState = nil
self:HideMinimapPins()
return
end
local miniState = self._lastMiniState
if miniState
and miniState.mapId == mapId
and miniState.zoom == zoom
and miniState.indoor == indoor
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 then
self.lastMiniReason = "throttled-small-move"
return
end
end
local xScale = mapZoom / mapWidth
local yScale = mapZoom / mapHeight
local xDraw = Minimap:GetWidth() / xScale / 100
local yDraw = Minimap:GetHeight() / yScale / 100
local nodes = self.nodesByZone[mapId]
local shown = 0
for i = 1, table.getn(nodes) do
@@ -1057,16 +1112,21 @@ function MTH_Map:UpdateMinimap()
if self.minimapPins[i] then self.minimapPins[i]:Hide() end
end
self._lastMiniState = {
mapId = mapId,
zoom = zoom,
indoor = indoor,
xPlayer = xPlayer,
yPlayer = yPlayer,
nodeRevision = self.nodeRevision or 0,
}
local lastState = self._lastMiniState or {}
lastState.mapId = mapId
lastState.zoom = zoom
lastState.indoor = indoor
lastState.xPlayer = xPlayer
lastState.yPlayer = yPlayer
lastState.nodeRevision = self.nodeRevision or 0
lastState.nextForceAt = now + (tonumber(self.minimapForceRefreshInterval) or 1.0)
self._lastMiniState = lastState
self.lastMiniReason = "ok(map=" .. tostring(mapId) .. ",shown=" .. tostring(shown) .. ")"
if self.verboseMiniReasons then
self.lastMiniReason = "ok(map=" .. tostring(mapId) .. ",shown=" .. tostring(shown) .. ")"
else
self.lastMiniReason = "ok"
end
end
function MTH_Map:RegisterDefaultProviders()
@@ -1193,7 +1253,6 @@ function MTH_Map:RegisterDefaultProviders()
end
if unresolvedCount > 0 then
MTH_Map_Log("drops source: " .. tostring(unresolvedCount) .. " drop-creature links have no loaded coordinates (sample IDs: " .. table.concat(unresolvedSamples, ", ") .. ")")
end
return nodes
@@ -1202,12 +1261,15 @@ function MTH_Map:RegisterDefaultProviders()
end
function MTH_Map:Init()
if self.controller then
return
end
self:BuildZoneLookup()
self:RegisterDefaultProviders()
self:RebuildNodes()
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")
@@ -1230,7 +1292,6 @@ function MTH_Map:Init()
if not frame then return end
local now = GetTime() or 0
if (frame._mthTick or 0) > now then return end
frame._mthTick = now + 0.12
if MTH_Map.pendingZoneOpenId and MTH_Map.pendingZoneOpenAttempts and MTH_Map.pendingZoneOpenAttempts > 0 then
if (MTH_Map.pendingZoneOpenNextAttemptAt or 0) <= now then
@@ -1238,9 +1299,9 @@ function MTH_Map:Init()
if WorldMapFrame and WorldMapFrame:IsShown() then
if pfMap and pfMap.SetMapByID then
pfMap:SetMapByID(MTH_Map.pendingZoneOpenId)
pfMap:SetMapByID(MTH_Map.pendingZoneOpenPfqId or MTH_Map.pendingZoneOpenId)
end
MTH_Map_ApplyWorldMapZone(MTH_Map.pendingZoneOpenId, MTH_Map.pendingZoneOpenSourceId)
MTH_Map_ApplyWorldMapZone(MTH_Map.pendingZoneOpenId or MTH_Map.pendingZoneOpenPfqId, MTH_Map.pendingZoneOpenId or MTH_Map.pendingZoneOpenPfqId)
local nowMapId = MTH_Map:GetCurrentMapID()
if tonumber(nowMapId) == tonumber(MTH_Map.pendingZoneOpenId) then
MTH_Map_ClearPendingZoneOpen()
@@ -1259,12 +1320,27 @@ function MTH_Map:Init()
end
if MTH_Map.enabled and MTH_Map.showMinimap then
MTH_Map:UpdateMinimap()
local mapId = MTH_Map:GetCurrentMapID()
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()
end
end
local nodes = mapId and MTH_Map.nodesByZone and MTH_Map.nodesByZone[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
MTH_Map:UpdateMinimap()
elseif (frame._mthNoNodeTick or 0) <= now then
frame._mthNoNodeTick = now + 1.0
MTH_Map:UpdateMinimap()
end
else
frame._mthTick = now + (tonumber(MTH_Map.minimapTickIdle) or 0.75)
end
end)
end
MTH_Map_Log("Map system initialized")
end
MTH_Map:Init()
+29 -22
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,12 +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(_, 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()
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
end
end)
+45 -45
View File
@@ -44,6 +44,8 @@ local MTH_AB_STATE = {
},
}
local MTH_AB_TRANSIENT_STORE = {}
local function MTH_AB_SetCheckedNoClick(check, checked)
if not check then return end
MTH_AB_STATE.syncing = true
@@ -51,6 +53,14 @@ local function MTH_AB_SetCheckedNoClick(check, checked)
MTH_AB_STATE.syncing = false
end
local function MTH_AB_IsChecked(control)
if not control or type(control.GetChecked) ~= "function" then
return false
end
local checked = control:GetChecked()
return checked == 1 or checked == true
end
local function MTH_AB_GetEngine()
return MTH_AutoBuyEngine
end
@@ -60,8 +70,7 @@ local function MTH_AB_GetStore()
if engine and engine.EnsureDefaults then
return engine:EnsureDefaults()
end
MTH_AutoBuy_Saved = MTH_AutoBuy_Saved or {}
return MTH_AutoBuy_Saved
return MTH_AB_TRANSIENT_STORE
end
local function MTH_AB_NormalizeRule(rule)
@@ -91,10 +100,10 @@ end
local function MTH_AB_EnsureConfig()
local store = MTH_AB_GetStore()
if store.enabled == nil then store.enabled = true end
if store.enabled == nil then store.enabled = false end
if type(store.projectiles) ~= "table" then store.projectiles = {} end
if type(store.petFood) ~= "table" then store.petFood = {} end
if store.projectiles.enabled == nil then store.projectiles.enabled = true end
if store.projectiles.enabled == nil then store.projectiles.enabled = false end
if store.petFood.enabled == nil then store.petFood.enabled = false end
if store.petFood.stacks == nil then store.petFood.stacks = 1 end
if store.petFood.scope == nil then store.petFood.scope = "current" end
@@ -509,7 +518,7 @@ local function MTH_AB_RefreshOptionsUI()
local store = MTH_AB_EnsureConfig()
local moduleEnabled = true
if MTH and MTH.IsModuleEnabled then
moduleEnabled = MTH:IsModuleEnabled("autobuy", true) and true or false
moduleEnabled = MTH:IsModuleEnabled("autobuy", false) and true or false
end
MTH_AB_SetCheckedNoClick(MTH_AB_STATE.controls.moduleEnabled, moduleEnabled)
@@ -648,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, self:GetChecked() and true or false)
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)
@@ -706,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", self:GetChecked() and true or false)
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
@@ -721,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 = self:GetChecked() and true or false
store.projectiles.enabled = MTH_AB_IsChecked(this)
MTH_AB_RefreshOptionsUI()
end)
end
@@ -760,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 = self:GetChecked() and true or false
store.petFood.enabled = MTH_AB_IsChecked(this)
MTH_AB_RefreshOptionsUI()
end)
end
@@ -782,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
@@ -807,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()
@@ -818,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()
@@ -842,6 +841,7 @@ local function MTH_AB_BuildUI(container)
text:SetText("")
controls.petFoodPetLines[i] = { icon = icon, text = text }
end
end
function MTH_SetupAutoBuyOptions()
+228
View File
@@ -0,0 +1,228 @@
local MTH_AQ_READY = MTH_OptionsRequire and MTH_OptionsRequire("options-autoquest", {
"MTH_GetFrame",
"MTH_ClearContainer",
"MTH_CreateCheckbox",
})
local function MTH_AQ_IsChecked(control)
if not control or type(control.GetChecked) ~= "function" then
return false
end
local checked = control:GetChecked()
return checked == 1 or checked == true
end
local function MTH_AQ_EnsureStore()
if type(MTH_CharSavedVariables) ~= "table" then
MTH_CharSavedVariables = {}
end
if type(MTH_CharSavedVariables.autoquest) ~= "table" then
MTH_CharSavedVariables.autoquest = {}
end
local store = MTH_CharSavedVariables.autoquest
if type(MTH_CharSavedVariables.questautomation) == "table" then
local legacy = MTH_CharSavedVariables.questautomation
if store.scorpokDrazial == nil and legacy.scorpokDrazial ~= nil then
store.scorpokDrazial = legacy.scorpokDrazial and true or false
end
if store.scorpokTooltip == nil and legacy.scorpokTooltip ~= nil then
store.scorpokTooltip = legacy.scorpokTooltip and true or false
end
end
if store.scorpokDrazial == nil then
store.scorpokDrazial = false
end
if store.arrowsForSissies == nil then
store.arrowsForSissies = false
end
return store
end
function MTH_SetupAutoQuestOptions()
if not MTH_AQ_READY then return end
local container = MTH_GetFrame("MetaHuntOptionsAutoQuest")
if not container then return end
MTH_ClearContainer(container)
local title = container:CreateFontString("MetaHuntOptionsAutoQuestTitle", "ARTWORK", "GameFontHighlight")
title:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -10)
title:SetText("Auto Quest")
local body = container:CreateFontString("MetaHuntOptionsAutoQuestBody", "ARTWORK", "GameFontNormalSmall")
body:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -30)
body:SetWidth(560)
body:SetJustifyH("LEFT")
body:SetJustifyV("TOP")
body:SetTextColor(0.45, 0.75, 1)
body:SetText("Automation for specific quest interactions.")
local moduleEnabled = true
if MTH and MTH.GetModule then
local module = MTH:GetModule("autoquest")
if type(module) == "table" and module.enabled ~= nil then
moduleEnabled = module.enabled and true or false
elseif MTH and MTH.IsModuleEnabled then
moduleEnabled = MTH:IsModuleEnabled("autoquest", false) and true or false
end
elseif MTH and MTH.IsModuleEnabled then
moduleEnabled = MTH:IsModuleEnabled("autoquest", false) and true or false
end
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()
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
MTH:Print("Failed to change Auto Quest module state: " .. tostring(err), "error")
end
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
this:SetChecked(actual and true or false)
end)
end
local store = MTH_AQ_EnsureStore()
if store.scorpokTooltip == nil then
store.scorpokTooltip = false
end
local sectionWidth = (container:GetWidth() or 600) - 40
if sectionWidth < 360 then
sectionWidth = 360
end
local function ensureSection(name, sectionTitle, yOffset, height)
local section = getglobal(name)
if not section then
section = CreateFrame("Frame", name, container, "OptionFrameBoxTemplate")
end
if not section then
return nil
end
section:SetParent(container)
section:ClearAllPoints()
section:SetPoint("TOPLEFT", container, "TOPLEFT", 20, yOffset)
section:SetWidth(sectionWidth)
section:SetHeight(height)
section:Show()
local titleFrame = getglobal(name .. "Title")
if titleFrame then
titleFrame:SetText(sectionTitle)
end
return section
end
local function ensureCheckbox(section, name, label, yOffset, checked)
local check = getglobal(name)
if not check then
check = CreateFrame("CheckButton", name, section, "OptionsCheckButtonTemplate")
end
if not check then
return nil
end
check:SetParent(section)
check:ClearAllPoints()
check:SetPoint("TOPLEFT", section, "TOPLEFT", 12, yOffset)
check:SetChecked(checked and true or false)
check:Show()
local text = getglobal(name .. "Text")
if text then
text:SetText(label)
end
return check
end
local scorpokSection = ensureSection("MetaHuntAutoQuestScorpokBox", "Salt of the Scorpok", -130, 112)
if scorpokSection then
local scorpokToggle = ensureCheckbox(
scorpokSection,
"MetaHuntOptionsAutoQuestScorpokToggle",
"Enable SHIFT-rightclick for Bloodmage Drazial",
-10,
store.scorpokDrazial and true or false
)
if scorpokToggle then
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")
if type(moduleStore) == "table" then
moduleStore.scorpokDrazial = store.scorpokDrazial and true or false
end
end
local module = MTH and MTH.GetModule and MTH:GetModule("autoquest")
if module and module.SetScorpokDrazialEnabled then
module:SetScorpokDrazialEnabled(enabled)
end
end)
end
local tooltipToggle = ensureCheckbox(
scorpokSection,
"MetaHuntOptionsAutoQuestScorpokTooltipToggle",
"Enhance Tooltip on Drazial and related beasts (requires Tooltips module enabled)",
-42,
store.scorpokTooltip and true or false
)
if tooltipToggle then
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")
if type(moduleStore) == "table" then
moduleStore.scorpokTooltip = store.scorpokTooltip and true or false
end
end
end)
end
end
local sissiesSection = ensureSection("MetaHuntAutoQuestSissiesBox", "Arrows Are For Sissies", -255, 108)
if sissiesSection then
local sissiesNote = getglobal("MetaHuntOptionsAutoQuestSissiesNote")
if not sissiesNote then
sissiesNote = sissiesSection:CreateFontString("MetaHuntOptionsAutoQuestSissiesNote", "ARTWORK", "GameFontNormalSmall")
end
sissiesNote:ClearAllPoints()
sissiesNote:SetPoint("TOPLEFT", sissiesSection, "TOPLEFT", 14, -10)
sissiesNote:SetWidth(sectionWidth - 28)
sissiesNote:SetJustifyH("LEFT")
sissiesNote:SetTextColor(0.45, 0.75, 1)
sissiesNote:SetText("Note that you should not activate this if you are using LazyPig")
sissiesNote:Show()
local sissiesToggle = ensureCheckbox(
sissiesSection,
"MetaHuntOptionsAutoQuestSissiesToggle",
"Enable SHIFT-rightclick for Artilleryman Sheldonore",
-40,
store.arrowsForSissies and true or false
)
if sissiesToggle then
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")
if type(moduleStore) == "table" then
moduleStore.arrowsForSissies = store.arrowsForSissies and true or false
end
end
local module = MTH and MTH.GetModule and MTH:GetModule("autoquest")
if module and module.SetArrowsForSissiesEnabled then
module:SetArrowsForSissiesEnabled(enabled)
end
end)
end
end
end
+52 -39
View File
@@ -98,11 +98,20 @@ local function MTH_CHRON_CopyTable(source)
end
local function MTH_CHRON_GetProfile()
if not (MTH and MTH.GetModuleSavedVariables) then
if not (MTH and MTH.GetModuleCharSavedVariables) then
return MTH_CHRON_CopyTable(MTH_CHRON_DEFAULTS)
end
local store = MTH:GetModuleSavedVariables("chronometer")
local store = MTH:GetModuleCharSavedVariables("chronometer")
if type(store) ~= "table" then
return MTH_CHRON_CopyTable(MTH_CHRON_DEFAULTS)
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 = MTH_CHRON_CopyTable(accountStore.profile)
end
end
if type(store.profile) ~= "table" then
store.profile = {}
end
@@ -298,6 +307,14 @@ local function MTH_CHRON_MakeSmall(parent, name, text, x, y)
return label
end
local function MTH_CHRON_IsChecked(control)
if not control or type(control.GetChecked) ~= "function" then
return false
end
local checked = control:GetChecked()
return checked == 1 or checked == true
end
local function MTH_CHRON_MakeInput(parent, name, x, y, width, height, value, onAccept)
local edit = CreateFrame("EditBox", name, parent, "InputBoxTemplate")
@@ -309,13 +326,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
@@ -368,14 +384,18 @@ 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
if self:GetChecked() then
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
this:SetChecked(true)
else
profile.disabledSpells[bucket][timerName] = {}
this:SetChecked(nil)
end
MTH_CHRON_ApplyLiveProfile(profile)
end)
end
timerY = timerY - 20
@@ -448,10 +468,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 = self:GetChecked() and true or false
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
@@ -473,40 +492,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 = self:GetChecked() and true or false
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 = self:GetChecked() and true or false
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 = self:GetChecked() and true or false
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 = self:GetChecked() and true or false
onlySelfCheck:SetScript("OnClick", function()
if not this then return end
profile.onlyself = MTH_CHRON_IsChecked(this)
end)
end
elseif section == "bar" then
@@ -588,10 +603,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 = self:GetChecked() and true or false
growCheck:SetScript("OnClick", function()
if not this then return end
profile.growup = MTH_CHRON_IsChecked(this)
MTH_CHRON_ApplyLiveProfile(profile)
end)
end
@@ -599,10 +613,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 = self:GetChecked() and true or false
reverseCheck:SetScript("OnClick", function()
if not this then return end
profile.reverse = MTH_CHRON_IsChecked(this)
MTH_CHRON_ApplyLiveProfile(profile)
end)
end
+10
View File
@@ -159,6 +159,16 @@ function MTH_SetupCreditsOptions()
cursor,
-4)
cursor = MTH_CreditsCreateText(content, cursor,
MTH_CR_L("CREDITS_ABOUT_ICU", "- ICU was an old vanilla addon. I used the following last revision and added many more features of config options to it."),
"GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateLink(content,
"https://github.com/wigan91/ICU-TWoW",
"https://github.com/wigan91/ICU-TWoW",
cursor,
-4)
cursor = MTH_CreditsCreateText(content, cursor,
MTH_CR_L("CREDITS_ABOUT_MAP", "- The Map/Marker system is the one of pfQuest from Master Shagu. Unfortunately he is no more reachable for some months and I could not have some talk with him about this. I mostly let it untouched, I just made slight modifications so it can integrate better within Metahunt, and doesn't conflict with pfQuest."),
"GameFontNormalSmall", -10)
+133
View File
@@ -0,0 +1,133 @@
------------------------------------------------------
-- MetaHunt: Experimental Ammunition Options Panel
-- TWoW 1.18.1 — "Nightmares of Ursol"
------------------------------------------------------
local function MTH_EA_OPT_Ready()
return type(MTH_GetFrame) == "function"
and type(MTH_CreateCheckbox) == "function"
and type(MTH_CreateSlider) == "function"
end
local function MTH_EA_OPT_GetCfg()
if type(MTH_SavedVariables) ~= "table" then return {} end
if type(MTH_SavedVariables.modules) ~= "table" then return {} end
return MTH_SavedVariables.modules["expammo"] or {}
end
local MTH_EA_OPT_built = false
-- Helper: create a pre-wired checkbox
local function MTH_EA_OPT_CB(container, name, label, yOff, isChecked, onClick)
local cb = MTH_CreateCheckbox(container, name, label, yOff, 20)
if cb then
cb:SetChecked(isChecked and 1 or 0)
cb:SetScript("OnClick", function()
local v = this:GetChecked()
onClick(v == 1 or v == true)
end)
end
return cb
end
-- Helper: section header label
local function MTH_EA_OPT_Header(container, text, yOff)
local fs = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
fs:SetPoint("TOPLEFT", container, "TOPLEFT", 20, yOff)
fs:SetText("|cffff9900" .. text .. "|r")
end
local function MTH_EA_OPT_BuildUI(container)
if MTH_EA_OPT_built then return end
MTH_EA_OPT_built = true
local cfg = MTH_EA_OPT_GetCfg()
-- Title
local title = container:CreateFontString(nil, "ARTWORK", "GameFontHighlightLarge")
title:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -16)
title:SetText("MM Widget — Experimental Ammunition Tracker")
-- Short description
local desc = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
desc:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -46)
desc:SetWidth(540)
desc:SetJustifyH("LEFT")
desc:SetJustifyV("TOP")
desc:SetText(
"Tracks the 1.18.1 ammo cycling mechanic.\n"
.. "Aimed Shot starts a 60s cycle: "
.. "|cffff8833Explosive|r \226\134\146 |cff33ff55Poisonous|r \226\134\146 |cff6633ffEnchanted|r\n"
.. "Each state is consumed by its matching shot (Multi-Shot / Serpent Sting / Arcane Shot)."
)
-- ── Section: Module ──────────────────────────────────────
MTH_EA_OPT_Header(container, "Module", -112)
local moduleEnabled = true
if MTH and MTH.IsModuleEnabled then
moduleEnabled = MTH:IsModuleEnabled("expammo", true) and true or false
end
MTH_EA_OPT_CB(container, "MTH_ExpAmmoModuleCB",
"Enable MM Widget module",
-132, moduleEnabled,
function(v)
if MTH and MTH.SetModuleEnabled then
MTH:SetModuleEnabled("expammo", v)
end
if MTH_ExpAmmo then MTH_ExpAmmo.SetEnabled(v) end
end)
-- ── Section: Tracker Visibility ─────────────────────────
MTH_EA_OPT_Header(container, "Tracker Visibility", -166)
MTH_EA_OPT_CB(container, "MTH_ExpAmmoShowLnLCB",
"Show Lock and Load cell (top)",
-186, cfg.showLnL ~= false,
function(v) if MTH_ExpAmmo then MTH_ExpAmmo.SetShowLnL(v) end end)
MTH_EA_OPT_CB(container, "MTH_ExpAmmoShowHintCB",
"Show action hint cell (bottom)",
-210, cfg.showHint ~= false,
function(v) if MTH_ExpAmmo then MTH_ExpAmmo.SetShowHint(v) end end)
MTH_EA_OPT_CB(container, "MTH_ExpAmmoHideOOCCB",
"Hide widget when out of combat",
-234, cfg.hideOOC == true,
function(v) if MTH_ExpAmmo then MTH_ExpAmmo.SetHideOOC(v) end end)
-- ── Section: Appearance ──────────────────────────────────
MTH_EA_OPT_Header(container, "Appearance", -268)
local sizeSlider = MTH_CreateSlider(container, "MTH_ExpAmmoCellSizeSlider",
"Cell Size (px)", 20, 80, 4, -288)
if sizeSlider then
sizeSlider:SetValue(math.max(20, math.min(80, cfg.cellSize or 40)))
sizeSlider.onChange = function(val)
if MTH_ExpAmmo then MTH_ExpAmmo.SetCellSize(val) end
end
end
MTH_EA_OPT_CB(container, "MTH_ExpAmmoBigCDCB",
"Large cooldown numbers (centered in cell)",
-352, cfg.bigCD == true,
function(v) if MTH_ExpAmmo then MTH_ExpAmmo.SetBigCD(v) end end)
-- ── Notes ───────────────────────────────────────────────
local tip = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
tip:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -388)
tip:SetWidth(540)
tip:SetJustifyH("LEFT")
tip:SetTextColor(0.3, 0.7, 1.0)
tip:SetText("Tip: hold ALT and drag the tracker widget to reposition it.")
end
function MTH_SetupExpAmmoOptions()
if not MTH_EA_OPT_Ready() then return end
local container = MTH_GetFrame("MetaHuntOptionsExpAmmo")
if not container then return end
local ok, err = pcall(MTH_EA_OPT_BuildUI, container)
if not ok and MTH and MTH.Print then
MTH:Print("[MTH ExpAmmo OPTIONS] " .. tostring(err), "error")
end
end
+18 -20
View File
@@ -226,10 +226,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
@@ -263,11 +262,11 @@ function MTH_SetupFeedOMaticOptions()
MTH_FOM_StartBindingCapture()
end
end)
bindButton:SetScript("OnKeyDown", function(_, key)
bindButton:SetScript("OnKeyDown", function()
if not MTH_FOM_STATE.bindingCapture then
return
end
key = key or arg1
local key = arg1
if not key or key == "" then
return
end
@@ -305,10 +304,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 +319,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 +354,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)
+590
View File
@@ -0,0 +1,590 @@
local MTH_ICU_OPT_READY = true
local MTH_ICU_OPT_MISSING = {}
if type(MTH_GetFrame) ~= "function" then table.insert(MTH_ICU_OPT_MISSING, "MTH_GetFrame") end
if type(MTH_ClearContainer) ~= "function" then table.insert(MTH_ICU_OPT_MISSING, "MTH_ClearContainer") end
if type(MTH_CreateCheckbox) ~= "function" then table.insert(MTH_ICU_OPT_MISSING, "MTH_CreateCheckbox") end
if table.getn(MTH_ICU_OPT_MISSING) > 0 then
MTH_ICU_OPT_READY = false
if MTH and MTH.Print then
MTH:Print("[MTH ICU OPTIONS] missing dependencies: " .. table.concat(MTH_ICU_OPT_MISSING, ", "), "error")
end
end
local MTH_ICU_OPT_STATE = {
container = nil,
built = false,
controls = {},
}
local MTH_ICU_LAYOUT = {
LEFT_X = 20,
LEFT_DROPDOWN_X = 94,
RIGHT_X = 280,
RIGHT_DROPDOWN_X = 366,
DROPDOWN_WIDTH = 108,
COLOR_BUTTON_X = 378,
COLOR_BUTTON_WIDTH = 78,
COLOR_ROW_STEP = 24,
}
local MTH_ICU_OPTIONS_FALLBACK = {
ALERT = { "AUTO", "PARTY", "RAID", "SELF", "MTH Message", "OFF" },
ANNOUNCE = { "AUTO", "SAY", "YELL", "PARTY", "RAID", "SELF", "MTH Message", "OFF" },
ANCHOR = { "TOP", "TOPLEFT", "TOPRIGHT", "BOTTOM", "BOTTOMLEFT", "BOTTOMRIGHT", "LEFT", "RIGHT", "CUSTOM" },
POPUP_HIDE_DELAY = { "INSTANT", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" },
HEALTH_TEXT_MODE = { "NONE", "PERCENT", "HP", "BOTH" },
PLAYER_COLOR_MODE = { "CLASS", "REACTION", "FACTION", "CUSTOM" },
}
local MTH_ICU_DEFAULT_BG_COLORS = {
PLAYER_HOSTILE = { 1.00, 0.20, 0.20 },
PLAYER_NEUTRAL = { 1.00, 0.85, 0.10 },
PLAYER_FRIENDLY = { 0.20, 0.75, 1.00 },
NPC_HOSTILE = { 1.00, 0.20, 0.20 },
NPC_NEUTRAL = { 1.00, 0.85, 0.10 },
NPC_FRIENDLY = { 0.10, 0.90, 0.10 },
UNKNOWN = { 0.70, 0.70, 0.70 },
}
local MTH_ICU_COLOR_ROWS = {
{ key = "PLAYER_HOSTILE", label = "Player hostile" },
{ key = "PLAYER_NEUTRAL", label = "Player neutral" },
{ key = "PLAYER_FRIENDLY", label = "Player friendly" },
{ key = "NPC_HOSTILE", label = "NPC hostile" },
{ key = "NPC_NEUTRAL", label = "NPC neutral" },
{ key = "NPC_FRIENDLY", label = "NPC friendly" },
{ key = "UNKNOWN", label = "Unknown fallback" },
}
local function MTH_ICU_GetStore()
local store = nil
if MTH and MTH.GetModuleCharSavedVariables then
store = MTH:GetModuleCharSavedVariables("icu")
end
if type(store) ~= "table" then
ICUvars = ICUvars or {}
store = ICUvars
end
if store.ALERT == nil then store.ALERT = "MTH Message" end
if store.ALERT == "SAY" or store.ALERT == "YELL" then store.ALERT = "SELF" end
if store.ANNOUNCE == nil then store.ANNOUNCE = "MTH Message" end
if store.ANCHOR == nil then store.ANCHOR = "BOTTOMRIGHT" end
if store.POPUP_HIDE_DELAY == nil then
store.POPUP_HIDE_DELAY = "INSTANT"
else
local rawDelay = tostring(store.POPUP_HIDE_DELAY)
if rawDelay ~= "INSTANT" then
local n = tonumber(rawDelay)
if not n then
store.POPUP_HIDE_DELAY = "INSTANT"
else
n = math.floor(n)
if n < 1 then
store.POPUP_HIDE_DELAY = "INSTANT"
elseif n > 10 then
store.POPUP_HIDE_DELAY = "10"
else
store.POPUP_HIDE_DELAY = tostring(n)
end
end
end
end
if store.mouseOver == nil then store.mouseOver = false end
if store.HEALTH_TEXT_MODE == nil then store.HEALTH_TEXT_MODE = "BOTH" end
if store.HEALTH_TEXT_MODE == "AUTO" then store.HEALTH_TEXT_MODE = "BOTH" end
if store.PLAYER_COLOR_MODE == nil then store.PLAYER_COLOR_MODE = "CLASS" end
if store.REACTION_ONLY_PVP_PLAYERS == nil then store.REACTION_ONLY_PVP_PLAYERS = true end
if store.SHOW_GUILD_NAME == nil then store.SHOW_GUILD_NAME = false end
if store.SHOW_PLAYER_CLASS == nil then store.SHOW_PLAYER_CLASS = true end
if store.SHOW_PLAYER_RACE == nil then store.SHOW_PLAYER_RACE = true end
if store.SHOW_CUSTOM_TITLES == nil then store.SHOW_CUSTOM_TITLES = false end
if store.EXPAND_UP == nil then store.EXPAND_UP = false end
if type(store.COLORS) ~= "table" then
store.COLORS = {}
end
for key, def in pairs(MTH_ICU_DEFAULT_BG_COLORS) do
if type(store.COLORS[key]) ~= "table" then
store.COLORS[key] = { def[1], def[2], def[3] }
end
end
ICUvars = store
return store
end
local function MTH_ICU_GetOptionValues(key)
if type(ICU_OPTIONS) == "table" and type(ICU_OPTIONS[key]) == "table" then
return ICU_OPTIONS[key]
end
return MTH_ICU_OPTIONS_FALLBACK[key] or {}
end
local function MTH_ICU_SetValue(key, value)
local store = MTH_ICU_GetStore()
store[key] = tostring(value or "")
if key == "ANCHOR" and type(ICU_SetPoints) == "function" then
ICU_SetPoints()
end
end
local function MTH_ICU_DropdownSetText(frame, text)
if UIDropDownMenu_SetText then
UIDropDownMenu_SetText(tostring(text or ""), frame)
return
end
if not frame or not frame.GetName then return end
local textRegion = getglobal(frame:GetName() .. "Text")
if textRegion and textRegion.SetText then
textRegion:SetText(tostring(text or ""))
end
end
local function MTH_ICU_GetColor(key)
local store = MTH_ICU_GetStore()
local row = store.COLORS and store.COLORS[key]
if type(row) == "table" then
return tonumber(row[1]) or 0, tonumber(row[2]) or 0, tonumber(row[3]) or 0
end
local def = MTH_ICU_DEFAULT_BG_COLORS[key] or MTH_ICU_DEFAULT_BG_COLORS.UNKNOWN
return def[1], def[2], def[3]
end
local function MTH_ICU_SetColor(key, r, g, b)
local store = MTH_ICU_GetStore()
if type(store.COLORS) ~= "table" then
store.COLORS = {}
end
store.COLORS[key] = {
math.max(0, math.min(1, tonumber(r) or 0)),
math.max(0, math.min(1, tonumber(g) or 0)),
math.max(0, math.min(1, tonumber(b) or 0)),
}
end
local function MTH_ICU_OpenColorPicker(colorKey)
if not ColorPickerFrame then
if MTH and MTH.Print then
MTH:Print("ColorPickerFrame is not available in this client", "error")
end
return
end
local currentR, currentG, currentB = MTH_ICU_GetColor(colorKey)
local function applyColor()
local r, g, b = ColorPickerFrame:GetColorRGB()
MTH_ICU_SetColor(colorKey, r, g, b)
MTH_RefreshICUOptions()
end
ColorPickerFrame.func = applyColor
ColorPickerFrame.cancelFunc = function(prev)
if type(prev) == "table" then
local r = prev.r or prev[1] or currentR
local g = prev.g or prev[2] or currentG
local b = prev.b or prev[3] or currentB
MTH_ICU_SetColor(colorKey, r, g, b)
MTH_RefreshICUOptions()
end
end
ColorPickerFrame:SetColorRGB(currentR, currentG, currentB)
ColorPickerFrame.hasOpacity = false
ColorPickerFrame.opacity = 1
ColorPickerFrame.previousValues = { currentR, currentG, currentB }
ColorPickerFrame:Show()
end
local function MTH_ICU_CreateOptionRow(container, rowName, label, key, y, labelX, dropdownX)
labelX = labelX or MTH_ICU_LAYOUT.LEFT_X
dropdownX = dropdownX or MTH_ICU_LAYOUT.LEFT_DROPDOWN_X
local labelFs = container:CreateFontString(rowName .. "Label", "ARTWORK", "GameFontNormalSmall")
labelFs:SetPoint("TOPLEFT", container, "TOPLEFT", labelX, y)
labelFs:SetText(label)
local dropdown = CreateFrame("Frame", rowName .. "Drop", container, "UIDropDownMenuTemplate")
dropdown:ClearAllPoints()
dropdown:SetPoint("TOPLEFT", container, "TOPLEFT", dropdownX, y + 12)
if UIDropDownMenu_SetWidth then
UIDropDownMenu_SetWidth(MTH_ICU_LAYOUT.DROPDOWN_WIDTH, dropdown)
end
if UIDropDownMenu_JustifyText then
UIDropDownMenu_JustifyText("LEFT", dropdown)
end
if UIDropDownMenu_Initialize then
UIDropDownMenu_Initialize(dropdown, function()
local values = MTH_ICU_GetOptionValues(key)
local store = MTH_ICU_GetStore()
local current = tostring(store[key] or "")
local infoFactory = UIDropDownMenu_CreateInfo
for i = 1, table.getn(values) do
local optionValue = tostring(values[i])
local info = type(infoFactory) == "function" and infoFactory() or {}
info.text = optionValue
info.checked = optionValue == current and 1 or nil
info.func = function()
MTH_ICU_SetValue(key, optionValue)
MTH_RefreshICUOptions()
end
if UIDropDownMenu_AddButton then UIDropDownMenu_AddButton(info) end
end
end)
end
MTH_ICU_OPT_STATE.controls[key] = {
label = labelFs,
dropdown = dropdown,
}
end
local function MTH_ICU_CreateColorRow(container, rowName, label, colorKey, y, labelX, buttonX)
labelX = labelX or MTH_ICU_LAYOUT.RIGHT_X
buttonX = buttonX or MTH_ICU_LAYOUT.COLOR_BUTTON_X
local labelFs = container:CreateFontString(rowName .. "Label", "ARTWORK", "GameFontNormalSmall")
labelFs:SetPoint("TOPLEFT", container, "TOPLEFT", labelX, y)
labelFs:SetText(label)
local button = CreateFrame("Button", rowName .. "Button", container, "UIPanelButtonTemplate")
button:SetPoint("TOPLEFT", container, "TOPLEFT", buttonX, y + 8)
button:SetWidth(MTH_ICU_LAYOUT.COLOR_BUTTON_WIDTH)
button:SetHeight(22)
button:SetText("Pick")
button:SetScript("OnClick", function()
MTH_ICU_OpenColorPicker(colorKey)
end)
local swatch = button:CreateTexture(rowName .. "Swatch", "ARTWORK")
swatch:SetTexture("Interface\\Buttons\\WHITE8X8")
swatch:SetPoint("LEFT", button, "LEFT", 6, 0)
swatch:SetWidth(14)
swatch:SetHeight(14)
MTH_ICU_OPT_STATE.controls[colorKey] = {
label = labelFs,
button = button,
swatch = swatch,
isColor = true,
}
end
local function MTH_ICU_BuildUI(container)
if MTH_ICU_OPT_STATE.built and MTH_ICU_OPT_STATE.container == container then
return
end
MTH_ClearContainer(container)
MTH_ICU_OPT_STATE.controls = {}
MTH_ICU_OPT_STATE.container = container
MTH_ICU_OPT_STATE.built = true
local title = container:CreateFontString("MetaHuntOptionsICUTitle", "ARTWORK", "GameFontHighlight")
title:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -10)
title:SetText("MTH ICU")
local note = container:CreateFontString("MetaHuntOptionsICUNote", "ARTWORK", "GameFontNormalSmall")
note:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -32)
note:SetWidth(560)
note:SetJustifyH("LEFT")
note:SetJustifyV("TOP")
note:SetTextColor(0.35, 0.65, 1)
note:SetText("ICU is a minimap targeting utility module for your Trackings.\nIt provides enhanced target information and click actions for players and NPCs on the minimap.")
local moduleEnabled = MTH_CreateCheckbox(container, "MetaHuntOptionsICUModuleEnabled", "Enable ICU module", -72, MTH_ICU_LAYOUT.LEFT_X)
if moduleEnabled then
moduleEnabled:SetScript("OnClick", function()
if not this then return end
if MTH and MTH.SetModuleEnabled then
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
end
MTH_RefreshICUOptions()
end)
MTH_ICU_OPT_STATE.controls.moduleEnabled = moduleEnabled
end
local mouseOver = MTH_CreateCheckbox(container, "MetaHuntOptionsICUMouseOver", "Enable CTRL mouseover scan", -98, MTH_ICU_LAYOUT.LEFT_X)
if mouseOver then
mouseOver:SetScript("OnClick", function()
if not this then return end
local store = MTH_ICU_GetStore()
store.mouseOver = this:GetChecked() == 1 and true or false
end)
MTH_ICU_OPT_STATE.controls.mouseOver = mouseOver
end
local sectionHeader = container:CreateFontString("MetaHuntOptionsICUSectionHeader", "ARTWORK", "GameFontHighlight")
sectionHeader:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_ICU_LAYOUT.LEFT_X, -146)
sectionHeader:SetText("General Settings")
MTH_ICU_CreateOptionRow(container, "MetaHuntOptionsICUAlert", "ALERT PVP", "ALERT", -172, MTH_ICU_LAYOUT.LEFT_X, MTH_ICU_LAYOUT.LEFT_DROPDOWN_X)
MTH_ICU_CreateOptionRow(container, "MetaHuntOptionsICUAnnounce", "ANNOUNCE on click", "ANNOUNCE", -198, MTH_ICU_LAYOUT.LEFT_X, MTH_ICU_LAYOUT.LEFT_DROPDOWN_X)
MTH_ICU_CreateOptionRow(container, "MetaHuntOptionsICUHealthMode", "Health text", "HEALTH_TEXT_MODE", -224, MTH_ICU_LAYOUT.LEFT_X, MTH_ICU_LAYOUT.LEFT_DROPDOWN_X)
MTH_ICU_CreateOptionRow(container, "MetaHuntOptionsICUHideDelay", "List hide delay", "POPUP_HIDE_DELAY", -250, MTH_ICU_LAYOUT.LEFT_X, MTH_ICU_LAYOUT.LEFT_DROPDOWN_X)
MTH_ICU_CreateOptionRow(container, "MetaHuntOptionsICUAnchor", "ANCHOR", "ANCHOR", -276, MTH_ICU_LAYOUT.LEFT_X, MTH_ICU_LAYOUT.LEFT_DROPDOWN_X)
local toggleAnchorButton = CreateFrame("Button", "MetaHuntOptionsICUToggleAnchorButton", container, "UIPanelButtonTemplate")
toggleAnchorButton:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_ICU_LAYOUT.LEFT_X, -302)
toggleAnchorButton:SetWidth(116)
toggleAnchorButton:SetHeight(22)
toggleAnchorButton:SetText("Toggle Anchor")
toggleAnchorButton:SetScript("OnClick", function()
if type(ICU_ToggleAnchor) == "function" then
ICU_ToggleAnchor()
end
end)
MTH_ICU_OPT_STATE.controls.customAnchorButton = toggleAnchorButton
local expandUp = MTH_CreateCheckbox(container, "MetaHuntOptionsICUExpandUp", "Expand Up", -301, MTH_ICU_LAYOUT.LEFT_X + 132)
if expandUp then
expandUp:SetScript("OnClick", function()
if not this then return end
local store = MTH_ICU_GetStore()
store.EXPAND_UP = this:GetChecked() == 1 and true or false
if type(ICU_SetPoints) == "function" then
ICU_SetPoints()
end
end)
MTH_ICU_OPT_STATE.controls.expandUp = expandUp
end
local showGuild = MTH_CreateCheckbox(container, "MetaHuntOptionsICUShowGuild", "Show player guild names", -344, MTH_ICU_LAYOUT.LEFT_X)
if showGuild then
showGuild:SetScript("OnClick", function()
if not this then return end
local store = MTH_ICU_GetStore()
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()
if not this then return end
local store = MTH_ICU_GetStore()
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()
if not this then return end
local store = MTH_ICU_GetStore()
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()
if not this then return end
local store = MTH_ICU_GetStore()
store.SHOW_CUSTOM_TITLES = this:GetChecked() == 1 and true or false
end)
MTH_ICU_OPT_STATE.controls.showCustomTitles = showTitles
end
local rightHeader = container:CreateFontString("MetaHuntOptionsICURightHeader", "ARTWORK", "GameFontHighlight")
rightHeader:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_ICU_LAYOUT.RIGHT_X, -146)
rightHeader:SetText("Background Colors")
MTH_ICU_CreateOptionRow(container, "MetaHuntOptionsICUPlayerColorMode", "Player colors", "PLAYER_COLOR_MODE", -172, MTH_ICU_LAYOUT.RIGHT_X, MTH_ICU_LAYOUT.RIGHT_DROPDOWN_X)
local reactionOnlyPvp = MTH_CreateCheckbox(container, "MetaHuntOptionsICUReactionOnlyPvp", "Reaction only when PvP-flagged", -200, MTH_ICU_LAYOUT.RIGHT_X)
if reactionOnlyPvp then
reactionOnlyPvp:SetScript("OnClick", function()
if not this then return end
local store = MTH_ICU_GetStore()
store.REACTION_ONLY_PVP_PLAYERS = this:GetChecked() == 1 and true or false
end)
MTH_ICU_OPT_STATE.controls.reactionOnlyPvp = reactionOnlyPvp
end
local colorHeader = container:CreateFontString("MetaHuntOptionsICUColorsHeader", "ARTWORK", "GameFontHighlight")
colorHeader:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_ICU_LAYOUT.RIGHT_X, -224)
local colorScope = container:CreateFontString("MetaHuntOptionsICUColorsScope", "ARTWORK", "GameFontNormalSmall")
colorScope:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_ICU_LAYOUT.RIGHT_X, -242)
colorScope:SetWidth(250)
colorScope:SetJustifyH("LEFT")
colorScope:SetJustifyV("TOP")
colorScope:SetTextColor(0.35, 0.65, 1)
colorScope:SetText("Player pickers: REACTION / FACTION / CUSTOM\nNPC pickers: always")
local colorY = -284
for i = 1, table.getn(MTH_ICU_COLOR_ROWS) do
local row = MTH_ICU_COLOR_ROWS[i]
MTH_ICU_CreateColorRow(container, "MetaHuntOptionsICUColor" .. tostring(i), row.label, row.key, colorY, MTH_ICU_LAYOUT.RIGHT_X, MTH_ICU_LAYOUT.COLOR_BUTTON_X)
colorY = colorY - MTH_ICU_LAYOUT.COLOR_ROW_STEP
end
end
function MTH_RefreshICUOptions()
if not MTH_ICU_OPT_READY then return end
local controls = MTH_ICU_OPT_STATE.controls
if type(controls) ~= "table" then
return
end
local store = MTH_ICU_GetStore()
local moduleEnabled = MTH and MTH.IsModuleEnabled and MTH:IsModuleEnabled("icu", false) or false
if controls.moduleEnabled then
controls.moduleEnabled:SetChecked(moduleEnabled and 1 or nil)
end
if controls.mouseOver then
controls.mouseOver:SetChecked(store.mouseOver and 1 or nil)
if controls.mouseOver.Enable then
if moduleEnabled then controls.mouseOver:Enable() else controls.mouseOver:Disable() end
end
end
if controls.showGuildName then
controls.showGuildName:SetChecked(store.SHOW_GUILD_NAME and 1 or nil)
if controls.showGuildName.Enable then
if moduleEnabled then controls.showGuildName:Enable() else controls.showGuildName:Disable() end
end
end
if controls.showPlayerClass then
controls.showPlayerClass:SetChecked(store.SHOW_PLAYER_CLASS and 1 or nil)
if controls.showPlayerClass.Enable then
if moduleEnabled then controls.showPlayerClass:Enable() else controls.showPlayerClass:Disable() end
end
end
if controls.showPlayerRace then
controls.showPlayerRace:SetChecked(store.SHOW_PLAYER_RACE and 1 or nil)
if controls.showPlayerRace.Enable then
if moduleEnabled then controls.showPlayerRace:Enable() else controls.showPlayerRace:Disable() end
end
end
if controls.showCustomTitles then
controls.showCustomTitles:SetChecked(store.SHOW_CUSTOM_TITLES and 1 or nil)
if controls.showCustomTitles.Enable then
if moduleEnabled then controls.showCustomTitles:Enable() else controls.showCustomTitles:Disable() end
end
end
local isCustomAnchor = tostring(store.ANCHOR or "") == "CUSTOM"
if controls.customAnchorButton then
if moduleEnabled and isCustomAnchor then
controls.customAnchorButton:Show()
if controls.customAnchorButton.Enable then controls.customAnchorButton:Enable() end
else
controls.customAnchorButton:Hide()
end
end
if controls.expandUp then
controls.expandUp:SetChecked(store.EXPAND_UP and 1 or nil)
if moduleEnabled and isCustomAnchor then
controls.expandUp:Show()
if controls.expandUp.Enable then controls.expandUp:Enable() end
else
controls.expandUp:Hide()
end
end
local optionKeys = { "ALERT", "ANNOUNCE", "ANCHOR", "HEALTH_TEXT_MODE", "POPUP_HIDE_DELAY", "PLAYER_COLOR_MODE" }
for i = 1, table.getn(optionKeys) do
local key = optionKeys[i]
local row = controls[key]
if row and row.dropdown then
MTH_ICU_DropdownSetText(row.dropdown, tostring(store[key] or ""))
if UIDropDownMenu_EnableDropDown and UIDropDownMenu_DisableDropDown then
if moduleEnabled then
UIDropDownMenu_EnableDropDown(row.dropdown)
else
UIDropDownMenu_DisableDropDown(row.dropdown)
end
end
end
end
if controls.reactionOnlyPvp then
controls.reactionOnlyPvp:SetChecked(store.REACTION_ONLY_PVP_PLAYERS and 1 or nil)
if controls.reactionOnlyPvp.Enable then
if moduleEnabled and tostring(store.PLAYER_COLOR_MODE or "") == "REACTION" then
controls.reactionOnlyPvp:Enable()
else
controls.reactionOnlyPvp:Disable()
end
end
end
for i = 1, table.getn(MTH_ICU_COLOR_ROWS) do
local row = MTH_ICU_COLOR_ROWS[i]
local ctrl = controls[row.key]
if ctrl and ctrl.swatch then
local r, g, b = MTH_ICU_GetColor(row.key)
ctrl.swatch:SetVertexColor(r, g, b)
if ctrl.button and ctrl.button.Enable then
if moduleEnabled then ctrl.button:Enable() else ctrl.button:Disable() end
end
end
end
end
function MTH_SetupICUOptions()
local container = MTH_GetFrame("MetaHuntOptionsICU")
if not container then return end
if not MTH_ICU_OPT_READY then
if type(MTH_ClearContainer) == "function" then
MTH_ClearContainer(container)
end
local title = container:CreateFontString("MetaHuntOptionsICUErrorTitle", "ARTWORK", "GameFontHighlight")
title:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -10)
title:SetText("MTH ICU")
local body = container:CreateFontString("MetaHuntOptionsICUErrorBody", "ARTWORK", "GameFontNormalSmall")
body:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -34)
body:SetWidth(620)
body:SetJustifyH("LEFT")
body:SetJustifyV("TOP")
body:SetTextColor(1, 0.3, 0.3)
body:SetText("ICU options cannot initialize. Missing dependencies: " .. table.concat(MTH_ICU_OPT_MISSING, ", "))
return
end
local ok, err = pcall(function()
MTH_ICU_BuildUI(container)
MTH_RefreshICUOptions()
end)
if not ok then
if MTH and MTH.Print then
MTH:Print("[MTH ICU OPTIONS] setup failed: " .. tostring(err), "error")
end
if type(MTH_ClearContainer) == "function" then
MTH_ClearContainer(container)
end
local title = container:CreateFontString("MetaHuntOptionsICUCrashTitle", "ARTWORK", "GameFontHighlight")
title:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -10)
title:SetText("MTH ICU")
local body = container:CreateFontString("MetaHuntOptionsICUCrashBody", "ARTWORK", "GameFontNormalSmall")
body:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -34)
body:SetWidth(620)
body:SetJustifyH("LEFT")
body:SetJustifyV("TOP")
body:SetTextColor(1, 0.3, 0.3)
body:SetText("ICU options failed to render. Check chat/debug for details.")
end
end
+403
View File
@@ -0,0 +1,403 @@
------------------------------------------------------
-- MetaHunt Profile Management Options Panel
------------------------------------------------------
local MAX_PROFILE_ROWS = 15
local MAX_CHAR_ROWS = 10
local ROW_H = 26
-- Row pools — anonymous frames created once against scroll content, reused each refresh
local gProfileRows = {} -- [i] = { frame, nameText, infoText, loadBtn, delBtn }
local gCharRows = {} -- [i] = { frame, nameText, infoText, copyBtn }
-- Static element refs created once
local gActiveLabel = nil
local gSetupDone = false
-- Pending confirmations — globals so StaticPopup OnAccept can read them
MTH_PROFILE_PENDING_LOAD = nil
MTH_PROFILE_PENDING_DELETE = nil
MTH_PROFILE_PENDING_SAVE = nil
-- ── Static popup dialogs ───────────────────────────────────────────────────
StaticPopupDialogs["MTH_PROFILE_LOAD_CONFIRM"] = {
text = "Load profile \"%s\"?\n\nCurrent settings will be replaced.\nThe UI will reload to apply all changes.",
button1 = "Load & Reload",
button2 = "Cancel",
OnAccept = function()
if MTH_PROFILE_PENDING_LOAD then
local ok, err = MTH_Profile_Load(MTH_PROFILE_PENDING_LOAD)
MTH_PROFILE_PENDING_LOAD = nil
if ok then
ReloadUI()
elseif MTH and MTH.Print then
MTH:Print("Profile load failed: " .. tostring(err), "error")
end
end
end,
timeout = 0, whileDead = 0, hideOnEscape = 1,
}
StaticPopupDialogs["MTH_PROFILE_DELETE_CONFIRM"] = {
text = "Delete profile \"%s\"?\n\nThis cannot be undone.",
button1 = "Delete",
button2 = "Cancel",
OnAccept = function()
if MTH_PROFILE_PENDING_DELETE then
MTH_Profile_Delete(MTH_PROFILE_PENDING_DELETE)
MTH_PROFILE_PENDING_DELETE = nil
MTH_SetupProfilesOptions()
end
end,
timeout = 0, whileDead = 0, hideOnEscape = 1,
}
StaticPopupDialogs["MTH_PROFILE_SAVE_CONFIRM"] = {
text = "Profile \"%s\" already exists.\nOverwrite with current settings?",
button1 = "Overwrite",
button2 = "Cancel",
OnAccept = function()
if MTH_PROFILE_PENDING_SAVE then
MTH_Profile_Save(MTH_PROFILE_PENDING_SAVE)
MTH_PROFILE_PENDING_SAVE = nil
MTH_SetupProfilesOptions()
end
end,
timeout = 0, whileDead = 0, hideOnEscape = 1,
}
-- ── internal helpers ───────────────────────────────────────────────────────
local function PROF_TrimString(s)
if type(s) ~= "string" then return "" end
return string.gsub(s, "^%s*(.-)%s*$", "%1")
end
local function PROF_DoSave(rawName)
local name = PROF_TrimString(rawName)
if string.len(name) == 0 then
if MTH and MTH.Print then MTH:Print("Enter a profile name first.", "error") end
return
end
if string.len(name) > 50 then
if MTH and MTH.Print then MTH:Print("Profile name too long (50 chars max).", "error") end
return
end
local existing = type(MTH_SavedVariables) == "table"
and type(MTH_SavedVariables.profiles) == "table"
and MTH_SavedVariables.profiles[name] ~= nil
if existing then
MTH_PROFILE_PENDING_SAVE = name
StaticPopup_Show("MTH_PROFILE_SAVE_CONFIRM", name)
else
MTH_Profile_Save(name)
if MTH and MTH.Print then MTH:Print("Profile saved: " .. name) end
MTH_SetupProfilesOptions()
end
end
-- ── scroll frames + row pool creation (idempotent) ────────────────────────
local function PROF_EnsureScrollFrames(container)
if not MTH_GetFrame("MetaHuntOptionsProfilesScroll") then
local sf = CreateFrame("ScrollFrame", "MetaHuntOptionsProfilesScroll", container, "UIPanelScrollFrameTemplate")
local sc = CreateFrame("Frame", "MetaHuntOptionsProfilesContent", sf)
sc:SetWidth(510)
sc:SetHeight(MAX_PROFILE_ROWS * ROW_H + 4)
sf:SetScrollChild(sc)
end
if not MTH_GetFrame("MetaHuntOptionsCharScroll") then
local csf = CreateFrame("ScrollFrame", "MetaHuntOptionsCharScroll", container, "UIPanelScrollFrameTemplate")
local csc = CreateFrame("Frame", "MetaHuntOptionsCharContent", csf)
csc:SetWidth(510)
csc:SetHeight(MAX_CHAR_ROWS * ROW_H + 4)
csf:SetScrollChild(csc)
end
end
local function PROF_EnsureProfileRows()
local sc = MTH_GetFrame("MetaHuntOptionsProfilesContent")
if not sc then return end
for i = 1, MAX_PROFILE_ROWS do
if not gProfileRows[i] then
local row = CreateFrame("Frame", nil, sc)
row:SetHeight(ROW_H)
row:SetWidth(510)
row:SetPoint("TOPLEFT", sc, "TOPLEFT", 0, -(i - 1) * ROW_H)
local nameText = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
nameText:SetPoint("LEFT", row, "LEFT", 4, 2)
nameText:SetWidth(210)
nameText:SetJustifyH("LEFT")
local infoText = row:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
infoText:SetPoint("LEFT", row, "LEFT", 218, 2)
infoText:SetWidth(155)
infoText:SetJustifyH("LEFT")
infoText:SetTextColor(0.55, 0.55, 0.55)
local loadBtn = CreateFrame("Button", nil, row, "UIPanelButtonTemplate")
loadBtn:SetWidth(58)
loadBtn:SetHeight(20)
loadBtn:SetPoint("RIGHT", row, "RIGHT", -62, 0)
loadBtn:SetText("Load")
local delBtn = CreateFrame("Button", nil, row, "UIPanelButtonTemplate")
delBtn:SetWidth(58)
delBtn:SetHeight(20)
delBtn:SetPoint("RIGHT", row, "RIGHT", 0, 0)
delBtn:SetText("Delete")
gProfileRows[i] = { frame = row, nameText = nameText, infoText = infoText, loadBtn = loadBtn, delBtn = delBtn }
end
end
end
local function PROF_EnsureCharRows()
local csc = MTH_GetFrame("MetaHuntOptionsCharContent")
if not csc then return end
for i = 1, MAX_CHAR_ROWS do
if not gCharRows[i] then
local row = CreateFrame("Frame", nil, csc)
row:SetHeight(ROW_H)
row:SetWidth(510)
row:SetPoint("TOPLEFT", csc, "TOPLEFT", 0, -(i - 1) * ROW_H)
local nameText = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
nameText:SetPoint("LEFT", row, "LEFT", 4, 2)
nameText:SetWidth(250)
nameText:SetJustifyH("LEFT")
nameText:SetTextColor(0.70, 0.85, 1.00)
local infoText = row:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
infoText:SetPoint("LEFT", row, "LEFT", 258, 2)
infoText:SetWidth(120)
infoText:SetJustifyH("LEFT")
infoText:SetTextColor(0.55, 0.55, 0.55)
local copyBtn = CreateFrame("Button", nil, row, "UIPanelButtonTemplate")
copyBtn:SetWidth(80)
copyBtn:SetHeight(20)
copyBtn:SetPoint("RIGHT", row, "RIGHT", 0, 0)
copyBtn:SetText("Import")
gCharRows[i] = { frame = row, nameText = nameText, infoText = infoText, copyBtn = copyBtn }
end
end
end
-- ── dynamic refresh functions ──────────────────────────────────────────────
local function PROF_RefreshActive()
if not gActiveLabel then return end
local active = type(MTH_Profile_GetActive) == "function" and MTH_Profile_GetActive()
if active then
gActiveLabel:SetText("Active: |cffffd200" .. active .. "|r")
gActiveLabel:SetTextColor(0.90, 0.90, 0.90)
else
gActiveLabel:SetText("Active: |cff888888Custom (unsaved)|r")
gActiveLabel:SetTextColor(0.90, 0.90, 0.90)
end
gActiveLabel:Show()
end
local function PROF_RefreshProfileList()
PROF_EnsureProfileRows()
local profiles = type(MTH_Profile_GetList) == "function" and MTH_Profile_GetList() or {}
local active = type(MTH_Profile_GetActive) == "function" and MTH_Profile_GetActive()
local n = table.getn(profiles)
local sc = MTH_GetFrame("MetaHuntOptionsProfilesContent")
if sc then
sc:SetHeight(math.max(n * ROW_H + 4, ROW_H))
end
for i = 1, MAX_PROFILE_ROWS do
local row = gProfileRows[i]
if not row then break end
local name = profiles[i]
if name then
-- Name (with active indicator)
if name == active then
row.nameText:SetText(name .. " |cff00cc44[active]|r")
else
row.nameText:SetText(name)
end
-- Info: date saved by who
local p = type(MTH_SavedVariables) == "table"
and type(MTH_SavedVariables.profiles) == "table"
and MTH_SavedVariables.profiles[name]
local info = ""
if type(p) == "table" then
if p.savedAt and p.savedAt ~= "" then
info = p.savedAt
end
end
row.infoText:SetText(info)
-- Wire buttons (SetScript replaces previous handler each refresh)
local rowName = name
row.loadBtn:SetScript("OnClick", function()
MTH_PROFILE_PENDING_LOAD = rowName
StaticPopup_Show("MTH_PROFILE_LOAD_CONFIRM", rowName)
end)
row.delBtn:SetScript("OnClick", function()
MTH_PROFILE_PENDING_DELETE = rowName
StaticPopup_Show("MTH_PROFILE_DELETE_CONFIRM", rowName)
end)
row.frame:Show()
row.loadBtn:Show()
row.delBtn:Show()
else
row.frame:Hide()
end
end
end
local function PROF_RefreshCharList()
PROF_EnsureCharRows()
local chars = type(MTH_Profile_GetCharList) == "function" and MTH_Profile_GetCharList() or {}
local n = table.getn(chars)
local csc = MTH_GetFrame("MetaHuntOptionsCharContent")
if csc then
csc:SetHeight(math.max(n * ROW_H + 4, ROW_H))
end
for i = 1, MAX_CHAR_ROWS do
local row = gCharRows[i]
if not row then break end
local charKey = chars[i]
if charKey then
local snap = type(MTH_SavedVariables) == "table"
and type(MTH_SavedVariables.charSnapshots) == "table"
and MTH_SavedVariables.charSnapshots[charKey]
local info = (type(snap) == "table" and snap.savedAt) and snap.savedAt or ""
row.nameText:SetText(charKey)
row.infoText:SetText(info)
local ck = charKey
row.copyBtn:SetScript("OnClick", function()
local ok, pName = MTH_Profile_CopyFromChar(ck)
if ok then
MTH_SetupProfilesOptions()
if MTH and MTH.Print then
MTH:Print('Imported "' .. ck .. '" as profile "' .. tostring(pName) .. '".')
end
end
end)
row.frame:Show()
row.copyBtn:Show()
else
row.frame:Hide()
end
end
end
-- ── main setup (static elements created once, dynamic lists refreshed every call) ──
function MTH_SetupProfilesOptions()
local container = MTH_GetFrame("MetaHuntOptionsProfiles")
if not container then return end
if not gSetupDone then
-- Title
local title = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
title:SetPoint("TOPLEFT", container, "TOPLEFT", 6, -6)
title:SetText("Profiles")
title:SetTextColor(1.00, 0.82, 0.00)
-- Active profile indicator
gActiveLabel = container:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
gActiveLabel:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -4)
gActiveLabel:SetWidth(540)
gActiveLabel:SetJustifyH("LEFT")
-- ── Save As section ───────────────────────────────────────────────
local saveHdr = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
saveHdr:SetPoint("TOPLEFT", gActiveLabel, "BOTTOMLEFT", 0, -10)
saveHdr:SetText("Save current settings as:")
saveHdr:SetTextColor(0.90, 0.90, 0.90)
local eb = CreateFrame("EditBox", "MTH_ProfileNameInput", container, "InputBoxTemplate")
eb:SetWidth(240)
eb:SetHeight(20)
eb:SetMaxLetters(50)
eb:SetAutoFocus(false)
eb:SetPoint("TOPLEFT", saveHdr, "BOTTOMLEFT", 2, -6)
eb:SetScript("OnEnterPressed", function()
PROF_DoSave(this:GetText())
this:SetText("")
this:ClearFocus()
end)
eb:SetScript("OnEscapePressed", function()
this:SetText("")
this:ClearFocus()
end)
local saveBtn = CreateFrame("Button", "MTH_ProfileSaveBtn", container, "UIPanelButtonTemplate")
saveBtn:SetWidth(70)
saveBtn:SetHeight(20)
saveBtn:SetPoint("LEFT", eb, "RIGHT", 6, 0)
saveBtn:SetText("Save")
saveBtn:SetScript("OnClick", function()
local box = MTH_GetFrame("MTH_ProfileNameInput")
if box then
PROF_DoSave(box:GetText())
box:SetText("")
box:ClearFocus()
end
end)
-- ── Saved Profiles section ────────────────────────────────────────
local profHdr = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
profHdr:SetPoint("TOPLEFT", eb, "BOTTOMLEFT", -2, -12)
profHdr:SetText("Saved Profiles")
profHdr:SetTextColor(1.00, 0.82, 0.00)
local rule1 = container:CreateTexture(nil, "ARTWORK")
rule1:SetPoint("TOPLEFT", profHdr, "BOTTOMLEFT", 0, -3)
rule1:SetWidth(540)
rule1:SetHeight(1)
rule1:SetTexture(0.30, 0.30, 0.30, 0.80)
PROF_EnsureScrollFrames(container)
local sf = MTH_GetFrame("MetaHuntOptionsProfilesScroll")
sf:SetPoint("TOPLEFT", rule1, "BOTTOMLEFT", 0, -4)
sf:SetWidth(530)
sf:SetHeight(170)
sf:Show()
-- ── Copy from Character section ───────────────────────────────────
local charHdr = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
charHdr:SetPoint("TOPLEFT", sf, "BOTTOMLEFT", 0, -10)
charHdr:SetText("Copy from Character")
charHdr:SetTextColor(1.00, 0.82, 0.00)
local charNote = container:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
charNote:SetPoint("TOPLEFT", charHdr, "BOTTOMLEFT", 0, -2)
charNote:SetWidth(540)
charNote:SetJustifyH("LEFT")
charNote:SetText("Snapshots are saved automatically on logout. Click Import to create a profile from one.")
charNote:SetTextColor(0.60, 0.60, 0.60)
local rule2 = container:CreateTexture(nil, "ARTWORK")
rule2:SetPoint("TOPLEFT", charNote, "BOTTOMLEFT", 0, -3)
rule2:SetWidth(540)
rule2:SetHeight(1)
rule2:SetTexture(0.30, 0.30, 0.30, 0.80)
local csf = MTH_GetFrame("MetaHuntOptionsCharScroll")
csf:SetPoint("TOPLEFT", rule2, "BOTTOMLEFT", 0, -4)
csf:SetWidth(530)
csf:SetHeight(140)
csf:Show()
gSetupDone = true
end
-- Refresh dynamic content every time the panel is opened
PROF_RefreshActive()
PROF_RefreshProfileList()
PROF_RefreshCharList()
end
+29 -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
@@ -195,6 +200,28 @@ function MTH_SelectOptionsTab(tabKey)
else
MTH_SetupAutoBuyOptions()
end
elseif tabKey == "AutoQuest" then
if type(MTH_SetupAutoQuestOptions) == "function" then
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()
MTH_OPTIONS_SETUP["ICU"] = true
if type(MTH_RefreshICUOptions) == "function" then
MTH_RefreshICUOptions()
end
elseif MTH and MTH.Print then
MTH:Print("ICU options setup function is not loaded", "error")
end
elseif tabKey == "Credits" then
local setupCredits = _G and _G["MTH_SetupCreditsOptions"]
if type(setupCredits) == "function" then
+10 -2
View File
@@ -27,7 +27,8 @@ MTH_OPTIONS_CONST = MTH_OPTIONS_CONST or {
}
MTH_OPTIONS_TABS = MTH_OPTIONS_TABS or {
{ key = "General", label = "General", frame = "MetaHuntOptionsGeneral" },
{ key = "General", label = "General", frame = "MetaHuntOptionsGeneral" },
{ key = "Profiles", label = "Profiles", frame = "MetaHuntOptionsProfiles" },
{ key = "Messages", label = "Messages", frame = "MetaHuntOptionsMessages" },
{ key = "Pet", label = "ZPet", frame = "MetaHuntOptionsPet" },
{ key = "Track", label = "ZTrack", frame = "MetaHuntOptionsTrack" },
@@ -41,6 +42,9 @@ MTH_OPTIONS_TABS = MTH_OPTIONS_TABS or {
{ 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" },
@@ -50,10 +54,14 @@ MTH_OPTIONS_TABS = MTH_OPTIONS_TABS or {
}
MTH_OPTIONS_TREE = MTH_OPTIONS_TREE or {
{ label = "General", key = "General" },
{ label = "General", key = "General" },
{ label = "Profiles", key = "Profiles" },
{ label = "Messages", key = "Messages" },
{ label = "Auto Buy", key = "AutoBuy" },
{ label = "Auto Quest", key = "AutoQuest" },
{ label = "Feed-O-Matic", key = "FeedOMatic" },
{ label = "ICU", key = "ICU" },
{ label = "MM Widget", key = "ExpAmmo" },
{
label = "zButtons",
node = "zButtons",
+185 -123
View File
@@ -21,6 +21,14 @@ local function MTH_ZB_RefreshTab(tabName)
end
end
local function MTH_ZB_IsChecked(control)
if not control or type(control.GetChecked) ~= "function" then
return false
end
local checked = control:GetChecked()
return checked == 1 or checked == true
end
local function MTH_RebuildSpellOrder(buttonName, buttonObj, itemList, maxButtons)
if not (buttonName and buttonObj and itemList and maxButtons) then return end
local info = {}
@@ -187,15 +195,15 @@ local function MTH_EnsureAmmoOptionsWatcher()
if MTH_AmmoOptionsWatcher then
return
end
MTH_AmmoOptionsWatcher = CreateFrame("Frame", "MTHAmmoOptionsWatcherFrame")
MTH_AmmoOptionsWatcher = CreateFrame("Frame", "MTH_AmmoOptionsWatcher")
if not MTH_AmmoOptionsWatcher then
return
end
MTH_AmmoOptionsWatcher:RegisterEvent("UNIT_INVENTORY_CHANGED")
MTH_AmmoOptionsWatcher:RegisterEvent("BAG_UPDATE")
MTH_AmmoOptionsWatcher:SetScript("OnEvent", function(_, eventName, unit)
eventName = eventName or event
unit = unit or arg1
MTH_AmmoOptionsWatcher:SetScript("OnEvent", function()
local eventName = event
local unit = arg1
if eventName == "UNIT_INVENTORY_CHANGED" and unit and unit ~= "player" then
return
end
@@ -239,6 +247,16 @@ local function MTH_ZB_EnsureButtonOptionDefaults(buttonName)
end
saved["children"]["hideonclick"] = saved["children"]["hideonclick"] and true or false
if saved["children"]["expandonhover"] == nil then
saved["children"]["expandonhover"] = false
end
saved["children"]["expandonhover"] = saved["children"]["expandonhover"] and true or false
if saved["children"]["fadetimer"] == nil then
saved["children"]["fadetimer"] = 0
end
saved["children"]["fadetimer"] = tonumber(saved["children"]["fadetimer"]) or 0
if type(saved["parent"]) ~= "table" then
saved["parent"] = {}
end
@@ -325,16 +343,15 @@ local function MTH_SetupButtonOptions(containerName, buttonName, displayName, ma
enableButton:SetChecked(enabledValue and true or false)
enableButton.buttonName = buttonName
enableButton.buttonObj = buttonObj
enableButton:SetScript("OnClick", function(self)
self = self or this
if not self then return end
local checked = self:GetChecked() and true or false
MTH_SetButtonEnabledState(self.buttonName, self.buttonObj, checked)
enableButton:SetScript("OnClick", function()
if not this then return end
local checked = MTH_ZB_IsChecked(this)
MTH_SetButtonEnabledState(this.buttonName, this.buttonObj, checked)
end)
end
local parentSection = MTH_ZB_EnsureSection(containerName.."ParentSection", MTH_ZB_L("ZB_SECTION_PARENT_BUTTON", "Parent Button"), -52, 116)
local childrenSection = MTH_ZB_EnsureSection(containerName.."ChildrenSection", MTH_ZB_L("ZB_SECTION_CHILDREN_BUTTONS", "Children Buttons"), -184, 235)
local childrenSection = MTH_ZB_EnsureSection(containerName.."ChildrenSection", MTH_ZB_L("ZB_SECTION_CHILDREN_BUTTONS", "Children Buttons"), -184, 310)
local parentYOffset = -18
local childrenYOffset = -18
@@ -384,16 +401,15 @@ local function MTH_SetupButtonOptions(containerName, buttonName, displayName, ma
expandLeft:SetChecked(saved["firstbutton"] == "LEFT")
expandLeft.buttonName = buttonName
expandLeft.buttonObj = buttonObj
expandLeft:SetScript("OnClick", function(self)
self = self or this
if not self then return end
local checked = self:GetChecked() and true or false
local btnName = self.buttonName
expandLeft:SetScript("OnClick", function()
if not this then return end
local checked = MTH_ZB_IsChecked(this)
local btnName = this.buttonName
if not btnName or not (ZHunterMod_Saved and ZHunterMod_Saved[btnName]) then return end
ZHunterMod_Saved[btnName]["firstbutton"] = checked and "LEFT" or "RIGHT"
ZHunterMod_Saved[btnName]["horizontal"] = checked and 1 or nil
ZHunterMod_Saved[btnName]["vertical"] = checked and 1 or nil
MTH_RefreshButtonGeometry(btnName, self.buttonObj)
MTH_RefreshButtonGeometry(btnName, this.buttonObj)
end)
end
childrenYOffset = childrenYOffset - 30
@@ -406,17 +422,64 @@ local function MTH_SetupButtonOptions(containerName, buttonName, displayName, ma
if hideClick.buttonObj then
hideClick.buttonObj.hideonclick = saved["children"]["hideonclick"] and true or false
end
hideClick:SetScript("OnClick", function(self)
self = self or this
if not self then return end
local checked = self:GetChecked() and true or false
ZHunterMod_Saved[self.buttonName]["children"]["hideonclick"] = checked
local btnObj = self.buttonObj or getglobal(self.buttonName)
hideClick:SetScript("OnClick", function()
if not this then return end
local checked = MTH_ZB_IsChecked(this)
ZHunterMod_Saved[this.buttonName]["children"]["hideonclick"] = checked
local btnObj = this.buttonObj or getglobal(this.buttonName)
if btnObj then btnObj.hideonclick = checked end
end)
end
childrenYOffset = childrenYOffset - 25
local expandOnHover = MTH_CreateCheckbox(childrenSection or container, containerName.."ExpandOnHover", MTH_ZB_L("ZB_LABEL_EXPAND_ON_HOVER", "Expand on Hover"), childrenYOffset)
if expandOnHover then
expandOnHover:SetChecked(saved["children"]["expandonhover"])
expandOnHover.buttonName = buttonName
expandOnHover.buttonObj = buttonObj
expandOnHover:SetScript("OnClick", function()
if not this then return end
local checked = MTH_ZB_IsChecked(this)
local btnName = this.buttonName
if not btnName or not (ZHunterMod_Saved and ZHunterMod_Saved[btnName]) then return end
if not ZHunterMod_Saved[btnName]["children"] then
ZHunterMod_Saved[btnName]["children"] = {}
end
ZHunterMod_Saved[btnName]["children"]["expandonhover"] = checked
local btnObj = this.buttonObj or getglobal(btnName)
if btnObj then btnObj.expandonhover = checked end
end)
end
childrenYOffset = childrenYOffset - 40
local fadeTimer = MTH_CreateSlider(childrenSection or container, containerName.."FadeTimer", MTH_ZB_L("ZB_LABEL_AUTO_HIDE_TIMER", "Auto-Hide Timer (seconds)"), 0, 10, 1, childrenYOffset)
if fadeTimer then
fadeTimer:SetWidth(leftWidth - 40)
fadeTimer:SetValue(saved["children"]["fadetimer"] or 0)
fadeTimer.buttonName = buttonName
fadeTimer.buttonObj = buttonObj
fadeTimer.onChange = function(val, slider)
local frame = slider or fadeTimer
local btnName = frame and frame.buttonName
if not btnName or not (ZHunterMod_Saved and ZHunterMod_Saved[btnName]) then return end
if not ZHunterMod_Saved[btnName]["children"] then
ZHunterMod_Saved[btnName]["children"] = {}
end
local secs = math.floor((val or 0) + 0.5)
ZHunterMod_Saved[btnName]["children"]["fadetimer"] = secs
local btnObj = frame.buttonObj or getglobal(btnName)
if btnObj then
btnObj.fadetimer = secs
if secs <= 0 then
ZSpellButton_StopFadeTimer(btnObj)
elseif btnObj.children and btnObj.children:IsVisible() then
ZSpellButton_StartFadeTimer(btnObj)
end
end
end
end
childrenYOffset = childrenYOffset - 50
local showTooltip = MTH_CreateCheckbox(childrenSection or container, containerName.."ShowTooltip", MTH_ZB_L("ZB_LABEL_SHOW_TOOLTIP", "Show Tooltip"), childrenYOffset)
if showTooltip then
showTooltip:SetChecked(saved["tooltip"])
@@ -425,12 +488,11 @@ local function MTH_SetupButtonOptions(containerName, buttonName, displayName, ma
if showTooltip.buttonObj then
showTooltip.buttonObj.tooltip = saved["tooltip"] and true or false
end
showTooltip:SetScript("OnClick", function(self)
self = self or this
if not self then return end
local checked = self:GetChecked() and true or false
ZHunterMod_Saved[self.buttonName]["tooltip"] = checked
local btnObj = self.buttonObj or getglobal(self.buttonName)
showTooltip:SetScript("OnClick", function()
if not this then return end
local checked = MTH_ZB_IsChecked(this)
ZHunterMod_Saved[this.buttonName]["tooltip"] = checked
local btnObj = this.buttonObj or getglobal(this.buttonName)
if btnObj then btnObj.tooltip = checked end
end)
end
@@ -443,13 +505,12 @@ local function MTH_SetupButtonOptions(containerName, buttonName, displayName, ma
showAmmoName.buttonName = buttonName
showAmmoName.buttonObj = buttonObj
showAmmoName.maxButtons = maxButtons
showAmmoName:SetScript("OnClick", function(self)
self = self or this
if not self then return end
local checked = self:GetChecked() and true or false
local btnName = self.buttonName
showAmmoName:SetScript("OnClick", function()
if not this then return end
local checked = MTH_ZB_IsChecked(this)
local btnName = this.buttonName
ZHunterMod_Saved[btnName]["showammoname"] = checked
local btnObj = self.buttonObj or getglobal(btnName)
local btnObj = this.buttonObj or getglobal(btnName)
if btnObj then
for i = 1, (btnObj.count or 0) do
local child = getglobal(btnName..i)
@@ -498,13 +559,12 @@ local function MTH_SetupButtonOptions(containerName, buttonName, displayName, ma
hideButton:SetChecked(saved["parent"]["hide"])
hideButton.buttonName = buttonName
hideButton.buttonObj = buttonObj
hideButton:SetScript("OnClick", function(self)
self = self or this
if not self then return end
local checked = self:GetChecked() == 1
ZHunterMod_Saved[self.buttonName]["parent"]["hide"] = checked
if self.buttonObj then
if checked then self.buttonObj:Hide() else self.buttonObj:Show() end
hideButton:SetScript("OnClick", function()
if not this then return end
local checked = this:GetChecked() == 1
ZHunterMod_Saved[this.buttonName]["parent"]["hide"] = checked
if this.buttonObj then
if checked then this.buttonObj:Hide() else this.buttonObj:Show() end
end
end)
end
@@ -515,13 +575,12 @@ local function MTH_SetupButtonOptions(containerName, buttonName, displayName, ma
useCircle:SetChecked(saved["parent"]["circle"])
useCircle.buttonName = buttonName
useCircle.buttonObj = buttonObj
useCircle:SetScript("OnClick", function(self)
self = self or this
if not self then return end
local checked = self:GetChecked() == 1
ZHunterMod_Saved[self.buttonName]["parent"]["circle"] = checked
if self.buttonObj and self.buttonObj.circle then
if checked then self.buttonObj.circle:Show() else self.buttonObj.circle:Hide() end
useCircle:SetScript("OnClick", function()
if not this then return end
local checked = this:GetChecked() == 1
ZHunterMod_Saved[this.buttonName]["parent"]["circle"] = checked
if this.buttonObj and this.buttonObj.circle then
if checked then this.buttonObj.circle:Show() else this.buttonObj.circle:Hide() end
end
end)
end
@@ -579,17 +638,16 @@ local function MTH_SetupButtonOptions(containerName, buttonName, displayName, ma
showText:SetText("")
end
showToggle:SetChecked(ZHunterMod_Saved[buttonName]["visible"][showToggle.spellIndex] ~= false)
showToggle:SetScript("OnClick", function(self)
self = self or this
if not self then return end
local checked = self:GetChecked() == 1
local btnName = self.buttonName
local visibleIndex = self.spellIndex
showToggle:SetScript("OnClick", function()
if not this then return end
local checked = this:GetChecked() == 1
local btnName = this.buttonName
local visibleIndex = this.spellIndex
if not ZHunterMod_Saved[btnName]["visible"] then
ZHunterMod_Saved[btnName]["visible"] = {}
end
ZHunterMod_Saved[btnName]["visible"][visibleIndex] = checked
MTH_RefreshButtonLayout(btnName, self.buttonObj, self.itemList, self.maxButtons)
MTH_RefreshButtonLayout(btnName, this.buttonObj, this.itemList, this.maxButtons)
end)
end
@@ -604,18 +662,17 @@ local function MTH_SetupButtonOptions(containerName, buttonName, displayName, ma
downBtn.buttonObj = buttonObj
downBtn.itemList = itemList
downBtn.maxButtons = maxButtons
downBtn:SetScript("OnClick", function(self)
self = self or this
if not self then return end
local idx = self.spellIndex
local btnName = self.buttonName
local contName = self.containerName
local maxBtns = self.maxButtons
downBtn:SetScript("OnClick", function()
if not this then return end
local idx = this.spellIndex
local btnName = this.buttonName
local contName = this.containerName
local maxBtns = this.maxButtons
if idx < maxBtns then
local temp = ZHunterMod_Saved[btnName]["spells"][idx+1]
ZHunterMod_Saved[btnName]["spells"][idx+1] = ZHunterMod_Saved[btnName]["spells"][idx]
ZHunterMod_Saved[btnName]["spells"][idx] = temp
MTH_RebuildSpellOrder(btnName, self.buttonObj, self.itemList, self.maxButtons)
MTH_RebuildSpellOrder(btnName, this.buttonObj, this.itemList, this.maxButtons)
local tabName = gsub(contName, "MetaHuntOptions", "")
MTH_ZB_RefreshTab(tabName)
end
@@ -632,17 +689,16 @@ local function MTH_SetupButtonOptions(containerName, buttonName, displayName, ma
upBtn.buttonObj = buttonObj
upBtn.itemList = itemList
upBtn.maxButtons = maxButtons
upBtn:SetScript("OnClick", function(self)
self = self or this
if not self then return end
local idx = self.spellIndex
local btnName = self.buttonName
local contName = self.containerName
upBtn:SetScript("OnClick", function()
if not this then return end
local idx = this.spellIndex
local btnName = this.buttonName
local contName = this.containerName
if idx > 1 then
local temp = ZHunterMod_Saved[btnName]["spells"][idx-1]
ZHunterMod_Saved[btnName]["spells"][idx-1] = ZHunterMod_Saved[btnName]["spells"][idx]
ZHunterMod_Saved[btnName]["spells"][idx] = temp
MTH_RebuildSpellOrder(btnName, self.buttonObj, self.itemList, self.maxButtons)
MTH_RebuildSpellOrder(btnName, this.buttonObj, this.itemList, this.maxButtons)
local tabName = gsub(contName, "MetaHuntOptions", "")
MTH_ZB_RefreshTab(tabName)
end
@@ -741,11 +797,10 @@ function MTH_SetupSmartAmmoOptions()
end
if moduleToggle then
moduleToggle:SetChecked(moduleEnabled)
moduleToggle:SetScript("OnClick", function(self)
self = self or this
if not self then return end
moduleToggle:SetScript("OnClick", function()
if not this then return end
if MTH and MTH.SetModuleEnabled then
MTH:SetModuleEnabled("smartammo", self:GetChecked() and true or false)
MTH:SetModuleEnabled("smartammo", MTH_ZB_IsChecked(this))
end
MTH_SetupSmartAmmoOptions()
end)
@@ -758,11 +813,10 @@ function MTH_SetupSmartAmmoOptions()
if not moduleEnabled then
smartToggle:Disable()
end
smartToggle:SetScript("OnClick", function(self)
self = self or this
if not self then return end
smartToggle:SetScript("OnClick", function()
if not this then return end
if type(MTHSmartAmmo_SetSmartEnabled) == "function" then
MTHSmartAmmo_SetSmartEnabled(self:GetChecked() and 1 or nil)
MTHSmartAmmo_SetSmartEnabled(MTH_ZB_IsChecked(this) and 1 or nil)
end
end)
end
@@ -777,11 +831,10 @@ function MTH_SetupSmartAmmoOptions()
if not moduleEnabled then
reloadToggle:Disable()
end
reloadToggle:SetScript("OnClick", function(self)
self = self or this
if not self then return end
reloadToggle:SetScript("OnClick", function()
if not this then return end
if type(MTHSmartAmmo_SetReloadEnabled) == "function" then
MTHSmartAmmo_SetReloadEnabled(self:GetChecked() and 1 or nil)
MTHSmartAmmo_SetReloadEnabled(MTH_ZB_IsChecked(this) and 1 or nil)
end
end)
end
@@ -817,11 +870,10 @@ function MTH_SetupMessagesOptions()
local initModulesToggle = MTH_CreateCheckbox(section, "MetaHuntMessagesInitModules", "Init : modules loaded", -36)
if initModulesToggle then
initModulesToggle:SetChecked(msgSettings.initModulesLoaded and true or false)
initModulesToggle:SetScript("OnClick", function(self)
self = self or this
if not self then return end
initModulesToggle:SetScript("OnClick", function()
if not this then return end
if MTH and MTH.SetMessageEnabled then
MTH:SetMessageEnabled("initModulesLoaded", self:GetChecked() and true or false)
MTH:SetMessageEnabled("initModulesLoaded", MTH_ZB_IsChecked(this))
end
end)
end
@@ -829,11 +881,10 @@ function MTH_SetupMessagesOptions()
local initWelcomeToggle = MTH_CreateCheckbox(section, "MetaHuntMessagesInitWelcome", "Init : Sarcastic welcome message", -64)
if initWelcomeToggle then
initWelcomeToggle:SetChecked(msgSettings.initSarcasticWelcome ~= false)
initWelcomeToggle:SetScript("OnClick", function(self)
self = self or this
if not self then return end
initWelcomeToggle:SetScript("OnClick", function()
if not this then return end
if MTH and MTH.SetMessageEnabled then
MTH:SetMessageEnabled("initSarcasticWelcome", self:GetChecked() and true or false)
MTH:SetMessageEnabled("initSarcasticWelcome", MTH_ZB_IsChecked(this))
end
end)
end
@@ -841,11 +892,10 @@ function MTH_SetupMessagesOptions()
local petHungryToggle = MTH_CreateCheckbox(section, "MetaHuntMessagesPetHungry", "Pet is hungry (spam)", -92)
if petHungryToggle then
petHungryToggle:SetChecked(msgSettings.petHungry and true or false)
petHungryToggle:SetScript("OnClick", function(self)
self = self or this
if not self then return end
petHungryToggle:SetScript("OnClick", function()
if not this then return end
if MTH and MTH.SetMessageEnabled then
MTH:SetMessageEnabled("petHungry", self:GetChecked() and true or false)
MTH:SetMessageEnabled("petHungry", MTH_ZB_IsChecked(this))
end
end)
end
@@ -853,11 +903,10 @@ function MTH_SetupMessagesOptions()
local beastTrainingScanToggle = MTH_CreateCheckbox(section, "MetaHuntMessagesBeastTrainingScan", "Beasttraining scan", -120)
if beastTrainingScanToggle then
beastTrainingScanToggle:SetChecked(msgSettings.beastTrainingScan ~= false)
beastTrainingScanToggle:SetScript("OnClick", function(self)
self = self or this
if not self then return end
beastTrainingScanToggle:SetScript("OnClick", function()
if not this then return end
if MTH and MTH.SetMessageEnabled then
MTH:SetMessageEnabled("beastTrainingScan", self:GetChecked() and true or false)
MTH:SetMessageEnabled("beastTrainingScan", MTH_ZB_IsChecked(this))
end
end)
end
@@ -865,11 +914,10 @@ function MTH_SetupMessagesOptions()
local spellbookScanToggle = MTH_CreateCheckbox(section, "MetaHuntMessagesSpellbookScan", "Spellbook scan", -148)
if spellbookScanToggle then
spellbookScanToggle:SetChecked(msgSettings.spellbookScan and true or false)
spellbookScanToggle:SetScript("OnClick", function(self)
self = self or this
if not self then return end
spellbookScanToggle:SetScript("OnClick", function()
if not this then return end
if MTH and MTH.SetMessageEnabled then
MTH:SetMessageEnabled("spellbookScan", self:GetChecked() and true or false)
MTH:SetMessageEnabled("spellbookScan", MTH_ZB_IsChecked(this))
end
end)
end
@@ -877,11 +925,10 @@ function MTH_SetupMessagesOptions()
local petRanAwayToggle = MTH_CreateCheckbox(section, "MetaHuntMessagesPetRanAway", "Pet ran away", -176)
if petRanAwayToggle then
petRanAwayToggle:SetChecked(msgSettings.petRanAway ~= false)
petRanAwayToggle:SetScript("OnClick", function(self)
self = self or this
if not self then return end
petRanAwayToggle:SetScript("OnClick", function()
if not this then return end
if MTH and MTH.SetMessageEnabled then
MTH:SetMessageEnabled("petRanAway", self:GetChecked() and true or false)
MTH:SetMessageEnabled("petRanAway", MTH_ZB_IsChecked(this))
end
end)
end
@@ -889,11 +936,10 @@ function MTH_SetupMessagesOptions()
local mapMarkersToggle = MTH_CreateCheckbox(section, "MetaHuntMessagesMapMarkers", "Map markers", -204)
if mapMarkersToggle then
mapMarkersToggle:SetChecked(msgSettings.mapMarkers ~= false)
mapMarkersToggle:SetScript("OnClick", function(self)
self = self or this
if not self then return end
mapMarkersToggle:SetScript("OnClick", function()
if not this then return end
if MTH and MTH.SetMessageEnabled then
MTH:SetMessageEnabled("mapMarkers", self:GetChecked() and true or false)
MTH:SetMessageEnabled("mapMarkers", MTH_ZB_IsChecked(this))
end
end)
end
@@ -1002,7 +1048,7 @@ function MTH_SetupGeneralOptions()
local smartModuleToggle = ensureCheckbox(smartAmmoSection, "MetaHuntGeneralSmartAmmoModuleToggle", "Enable module", -10, smartModuleEnabled)
if smartModuleToggle then
smartModuleToggle:SetScript("OnClick", function()
local enabled = this:GetChecked() and true or false
local enabled = MTH_ZB_IsChecked(this)
if MTH and MTH.SetModuleEnabled then
local ok = MTH:SetModuleEnabled("smartammo", enabled)
if not ok then
@@ -1031,7 +1077,7 @@ function MTH_SetupGeneralOptions()
end
smartToggle:SetScript("OnClick", function()
if type(MTHSmartAmmo_SetSmartEnabled) == "function" then
MTHSmartAmmo_SetSmartEnabled(this:GetChecked() and 1 or nil)
MTHSmartAmmo_SetSmartEnabled(MTH_ZB_IsChecked(this) and 1 or nil)
end
end)
end
@@ -1045,7 +1091,7 @@ function MTH_SetupGeneralOptions()
end
reloadToggle:SetScript("OnClick", function()
if type(MTHSmartAmmo_SetReloadEnabled) == "function" then
MTHSmartAmmo_SetReloadEnabled(this:GetChecked() and 1 or nil)
MTHSmartAmmo_SetReloadEnabled(MTH_ZB_IsChecked(this) and 1 or nil)
end
end)
end
@@ -1059,7 +1105,7 @@ function MTH_SetupGeneralOptions()
end
weaponSwapToggle:SetScript("OnClick", function()
if type(MTHSmartAmmo_SetWeaponSwapEnabled) == "function" then
MTHSmartAmmo_SetWeaponSwapEnabled(this:GetChecked() and 1 or nil)
MTHSmartAmmo_SetWeaponSwapEnabled(MTH_ZB_IsChecked(this) and 1 or nil)
end
end)
end
@@ -1072,7 +1118,7 @@ function MTH_SetupGeneralOptions()
if autoStripToggle then
autoStripToggle:SetScript("OnClick", function()
if type(AutoStrip_SetAutoStripToggle) == "function" then
AutoStrip_SetAutoStripToggle(this:GetChecked())
AutoStrip_SetAutoStripToggle(MTH_ZB_IsChecked(this))
end
end)
end
@@ -1081,7 +1127,7 @@ function MTH_SetupGeneralOptions()
if displayToggle then
displayToggle:SetScript("OnClick", function()
if type(AutoStrip_SetDisplayToggle) == "function" then
AutoStrip_SetDisplayToggle(this:GetChecked())
AutoStrip_SetDisplayToggle(MTH_ZB_IsChecked(this))
end
end)
end
@@ -1095,7 +1141,7 @@ function MTH_SetupGeneralOptions()
local antiToggle = ensureCheckbox(antiSection, "MetaHuntGeneralAntiDazeToggle", "Enable Anti-Daze", -10, antiDazeEnabled)
if antiToggle then
antiToggle:SetScript("OnClick", function()
local enabled = this:GetChecked() and true or false
local enabled = MTH_ZB_IsChecked(this)
if type(AntiDaze_SetEnabled) == "function" then
AntiDaze_SetEnabled(enabled, 1)
end
@@ -1114,10 +1160,26 @@ function MTH_SetupGeneralOptions()
local tooltipsSection = ensureSection("MetaHuntGeneralTooltipsBox", "Tooltips", topY, 356, "right")
if tooltipsSection then
local tooltipsStore = MTH and MTH.GetModuleSavedVariables and MTH:GetModuleSavedVariables("tooltips")
local tooltipsStore = MTH and MTH.GetModuleCharSavedVariables and MTH:GetModuleCharSavedVariables("tooltips")
if type(tooltipsStore) ~= "table" then
tooltipsStore = {}
end
if MTH and MTH.GetModuleSavedVariables and next(tooltipsStore) == nil then
local accountStore = MTH:GetModuleSavedVariables("tooltips")
if type(accountStore) == "table" and next(accountStore) ~= nil then
for key, value in pairs(accountStore) do
if type(value) == "table" then
local copied = {}
for subKey, subValue in pairs(value) do
copied[subKey] = subValue
end
tooltipsStore[key] = copied
else
tooltipsStore[key] = value
end
end
end
end
if tooltipsStore.beastTooltips == nil then
tooltipsStore.beastTooltips = true
end
@@ -1142,7 +1204,7 @@ function MTH_SetupGeneralOptions()
local moduleToggle = ensureCheckbox(tooltipsSection, "MetaHuntGeneralTooltipsModuleToggle", "Enable module", -10, moduleEnabled)
if moduleToggle then
moduleToggle:SetScript("OnClick", function()
local enabled = this:GetChecked() and true or false
local enabled = MTH_ZB_IsChecked(this)
if MTH and MTH.SetModuleEnabled then
local ok = MTH:SetModuleEnabled("tooltips", enabled)
if not ok then
@@ -1159,7 +1221,7 @@ function MTH_SetupGeneralOptions()
local beastToggle = ensureCheckbox(tooltipsSection, "MetaHuntGeneralTooltipsBeastToggle", "Activate on Beasts", -86, tooltipsStore.beastTooltips and true or false)
if beastToggle then
beastToggle:SetScript("OnClick", function()
local enabled = this:GetChecked() and true or false
local enabled = MTH_ZB_IsChecked(this)
tooltipsStore.beastTooltips = enabled
local tooltipsModule = MTH and MTH.GetModule and MTH:GetModule("tooltips")
if tooltipsModule and tooltipsModule.SetBeastTooltipsEnabled then
@@ -1175,7 +1237,7 @@ function MTH_SetupGeneralOptions()
local ownPetToggle = ensureCheckbox(tooltipsSection, "MetaHuntGeneralTooltipsOwnPetToggle", ownPetLabel, -158, tooltipsStore.ownPetTooltips and true or false)
if ownPetToggle then
ownPetToggle:SetScript("OnClick", function()
local enabled = this:GetChecked() and true or false
local enabled = MTH_ZB_IsChecked(this)
tooltipsStore.ownPetTooltips = enabled
local tooltipsModule = MTH and MTH.GetModule and MTH:GetModule("tooltips")
if tooltipsModule and tooltipsModule.SetOwnPetTooltipsEnabled then
@@ -1189,7 +1251,7 @@ function MTH_SetupGeneralOptions()
local ammoToggle = ensureCheckbox(tooltipsSection, "MetaHuntGeneralTooltipsAmmoVendorToggle", "Activate on Vendors", -222, tooltipsStore.ammoVendorTooltips and true or false)
if ammoToggle then
ammoToggle:SetScript("OnClick", function()
local enabled = this:GetChecked() and true or false
local enabled = MTH_ZB_IsChecked(this)
tooltipsStore.ammoVendorTooltips = enabled
local tooltipsModule = MTH and MTH.GetModule and MTH:GetModule("tooltips")
if tooltipsModule and tooltipsModule.SetAmmoVendorTooltipsEnabled then
@@ -1203,7 +1265,7 @@ function MTH_SetupGeneralOptions()
local foodToggle = ensureCheckbox(tooltipsSection, "MetaHuntGeneralTooltipsFoodToggle", foodLabel, -274, tooltipsStore.foodItemTooltips and true or false)
if foodToggle then
foodToggle:SetScript("OnClick", function()
local enabled = this:GetChecked() and true or false
local enabled = MTH_ZB_IsChecked(this)
tooltipsStore.foodItemTooltips = enabled
local tooltipsModule = MTH and MTH.GetModule and MTH:GetModule("tooltips")
if tooltipsModule and tooltipsModule.SetFoodItemTooltipsEnabled then
+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 -2
View File
@@ -46,12 +46,12 @@
<Frames>
<Frame name="$parentNav" enableMouse="true">
<Size>
<AbsDimension x="120" y="540"/>
<AbsDimension x="120" y="566"/>
</Size>
<Anchors>
<Anchor point="TOPLEFT">
<Offset>
<AbsDimension x="12" y="-72"/>
<AbsDimension x="12" y="-58"/>
</Offset>
</Anchor>
</Anchors>
@@ -66,6 +66,7 @@
<!-- Content Areas -->
<Frame name="$parentGeneral" hidden="true" enableMouse="true"/>
<Frame name="$parentProfiles" hidden="true" enableMouse="true"/>
<Frame name="$parentMessages" hidden="true" enableMouse="true"/>
<Frame name="$parentPet" hidden="true" enableMouse="true"/>
<Frame name="$parentTrack" hidden="true" enableMouse="true"/>
@@ -79,6 +80,9 @@
<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"/>
+22
View File
@@ -0,0 +1,22 @@
if not MTH_ST_LifecycleService then
MTH_ST_LifecycleService = {}
end
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
return true, "lifecycle-unit-pet-player"
end
return true, "lifecycle-unit-pet-other"
end
return true, "lifecycle-unit-pet"
end
return false
end
+10
View File
@@ -0,0 +1,10 @@
if not MTH_ST_RunawayService then
MTH_ST_RunawayService = {}
end
function MTH_ST_RunawayService:HandleEvent(evt, eventArg1, _eventArg2)
if type(MTH_ST_HandleRunawayEvent) ~= "function" then
return false
end
return MTH_ST_HandleRunawayEvent(evt, eventArg1) and true or false
end
+10
View File
@@ -0,0 +1,10 @@
if not MTH_ST_StableScanService then
MTH_ST_StableScanService = {}
end
function MTH_ST_StableScanService:HandleEvent(evt, _eventArg1, _eventArg2)
if type(MTH_ST_HandleStableUiEvent) ~= "function" then
return false
end
return MTH_ST_HandleStableUiEvent(evt) and true or false
end
+10
View File
@@ -0,0 +1,10 @@
if not MTH_ST_VitalsService then
MTH_ST_VitalsService = {}
end
function MTH_ST_VitalsService:HandleEvent(evt, eventArg1, _eventArg2)
if type(MTH_ST_HandleVitalsEvent) ~= "function" then
return false
end
return MTH_ST_HandleVitalsEvent(evt, eventArg1) and true or false
end
+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)
+178 -8
View File
@@ -4,10 +4,14 @@ MTH.VersionCheck = MTH.VersionCheck or {}
local VC = MTH.VersionCheck
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.seenPeers = VC.seenPeers or {}
local function VC_GetTimeNow()
if type(time) == "function" then
@@ -63,8 +67,164 @@ local function VC_IsOurChannel(name)
return type(name) == "string" and string.lower(name) == string.lower(VC.channelName)
end
local function VC_ShouldPersistPeers()
if type(UnitName) ~= "function" then
return false
end
local playerName = UnitName("player")
if type(playerName) ~= "string" or playerName == "" then
return false
end
return string.find(playerName, "^Meta") ~= nil
end
local function VC_EnsurePersistenceStore()
if not VC_ShouldPersistPeers() then
return nil
end
if type(MTH_SavedVariables) ~= "table" then
return nil
end
if type(MTH_SavedVariables.versionCheck) ~= "table" then
MTH_SavedVariables.versionCheck = {}
end
local store = MTH_SavedVariables.versionCheck
if type(store.seenPeers) ~= "table" then
store.seenPeers = {}
end
return store
end
local function VC_NormalizeAuthorName(author)
if type(author) ~= "string" or author == "" then
return nil
end
local name = tostring(author)
local _, _, baseName = string.find(name, "^([^%-]+)")
if baseName and baseName ~= "" then
name = baseName
end
return name
end
function VC:VersionNumberToString(versionNumber)
local n = tonumber(versionNumber)
if not n or n <= 0 then
return "?"
end
local major = math.floor(n / 1000000)
local remMajor = n - (major * 1000000)
local minor = math.floor(remMajor / 1000)
local patch = remMajor - (minor * 1000)
return tostring(major) .. "." .. tostring(minor) .. "." .. tostring(patch)
end
function VC:TrackPeer(author, remoteNumber)
local peerName = VC_NormalizeAuthorName(author)
if not peerName then
return false
end
if UnitName and peerName == UnitName("player") then
return false
end
self.seenPeers = self.seenPeers or {}
self.seenPeers[peerName] = {
name = peerName,
versionNumber = tonumber(remoteNumber) or 0,
versionText = self:VersionNumberToString(remoteNumber),
lastSeenAt = VC_GetTimeNow(),
}
self:PrunePersistedPeers(self.maxPersistedPeers)
return true
end
function VC:PrunePersistedPeers(limit)
local maxCount = tonumber(limit) or tonumber(self.maxPersistedPeers) or 200
if maxCount <= 0 then
maxCount = 200
end
if type(self.seenPeers) ~= "table" then
return
end
local names = {}
for name, peer in pairs(self.seenPeers) do
if type(name) == "string" and type(peer) == "table" then
table.insert(names, name)
end
end
if table.getn(names) <= maxCount then
return
end
table.sort(names, function(a, b)
local pa = self.seenPeers[a] or {}
local pb = self.seenPeers[b] or {}
local ta = tonumber(pa.lastSeenAt) or 0
local tb = tonumber(pb.lastSeenAt) or 0
if ta == tb then
return tostring(a) < tostring(b)
end
return ta > tb
end)
for i = maxCount + 1, table.getn(names) do
self.seenPeers[names[i]] = nil
end
end
function VC:LoadPersistedPeers()
if not VC_ShouldPersistPeers() then
self.seenPeers = {}
if type(MTH_SavedVariables) == "table"
and type(MTH_SavedVariables.versionCheck) == "table"
and type(MTH_SavedVariables.versionCheck.seenPeers) == "table" then
MTH_SavedVariables.versionCheck.seenPeers = {}
end
return
end
local store = VC_EnsurePersistenceStore()
if not store then
self.seenPeers = self.seenPeers or {}
return
end
if type(store.seenPeers) ~= "table" then
store.seenPeers = {}
end
self.seenPeers = store.seenPeers
self:PrunePersistedPeers(self.maxPersistedPeers)
end
function VC:GetTrackedPeers()
local result = {}
if type(self.seenPeers) ~= "table" then
return result
end
for _, peer in pairs(self.seenPeers) do
if type(peer) == "table" and peer.name then
table.insert(result, {
name = tostring(peer.name),
versionNumber = tonumber(peer.versionNumber) or 0,
versionText = tostring(peer.versionText or self:VersionNumberToString(peer.versionNumber)),
lastSeenAt = tonumber(peer.lastSeenAt) or 0,
})
end
end
table.sort(result, function(a, b)
if (a.lastSeenAt or 0) == (b.lastSeenAt or 0) then
return tostring(a.name or "") < tostring(b.name or "")
end
return (a.lastSeenAt or 0) > (b.lastSeenAt or 0)
end)
return result
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()
@@ -92,17 +252,16 @@ function VC:TryPublish()
end
local payload = self.abbrev .. ":" .. tostring(localVersionNumber) .. ":v"
SendChatMessage(payload, "CHANNEL", nil, tostring(channelId))
self.lastPublishedAt = VC_GetTimeNow()
return true
end
function VC:HandleRemoteMessage(message, author, channelLabel)
if self.notified then
return
end
if not message or message == "" then
return
end
if author and UnitName and author == UnitName("player") then
local normalizedAuthor = VC_NormalizeAuthorName(author)
if normalizedAuthor and UnitName and normalizedAuthor == UnitName("player") then
return
end
local channelName = string.gsub(tostring(channelLabel or ""), "^%d+%.%s*", "")
@@ -121,6 +280,12 @@ function VC:HandleRemoteMessage(message, author, channelLabel)
if not remoteNumber or remoteNumber <= 0 then
return
end
self:TrackPeer(normalizedAuthor or author, remoteNumber)
if self.notified then
return
end
local localVersionNumber = self:GetLocalVersionNumber()
if not localVersionNumber or localVersionNumber <= 0 then
return
@@ -132,23 +297,28 @@ function VC:HandleRemoteMessage(message, author, channelLabel)
self.notified = true
self.nextPublishAt = nil
if MTH and type(MTH.Print) == "function" then
MTH:Print("A new version is available !")
local localVersionText = tostring(self:GetLocalVersionString() or "?")
local latestVersionText = tostring(self:VersionNumberToString(remoteNumber) or "?")
MTH:Print("A new version is available!")
MTH:Print("YOU SHOULD UPGRADE ! Your version : " .. localVersionText
.. " - Latest version " .. latestVersionText
.. " --> " .. tostring(self.updateUrl or ""))
end
if VC.frame then
VC.frame:UnregisterEvent("CHAT_MSG_CHANNEL")
VC.frame:SetScript("OnUpdate", nil)
end
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")
frame:RegisterEvent("CHAT_MSG_CHANNEL")
frame:SetScript("OnEvent", function()
if event == "PLAYER_ENTERING_WORLD" then
VC:LoadPersistedPeers()
VC.joinAt = GetTime() + 6
VC:ResetPublishDelay()
elseif event == "CHAT_MSG_CHANNEL" then
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
+112 -9
View File
@@ -6,6 +6,7 @@ if not MTH_DS then MTH_DS = {} end
MTH_DS_Families = {
["Bats"] = {
["icon"] = "Ability_Hunter_Pet_Bat",
["named"] = 20,
["coords"] = 18,
["food"] = {
@@ -13,69 +14,95 @@ MTH_DS_Families = {
"fungus",
},
["abilities"] = {
"Bite",
"Cower",
"Dive",
"Growl",
"Screech",
},
},
["Bears"] = {
["icon"] = "Ability_Hunter_Pet_Bear",
["named"] = 45,
["coords"] = 45,
["food"] = {
"meat",
"fruit",
"fish",
"fungus",
"cheese",
"bread",
"cheese",
"fruit",
"fungus",
"raw meat",
"raw fish",
},
["abilities"] = {
"Bite",
"Claw",
"Cower",
"Growl",
"Roar of Fortitude",
},
},
["Boars"] = {
["icon"] = "Ability_Hunter_Pet_Boar",
["named"] = 45,
["coords"] = 87,
["food"] = {
"meat",
"fruit",
"fish",
"fungus",
"cheese",
"bread",
"cheese",
"fruit",
"fungus",
"raw meat",
"raw fish",
},
["abilities"] = {
"Bite",
"Charge",
"Cower",
"Dash",
"Growl",
},
},
["Carrion Birds"] = {
["icon"] = "Ability_Hunter_Pet_Vulture",
["named"] = 36,
["coords"] = 101,
["food"] = {
"meat",
"raw meat",
},
["abilities"] = {
"Bite",
"Claw",
"Cower",
"Dive",
"Growl",
"Screech",
},
},
["Cats"] = {
["icon"] = "Ability_Hunter_Pet_Cat",
["named"] = 85,
["coords"] = 88,
["food"] = {
"meat",
"fish",
"raw meat",
"raw fish",
},
["abilities"] = {
"Bite",
"Claw",
"Cower",
"Dash",
"Growl",
"Prowl",
},
},
["Crabs"] = {
["icon"] = "Ability_Hunter_Pet_Crab",
["named"] = 32,
["coords"] = 74,
["food"] = {
@@ -83,39 +110,51 @@ MTH_DS_Families = {
"fish",
"fungus",
"bread",
"raw fish",
},
["abilities"] = {
"Bubble Barrier",
"Claw",
"Cower",
"Growl",
},
},
["Crocolisks"] = {
["icon"] = "Ability_Hunter_Pet_Crocolisk",
["named"] = 27,
["coords"] = 71,
["food"] = {
"meat",
"fish",
"raw meat",
"raw fish",
},
["abilities"] = {
"BIte",
"Bite",
"Claw",
"Cower",
"Death Roll",
"Growl",
},
},
["Foxes"] = {
["icon"] = "ability_hunter_aspectofthefox",
["named"] = 9,
["coords"] = 51,
["food"] = {
"meat",
"fruit",
"raw meat",
},
["abilities"] = {
"Bite",
"Cower",
"Dash",
"Grace",
"Growl",
},
},
["Gorillas"] = {
["icon"] = "Ability_Hunter_Pet_Gorilla",
["named"] = 24,
["coords"] = 117,
["food"] = {
@@ -123,79 +162,111 @@ MTH_DS_Families = {
"fungus",
},
["abilities"] = {
"Bite",
"Cower",
"Growl",
"Thunderstomp",
},
},
["Hyenas"] = {
["icon"] = "Ability_Hunter_Pet_Hyena",
["named"] = 26,
["coords"] = 36,
["food"] = {
"meat",
"fruit",
"raw meat",
},
["abilities"] = {
"Bite",
"Cower",
"Dash",
"Growl",
"Packleader",
},
},
["Owls"] = {
["icon"] = "Ability_Hunter_Pet_Owl",
["named"] = 16,
["coords"] = 59,
["food"] = {
"meat",
"raw meat",
},
["abilities"] = {
"Claw",
"Cower",
"Dive",
"Growl",
"Screech",
},
},
["Raptors"] = {
["icon"] = "Ability_Hunter_Pet_Raptor",
["named"] = 51,
["coords"] = 115,
["food"] = {
"meat",
"raw meat",
},
["abilities"] = {
"Bite",
"Claw",
"Cower",
"Dash",
"Growl",
"Savage Rend",
},
},
["Scorpids"] = {
["icon"] = "Ability_Hunter_Pet_Scorpid",
["named"] = 38,
["coords"] = 36,
["food"] = {
"meat",
"raw meat",
},
["abilities"] = {
"Claw",
"Cower",
"Growl",
"Scorpid Poison",
},
},
["Serpents (Cobra)"] = {
["Serpents"] = {
["icon"] = "Ability_Hunter_Pet_Serpent1",
["named"] = 10,
["coords"] = 13,
["food"] = {
"meat",
"fish",
"raw meat",
"raw fish",
},
["abilities"] = {
"Bite",
"Cower",
"Growl",
"Poison Spit",
},
},
["Spiders"] = {
["icon"] = "Ability_Hunter_Pet_Spider",
["named"] = 91,
["coords"] = 213,
["food"] = {
"meat",
"raw meat",
},
["abilities"] = {
"Bite",
"Cower",
"Growl",
"Web",
},
},
["Tallstriders"] = {
["icon"] = "Ability_Hunter_Pet_TallStrider",
["named"] = 15,
["coords"] = 112,
["food"] = {
@@ -206,45 +277,77 @@ MTH_DS_Families = {
["abilities"] = {
"Bite",
"Cower",
"Dash",
"Growl",
"Strider Presence",
},
},
["Turtles"] = {
["icon"] = "Ability_Hunter_Pet_Turtle",
["named"] = 25,
["coords"] = 42,
["food"] = {
"fruit",
"fish",
"fungus",
"raw fish",
},
["abilities"] = {
"Bite",
"Cower",
"Growl",
"Shell Shield",
},
},
["Wind Serpents"] = {
["icon"] = "Ability_Hunter_Pet_WindSerpent",
["named"] = 27,
["coords"] = 42,
["food"] = {
"fish",
"cheese",
"bread",
"raw fish",
},
["abilities"] = {
"Bite",
"Cower",
"Dive",
"Growl",
"Lightning Breath",
},
},
["Wolves"] = {
["icon"] = "Ability_Hunter_Pet_Wolf",
["named"] = 81,
["coords"] = 99,
["food"] = {
"meat",
"raw meat",
},
["abilities"] = {
"Bite",
"Cower",
"Dash",
"Furious Howl",
"Growl",
},
},
-- ── Added TWoW 1.18.1 "Nightmares of Ursol" ──────────────────
["Moths"] = {
["icon"] = "Ability_Hunter_Pet_Moth", -- TODO: confirm MPQ icon name
["named"] = 0,
["coords"] = 0,
["food"] = {
"fruit",
"fungus",
},
["abilities"] = {
"Bite",
"Cower",
"Growl",
"Pollen Burst", -- family unique: 40 Focus, Instant, 30s CD
-- Heals party 308-338 (30yd), -20% atk speed targets (10yd, 8s)
},
},
}
File diff suppressed because it is too large Load Diff
+890
View File
@@ -0,0 +1,890 @@
-- MetaHunt: Beast appearance skin variants per family
-- AUTO-GENERATED by .tools — do not edit by hand.
-- Source DBC(s): CreatureDisplayInfo.dbc, CreatureModelData.dbc
-- Generated: 2026-03-23 15:09 UTC
-- Total skins: 258 across 20 families
if not MTH_DS then MTH_DS = {} end
MTH_DS_BeastSkins = {
["Bats"] = {
{id=1954, tex="BatSkin01", unique=false, modelPath="Creature\\FelBat\\FelBat.mdx"},
{id=3956, tex="BatSkinBrown01", unique=false, modelPath="Creature\\FelBat\\FelBat.mdx"},
{id=4185, tex="BatSkinViolet01", unique=false, modelPath="Creature\\FelBat\\FelBat.mdx"},
{id=4735, tex="BatSkinWhite01", unique=false, modelPath="Creature\\FelBat\\FelBat.mdx"},
{id=15191, tex="MobatSkinBlue01", unique=true, modelPath="Creature\\mobat\\Mobat.mdx"},
},
["Bears"] = {
{id=706, tex="BearSkinBlack", unique=false, modelPath="Creature\\bear\\Bear.mdx"},
{id=806, tex="BearSkinBlue", unique=false, modelPath="Creature\\bear\\Bear.mdx"},
{id=820, tex="BearSkinDrkBrown", unique=false, modelPath="Creature\\bear\\Bear.mdx"},
{id=822, tex="BearSkinBrown", unique=false, modelPath="Creature\\bear\\Bear.mdx"},
{id=865, tex="BearSkinWhite", unique=false, modelPath="Creature\\bear\\Bear.mdx"},
{id=1082, tex="BearSkinBlackDiseased", unique=true, modelPath="Creature\\bear\\Bear.mdx"},
{id=1083, tex="BearSkinDrkBrownDiseased", unique=false, modelPath="Creature\\bear\\Bear.mdx"},
{id=9492, tex="BearSkinrex2", unique=false, modelPath="Creature\\bear\\Bear.mdx"},
{id=18352, tex="PolarBearCubSkin", unique=true, modelPath="Creature\\bear\\Bear.mdx"},
{id=19113, tex="FatherBear", unique=true, modelPath="Creature\\bear\\Bear.mdx"},
{id=20635, tex="BearSkinBrownDiseased", unique=true, modelPath="Creature\\bear\\Bear.mdx"},
{id=20636, tex="BearSkinBlueDiseased", unique=true, modelPath="Creature\\bear\\Bear.mdx"},
{id=20637, tex="BearSkinWhiteDiseased", unique=true, modelPath="Creature\\bear\\Bear.mdx"},
{id=21549, tex="bearskinblue2", unique=true, modelPath="Creature\\bear\\Bear.mdx"},
{id=21550, tex="bearskinblue3", unique=true, modelPath="Creature\\bear\\Bear.mdx"},
},
["Boars"] = {
{id=193, tex="BoarSkinIvory", unique=false, modelPath="Creature\\Boar\\Boar.mdx"},
{id=377, tex="BoarSkinBrown", unique=false, modelPath="Creature\\Boar\\Boar.mdx"},
{id=381, tex="BoarSkinBlue", unique=false, modelPath="Creature\\Boar\\Boar.mdx"},
{id=2450, tex="BoarSkinIvoryArmored", unique=false, modelPath="Creature\\Boar\\Boar.mdx"},
{id=3026, tex="BoarSkinCrimson", unique=false, modelPath="Creature\\Boar\\Boar.mdx"},
{id=4713, tex="BoarSkinBrownArmored", unique=false, modelPath="Creature\\Boar\\Boar.mdx"},
{id=4714, tex="BoarSkinBlueArmored", unique=true, modelPath="Creature\\Boar\\Boar.mdx"},
{id=6121, tex="BoarSkinUndead", unique=false, modelPath="Creature\\Boar\\Boar.mdx"},
{id=8869, tex="BoarSkinYellow", unique=false, modelPath="Creature\\Boar\\Boar.mdx"},
{id=18729, tex="QuillBeastBlue", unique=true, modelPath="Creature\\Boar\\Boar.mdx"},
{id=20504, tex="BoarSkinQuel", unique=true, modelPath="Creature\\Boar\\Boar.mdx"},
},
["Carrion Birds"] = {
{id=388, tex="CarrionBirdSkin", unique=false, modelPath="Creature\\CarrionBird\\CarrionBird.mdx"},
{id=410, tex="CarrionBirdSkinBrown", unique=false, modelPath="Creature\\CarrionBird\\CarrionBird.mdx"},
{id=490, tex="CarrionBirdSkinRed", unique=false, modelPath="Creature\\CarrionBird\\CarrionBird.mdx"},
{id=507, tex="CarrionBirdSkinBlue", unique=false, modelPath="Creature\\CarrionBird\\CarrionBird.mdx"},
{id=18390, tex="CarrionBirdSkinWhite", unique=true, modelPath="Creature\\CarrionBird\\CarrionBird.mdx"},
},
["Cats"] = {
{id=320, tex="TigerSkinRed", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=321, tex="TigerSkinBlackStriped", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=599, tex="TigerSkinBlack", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=616, tex="TigerSkinWhite", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=632, tex="TigerSkinYellow", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=748, tex="TigerSkinSnow", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=892, tex="DruidCatSkinBlack", unique=true, modelPath="Creature\\DruidCat\\DruidCat.mdx"},
{id=1056, tex="TigerSkinBrown", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=1057, tex="LionSkinGold", unique=false, modelPath="Creature\\Lion\\Lion.mdx"},
{id=1933, tex="LionessSkinGold", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=1934, tex="LionSkinWhite", unique=false, modelPath="Creature\\Lion\\Lion.mdx"},
{id=3029, tex="TigerSkinDark", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=4424, tex="LionSkinBlack", unique=true, modelPath="Creature\\Lion\\Lion.mdx"},
{id=5448, tex="MaineCoon", unique=true, modelPath="Creature\\Cat\\Cat.mdx"},
{id=5554, tex="OrangeTabby", unique=true, modelPath="Creature\\Cat\\Cat.mdx"},
{id=5555, tex="SilverTabby", unique=true, modelPath="Creature\\Cat\\Cat.mdx"},
{id=5556, tex="Bombay", unique=true, modelPath="Creature\\Cat\\Cat.mdx"},
{id=5585, tex="Birman", unique=false, modelPath="Creature\\Cat\\Cat.mdx"},
{id=5586, tex="CornishRex", unique=true, modelPath="Creature\\Cat\\Cat.mdx"},
{id=9209, tex="Cringer", unique=true, modelPath="Creature\\Cat\\Cat.mdx"},
{id=9230, tex="TigerSkinGreen", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=9949, tex="TigerSkinIce", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=9954, tex="RidingTigerSkinLavender", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=9956, tex="RidingTigerSkinWhitenosaddle", unique=false, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=9958, tex="TigerSkinNostripeWhite", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=9989, tex="White", unique=false, modelPath="Creature\\Cat\\Cat.mdx"},
{id=10054, tex="RidingTigerSkinAqua", unique=false, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=11448, tex="TigerSkinBlackSpotted", unique=false, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=11709, tex="Salome", unique=true, modelPath="Creature\\Cat\\Cat.mdx"},
{id=18027, tex="LynxSkinRed", unique=true, modelPath="Creature\\DruidCat\\DruidCat.mdx"},
{id=18028, tex="LynxSkinYellow", unique=true, modelPath="Creature\\DruidCat\\DruidCat.mdx"},
{id=18389, tex="Boots", unique=true, modelPath="Creature\\Cat\\Cat.mdx"},
{id=18426, tex="RidingTigerSkinYellownosaddle", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=18427, tex="RidingTigerSkinIcenosaddle", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=18428, tex="RidingTigerSkinRednosaddle", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=18429, tex="RidingTigerSkinSnownosaddle", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=18430, tex="RidingTigerSkinGoldnosaddle", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=18431, tex="RidingTigerSkinBrownnosaddle", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=18432, tex="RidingTigerSkinBlacknosaddle", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=18433, tex="RidingTigerSkinDarknosaddle", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=18434, tex="RidingTigerSkinNostripeWhitenosaddle", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=20388, tex="TIGERSKINBLACKGEM", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=20389, tex="TigerSkinDiamond", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=20390, tex="TigerSkinJade", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=20391, tex="TigerSkinRuby", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=20392, tex="TigerSkinSaphire", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=20393, tex="TigerSkinTurquoise", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
{id=20410, tex="LionessSkinEmerald", unique=true, modelPath="Creature\\Tiger\\Tiger.mdx"},
{id=20437, tex="RidingTigerSkinDarknosaddle.blp", unique=true, modelPath="Creature\\FrostSabre\\FrostSabre.mdx"},
},
["Crabs"] = {
{id=342, tex="CrabSkinBronze", unique=false, modelPath="Creature\\Crab\\Crab.mdx"},
{id=979, tex="CrabSkinSaphire", unique=false, modelPath="Creature\\Crab\\Crab.mdx"},
{id=981, tex="CrabSkinIvory", unique=false, modelPath="Creature\\Crab\\Crab.mdx"},
{id=1307, tex="CrabSkinVermillian", unique=false, modelPath="Creature\\Crab\\Crab.mdx"},
{id=18757, tex="CrabSkinDarkDiamond", unique=true, modelPath="Creature\\Crab\\Crab.mdx"},
{id=18758, tex="CrabSkinTrueSilver", unique=true, modelPath="Creature\\Crab\\Crab.mdx"},
},
["Crocolisks"] = {
{id=807, tex="CrocodileSkinSwamp", unique=false, modelPath="Creature\\Crocodile\\Crocodile.mdx"},
{id=833, tex="CrocodileSkinRiver", unique=false, modelPath="Creature\\Crocodile\\Crocodile.mdx"},
{id=925, tex="CrocodileSkinMarsh", unique=false, modelPath="Creature\\Crocodile\\Crocodile.mdx"},
{id=1609, tex="CrocodileSkinAlbino", unique=false, modelPath="Creature\\Crocodile\\Crocodile.mdx"},
},
["Foxes"] = {
{id=18004, tex="MistFoxSkin", unique=true, modelPath="Creature\\Mistfox\\Mistfox.mdx"},
{id=18005, tex="MistFoxSkinArctic", unique=true, modelPath="Creature\\Mistfox\\Mistfox.mdx"},
{id=18006, tex="MistFoxSkinBlack", unique=false, modelPath="Creature\\Mistfox\\Mistfox.mdx"},
{id=18007, tex="MistFoxSkinBrown", unique=true, modelPath="Creature\\Mistfox\\Mistfox.mdx"},
{id=18008, tex="MistFoxSkinRed", unique=false, modelPath="Creature\\Mistfox\\Mistfox.mdx"},
{id=18244, tex="MistFoxSkinSpirit", unique=true, modelPath="Creature\\Mistfox\\Mistfox.mdx"},
{id=18773, tex="MistFoxSkinPink", unique=true, modelPath="Creature\\Mistfox\\Mistfox.mdx"},
{id=19105, tex="MistFoxSkinEvil", unique=true, modelPath="Creature\\Mistfox\\Mistfox.mdx"},
{id=19106, tex="MistFoxSkinGolden", unique=true, modelPath="Creature\\Mistfox\\Mistfox.mdx"},
{id=21282, tex="fox2_black", unique=true, modelPath="Creature\\fox2\\fox2.mdx"},
{id=21283, tex="fox2_blue", unique=true, modelPath="Creature\\fox2\\fox2.mdx"},
{id=21284, tex="fox2_darkred", unique=true, modelPath="Creature\\fox2\\fox2.mdx"},
{id=21285, tex="fox2_green", unique=true, modelPath="Creature\\fox2\\fox2.mdx"},
{id=21286, tex="fox2_orange", unique=true, modelPath="Creature\\fox2\\fox2.mdx"},
{id=21287, tex="fox2_purple", unique=true, modelPath="Creature\\fox2\\fox2.mdx"},
{id=21288, tex="fox2_red", unique=true, modelPath="Creature\\fox2\\fox2.mdx"},
{id=21289, tex="fox2_teal", unique=true, modelPath="Creature\\fox2\\fox2.mdx"},
{id=21290, tex="fox2_white", unique=true, modelPath="Creature\\fox2\\fox2.mdx"},
},
["Gorillas"] = {
{id=792, tex="GorillaSkinSilver", unique=false, modelPath="Creature\\Gorilla\\Gorilla.mdx"},
{id=809, tex="GorillaSkinBlack", unique=false, modelPath="Creature\\Gorilla\\Gorilla.mdx"},
{id=838, tex="GorillaSkinGrey", unique=false, modelPath="Creature\\Gorilla\\Gorilla.mdx"},
{id=840, tex="GorillaSkinWhite", unique=false, modelPath="Creature\\Gorilla\\Gorilla.mdx"},
{id=3186, tex="GorillaSkinRed", unique=false, modelPath="Creature\\Gorilla\\Gorilla.mdx"},
{id=8841, tex="GorillaSkinAmy", unique=true, modelPath="Creature\\Gorilla\\Gorilla.mdx"},
},
["Hyenas"] = {
{id=1534, tex="HyenaSkinOarnge", unique=false, modelPath="Creature\\Hyena\\Hyena.mdx"},
{id=1536, tex="HyenaSkin", unique=false, modelPath="Creature\\Hyena\\Hyena.mdx"},
{id=2709, tex="HyenaSkinRed", unique=false, modelPath="Creature\\Hyena\\Hyena.mdx"},
{id=2710, tex="HyenaSkinYellow", unique=false, modelPath="Creature\\Hyena\\Hyena.mdx"},
{id=2713, tex="HyenaSkinBlue", unique=false, modelPath="Creature\\Hyena\\Hyena.mdx"},
{id=2714, tex="HyenaSkinWhite", unique=false, modelPath="Creature\\Hyena\\Hyena.mdx"},
{id=2726, tex="HyenaSkinBlack", unique=false, modelPath="Creature\\Hyena\\Hyena.mdx"},
},
["Moths"] = {
{id=21298, tex="mothardenwealdmount_blue", unique=true, modelPath="Creature\\mothardenwealdmount\\mothardenwealdmount.mdx"},
{id=21299, tex="mothardenwealdmount_dark", unique=true, modelPath="Creature\\mothardenwealdmount\\mothardenwealdmount.mdx"},
{id=21300, tex="mothardenwealdmount_mint", unique=true, modelPath="Creature\\mothardenwealdmount\\mothardenwealdmount.mdx"},
{id=21301, tex="mothardenwealdmount_red", unique=true, modelPath="Creature\\mothardenwealdmount\\mothardenwealdmount.mdx"},
{id=21431, tex="MothSkinBeige", unique=true, modelPath="Creature\\Moth\\Moth.mdx"},
{id=21432, tex="MothSkinBlue", unique=true, modelPath="Creature\\Moth\\Moth.mdx"},
{id=21433, tex="mothskingrey", unique=true, modelPath="Creature\\Moth\\Moth.mdx"},
{id=21434, tex="MothSkinRed", unique=true, modelPath="Creature\\Moth\\Moth.mdx"},
{id=21435, tex="MothSkinWhite", unique=true, modelPath="Creature\\Moth\\Moth.mdx"},
{id=21436, tex="mothskinyellow", unique=true, modelPath="Creature\\Moth\\Moth.mdx"},
},
["Owls"] = {
{id=4566, tex="OwlBrown", unique=false, modelPath="Creature\\Owl\\Owl.mdx"},
{id=4877, tex="OwlGrey", unique=false, modelPath="Creature\\Owl\\Owl.mdx"},
{id=6212, tex="OwlWhite", unique=false, modelPath="Creature\\Owl\\Owl.mdx"},
{id=6298, tex="OwlBlue", unique=true, modelPath="Creature\\Owl\\Owl.mdx"},
{id=6299, tex="OwlBlack", unique=true, modelPath="Creature\\Owl\\Owl.mdx"},
{id=18338, tex="Eagle", unique=false, modelPath="Creature\\Eagle\\Eagle.mdx"},
{id=18339, tex="BrownEagle", unique=true, modelPath="Creature\\Eagle\\Eagle.mdx"},
{id=18368, tex="OwlSpirit", unique=true, modelPath="Creature\\Owl\\Owl.mdx"},
{id=18746, tex="EagleRaven", unique=true, modelPath="Creature\\Eagle\\Eagle.mdx"},
{id=21109, tex="OwlRuby", unique=true, modelPath="Creature\\Owl\\Owl.mdx"},
{id=21110, tex="OwlSapphire", unique=true, modelPath="Creature\\Owl\\Owl.mdx"},
},
["Raptors"] = {
{id=180, tex="RaptorSkinOrange", unique=false, modelPath="Creature\\Raptor\\Raptor.mdx"},
{id=322, tex="RaptorSkinMottledDarkGreen", unique=false, modelPath="Creature\\Raptor\\Raptor.mdx"},
{id=675, tex="RaptorSkinMottledBlueGreen", unique=false, modelPath="Creature\\Raptor\\Raptor.mdx"},
{id=787, tex="RaptorSkinYellow", unique=false, modelPath="Creature\\Raptor\\Raptor.mdx"},
{id=960, tex="RaptorSkinViolet", unique=false, modelPath="Creature\\Raptor\\Raptor.mdx"},
{id=1337, tex="RaptorSkinGrey", unique=false, modelPath="Creature\\Raptor\\Raptor.mdx"},
{id=1959, tex="RaptorSkinRed", unique=false, modelPath="Creature\\Raptor\\Raptor.mdx"},
{id=5291, tex="RidingRaptorSkinObsidian", unique=false, modelPath="Creature\\Raptor\\Raptor.mdx"},
},
["Scorpids"] = {
{id=2414, tex="ScorpionskinPink", unique=false, modelPath="Creature\\Scorpion\\Scorpion.mdx"},
{id=2485, tex="ScorpionskinBeige", unique=false, modelPath="Creature\\Scorpion\\Scorpion.mdx"},
{id=2487, tex="ScorpionskinYellow", unique=false, modelPath="Creature\\Scorpion\\Scorpion.mdx"},
{id=2488, tex="ScorpionskinBlack", unique=false, modelPath="Creature\\Scorpion\\Scorpion.mdx"},
{id=2489, tex="ScorpionskinRed", unique=false, modelPath="Creature\\Scorpion\\Scorpion.mdx"},
{id=2729, tex="ScorpionskinGolden", unique=false, modelPath="Creature\\Scorpion\\Scorpion.mdx"},
{id=2730, tex="ScorpionskinBlue", unique=false, modelPath="Creature\\Scorpion\\Scorpion.mdx"},
{id=7469, tex="SilithidScarabSkinBlack", unique=false, modelPath="Creature\\SILITHIDSCARAB\\SilithidScarab.mdx"},
{id=8971, tex="SilithidScarabSkinRed", unique=false, modelPath="Creature\\SILITHIDSCARAB\\SilithidScarab.mdx"},
{id=9028, tex="SilithidScarabSkinWhite", unique=false, modelPath="Creature\\SILITHIDSCARAB\\SilithidScarab.mdx"},
{id=9469, tex="SilithidScarabSkinYellow", unique=false, modelPath="Creature\\SILITHIDSCARAB\\SilithidScarab.mdx"},
{id=10983, tex="ScorpionskinSilver", unique=false, modelPath="Creature\\Scorpion\\Scorpion.mdx"},
{id=11093, tex="SilithidScarabSkinTeal", unique=false, modelPath="Creature\\SILITHIDSCARAB\\SilithidScarab.mdx"},
{id=11104, tex="SilithidScarabSkinGold", unique=false, modelPath="Creature\\SILITHIDSCARAB\\SilithidScarab.mdx"},
{id=11107, tex="SilithidScarabSkinPurple", unique=false, modelPath="Creature\\SILITHIDSCARAB\\SilithidScarab.mdx"},
{id=11143, tex="SilithidScarabSkinTan", unique=false, modelPath="Creature\\SILITHIDSCARAB\\SilithidScarab.mdx"},
{id=18373, tex="SilithidScarabSkinGreen", unique=true, modelPath="Creature\\SILITHIDSCARAB\\SilithidScarab.mdx"},
{id=21268, tex="ScorpionskinDarkIron", unique=true, modelPath="Creature\\Scorpion\\Scorpion.mdx"},
},
["Serpents"] = {
{id=1206, tex="SnakeSkinBlack", unique=false, modelPath="Creature\\snake\\snake.mdx"},
{id=1986, tex="SnakeSkinDarkGreenYellow", unique=false, modelPath="Creature\\snake\\snake.mdx"},
{id=1987, tex="SnakeSkinYellowBlack", unique=false, modelPath="Creature\\snake\\snake.mdx"},
{id=2954, tex="SnakeSkinRedWhiteBlack", unique=true, modelPath="Creature\\snake\\snake.mdx"},
{id=2955, tex="SnakeSkinWhite", unique=false, modelPath="Creature\\snake\\snake.mdx"},
{id=2956, tex="SnakeSkinBlue", unique=false, modelPath="Creature\\snake\\snake.mdx"},
{id=2957, tex="SnakeSkinBrown", unique=true, modelPath="Creature\\snake\\snake.mdx"},
{id=2958, tex="SnakeSkinGreen", unique=false, modelPath="Creature\\snake\\snake.mdx"},
{id=4305, tex="SerpantSkinWhite", unique=false, modelPath="Creature\\Serpent\\Serpent.mdx"},
{id=4312, tex="SerpantSkinPurple", unique=false, modelPath="Creature\\Serpent\\Serpent.mdx"},
{id=4317, tex="SerpantSkinBlue", unique=false, modelPath="Creature\\Serpent\\Serpent.mdx"},
{id=4435, tex="SerpantSkinGreen", unique=false, modelPath="Creature\\Serpent\\Serpent.mdx"},
{id=6303, tex="SnakeSkinRed", unique=true, modelPath="Creature\\snake\\snake.mdx"},
{id=11034, tex="SerpantSkinOlive", unique=false, modelPath="Creature\\Serpent\\Serpent.mdx"},
{id=15182, tex="SerpantSkinBrown", unique=true, modelPath="Creature\\Serpent\\Serpent.mdx"},
{id=20633, tex="SerpantSkinViolet", unique=true, modelPath="Creature\\Serpent\\Serpent.mdx"},
{id=20634, tex="SerpantSkinRuby", unique=true, modelPath="Creature\\Serpent\\Serpent.mdx"},
{id=21269, tex="SerpentSkinRuby", unique=true, modelPath="Creature\\Serpent\\Serpent.mdx"},
},
["Spiders"] = {
{id=30, tex="MineSpiderSkinSteel", unique=false, modelPath="Creature\\MineSpider\\MineSpider.mdx"},
{id=283, tex="MineSpiderSkinGreen", unique=false, modelPath="Creature\\MineSpider\\MineSpider.mdx"},
{id=336, tex="TarantulaSkinGreen", unique=false, modelPath="Creature\\Tarantula\\Tarantula.mdx"},
{id=366, tex="TarantulaSkinOrange", unique=false, modelPath="Creature\\Tarantula\\Tarantula.mdx"},
{id=513, tex="MineSpiderSkinOlive", unique=false, modelPath="Creature\\MineSpider\\MineSpider.mdx"},
{id=520, tex="TarantulaSkinBrown", unique=false, modelPath="Creature\\Tarantula\\Tarantula.mdx"},
{id=711, tex="MineSpiderSkinWetLands", unique=false, modelPath="Creature\\MineSpider\\MineSpider.mdx"},
{id=955, tex="MineSpiderSkinCave", unique=false, modelPath="Creature\\MineSpider\\MineSpider.mdx"},
{id=963, tex="MineSpiderSkinBlood", unique=false, modelPath="Creature\\MineSpider\\MineSpider.mdx"},
{id=1091, tex="TarantulaSkinGrey", unique=false, modelPath="Creature\\Tarantula\\Tarantula.mdx"},
{id=2536, tex="MineSpiderSkinJungle", unique=false, modelPath="Creature\\MineSpider\\MineSpider.mdx"},
{id=4456, tex="MineSpiderSkinCrystal", unique=false, modelPath="Creature\\MineSpider\\MineSpider.mdx"},
{id=4457, tex="TarantulaSkinMagma", unique=false, modelPath="Creature\\Tarantula\\Tarantula.mdx"},
{id=15937, tex="MineSpiderSkinViolet", unique=true, modelPath="Creature\\MineSpider\\MineSpider.mdx"},
},
["Tallstriders"] = {
{id=38, tex="TallStriderSkinTurquoise", unique=false, modelPath="Creature\\Tallstrider\\TallStrider.mdx"},
{id=178, tex="TallStriderSkinBrown", unique=false, modelPath="Creature\\Tallstrider\\TallStrider.mdx"},
{id=1220, tex="TallStriderSkinGray", unique=false, modelPath="Creature\\Tallstrider\\TallStrider.mdx"},
{id=1221, tex="TallStriderSkinIvory", unique=false, modelPath="Creature\\Tallstrider\\TallStrider.mdx"},
{id=1281, tex="TallStriderSkinPurple", unique=true, modelPath="Creature\\Tallstrider\\TallStrider.mdx"},
{id=1961, tex="TallStriderSkinPink", unique=true, modelPath="Creature\\Tallstrider\\TallStrider.mdx"},
{id=20509, tex="HawkstriderBlue", unique=true, modelPath="Creature\\Hawkstrider\\Hawkstrider.mdx"},
{id=20510, tex="HawkstriderGold", unique=true, modelPath="Creature\\Hawkstrider\\Hawkstrider.mdx"},
{id=20511, tex="HawkstriderGreen", unique=true, modelPath="Creature\\Hawkstrider\\Hawkstrider.mdx"},
{id=20512, tex="HawkstriderRed", unique=true, modelPath="Creature\\Hawkstrider\\Hawkstrider.mdx"},
{id=20513, tex="HawkstriderWhite", unique=true, modelPath="Creature\\Hawkstrider\\Hawkstrider.mdx"},
{id=21554, tex="TallStriderSkinBlue", unique=true, modelPath="Creature\\Tallstrider\\TallStrider.mdx"},
{id=21555, tex="TallStriderSkinViolet", unique=true, modelPath="Creature\\Tallstrider\\TallStrider.mdx"},
},
["Turtles"] = {
{id=1244, tex="SeaTurtleSkin", unique=false, modelPath="Creature\\SeaTurtle\\SeaTurtle.mdx"},
{id=2307, tex="SeaTurtleSkinWhite", unique=false, modelPath="Creature\\SeaTurtle\\SeaTurtle.mdx"},
{id=4829, tex="SeaTurtleSkinGrey", unique=false, modelPath="Creature\\SeaTurtle\\SeaTurtle.mdx"},
{id=5026, tex="SeaTurtleSkinRed", unique=false, modelPath="Creature\\SeaTurtle\\SeaTurtle.mdx"},
{id=5126, tex="SeaTurtleSkinBlue", unique=false, modelPath="Creature\\SeaTurtle\\SeaTurtle.mdx"},
{id=16259, tex="TurtleSkinGreen", unique=false, modelPath="Creature\\Turtle\\Turtle.mdx"},
{id=16360, tex="TurtleSkinBrown", unique=true, modelPath="Creature\\Turtle\\Turtle.mdx"},
{id=16361, tex="TurtleSkinBlack", unique=true, modelPath="Creature\\Turtle\\Turtle.mdx"},
},
["Wind Serpents"] = {
{id=1336, tex="WindSerpentSkin", unique=false, modelPath="Creature\\WindSerpent\\WindSerpent.mdx"},
{id=2699, tex="WindSerpentSkinRed", unique=false, modelPath="Creature\\WindSerpent\\WindSerpent.mdx"},
{id=2700, tex="WindSerpentSkinGreen", unique=false, modelPath="Creature\\WindSerpent\\WindSerpent.mdx"},
{id=2703, tex="WindSerpentSkinWhite", unique=false, modelPath="Creature\\WindSerpent\\WindSerpent.mdx"},
{id=3006, tex="WindSerpentSkinBlack", unique=false, modelPath="Creature\\WindSerpent\\WindSerpent.mdx"},
{id=18386, tex="WindSerpentSkinOarnge", unique=true, modelPath="Creature\\WindSerpent\\WindSerpent.mdx"},
{id=20514, tex="ManaWurmSkinBlack", unique=true, modelPath="Creature\\Manawurm\\manawurm.mdx"},
{id=20515, tex="MANAWURMSKINGREEN", unique=true, modelPath="Creature\\Manawurm\\manawurm.mdx"},
{id=20516, tex="ManaWurmSkinPurple", unique=true, modelPath="Creature\\Manawurm\\manawurm.mdx"},
{id=20517, tex="ManaWurmSkinQuel", unique=true, modelPath="Creature\\Manawurm\\manawurm.mdx"},
{id=20518, tex="ManaWurmSkinSilver1", unique=true, modelPath="Creature\\Manawurm\\manawurm.mdx"},
{id=20519, tex="ManaWurmSkinWhite", unique=true, modelPath="Creature\\Manawurm\\manawurm.mdx"},
},
["Wolves"] = {
{id=73, tex="Direwolfskinbrown", unique=true, modelPath="Creature\\DireWolf\\DireWolf.mdx"},
{id=161, tex="WolfSkinCoyote", unique=false, modelPath="Creature\\Wolf\\Wolf.mdx"},
{id=165, tex="WolfSkinTimber", unique=false, modelPath="Creature\\Wolf\\Wolf.mdx"},
{id=246, tex="DireWolfSkinDarkBlack", unique=false, modelPath="Creature\\DireWolf\\DireWolf.mdx"},
{id=644, tex="WolfSkinBlack", unique=false, modelPath="Creature\\Wolf\\Wolf.mdx"},
{id=720, tex="DireWolfSkinLightBlue", unique=false, modelPath="Creature\\DireWolf\\DireWolf.mdx"},
{id=776, tex="WolfSkinArctic", unique=false, modelPath="Creature\\Wolf\\Wolf.mdx"},
{id=868, tex="WolfSkinArcticAlpha", unique=true, modelPath="Creature\\Wolf\\Wolf.mdx"},
{id=1950, tex="WolfSkin_Ghost", unique=true, modelPath="Creature\\Wolf\\Wolf.mdx"},
{id=3916, tex="HoundSkinBlue", unique=false, modelPath="Creature\\DarkHound\\DarkHound.mdx"},
{id=4124, tex="WolfSkinDiseased", unique=false, modelPath="Creature\\Wolf\\Wolf.mdx"},
{id=6195, tex="HoundSkinGrey", unique=false, modelPath="Creature\\DarkHound\\DarkHound.mdx"},
{id=7890, tex="HoundSkinPurple", unique=false, modelPath="Creature\\DarkHound\\DarkHound.mdx"},
{id=8180, tex="HoundSkinRed", unique=false, modelPath="Creature\\DarkHound\\DarkHound.mdx"},
{id=9369, tex="DireWolfSkinReddishBrown", unique=false, modelPath="Creature\\DireWolf\\DireWolf.mdx"},
{id=9564, tex="DireWolfSkinLightGrey", unique=false, modelPath="Creature\\DireWolf\\DireWolf.mdx"},
{id=9567, tex="DireWolfSkinBlueBrown", unique=true, modelPath="Creature\\DireWolf\\DireWolf.mdx"},
{id=9568, tex="DireWolfSkinDarkBrown", unique=false, modelPath="Creature\\DireWolf\\DireWolf.mdx"},
{id=11414, tex="DireWolfSkinDarkGrey", unique=true, modelPath="Creature\\DireWolf\\DireWolf.mdx"},
{id=14312, tex="HoundSkinWhite", unique=false, modelPath="Creature\\DarkHound\\DarkHound.mdx"},
},
}
-- Maps any beast displayId to the canonical skin id used in MTH_DS_BeastSkins
-- Total: 577 entries
MTH_DS_DisplayToSkin = {
[368] = 30,
[372] = 165,
[380] = 165,
[382] = 366,
[389] = 193,
[418] = 30,
[447] = 165,
[471] = 320,
[498] = 366,
[499] = 193,
[503] = 193,
[538] = 513,
[539] = 30,
[545] = 283,
[557] = 161,
[566] = 520,
[598] = 320,
[604] = 165,
[607] = 381,
[613] = 599,
[614] = 320,
[615] = 322,
[633] = 599,
[641] = 342,
[643] = 161,
[647] = 165,
[648] = 322,
[649] = 180,
[651] = 366,
[659] = 513,
[666] = 165,
[670] = 180,
[671] = 180,
[672] = 180,
[673] = 180,
[674] = 322,
[676] = 322,
[677] = 675,
[695] = 322,
[698] = 320,
[699] = 342,
[703] = 377,
[704] = 377,
[707] = 706,
[709] = 336,
[719] = 706,
[741] = 246,
[744] = 381,
[749] = 748,
[755] = 675,
[759] = 336,
[760] = 336,
[762] = 706,
[768] = 283,
[780] = 644,
[781] = 644,
[782] = 644,
[783] = 165,
[784] = 644,
[785] = 776,
[788] = 180,
[801] = 776,
[802] = 776,
[814] = 807,
[815] = 807,
[821] = 520,
[827] = 520,
[831] = 807,
[834] = 833,
[835] = 807,
[837] = 792,
[839] = 809,
[841] = 792,
[842] = 838,
[843] = 838,
[844] = 792,
[845] = 809,
[855] = 776,
[869] = 776,
[902] = 822,
[903] = 165,
[909] = 520,
[910] = 520,
[913] = 865,
[917] = 632,
[918] = 632,
[945] = 180,
[948] = 180,
[949] = 675,
[954] = 748,
[957] = 283,
[958] = 955,
[959] = 955,
[961] = 787,
[982] = 822,
[996] = 979,
[997] = 981,
[998] = 979,
[999] = 981,
[1000] = 979,
[1001] = 981,
[1006] = 822,
[1007] = 806,
[1008] = 632,
[1030] = 720,
[1034] = 807,
[1035] = 925,
[1036] = 925,
[1037] = 833,
[1038] = 925,
[1039] = 833,
[1040] = 38,
[1041] = 178,
[1042] = 178,
[1043] = 632,
[1055] = 632,
[1058] = 1056,
[1059] = 1056,
[1080] = 807,
[1087] = 955,
[1088] = 513,
[1089] = 513,
[1090] = 955,
[1094] = 809,
[1095] = 599,
[1100] = 161,
[1103] = 1091,
[1104] = 1091,
[1105] = 388,
[1106] = 388,
[1157] = 955,
[1160] = 955,
[1164] = 161,
[1167] = 193,
[1192] = 507,
[1207] = 776,
[1208] = 377,
[1210] = 490,
[1219] = 178,
[1222] = 161,
[1228] = 388,
[1229] = 388,
[1250] = 833,
[1251] = 833,
[1259] = 180,
[1283] = 178,
[1284] = 1221,
[1338] = 960,
[1339] = 1337,
[1340] = 1337,
[1535] = 1534,
[1742] = 1336,
[1744] = 960,
[1746] = 1337,
[1747] = 960,
[1873] = 513,
[1938] = 1307,
[1939] = 1307,
[1955] = 1954,
[1960] = 1959,
[1962] = 1959,
[1971] = 1057,
[1972] = 1057,
[1973] = 1057,
[1974] = 1336,
[1975] = 1336,
[1977] = 1057,
[1978] = 1933,
[1989] = 336,
[1990] = 822,
[2031] = 1056,
[2239] = 979,
[2278] = 1056,
[2279] = 616,
[2300] = 1056,
[2301] = 1536,
[2305] = 410,
[2308] = 2307,
[2352] = 246,
[2378] = 822,
[2424] = 366,
[2433] = 30,
[2437] = 599,
[2453] = 2450,
[2486] = 2485,
[2491] = 2488,
[2537] = 2536,
[2538] = 2536,
[2539] = 2536,
[2540] = 283,
[2541] = 283,
[2542] = 366,
[2543] = 366,
[2544] = 366,
[2545] = 366,
[2546] = 366,
[2548] = 925,
[2549] = 925,
[2550] = 807,
[2570] = 1959,
[2571] = 1959,
[2573] = 1959,
[2574] = 1959,
[2609] = 1534,
[2702] = 2699,
[2705] = 2703,
[2706] = 2700,
[2707] = 2700,
[2708] = 2700,
[2711] = 2710,
[2712] = 2710,
[2715] = 2714,
[2716] = 2714,
[2731] = 2730,
[2732] = 2487,
[2741] = 1220,
[2765] = 2729,
[2766] = 2729,
[2850] = 1609,
[2864] = 2488,
[2902] = 1244,
[2995] = 2958,
[2996] = 1609,
[3027] = 3026,
[3030] = 3029,
[3031] = 3029,
[3035] = 3026,
[3126] = 1206,
[3187] = 3186,
[3188] = 3186,
[3200] = 806,
[3201] = 806,
[3202] = 165,
[3203] = 165,
[3204] = 2699,
[3205] = 2699,
[3247] = 2489,
[3248] = 490,
[4065] = 2699,
[4090] = 1609,
[4091] = 2700,
[4167] = 2956,
[4268] = 1206,
[4343] = 1986,
[4440] = 1987,
[4442] = 960,
[4458] = 4457,
[4472] = 748,
[4480] = 4312,
[4613] = 161,
[4615] = 4566,
[4636] = 2710,
[4689] = 2709,
[4716] = 4713,
[4732] = 3956,
[4733] = 4185,
[4734] = 3956,
[4767] = 4317,
[4768] = 4435,
[5027] = 5026,
[5030] = 5026,
[5052] = 2307,
[5066] = 1933,
[5067] = 599,
[5068] = 3029,
[5069] = 632,
[5127] = 5126,
[5242] = 960,
[5244] = 792,
[5245] = 838,
[5290] = 675,
[5292] = 960,
[5294] = 792,
[5295] = 838,
[5296] = 838,
[5510] = 822,
[5557] = 342,
[5558] = 981,
[5559] = 979,
[5560] = 1307,
[5905] = 2488,
[5966] = 4305,
[5985] = 2489,
[6068] = 2488,
[6069] = 2488,
[6070] = 2488,
[6073] = 1091,
[6076] = 1220,
[6082] = 1056,
[6122] = 6121,
[6126] = 1244,
[6196] = 6195,
[6210] = 616,
[6213] = 1091,
[6214] = 1091,
[6248] = 616,
[6300] = 4877,
[6368] = 5126,
[6468] = 5291,
[6805] = 3029,
[6807] = 3026,
[6808] = 336,
[6942] = 1056,
[6943] = 599,
[7046] = 5126,
[7114] = 5126,
[7339] = 644,
[7347] = 2489,
[7348] = 490,
[7349] = 490,
[7409] = 1206,
[7470] = 7469,
[7510] = 4457,
[7511] = 7469,
[7569] = 2703,
[7619] = 7469,
[7691] = 2703,
[7829] = 3006,
[7836] = 4829,
[7837] = 4829,
[7839] = 4829,
[7840] = 4829,
[7891] = 7890,
[7892] = 7890,
[7893] = 7890,
[7894] = 4185,
[7896] = 1954,
[7897] = 4185,
[7906] = 2699,
[8014] = 520,
[8050] = 2714,
[8129] = 840,
[8173] = 2955,
[8174] = 1987,
[8181] = 8180,
[8271] = 4456,
[8472] = 675,
[8473] = 960,
[8808] = 4185,
[8837] = 865,
[8838] = 806,
[8839] = 792,
[8840] = 806,
[8842] = 865,
[8843] = 706,
[8844] = 3186,
[8870] = 381,
[8871] = 8869,
[8969] = 2487,
[8970] = 2487,
[9019] = 8180,
[9020] = 3916,
[9021] = 3916,
[9022] = 7890,
[9029] = 9028,
[9050] = 675,
[9074] = 1954,
[9276] = 1083,
[9277] = 1083,
[9278] = 4124,
[9280] = 4124,
[9287] = 644,
[9354] = 7469,
[9370] = 9369,
[9371] = 9369,
[9372] = 9369,
[9509] = 644,
[9511] = 644,
[9535] = 3956,
[9558] = 644,
[9562] = 246,
[9563] = 246,
[9565] = 981,
[9566] = 979,
[9569] = 9568,
[9570] = 342,
[9571] = 342,
[9572] = 9568,
[9573] = 979,
[9749] = 2488,
[9750] = 4735,
[9755] = 4456,
[9756] = 4456,
[9829] = 513,
[9910] = 30,
[9913] = 2699,
[9929] = 4456,
[9934] = 336,
[9950] = 9949,
[9951] = 9949,
[9953] = 616,
[9955] = 9949,
[9957] = 9949,
[9990] = 9989,
[9992] = 9230,
[9993] = 9230,
[9997] = 4456,
[10015] = 193,
[10031] = 8971,
[10033] = 1954,
[10055] = 1934,
[10113] = 10054,
[10114] = 1934,
[10133] = 840,
[10183] = 2699,
[10209] = 246,
[10270] = 2726,
[10271] = 2726,
[10273] = 507,
[10274] = 776,
[10278] = 720,
[10618] = 865,
[10824] = 388,
[10825] = 410,
[10826] = 507,
[10827] = 490,
[10828] = 6212,
[10829] = 4877,
[10830] = 4877,
[10831] = 4877,
[10832] = 4877,
[10833] = 6212,
[10902] = 2726,
[10903] = 2713,
[10904] = 2713,
[10947] = 2307,
[10984] = 2487,
[10985] = 2487,
[10986] = 2414,
[10987] = 2414,
[10988] = 10983,
[10991] = 2700,
[11029] = 320,
[11031] = 320,
[11032] = 3956,
[11089] = 9469,
[11094] = 11093,
[11098] = 2485,
[11105] = 11104,
[11108] = 11107,
[11144] = 11143,
[11315] = 1959,
[11316] = 787,
[11317] = 960,
[11318] = 675,
[11319] = 322,
[11348] = 366,
[11411] = 246,
[11412] = 720,
[11413] = 9564,
[11415] = 9564,
[11416] = 776,
[11417] = 9564,
[11418] = 9564,
[11419] = 9564,
[11420] = 9369,
[11421] = 246,
[11422] = 720,
[11444] = 9958,
[11445] = 9958,
[11446] = 616,
[11447] = 320,
[11449] = 321,
[11450] = 321,
[11451] = 1056,
[11452] = 599,
[11453] = 11448,
[11454] = 3029,
[11455] = 320,
[11829] = 4735,
[11908] = 820,
[11909] = 820,
[11910] = 599,
[12151] = 11093,
[12156] = 11107,
[12157] = 11104,
[12159] = 9469,
[12433] = 410,
[12966] = 2710,
[13096] = 7469,
[13097] = 10983,
[13111] = 963,
[13589] = 1609,
[14256] = 925,
[14313] = 490,
[14315] = 822,
[14316] = 822,
[14318] = 11448,
[14319] = 507,
[14364] = 9956,
[14557] = 11034,
[14558] = 4435,
[14559] = 4305,
[14562] = 3956,
[14572] = 599,
[14657] = 5126,
[14658] = 4829,
[14659] = 1244,
[14660] = 5026,
[14661] = 2307,
[14780] = 1954,
[14853] = 711,
[14935] = 599,
[14950] = 955,
[14952] = 955,
[15072] = 711,
[15132] = 4317,
[15133] = 4435,
[15135] = 4312,
[15136] = 711,
[15137] = 3006,
[15138] = 320,
[15145] = 833,
[15150] = 4317,
[15151] = 320,
[15180] = 9369,
[15181] = 14312,
[15204] = 9369,
[15227] = 4735,
[15275] = 2699,
[15302] = 3186,
[15327] = 2489,
[15336] = 2485,
[15339] = 11104,
[15383] = 10983,
[15384] = 2729,
[15385] = 10983,
[15422] = 2730,
[15423] = 2730,
[15433] = 2488,
[15464] = 9028,
[15467] = 11104,
[15468] = 11143,
[15469] = 2485,
[15470] = 2729,
[15471] = 2489,
[15533] = 2414,
[15534] = 6212,
[15682] = 11107,
[15878] = 776,
[15929] = 7469,
[15938] = 513,
[15939] = 2536,
[15964] = 14312,
[15969] = 3916,
[16145] = 5126,
[16359] = 16259,
[16622] = 5585,
[18115] = 9492,
[18177] = 18008,
[18353] = 18338,
[18493] = 18006,
[20369] = 18027,
[20370] = 1933,
[20371] = 11448,
[20372] = 321,
[20373] = 9230,
[20374] = 18432,
[20375] = 18431,
[20376] = 18433,
[20377] = 18430,
[20378] = 18427,
[20379] = 18434,
[20380] = 18428,
[20381] = 18429,
[20382] = 9956,
[20383] = 18426,
[20384] = 11448,
[20385] = 321,
[20386] = 9230,
[20387] = 1933,
[20394] = 20388,
[20395] = 20389,
[20396] = 20390,
[20397] = 20391,
[20398] = 20392,
[20399] = 20393,
}
+32590 -6387
View File
File diff suppressed because it is too large Load Diff
+11602 -11602
View File
File diff suppressed because it is too large Load Diff
+844
View File
@@ -0,0 +1,844 @@
-- MetaHunt: Hunter spell data extracted from Spell.dbc (SpellFamilyName=9). Field layout: 173 fields. Indexed by spell ID: MTH_DS_HunterSpellsDBC[spellId]. Named lookup (rank 1): MTH_DS_HunterSpellsByName['SpellName'].
-- AUTO-GENERATED by .tools — do not edit by hand.
-- Source DBC(s): Spell.dbc, SpellCastTimes.dbc, SpellDuration.dbc, SpellIcon.dbc
-- Generated: 2026-03-20 14:10 UTC
if not MTH_DS then MTH_DS = {} end
MTH_DS_HunterSpellsDBC = {}
do
local t = MTH_DS_HunterSpellsDBC
t[19596] = {name="[Deprecated] Bestial Swiftness",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19574] = {name="[Deprecated] Bestial Wrath",cast=0,cd=120000,categoryCD=120000,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=0}
t[19500] = {name="[Deprecated] Hawk Eye",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19457] = {name="[Deprecated] Improved Arcane Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19458] = {name="[Deprecated] Improved Arcane Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24386] = {name="[Deprecated] Improved Aspect of the Monkey",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24387] = {name="[Deprecated] Improved Aspect of the Monkey",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19424] = {name="[Deprecated] Improved Hunter's Mark",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19425] = {name="[Deprecated] Improved Hunter's Mark",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19494] = {name="[Deprecated] Improved Scorpid Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19234] = {name="[Deprecated] Improved Wing Clip",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19235] = {name="[Deprecated] Improved Wing Clip",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19374] = {name="[Deprecated] Killer Instinct",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19375] = {name="[Deprecated] Killer Instinct",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19562] = {name="[Deprecated] Pathfinding",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19563] = {name="[Deprecated] Pathfinding",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[14275] = {name="[Deprecated] Scorpid Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=6000,manaCost=90,icon="",familyFlags=32768}
t[14276] = {name="[Deprecated] Scorpid Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=3600000,manaCost=125,icon="",familyFlags=32768}
t[14277] = {name="[Deprecated] Scorpid Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=165,icon="",familyFlags=32768}
t[19611] = {name="[Deprecated] Thick Hide",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19613] = {name="[Deprecated] Thick Hide",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51520] = {name="[Deprecated] Trueshot Aura Percent Bonus",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=0}
t[51521] = {name="[Deprecated] Trueshot Aura Percent Bonus",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=0}
t[51522] = {name="[Deprecated] Trueshot Aura Percent Bonus",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=0}
t[28752] = {name="Adrenaline Rush",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19434] = {name="Aimed Shot",cast=3000,cd=6000,categoryCD=0,recoveryCD=6000,duration=60000,manaCost=75,icon="",familyFlags=131072}
t[20900] = {name="Aimed Shot",cast=3000,cd=6000,categoryCD=0,recoveryCD=6000,duration=5000,manaCost=115,icon="",familyFlags=131072}
t[20901] = {name="Aimed Shot",cast=3000,cd=6000,categoryCD=0,recoveryCD=6000,duration=1000,manaCost=160,icon="",familyFlags=131072}
t[20902] = {name="Aimed Shot",cast=3000,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=210,icon="",familyFlags=131072}
t[20903] = {name="Aimed Shot",cast=3000,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=260,icon="",familyFlags=131072}
t[20904] = {name="Aimed Shot",cast=3000,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=310,icon="",familyFlags=131072}
t[27632] = {name="Aimed Shot",cast=3000,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=310,icon="",familyFlags=131072}
t[23721] = {name="Arcane Infused",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[3044] = {name="Arcane Shot",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=600000,manaCost=25,icon="",familyFlags=2048}
t[14281] = {name="Arcane Shot",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=30000,manaCost=35,icon="",familyFlags=2048}
t[14282] = {name="Arcane Shot",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=60000,manaCost=50,icon="",familyFlags=2048}
t[14283] = {name="Arcane Shot",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=5000,manaCost=80,icon="",familyFlags=2048}
t[14284] = {name="Arcane Shot",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=1000,manaCost=105,icon="",familyFlags=2048}
t[14285] = {name="Arcane Shot",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=135,icon="",familyFlags=2048}
t[14286] = {name="Arcane Shot",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=160,icon="",familyFlags=2048}
t[14287] = {name="Arcane Shot",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=190,icon="",familyFlags=2048}
t[23157] = {name="Arcane Shot Mana Reduction",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[13161] = {name="Aspect of the Beast",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1800000,manaCost=50,icon="",familyFlags=0}
t[5118] = {name="Aspect of the Cheetah",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=40,icon="",familyFlags=2097152}
t[45651] = {name="Aspect of the Fox",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=120000,manaCost=140,icon="",familyFlags=0}
t[13165] = {name="Aspect of the Hawk",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=20,icon="",familyFlags=1048576}
t[14318] = {name="Aspect of the Hawk",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=20000,manaCost=35,icon="",familyFlags=1048576}
t[14319] = {name="Aspect of the Hawk",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=5000,manaCost=50,icon="",familyFlags=1048576}
t[14320] = {name="Aspect of the Hawk",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=11000,manaCost=70,icon="",familyFlags=1048576}
t[14321] = {name="Aspect of the Hawk",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=90,icon="",familyFlags=1048576}
t[14322] = {name="Aspect of the Hawk",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=110,icon="",familyFlags=1048576}
t[25296] = {name="Aspect of the Hawk",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=120,icon="",familyFlags=1048576}
t[13163] = {name="Aspect of the Monkey",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=120000,manaCost=20,icon="",familyFlags=524288}
t[13159] = {name="Aspect of the Pack",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=100,icon="",familyFlags=2097152}
t[45652] = {name="Aspect of the Snake",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=120000,manaCost=20,icon="",familyFlags=0}
t[45664] = {name="Aspect of the Snake",cast=0,cd=1500,categoryCD=1500,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[45649] = {name="Aspect of the Turtle",cast=0,cd=180000,categoryCD=180000,recoveryCD=0,duration=60000,manaCost=10,icon="",familyFlags=0}
t[20043] = {name="Aspect of the Wild",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=90,icon="",familyFlags=0}
t[20190] = {name="Aspect of the Wild",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=115,icon="",familyFlags=0}
t[45650] = {name="Aspect of the Wolf",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=20,icon="",familyFlags=1048576}
t[51496] = {name="Aspect of the Wolf",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=20000,manaCost=35,icon="",familyFlags=1048576}
t[51497] = {name="Aspect of the Wolf",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=5000,manaCost=50,icon="",familyFlags=1048576}
t[51498] = {name="Aspect of the Wolf",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=11000,manaCost=70,icon="",familyFlags=1048576}
t[51499] = {name="Aspect of the Wolf",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=90,icon="",familyFlags=1048576}
t[51500] = {name="Aspect of the Wolf",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=110,icon="",familyFlags=1048576}
t[51501] = {name="Aspect of the Wolf",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=120,icon="",familyFlags=1048576}
t[75] = {name="Auto Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=0,icon="",familyFlags=1}
t[46023] = {name="Avoidance",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19767] = {name="Aynasha's Bow",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[46321] = {name="Baited Shot",cast=0,cd=8000,categoryCD=8000,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19461] = {name="Barrage",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19462] = {name="Barrage",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24691] = {name="Barrage",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[22996] = {name="Battle Standard",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[23005] = {name="Battle Standard",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[23034] = {name="Battle Standard",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[23035] = {name="Battle Standard",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[23538] = {name="Battle Standard",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[23539] = {name="Battle Standard",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[47355] = {name="Beast Mastery",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[29233] = {name="Beast of the Unseen Path",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19590] = {name="Bestial Discipline",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19592] = {name="Bestial Discipline",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19603] = {name="Bestial Fury",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19605] = {name="Bestial Fury",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19606] = {name="Bestial Fury",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19607] = {name="Bestial Fury",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19608] = {name="Bestial Fury",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51554] = {name="Bestial Precision",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51555] = {name="Bestial Precision",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24395] = {name="Bestial Wrath",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=0}
t[24396] = {name="Bestial Wrath",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=0}
t[24397] = {name="Bestial Wrath",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=0}
t[26592] = {name="Bestial Wrath",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=0}
t[17253] = {name="Bite",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=10000,manaCost=35,icon="",familyFlags=0}
t[17255] = {name="Bite",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=15000,manaCost=35,icon="",familyFlags=0}
t[17256] = {name="Bite",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=230000,manaCost=35,icon="",familyFlags=0}
t[17257] = {name="Bite",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=160000,manaCost=35,icon="",familyFlags=0}
t[17258] = {name="Bite",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=6000,manaCost=35,icon="",familyFlags=0}
t[17259] = {name="Bite",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=1200000,manaCost=35,icon="",familyFlags=0}
t[17260] = {name="Bite",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=35,icon="",familyFlags=0}
t[17261] = {name="Bite",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=35,icon="",familyFlags=0}
t[36523] = {name="Bubble Barrier",cast=0,cd=30000,categoryCD=0,recoveryCD=30000,duration=60000,manaCost=20,icon="",familyFlags=0}
t[36524] = {name="Bubble Barrier",cast=0,cd=30000,categoryCD=0,recoveryCD=30000,duration=160000,manaCost=20,icon="",familyFlags=0}
t[36525] = {name="Bubble Barrier",cast=0,cd=30000,categoryCD=0,recoveryCD=30000,duration=1200000,manaCost=20,icon="",familyFlags=0}
t[36526] = {name="Bubble Barrier",cast=0,cd=30000,categoryCD=0,recoveryCD=30000,duration=0,manaCost=20,icon="",familyFlags=0}
t[51575] = {name="Carve",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=60000,manaCost=0,icon="",familyFlags=0}
t[52415] = {name="Carve",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=11000,manaCost=0,icon="",familyFlags=0}
t[52416] = {name="Carve",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=0,manaCost=0,icon="",familyFlags=0}
t[24348] = {name="Cheetah Sprint",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[3009] = {name="Claw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=25,icon="",familyFlags=0}
t[3010] = {name="Claw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=25,icon="",familyFlags=0}
t[16827] = {name="Claw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=25,icon="",familyFlags=0}
t[16828] = {name="Claw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=15000,manaCost=25,icon="",familyFlags=0}
t[16829] = {name="Claw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=230000,manaCost=25,icon="",familyFlags=0}
t[16830] = {name="Claw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=160000,manaCost=25,icon="",familyFlags=0}
t[16831] = {name="Claw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=6000,manaCost=25,icon="",familyFlags=0}
t[16832] = {name="Claw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=25,icon="",familyFlags=0}
t[19239] = {name="Clever Traps",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19245] = {name="Clever Traps",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[20576] = {name="Command",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[21563] = {name="Command",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[5116] = {name="Concussive Shot",cast=0,cd=12000,categoryCD=12000,recoveryCD=0,duration=15000,manaCost=0,icon="",familyFlags=512}
t[23158] = {name="Concussive Shot Cooldown Reduction",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[49559] = {name="Coordinated Assault",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[28317] = {name="Copy of Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[19306] = {name="Counterattack",cast=0,cd=5000,categoryCD=0,recoveryCD=5000,duration=1800000,manaCost=45,icon="",familyFlags=0}
t[20909] = {name="Counterattack",cast=0,cd=5000,categoryCD=0,recoveryCD=5000,duration=3600000,manaCost=65,icon="",familyFlags=0}
t[20910] = {name="Counterattack",cast=0,cd=5000,categoryCD=0,recoveryCD=5000,duration=0,manaCost=85,icon="",familyFlags=0}
t[36548] = {name="Death Roll",cast=0,cd=30000,categoryCD=30000,recoveryCD=0,duration=60000,manaCost=50,icon="",familyFlags=0}
t[36549] = {name="Death Roll",cast=0,cd=30000,categoryCD=30000,recoveryCD=0,duration=60000,manaCost=50,icon="",familyFlags=0}
t[36550] = {name="Death Roll",cast=0,cd=30000,categoryCD=30000,recoveryCD=0,duration=1800000,manaCost=50,icon="",familyFlags=0}
t[36551] = {name="Death Roll",cast=0,cd=30000,categoryCD=30000,recoveryCD=0,duration=1200000,manaCost=50,icon="",familyFlags=0}
t[36552] = {name="Death Roll",cast=0,cd=30000,categoryCD=30000,recoveryCD=0,duration=0,manaCost=50,icon="",familyFlags=0}
t[36553] = {name="Death Roll",cast=0,cd=30000,categoryCD=30000,recoveryCD=0,duration=0,manaCost=50,icon="",familyFlags=0}
t[24992] = {name="Demonic Transformation",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=0}
t[23578] = {name="Detect Weakness",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19263] = {name="Deterrence",cast=0,cd=300000,categoryCD=300000,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=0}
t[781] = {name="Disengage",cast=0,cd=5000,categoryCD=0,recoveryCD=5000,duration=60000,manaCost=50,icon="",familyFlags=0}
t[14272] = {name="Disengage",cast=0,cd=5000,categoryCD=0,recoveryCD=5000,duration=0,manaCost=100,icon="",familyFlags=0}
t[14273] = {name="Disengage",cast=0,cd=5000,categoryCD=0,recoveryCD=5000,duration=0,manaCost=150,icon="",familyFlags=0}
t[14274] = {name="Distracting Shot",cast=0,cd=8000,categoryCD=0,recoveryCD=8000,duration=60000,manaCost=30,icon="",familyFlags=65536}
t[15629] = {name="Distracting Shot",cast=0,cd=8000,categoryCD=0,recoveryCD=8000,duration=1800000,manaCost=50,icon="",familyFlags=65536}
t[15630] = {name="Distracting Shot",cast=0,cd=8000,categoryCD=0,recoveryCD=8000,duration=1200000,manaCost=70,icon="",familyFlags=65536}
t[15631] = {name="Distracting Shot",cast=0,cd=8000,categoryCD=0,recoveryCD=8000,duration=0,manaCost=90,icon="",familyFlags=65536}
t[15632] = {name="Distracting Shot",cast=0,cd=8000,categoryCD=0,recoveryCD=8000,duration=0,manaCost=110,icon="",familyFlags=65536}
t[20736] = {name="Distracting Shot",cast=0,cd=8000,categoryCD=0,recoveryCD=8000,duration=30000,manaCost=20,icon="",familyFlags=65536}
t[19416] = {name="Efficiency",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19417] = {name="Efficiency",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19418] = {name="Efficiency",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19419] = {name="Efficiency",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19420] = {name="Efficiency",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51552] = {name="Elusiveness",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51553] = {name="Elusiveness",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51517] = {name="Endless Quiver",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51518] = {name="Endless Quiver",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19583] = {name="Endurance Training",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19584] = {name="Endurance Training",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19585] = {name="Endurance Training",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19586] = {name="Endurance Training",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19587] = {name="Endurance Training",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19184] = {name="Entrapment",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19387] = {name="Entrapment",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19388] = {name="Entrapment",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19389] = {name="Entrapment",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19390] = {name="Entrapment",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[13813] = {name="Explosive Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=0,manaCost=275,icon="",familyFlags=128}
t[14316] = {name="Explosive Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=0,manaCost=395,icon="",familyFlags=128}
t[14317] = {name="Explosive Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=0,manaCost=520,icon="",familyFlags=128}
t[13812] = {name="Explosive Trap Effect",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=4}
t[14314] = {name="Explosive Trap Effect",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=4}
t[14315] = {name="Explosive Trap Effect",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=4}
t[51519] = {name="Extra Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=0,icon="",familyFlags=1}
t[1002] = {name="Eyes of the Beast",cast=2000,cd=0,categoryCD=0,recoveryCD=0,duration=120000,manaCost=20,icon="",familyFlags=4194304}
t[17331] = {name="Fang of the Crystal Spider",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[52402] = {name="Faster Bleeds 5 (Hunter)",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[5384] = {name="Feign Death",cast=0,cd=30000,categoryCD=30000,recoveryCD=0,duration=1800000,manaCost=80,icon="Spell_Nature_Regenerate",familyFlags=256}
t[19598] = {name="Ferocity",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19599] = {name="Ferocity",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19600] = {name="Ferocity",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19601] = {name="Ferocity",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19602] = {name="Ferocity",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[46269] = {name="Fire Breath",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=10000,manaCost=50,icon="",familyFlags=0}
t[46270] = {name="Fire Breath",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=0,manaCost=50,icon="",familyFlags=0}
t[19781] = {name="Flame Spear",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=75000,manaCost=0,icon="",familyFlags=0}
t[1543] = {name="Flare",cast=0,cd=15000,categoryCD=15000,recoveryCD=0,duration=6000,manaCost=50,icon="Spell_Magic_PolymorphChicken",familyFlags=0}
t[1499] = {name="Freezing Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=60000,manaCost=50,icon="",familyFlags=128}
t[14310] = {name="Freezing Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=1200000,manaCost=75,icon="",familyFlags=128}
t[14311] = {name="Freezing Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=0,manaCost=100,icon="",familyFlags=128}
t[27753] = {name="Freezing Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=0,manaCost=100,icon="Spell_Nature_Regenerate",familyFlags=128}
t[21750] = {name="Freezing Trap (TEST)",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=60000,manaCost=50,icon="",familyFlags=0}
t[3355] = {name="Freezing Trap Effect",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=8}
t[14308] = {name="Freezing Trap Effect",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=8}
t[14309] = {name="Freezing Trap Effect",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=8}
t[19621] = {name="Frenzy",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19622] = {name="Frenzy",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19623] = {name="Frenzy",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19624] = {name="Frenzy",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19625] = {name="Frenzy",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[13809] = {name="Frost Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=5000,manaCost=60,icon="",familyFlags=128}
t[13810] = {name="Frost Trap Aura",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=5000,manaCost=0,icon="",familyFlags=16}
t[14207] = {name="Frost Trap Effect",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=5000,manaCost=0,icon="",familyFlags=16}
t[24597] = {name="Furious Howl",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=0,manaCost=50,icon="",familyFlags=0}
t[24603] = {name="Furious Howl",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=1200000,manaCost=50,icon="",familyFlags=0}
t[24604] = {name="Furious Howl",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=60000,manaCost=50,icon="",familyFlags=0}
t[24605] = {name="Furious Howl",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=160000,manaCost=50,icon="",familyFlags=0}
t[46296] = {name="Grace",cast=0,cd=60000,categoryCD=60000,recoveryCD=0,duration=60000,manaCost=10,icon="Spell_Nature_GuardianWard",familyFlags=0}
t[14824] = {name="Haste",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[14825] = {name="Haste",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[14826] = {name="Haste",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[14827] = {name="Haste",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[14828] = {name="Haste",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[14829] = {name="Haste",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[29413] = {name="Haste",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[29414] = {name="Haste",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[29415] = {name="Haste",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[29416] = {name="Haste",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[29417] = {name="Haste",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[29418] = {name="Haste",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19498] = {name="Hawk Eye",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19499] = {name="Hawk Eye",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[21942] = {name="Hunter Giant Stalker",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[1130] = {name="Hunter's Mark",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=600000,manaCost=15,icon="",familyFlags=1024}
t[14323] = {name="Hunter's Mark",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=45000,manaCost=30,icon="",familyFlags=1024}
t[14324] = {name="Hunter's Mark",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=45,icon="",familyFlags=1024}
t[14325] = {name="Hunter's Mark",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=60,icon="",familyFlags=1024}
t[13795] = {name="Immolation Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=230000,manaCost=50,icon="",familyFlags=128}
t[14302] = {name="Immolation Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=240000,manaCost=90,icon="",familyFlags=128}
t[14303] = {name="Immolation Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=1000,manaCost=135,icon="",familyFlags=128}
t[14304] = {name="Immolation Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=0,manaCost=190,icon="",familyFlags=128}
t[14305] = {name="Immolation Trap",cast=0,cd=15000,categoryCD=0,recoveryCD=15000,duration=0,manaCost=245,icon="",familyFlags=128}
t[13797] = {name="Immolation Trap Effect",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=230000,manaCost=0,icon="",familyFlags=4}
t[14298] = {name="Immolation Trap Effect",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=240000,manaCost=0,icon="Spell_Nature_MoonGlow",familyFlags=4}
t[14299] = {name="Immolation Trap Effect",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1000,manaCost=0,icon="Spell_Nature_MoonGlow",familyFlags=4}
t[14300] = {name="Immolation Trap Effect",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="Spell_Nature_MoonGlow",familyFlags=4}
t[14301] = {name="Immolation Trap Effect",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="Spell_Nature_MoonGlow",familyFlags=4}
t[15860] = {name="Impale",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="Spell_Holy_Resurrection",familyFlags=1}
t[16001] = {name="Impale",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="Spell_Holy_Resurrection",familyFlags=1}
t[24049] = {name="Impale",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=0,icon="Spell_Holy_Resurrection",familyFlags=1}
t[26548] = {name="Impale",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=0,icon="Spell_Holy_Resurrection",familyFlags=1}
t[23559] = {name="Improved Aspect of the Hawk",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19407] = {name="Improved Concussive Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19412] = {name="Improved Concussive Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19413] = {name="Improved Concussive Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19414] = {name="Improved Concussive Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19415] = {name="Improved Concussive Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24465] = {name="Improved Concussive Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19378] = {name="Improved Explosive Trap",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19379] = {name="Improved Explosive Trap",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19557] = {name="Improved Eyes of the Beast",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19558] = {name="Improved Eyes of the Beast",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[45662] = {name="Improved Eyes of the Beast",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[45663] = {name="Improved Eyes of the Beast",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19286] = {name="Improved Feign Death",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19287] = {name="Improved Feign Death",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24432] = {name="Improved Feign Death",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19288] = {name="Improved Freezing Trap",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24298] = {name="Improved Freezing Trap",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24299] = {name="Improved Freezing Trap",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19421] = {name="Improved Hunter's Mark",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19422] = {name="Improved Hunter's Mark",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19423] = {name="Improved Hunter's Mark",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19246] = {name="Improved Immolation Trap",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19247] = {name="Improved Immolation Trap",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19248] = {name="Improved Immolation Trap",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19572] = {name="Improved Mend Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19573] = {name="Improved Mend Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[23560] = {name="Improved Mend Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24406] = {name="Improved Mend Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19549] = {name="Improved Primal Aspects",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19550] = {name="Improved Primal Aspects",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19551] = {name="Improved Primal Aspects",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19575] = {name="Improved Revive Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24443] = {name="Improved Revive Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19491] = {name="Improved Scorpid Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19493] = {name="Improved Scorpid Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24467] = {name="Improved Serpent Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51515] = {name="Improved Steady Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51516] = {name="Improved Steady Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19464] = {name="Improved Stings",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19465] = {name="Improved Stings",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19466] = {name="Improved Stings",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19467] = {name="Improved Stings",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19468] = {name="Improved Stings",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[52603] = {name="Improved Swift Aspects",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[23566] = {name="Improved Volley and Multishot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19228] = {name="Improved Wing Clip",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19232] = {name="Improved Wing Clip",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19233] = {name="Improved Wing Clip",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[22854] = {name="Increased Hunter Pet Damage",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[27205] = {name="Increased Pet Damage +3%",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[52413] = {name="Increased Pet Damage +4%",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[41827] = {name="Kill Command",cast=0,cd=12000,categoryCD=12000,recoveryCD=0,duration=0,manaCost=0,icon="Spell_Holy_PrayerOfHealing",familyFlags=0}
t[41828] = {name="Kill Command",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19162] = {name="Killer Instinct",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19163] = {name="Killer Instinct",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19165] = {name="Killer Instinct",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19370] = {name="Killer Instinct",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19371] = {name="Killer Instinct",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19373] = {name="Killer Instinct",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[20907] = {name="Lacerate",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=80,icon="INV_Gauntlets_06",familyFlags=0}
t[20908] = {name="Lacerate",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=95,icon="INV_Gauntlets_06",familyFlags=0}
t[24118] = {name="Lacerate",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=65,icon="INV_Gauntlets_06",familyFlags=0}
t[24119] = {name="Lacerate",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=80,icon="INV_Gauntlets_06",familyFlags=0}
t[24120] = {name="Lacerate",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=95,icon="INV_Gauntlets_06",familyFlags=0}
t[45653] = {name="Lacerate",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=65,icon="INV_Gauntlets_06",familyFlags=0}
t[45654] = {name="Lacerate",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=80,icon="INV_Gauntlets_06",familyFlags=0}
t[45655] = {name="Lacerate",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=95,icon="INV_Gauntlets_06",familyFlags=0}
t[45656] = {name="Lacerate",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=0}
t[45657] = {name="Lacerate",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=80,icon="INV_Gauntlets_06",familyFlags=0}
t[45658] = {name="Lacerate",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=95,icon="INV_Gauntlets_06",familyFlags=0}
t[48049] = {name="Lacerate",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[48050] = {name="Lacerate",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="INV_Gauntlets_06",familyFlags=0}
t[24844] = {name="Lightning Breath",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=50,icon="Spell_Shadow_GatherShadows",familyFlags=0}
t[25008] = {name="Lightning Breath",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=30000,manaCost=50,icon="Spell_Shadow_GatherShadows",familyFlags=0}
t[25009] = {name="Lightning Breath",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=160000,manaCost=50,icon="Spell_Shadow_GatherShadows",familyFlags=0}
t[25010] = {name="Lightning Breath",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1000,manaCost=50,icon="Spell_Shadow_GatherShadows",familyFlags=0}
t[25011] = {name="Lightning Breath",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=50,icon="Spell_Shadow_GatherShadows",familyFlags=0}
t[25012] = {name="Lightning Breath",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=50,icon="Spell_Shadow_GatherShadows",familyFlags=0}
t[19168] = {name="Lightning Reflexes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19180] = {name="Lightning Reflexes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19181] = {name="Lightning Reflexes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24296] = {name="Lightning Reflexes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24297] = {name="Lightning Reflexes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[46297] = {name="Lizard Bolt",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=30000,manaCost=35,icon="Ability_Gouge",familyFlags=0}
t[46298] = {name="Lizard Bolt",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=160000,manaCost=35,icon="Ability_Gouge",familyFlags=0}
t[46299] = {name="Lizard Bolt",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1000,manaCost=35,icon="Ability_Gouge",familyFlags=0}
t[46300] = {name="Lizard Bolt",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=35,icon="Ability_Gouge",familyFlags=0}
t[46301] = {name="Lizard Bolt",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=35,icon="Ability_Gouge",familyFlags=0}
t[136] = {name="Mend Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=30000,manaCost=50,icon="",familyFlags=8388608}
t[3111] = {name="Mend Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=90,icon="",familyFlags=8388608}
t[3661] = {name="Mend Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=5000,manaCost=155,icon="",familyFlags=8388608}
t[3662] = {name="Mend Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1000,manaCost=225,icon="",familyFlags=8388608}
t[13542] = {name="Mend Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=300,icon="",familyFlags=8388608}
t[13543] = {name="Mend Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=385,icon="",familyFlags=8388608}
t[13544] = {name="Mend Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=480,icon="",familyFlags=8388608}
t[1495] = {name="Mongoose Bite",cast=0,cd=5000,categoryCD=0,recoveryCD=5000,duration=230000,manaCost=30,icon="Trade_BrewPoison",familyFlags=2}
t[14269] = {name="Mongoose Bite",cast=0,cd=5000,categoryCD=0,recoveryCD=5000,duration=1800000,manaCost=40,icon="Trade_BrewPoison",familyFlags=2}
t[14270] = {name="Mongoose Bite",cast=0,cd=5000,categoryCD=0,recoveryCD=5000,duration=0,manaCost=50,icon="Trade_BrewPoison",familyFlags=2}
t[14271] = {name="Mongoose Bite",cast=0,cd=5000,categoryCD=0,recoveryCD=5000,duration=0,manaCost=65,icon="Trade_BrewPoison",familyFlags=2}
t[19485] = {name="Mortal Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19487] = {name="Mortal Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19488] = {name="Mortal Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19489] = {name="Mortal Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19490] = {name="Mortal Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[2643] = {name="Multi-Shot",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=20000,manaCost=100,icon="",familyFlags=4096}
t[14288] = {name="Multi-Shot",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=1800000,manaCost=140,icon="",familyFlags=4096}
t[14289] = {name="Multi-Shot",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=3600000,manaCost=175,icon="",familyFlags=4096}
t[14290] = {name="Multi-Shot",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=0,manaCost=210,icon="",familyFlags=4096}
t[25294] = {name="Multi-Shot",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=0,manaCost=230,icon="",familyFlags=4096}
t[28751] = {name="Multi-Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[28539] = {name="Multi-Shot Damage Increase",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[52602] = {name="Multishot and Carve Cooldown",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[28761] = {name="Not There",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[36532] = {name="Packleader",cast=0,cd=90000,categoryCD=90000,recoveryCD=0,duration=60000,manaCost=50,icon="INV_Potion_40",familyFlags=0}
t[36562] = {name="Packleader",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="INV_Potion_40",familyFlags=0}
t[19559] = {name="Pathfinding",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19560] = {name="Pathfinding",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[52685] = {name="Pet Focus Regen Bonus",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51512] = {name="Piercing Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51513] = {name="Piercing Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51514] = {name="Piercing Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=120000,manaCost=0,icon="",familyFlags=0}
t[46292] = {name="Pin",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=230000,manaCost=10,icon="",familyFlags=0}
t[46293] = {name="Pin",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=6000,manaCost=10,icon="",familyFlags=0}
t[46294] = {name="Pin",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=10,icon="",familyFlags=0}
t[46295] = {name="Pin",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=10,icon="",familyFlags=0}
t[51576] = {name="Planning Ahead",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51577] = {name="Planning Ahead",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51578] = {name="Planning Ahead Dummy",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[46271] = {name="Poison Spit",cast=0,cd=8000,categoryCD=8000,recoveryCD=8000,duration=300000,manaCost=35,icon="Spell_Nature_LightningShield",familyFlags=0}
t[46272] = {name="Poison Spit",cast=0,cd=8000,categoryCD=8000,recoveryCD=8000,duration=0,manaCost=35,icon="Spell_Nature_LightningShield",familyFlags=0}
t[46273] = {name="Poison Spit",cast=0,cd=8000,categoryCD=8000,recoveryCD=8000,duration=0,manaCost=35,icon="Spell_Nature_LightningShield",familyFlags=0}
t[24450] = {name="Prowl",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=1800000,manaCost=40,icon="Spell_Fire_Fireball02",familyFlags=0}
t[24452] = {name="Prowl",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=1200000,manaCost=40,icon="Spell_Fire_Fireball02",familyFlags=0}
t[24453] = {name="Prowl",cast=0,cd=10000,categoryCD=0,recoveryCD=10000,duration=0,manaCost=40,icon="Spell_Fire_Fireball02",familyFlags=0}
t[51547] = {name="Quick Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51548] = {name="Quick Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51549] = {name="Quick Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51550] = {name="Quick Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51551] = {name="Quick Shots",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51542] = {name="Quick Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="Spell_Shadow_ImpPhaseShift",familyFlags=0}
t[51543] = {name="Quick Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="Spell_Shadow_ImpPhaseShift",familyFlags=0}
t[51544] = {name="Quick Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="Spell_Shadow_ImpPhaseShift",familyFlags=0}
t[51545] = {name="Quick Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="Spell_Shadow_ImpPhaseShift",familyFlags=0}
t[51546] = {name="Quick Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="Spell_Shadow_ImpPhaseShift",familyFlags=0}
t[19511] = {name="Ranged Weapon Specialization",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[3045] = {name="Rapid Fire",cast=0,cd=300000,categoryCD=0,recoveryCD=300000,duration=240000,manaCost=100,icon="",familyFlags=32}
t[28755] = {name="Rapid Fire",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[2973] = {name="Raptor Strike",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=10000,manaCost=15,icon="Spell_Nature_NatureTouchGrow",familyFlags=2}
t[14260] = {name="Raptor Strike",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=15000,manaCost=25,icon="Spell_Nature_NatureTouchGrow",familyFlags=2}
t[14261] = {name="Raptor Strike",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=230000,manaCost=35,icon="Spell_Nature_NatureTouchGrow",familyFlags=2}
t[14262] = {name="Raptor Strike",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=160000,manaCost=45,icon="Spell_Nature_NatureTouchGrow",familyFlags=2}
t[14263] = {name="Raptor Strike",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=6000,manaCost=55,icon="Spell_Nature_NatureTouchGrow",familyFlags=2}
t[14264] = {name="Raptor Strike",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=1200000,manaCost=70,icon="Spell_Nature_NatureTouchGrow",familyFlags=2}
t[14265] = {name="Raptor Strike",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=85,icon="Spell_Nature_NatureTouchGrow",familyFlags=2}
t[14266] = {name="Raptor Strike",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=100,icon="Spell_Nature_NatureTouchGrow",familyFlags=2}
t[23989] = {name="Readiness",cast=0,cd=300000,categoryCD=300000,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51570] = {name="Resourcefulness",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51571] = {name="Resourcefulness",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51572] = {name="Resourcefulness",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51573] = {name="Resourcefulness",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51574] = {name="Resourcefulness",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[982] = {name="Revive Pet",cast=10000,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=16777216}
t[23499] = {name="Revive Pet",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=16777216}
t[36535] = {name="Roar of Fortitude",cast=0,cd=120000,categoryCD=120000,recoveryCD=0,duration=1800000,manaCost=50,icon="",familyFlags=0}
t[36536] = {name="Savage Rend",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=10000,manaCost=35,icon="INV_Gauntlets_06",familyFlags=0}
t[36537] = {name="Savage Rend",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=30000,manaCost=35,icon="INV_Gauntlets_06",familyFlags=0}
t[36538] = {name="Savage Rend",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=160000,manaCost=35,icon="INV_Gauntlets_06",familyFlags=0}
t[36539] = {name="Savage Rend",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=1000,manaCost=35,icon="INV_Gauntlets_06",familyFlags=0}
t[36540] = {name="Savage Rend",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=0,manaCost=35,icon="INV_Gauntlets_06",familyFlags=0}
t[36541] = {name="Savage Rend",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=0,manaCost=35,icon="INV_Gauntlets_06",familyFlags=0}
t[46282] = {name="Savage Rend",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=25,icon="",familyFlags=0}
t[46283] = {name="Savage Rend",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=25,icon="",familyFlags=0}
t[46284] = {name="Savage Rend",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=25,icon="",familyFlags=0}
t[46285] = {name="Savage Rend",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=25,icon="",familyFlags=0}
t[46286] = {name="Savage Rend",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=25,icon="",familyFlags=0}
t[19159] = {name="Savage Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19160] = {name="Savage Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19503] = {name="Scatter Shot",cast=0,cd=30000,categoryCD=30000,recoveryCD=0,duration=1800000,manaCost=0,icon="INV_Flask_05",familyFlags=262144}
t[23601] = {name="Scatter Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1800000,manaCost=0,icon="INV_Flask_05",familyFlags=262144}
t[24583] = {name="Scorpid Poison",cast=0,cd=3000,categoryCD=0,recoveryCD=3000,duration=160000,manaCost=25,icon="Spell_Nature_LightningShield",familyFlags=0}
t[24586] = {name="Scorpid Poison",cast=0,cd=3000,categoryCD=0,recoveryCD=3000,duration=1200000,manaCost=25,icon="Spell_Nature_LightningShield",familyFlags=0}
t[24587] = {name="Scorpid Poison",cast=0,cd=3000,categoryCD=0,recoveryCD=3000,duration=0,manaCost=25,icon="Spell_Nature_LightningShield",familyFlags=0}
t[24640] = {name="Scorpid Poison",cast=0,cd=3000,categoryCD=0,recoveryCD=3000,duration=15000,manaCost=25,icon="Spell_Nature_LightningShield",familyFlags=0}
t[45659] = {name="Scorpid Stike",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=165,icon="",familyFlags=32768}
t[3043] = {name="Scorpid Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=45000,manaCost=70,icon="",familyFlags=32768}
t[51509] = {name="Scorpid Sting Damage Reduction",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=45000,manaCost=0,icon="",familyFlags=0}
t[24423] = {name="Screech",cast=0,cd=5000,categoryCD=5000,recoveryCD=0,duration=15000,manaCost=20,icon="",familyFlags=0}
t[24577] = {name="Screech",cast=0,cd=5000,categoryCD=5000,recoveryCD=0,duration=160000,manaCost=20,icon="",familyFlags=0}
t[24578] = {name="Screech",cast=0,cd=5000,categoryCD=5000,recoveryCD=0,duration=0,manaCost=20,icon="",familyFlags=0}
t[24579] = {name="Screech",cast=0,cd=5000,categoryCD=5000,recoveryCD=0,duration=0,manaCost=20,icon="",familyFlags=0}
t[1978] = {name="Serpent Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=120000,manaCost=15,icon="",familyFlags=16384}
t[13549] = {name="Serpent Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=30,icon="",familyFlags=16384}
t[13550] = {name="Serpent Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=20000,manaCost=50,icon="",familyFlags=16384}
t[13551] = {name="Serpent Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=240000,manaCost=80,icon="",familyFlags=16384}
t[13552] = {name="Serpent Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=115,icon="",familyFlags=16384}
t[13553] = {name="Serpent Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=3600000,manaCost=150,icon="",familyFlags=16384}
t[13554] = {name="Serpent Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=190,icon="",familyFlags=16384}
t[13555] = {name="Serpent Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=230,icon="",familyFlags=16384}
t[25295] = {name="Serpent Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=250,icon="",familyFlags=16384}
t[45661] = {name="Serpent Strike",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=250,icon="",familyFlags=16384}
t[29218] = {name="Server Spell",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[26064] = {name="Shell Shield",cast=0,cd=60000,categoryCD=60000,recoveryCD=0,duration=60000,manaCost=10,icon="",familyFlags=0}
t[6660] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[15547] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[15620] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[16100] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[16496] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[17353] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[18561] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[20463] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[22121] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[22411] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[22907] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[23073] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[23337] = {name="Shoot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[2480] = {name="Shoot Bow",cast=1000,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=0,icon="",familyFlags=1}
t[7919] = {name="Shoot Crossbow",cast=1000,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=0,icon="",familyFlags=1}
t[7918] = {name="Shoot Gun",cast=1000,cd=0,categoryCD=0,recoveryCD=0,duration=10000,manaCost=0,icon="",familyFlags=1}
t[25200] = {name="Shoot Silithus Rifleman Long Range",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[26282] = {name="Shoot Tauren Rifleman",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[28318] = {name="Slime Shot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[19501] = {name="Spell - Reuse 19501",cast=0,cd=30000,categoryCD=30000,recoveryCD=0,duration=1800000,manaCost=0,icon="",familyFlags=262144}
t[27919] = {name="Spit",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=1}
t[28758] = {name="Stalker's Ally",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[3035] = {name="Steady Shot",cast=1000,cd=0,categoryCD=0,recoveryCD=0,duration=120000,manaCost=0,icon="",familyFlags=0}
t[3036] = {name="Steady Shot",cast=1000,cd=0,categoryCD=0,recoveryCD=0,duration=120000,manaCost=0,icon="",familyFlags=0}
t[3037] = {name="Steady Shot",cast=1000,cd=0,categoryCD=0,recoveryCD=0,duration=45000,manaCost=0,icon="",familyFlags=0}
t[3038] = {name="Steady Shot",cast=1000,cd=0,categoryCD=0,recoveryCD=0,duration=1800000,manaCost=0,icon="",familyFlags=0}
t[3668] = {name="Steady Shot",cast=1000,cd=0,categoryCD=0,recoveryCD=0,duration=11000,manaCost=0,icon="",familyFlags=0}
t[45970] = {name="Steady Shot",cast=1000,cd=0,categoryCD=0,recoveryCD=0,duration=11000,manaCost=0,icon="",familyFlags=0}
t[45972] = {name="Steady Shot",cast=1000,cd=0,categoryCD=0,recoveryCD=0,duration=11000,manaCost=0,icon="",familyFlags=0}
t[45974] = {name="Steady Shot",cast=1000,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[52684] = {name="Steady Shot and Raptor Strike Crit Bonus",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51579] = {name="Stinging Nettle",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51580] = {name="Stinging Nettle",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[36531] = {name="Strider Presence",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=50,icon="",familyFlags=0}
t[49557] = {name="Strike Together",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[49558] = {name="Strike Together",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[26173] = {name="Striker's Arcane Shot Bonus",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[26174] = {name="Striker's Rapid Fire Bonus",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24416] = {name="Superior Aspects",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24269] = {name="Superior Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24270] = {name="Superior Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24271] = {name="Superior Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24272] = {name="Superior Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24273] = {name="Superior Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19290] = {name="Surefooted",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19294] = {name="Surefooted",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24283] = {name="Surefooted",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24286] = {name="Surefooted",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[24287] = {name="Surefooted",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19255] = {name="Survivalist",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19256] = {name="Survivalist",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19257] = {name="Survivalist",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19258] = {name="Survivalist",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19259] = {name="Survivalist",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19552] = {name="Swift Aspects",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19553] = {name="Swift Aspects",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19554] = {name="Swift Aspects",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19555] = {name="Swift Aspects",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19556] = {name="Swift Aspects",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19454] = {name="Swiftshot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19455] = {name="Swiftshot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19456] = {name="Swiftshot",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[46287] = {name="Swipe",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=10000,manaCost=20,icon="Spell_Frost_ChillingBolt",familyFlags=0}
t[46288] = {name="Swipe",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=10000,manaCost=20,icon="Spell_Frost_ChillingBolt",familyFlags=0}
t[46289] = {name="Swipe",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=10000,manaCost=20,icon="Spell_Frost_ChillingBolt",familyFlags=0}
t[46290] = {name="Swipe",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=10000,manaCost=20,icon="Spell_Frost_ChillingBolt",familyFlags=0}
t[46291] = {name="Swipe",cast=0,cd=10000,categoryCD=10000,recoveryCD=0,duration=10000,manaCost=20,icon="Spell_Frost_ChillingBolt",familyFlags=0}
t[19678] = {name="Tame Adult Plainstrider",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[19683] = {name="Tame Armored Scorpid",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[44017] = {name="Tame Armored Scorpid",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[46705] = {name="Tame Ashfeather Swooper",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[46701] = {name="Tame Blackvenom Scorpid",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[50904] = {name="Tame Decrepit Darkhound",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[44007] = {name="Tame Decrepit Darkhound.",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[19681] = {name="Tame Dire Mottled Boar",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[44013] = {name="Tame Dire Mottled Boar.",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[46091] = {name="Tame Elder Crimson Lynx",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[50905] = {name="Tame Greater Dusk Bat",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[44009] = {name="Tame Greater Duskbat",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[19597] = {name="Tame Ice Claw Bear",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[44023] = {name="Tame Ice Claw Bear",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[46093] = {name="Tame Ivory Hawkstrider",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[19677] = {name="Tame Large Crag Boar",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[44019] = {name="Tame Large Crag Boar.",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[46095] = {name="Tame Lavender Mana Wyrm",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[46703] = {name="Tame Muckreef Crawler",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[19685] = {name="Tame Nightsaber Stalker",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[19679] = {name="Tame Prairie Stalker",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[44003] = {name="Tame Prowler",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[19676] = {name="Tame Snow Leopard",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[44021] = {name="Tame Snow Leopard",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[44001] = {name="Tame Stonetusk Boar",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[19686] = {name="Tame Strigid Screecher",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[19682] = {name="Tame Surf Crawler",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[44015] = {name="Tame Surf Crawler",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[19680] = {name="Tame Swoop",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[44011] = {name="Tame Tirisfal Plagued Bear",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[50906] = {name="Tame Tirisfal Plagued Bear",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[19684] = {name="Tame Webwood Lurker",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[44005] = {name="Tame Young Forest Bear",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2048}
t[8875] = {name="Tamed Pet Passive (DND)",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=33554432}
t[19580] = {name="Tamed Pet Passive (DND)",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=67108864}
t[19581] = {name="Tamed Pet Passive (DND)",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=134217728}
t[19582] = {name="Tamed Pet Passive (DND)",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=268435456}
t[19589] = {name="Tamed Pet Passive (DND)",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=536870912}
t[19591] = {name="Tamed Pet Passive (DND)",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=1073741824}
t[20782] = {name="Tamed Pet Passive (DND)",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[20784] = {name="Tamed Pet Passive (DND)",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=2147483648}
t[19609] = {name="Thick Hide",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19610] = {name="Thick Hide",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19612] = {name="Thick Hide",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[10277] = {name="Throw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=0}
t[15607] = {name="Throw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=0}
t[15795] = {name="Throw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=0}
t[16000] = {name="Throw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=0}
t[19785] = {name="Throw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=75000,manaCost=0,icon="",familyFlags=0}
t[22887] = {name="Throw",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=60000,manaCost=0,icon="",familyFlags=0}
t[26090] = {name="Thunderstomp",cast=0,cd=40000,categoryCD=40000,recoveryCD=0,duration=1800000,manaCost=60,icon="",familyFlags=0}
t[26187] = {name="Thunderstomp",cast=0,cd=40000,categoryCD=40000,recoveryCD=0,duration=1200000,manaCost=60,icon="",familyFlags=0}
t[26188] = {name="Thunderstomp",cast=0,cd=40000,categoryCD=40000,recoveryCD=0,duration=0,manaCost=60,icon="",familyFlags=0}
t[51156] = {name="Thunderstomp",cast=0,cd=40000,categoryCD=40000,recoveryCD=0,duration=0,manaCost=60,icon="",familyFlags=0}
t[19801] = {name="Tranquilizing Shot",cast=0,cd=20000,categoryCD=20000,recoveryCD=0,duration=0,manaCost=270,icon="Ability_Warrior_Disarm",familyFlags=65536}
t[19376] = {name="Trap Mastery",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19377] = {name="Trap Mastery",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51581] = {name="Trap Mastery",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19506] = {name="Trueshot Aura",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=0}
t[20905] = {name="Trueshot Aura",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[20906] = {name="Trueshot Aura",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19616] = {name="Unleashed Fury",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19617] = {name="Unleashed Fury",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19618] = {name="Unleashed Fury",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19619] = {name="Unleashed Fury",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[19620] = {name="Unleashed Fury",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[26176] = {name="Unseen Path Pet Bonus",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51586] = {name="Untamed Trapper",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51584] = {name="Vicious Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[51585] = {name="Vicious Strikes",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[3034] = {name="Viper Sting",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=1000,manaCost=135,icon="",familyFlags=65536}
t[14279] = {name="Viper Sting",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=175,icon="",familyFlags=65536}
t[14280] = {name="Viper Sting",cast=0,cd=6000,categoryCD=0,recoveryCD=6000,duration=0,manaCost=215,icon="",familyFlags=65536}
t[45660] = {name="Viper Strike",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=215,icon="",familyFlags=65536}
t[1510] = {name="Volley",cast=0,cd=60000,categoryCD=0,recoveryCD=60000,duration=1200000,manaCost=350,icon="",familyFlags=8192}
t[14294] = {name="Volley",cast=0,cd=60000,categoryCD=0,recoveryCD=60000,duration=0,manaCost=420,icon="",familyFlags=8192}
t[14295] = {name="Volley",cast=0,cd=60000,categoryCD=0,recoveryCD=60000,duration=0,manaCost=490,icon="",familyFlags=8192}
t[22908] = {name="Volley",cast=0,cd=60000,categoryCD=0,recoveryCD=60000,duration=0,manaCost=0,icon="",familyFlags=8192}
t[36533] = {name="Web",cast=0,cd=60000,categoryCD=0,recoveryCD=60000,duration=0,manaCost=50,icon="Spell_Holy_Heal02",familyFlags=0}
t[26226] = {name="Widow Bite",cast=0,cd=4000,categoryCD=0,recoveryCD=4000,duration=60000,manaCost=40,icon="Ability_Hunter_AimedShot",familyFlags=0}
t[2974] = {name="Wing Clip",cast=0,cd=3000,categoryCD=0,recoveryCD=3000,duration=30000,manaCost=40,icon="INV_Food_Egg_02",familyFlags=64}
t[14267] = {name="Wing Clip",cast=0,cd=3000,categoryCD=0,recoveryCD=3000,duration=11000,manaCost=60,icon="INV_Food_Egg_02",familyFlags=64}
t[14268] = {name="Wing Clip",cast=0,cd=3000,categoryCD=0,recoveryCD=3000,duration=0,manaCost=80,icon="INV_Food_Egg_02",familyFlags=64}
t[27633] = {name="Wing Clip",cast=0,cd=3000,categoryCD=0,recoveryCD=3000,duration=0,manaCost=80,icon="INV_Food_Egg_02",familyFlags=64}
t[19386] = {name="Wyvern Sting",cast=0,cd=120000,categoryCD=0,recoveryCD=120000,duration=1200000,manaCost=115,icon="",familyFlags=65536}
t[24131] = {name="Wyvern Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=1200000,manaCost=0,icon="",familyFlags=65536}
t[24132] = {name="Wyvern Sting",cast=0,cd=120000,categoryCD=0,recoveryCD=120000,duration=0,manaCost=155,icon="",familyFlags=65536}
t[24133] = {name="Wyvern Sting",cast=0,cd=120000,categoryCD=0,recoveryCD=120000,duration=0,manaCost=205,icon="",familyFlags=65536}
t[24134] = {name="Wyvern Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=65536}
t[24135] = {name="Wyvern Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=65536}
t[24335] = {name="Wyvern Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=205,icon="",familyFlags=0}
t[24336] = {name="Wyvern Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=65536}
t[26180] = {name="Wyvern Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=0}
t[26233] = {name="Wyvern Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=65536}
t[26748] = {name="Wyvern Sting",cast=0,cd=0,categoryCD=0,recoveryCD=0,duration=0,manaCost=0,icon="",familyFlags=65536}
end
MTH_DS_HunterSpellsByName = {}
do
local t = MTH_DS_HunterSpellsByName
t["[Deprecated] Bestial Swiftness"] = MTH_DS_HunterSpellsDBC[19596]
t["[Deprecated] Bestial Wrath"] = MTH_DS_HunterSpellsDBC[19574]
t["[Deprecated] Hawk Eye"] = MTH_DS_HunterSpellsDBC[19500]
t["[Deprecated] Improved Arcane Shot"] = MTH_DS_HunterSpellsDBC[19457]
t["[Deprecated] Improved Aspect of the Monkey"] = MTH_DS_HunterSpellsDBC[24386]
t["[Deprecated] Improved Hunter's Mark"] = MTH_DS_HunterSpellsDBC[19424]
t["[Deprecated] Improved Scorpid Sting"] = MTH_DS_HunterSpellsDBC[19494]
t["[Deprecated] Improved Wing Clip"] = MTH_DS_HunterSpellsDBC[19234]
t["[Deprecated] Killer Instinct"] = MTH_DS_HunterSpellsDBC[19374]
t["[Deprecated] Pathfinding"] = MTH_DS_HunterSpellsDBC[19562]
t["[Deprecated] Scorpid Sting"] = MTH_DS_HunterSpellsDBC[14275]
t["[Deprecated] Thick Hide"] = MTH_DS_HunterSpellsDBC[19611]
t["[Deprecated] Trueshot Aura Percent Bonus"] = MTH_DS_HunterSpellsDBC[51520]
t["Adrenaline Rush"] = MTH_DS_HunterSpellsDBC[28752]
t["Aimed Shot"] = MTH_DS_HunterSpellsDBC[19434]
t["Arcane Infused"] = MTH_DS_HunterSpellsDBC[23721]
t["Arcane Shot"] = MTH_DS_HunterSpellsDBC[3044]
t["Arcane Shot Mana Reduction"] = MTH_DS_HunterSpellsDBC[23157]
t["Aspect of the Beast"] = MTH_DS_HunterSpellsDBC[13161]
t["Aspect of the Cheetah"] = MTH_DS_HunterSpellsDBC[5118]
t["Aspect of the Fox"] = MTH_DS_HunterSpellsDBC[45651]
t["Aspect of the Hawk"] = MTH_DS_HunterSpellsDBC[13165]
t["Aspect of the Monkey"] = MTH_DS_HunterSpellsDBC[13163]
t["Aspect of the Pack"] = MTH_DS_HunterSpellsDBC[13159]
t["Aspect of the Snake"] = MTH_DS_HunterSpellsDBC[45652]
t["Aspect of the Turtle"] = MTH_DS_HunterSpellsDBC[45649]
t["Aspect of the Wild"] = MTH_DS_HunterSpellsDBC[20043]
t["Aspect of the Wolf"] = MTH_DS_HunterSpellsDBC[45650]
t["Auto Shot"] = MTH_DS_HunterSpellsDBC[75]
t["Avoidance"] = MTH_DS_HunterSpellsDBC[46023]
t["Aynasha's Bow"] = MTH_DS_HunterSpellsDBC[19767]
t["Baited Shot"] = MTH_DS_HunterSpellsDBC[46321]
t["Barrage"] = MTH_DS_HunterSpellsDBC[19461]
t["Battle Standard"] = MTH_DS_HunterSpellsDBC[22996]
t["Beast Mastery"] = MTH_DS_HunterSpellsDBC[47355]
t["Beast of the Unseen Path"] = MTH_DS_HunterSpellsDBC[29233]
t["Bestial Discipline"] = MTH_DS_HunterSpellsDBC[19590]
t["Bestial Fury"] = MTH_DS_HunterSpellsDBC[19603]
t["Bestial Precision"] = MTH_DS_HunterSpellsDBC[51554]
t["Bestial Wrath"] = MTH_DS_HunterSpellsDBC[24395]
t["Bite"] = MTH_DS_HunterSpellsDBC[17253]
t["Bubble Barrier"] = MTH_DS_HunterSpellsDBC[36523]
t["Carve"] = MTH_DS_HunterSpellsDBC[51575]
t["Cheetah Sprint"] = MTH_DS_HunterSpellsDBC[24348]
t["Claw"] = MTH_DS_HunterSpellsDBC[3009]
t["Clever Traps"] = MTH_DS_HunterSpellsDBC[19239]
t["Command"] = MTH_DS_HunterSpellsDBC[20576]
t["Concussive Shot"] = MTH_DS_HunterSpellsDBC[5116]
t["Concussive Shot Cooldown Reduction"] = MTH_DS_HunterSpellsDBC[23158]
t["Coordinated Assault"] = MTH_DS_HunterSpellsDBC[49559]
t["Copy of Shoot"] = MTH_DS_HunterSpellsDBC[28317]
t["Counterattack"] = MTH_DS_HunterSpellsDBC[19306]
t["Death Roll"] = MTH_DS_HunterSpellsDBC[36548]
t["Demonic Transformation"] = MTH_DS_HunterSpellsDBC[24992]
t["Detect Weakness"] = MTH_DS_HunterSpellsDBC[23578]
t["Deterrence"] = MTH_DS_HunterSpellsDBC[19263]
t["Disengage"] = MTH_DS_HunterSpellsDBC[781]
t["Distracting Shot"] = MTH_DS_HunterSpellsDBC[14274]
t["Efficiency"] = MTH_DS_HunterSpellsDBC[19416]
t["Elusiveness"] = MTH_DS_HunterSpellsDBC[51552]
t["Endless Quiver"] = MTH_DS_HunterSpellsDBC[51517]
t["Endurance Training"] = MTH_DS_HunterSpellsDBC[19583]
t["Entrapment"] = MTH_DS_HunterSpellsDBC[19184]
t["Explosive Trap"] = MTH_DS_HunterSpellsDBC[13813]
t["Explosive Trap Effect"] = MTH_DS_HunterSpellsDBC[13812]
t["Extra Shot"] = MTH_DS_HunterSpellsDBC[51519]
t["Eyes of the Beast"] = MTH_DS_HunterSpellsDBC[1002]
t["Fang of the Crystal Spider"] = MTH_DS_HunterSpellsDBC[17331]
t["Faster Bleeds 5 (Hunter)"] = MTH_DS_HunterSpellsDBC[52402]
t["Feign Death"] = MTH_DS_HunterSpellsDBC[5384]
t["Ferocity"] = MTH_DS_HunterSpellsDBC[19598]
t["Fire Breath"] = MTH_DS_HunterSpellsDBC[46269]
t["Flame Spear"] = MTH_DS_HunterSpellsDBC[19781]
t["Flare"] = MTH_DS_HunterSpellsDBC[1543]
t["Freezing Trap"] = MTH_DS_HunterSpellsDBC[1499]
t["Freezing Trap (TEST)"] = MTH_DS_HunterSpellsDBC[21750]
t["Freezing Trap Effect"] = MTH_DS_HunterSpellsDBC[3355]
t["Frenzy"] = MTH_DS_HunterSpellsDBC[19621]
t["Frost Trap"] = MTH_DS_HunterSpellsDBC[13809]
t["Frost Trap Aura"] = MTH_DS_HunterSpellsDBC[13810]
t["Frost Trap Effect"] = MTH_DS_HunterSpellsDBC[14207]
t["Furious Howl"] = MTH_DS_HunterSpellsDBC[24597]
t["Grace"] = MTH_DS_HunterSpellsDBC[46296]
t["Haste"] = MTH_DS_HunterSpellsDBC[14824]
t["Hawk Eye"] = MTH_DS_HunterSpellsDBC[19498]
t["Hunter Giant Stalker"] = MTH_DS_HunterSpellsDBC[21942]
t["Hunter's Mark"] = MTH_DS_HunterSpellsDBC[1130]
t["Immolation Trap"] = MTH_DS_HunterSpellsDBC[13795]
t["Immolation Trap Effect"] = MTH_DS_HunterSpellsDBC[13797]
t["Impale"] = MTH_DS_HunterSpellsDBC[15860]
t["Improved Aspect of the Hawk"] = MTH_DS_HunterSpellsDBC[23559]
t["Improved Concussive Shot"] = MTH_DS_HunterSpellsDBC[19407]
t["Improved Explosive Trap"] = MTH_DS_HunterSpellsDBC[19378]
t["Improved Eyes of the Beast"] = MTH_DS_HunterSpellsDBC[19557]
t["Improved Feign Death"] = MTH_DS_HunterSpellsDBC[19286]
t["Improved Freezing Trap"] = MTH_DS_HunterSpellsDBC[19288]
t["Improved Hunter's Mark"] = MTH_DS_HunterSpellsDBC[19421]
t["Improved Immolation Trap"] = MTH_DS_HunterSpellsDBC[19246]
t["Improved Mend Pet"] = MTH_DS_HunterSpellsDBC[19572]
t["Improved Primal Aspects"] = MTH_DS_HunterSpellsDBC[19549]
t["Improved Revive Pet"] = MTH_DS_HunterSpellsDBC[19575]
t["Improved Scorpid Sting"] = MTH_DS_HunterSpellsDBC[19491]
t["Improved Serpent Sting"] = MTH_DS_HunterSpellsDBC[24467]
t["Improved Steady Shot"] = MTH_DS_HunterSpellsDBC[51515]
t["Improved Stings"] = MTH_DS_HunterSpellsDBC[19464]
t["Improved Swift Aspects"] = MTH_DS_HunterSpellsDBC[52603]
t["Improved Volley and Multishot"] = MTH_DS_HunterSpellsDBC[23566]
t["Improved Wing Clip"] = MTH_DS_HunterSpellsDBC[19228]
t["Increased Hunter Pet Damage"] = MTH_DS_HunterSpellsDBC[22854]
t["Increased Pet Damage +3%"] = MTH_DS_HunterSpellsDBC[27205]
t["Increased Pet Damage +4%"] = MTH_DS_HunterSpellsDBC[52413]
t["Kill Command"] = MTH_DS_HunterSpellsDBC[41827]
t["Killer Instinct"] = MTH_DS_HunterSpellsDBC[19162]
t["Lacerate"] = MTH_DS_HunterSpellsDBC[20907]
t["Lightning Breath"] = MTH_DS_HunterSpellsDBC[24844]
t["Lightning Reflexes"] = MTH_DS_HunterSpellsDBC[19168]
t["Lizard Bolt"] = MTH_DS_HunterSpellsDBC[46297]
t["Mend Pet"] = MTH_DS_HunterSpellsDBC[136]
t["Mongoose Bite"] = MTH_DS_HunterSpellsDBC[1495]
t["Mortal Shots"] = MTH_DS_HunterSpellsDBC[19485]
t["Multi-Shot"] = MTH_DS_HunterSpellsDBC[2643]
t["Multi-Shot Damage Increase"] = MTH_DS_HunterSpellsDBC[28539]
t["Multishot and Carve Cooldown"] = MTH_DS_HunterSpellsDBC[52602]
t["Not There"] = MTH_DS_HunterSpellsDBC[28761]
t["Packleader"] = MTH_DS_HunterSpellsDBC[36532]
t["Pathfinding"] = MTH_DS_HunterSpellsDBC[19559]
t["Pet Focus Regen Bonus"] = MTH_DS_HunterSpellsDBC[52685]
t["Piercing Shots"] = MTH_DS_HunterSpellsDBC[51512]
t["Pin"] = MTH_DS_HunterSpellsDBC[46292]
t["Planning Ahead"] = MTH_DS_HunterSpellsDBC[51576]
t["Planning Ahead Dummy"] = MTH_DS_HunterSpellsDBC[51578]
t["Poison Spit"] = MTH_DS_HunterSpellsDBC[46271]
t["Prowl"] = MTH_DS_HunterSpellsDBC[24450]
t["Quick Shots"] = MTH_DS_HunterSpellsDBC[51547]
t["Quick Strikes"] = MTH_DS_HunterSpellsDBC[51542]
t["Ranged Weapon Specialization"] = MTH_DS_HunterSpellsDBC[19511]
t["Rapid Fire"] = MTH_DS_HunterSpellsDBC[3045]
t["Raptor Strike"] = MTH_DS_HunterSpellsDBC[2973]
t["Readiness"] = MTH_DS_HunterSpellsDBC[23989]
t["Resourcefulness"] = MTH_DS_HunterSpellsDBC[51570]
t["Revive Pet"] = MTH_DS_HunterSpellsDBC[982]
t["Roar of Fortitude"] = MTH_DS_HunterSpellsDBC[36535]
t["Savage Rend"] = MTH_DS_HunterSpellsDBC[36536]
t["Savage Strikes"] = MTH_DS_HunterSpellsDBC[19159]
t["Scatter Shot"] = MTH_DS_HunterSpellsDBC[19503]
t["Scorpid Poison"] = MTH_DS_HunterSpellsDBC[24583]
t["Scorpid Stike"] = MTH_DS_HunterSpellsDBC[45659]
t["Scorpid Sting"] = MTH_DS_HunterSpellsDBC[3043]
t["Scorpid Sting Damage Reduction"] = MTH_DS_HunterSpellsDBC[51509]
t["Screech"] = MTH_DS_HunterSpellsDBC[24423]
t["Serpent Sting"] = MTH_DS_HunterSpellsDBC[1978]
t["Serpent Strike"] = MTH_DS_HunterSpellsDBC[45661]
t["Server Spell"] = MTH_DS_HunterSpellsDBC[29218]
t["Shell Shield"] = MTH_DS_HunterSpellsDBC[26064]
t["Shoot"] = MTH_DS_HunterSpellsDBC[6660]
t["Shoot Bow"] = MTH_DS_HunterSpellsDBC[2480]
t["Shoot Crossbow"] = MTH_DS_HunterSpellsDBC[7919]
t["Shoot Gun"] = MTH_DS_HunterSpellsDBC[7918]
t["Shoot Silithus Rifleman Long Range"] = MTH_DS_HunterSpellsDBC[25200]
t["Shoot Tauren Rifleman"] = MTH_DS_HunterSpellsDBC[26282]
t["Slime Shot"] = MTH_DS_HunterSpellsDBC[28318]
t["Spell - Reuse 19501"] = MTH_DS_HunterSpellsDBC[19501]
t["Spit"] = MTH_DS_HunterSpellsDBC[27919]
t["Stalker's Ally"] = MTH_DS_HunterSpellsDBC[28758]
t["Steady Shot"] = MTH_DS_HunterSpellsDBC[3035]
t["Steady Shot and Raptor Strike Crit Bonus"] = MTH_DS_HunterSpellsDBC[52684]
t["Stinging Nettle"] = MTH_DS_HunterSpellsDBC[51579]
t["Strider Presence"] = MTH_DS_HunterSpellsDBC[36531]
t["Strike Together"] = MTH_DS_HunterSpellsDBC[49557]
t["Striker's Arcane Shot Bonus"] = MTH_DS_HunterSpellsDBC[26173]
t["Striker's Rapid Fire Bonus"] = MTH_DS_HunterSpellsDBC[26174]
t["Superior Aspects"] = MTH_DS_HunterSpellsDBC[24416]
t["Superior Strikes"] = MTH_DS_HunterSpellsDBC[24269]
t["Surefooted"] = MTH_DS_HunterSpellsDBC[19290]
t["Survivalist"] = MTH_DS_HunterSpellsDBC[19255]
t["Swift Aspects"] = MTH_DS_HunterSpellsDBC[19552]
t["Swiftshot"] = MTH_DS_HunterSpellsDBC[19454]
t["Swipe"] = MTH_DS_HunterSpellsDBC[46287]
t["Tame Adult Plainstrider"] = MTH_DS_HunterSpellsDBC[19678]
t["Tame Armored Scorpid"] = MTH_DS_HunterSpellsDBC[19683]
t["Tame Ashfeather Swooper"] = MTH_DS_HunterSpellsDBC[46705]
t["Tame Blackvenom Scorpid"] = MTH_DS_HunterSpellsDBC[46701]
t["Tame Decrepit Darkhound"] = MTH_DS_HunterSpellsDBC[50904]
t["Tame Decrepit Darkhound."] = MTH_DS_HunterSpellsDBC[44007]
t["Tame Dire Mottled Boar"] = MTH_DS_HunterSpellsDBC[19681]
t["Tame Dire Mottled Boar."] = MTH_DS_HunterSpellsDBC[44013]
t["Tame Elder Crimson Lynx"] = MTH_DS_HunterSpellsDBC[46091]
t["Tame Greater Dusk Bat"] = MTH_DS_HunterSpellsDBC[50905]
t["Tame Greater Duskbat"] = MTH_DS_HunterSpellsDBC[44009]
t["Tame Ice Claw Bear"] = MTH_DS_HunterSpellsDBC[19597]
t["Tame Ivory Hawkstrider"] = MTH_DS_HunterSpellsDBC[46093]
t["Tame Large Crag Boar"] = MTH_DS_HunterSpellsDBC[19677]
t["Tame Large Crag Boar."] = MTH_DS_HunterSpellsDBC[44019]
t["Tame Lavender Mana Wyrm"] = MTH_DS_HunterSpellsDBC[46095]
t["Tame Muckreef Crawler"] = MTH_DS_HunterSpellsDBC[46703]
t["Tame Nightsaber Stalker"] = MTH_DS_HunterSpellsDBC[19685]
t["Tame Prairie Stalker"] = MTH_DS_HunterSpellsDBC[19679]
t["Tame Prowler"] = MTH_DS_HunterSpellsDBC[44003]
t["Tame Snow Leopard"] = MTH_DS_HunterSpellsDBC[19676]
t["Tame Stonetusk Boar"] = MTH_DS_HunterSpellsDBC[44001]
t["Tame Strigid Screecher"] = MTH_DS_HunterSpellsDBC[19686]
t["Tame Surf Crawler"] = MTH_DS_HunterSpellsDBC[19682]
t["Tame Swoop"] = MTH_DS_HunterSpellsDBC[19680]
t["Tame Tirisfal Plagued Bear"] = MTH_DS_HunterSpellsDBC[44011]
t["Tame Webwood Lurker"] = MTH_DS_HunterSpellsDBC[19684]
t["Tame Young Forest Bear"] = MTH_DS_HunterSpellsDBC[44005]
t["Tamed Pet Passive (DND)"] = MTH_DS_HunterSpellsDBC[8875]
t["Thick Hide"] = MTH_DS_HunterSpellsDBC[19609]
t["Throw"] = MTH_DS_HunterSpellsDBC[10277]
t["Thunderstomp"] = MTH_DS_HunterSpellsDBC[26090]
t["Tranquilizing Shot"] = MTH_DS_HunterSpellsDBC[19801]
t["Trap Mastery"] = MTH_DS_HunterSpellsDBC[19376]
t["Trueshot Aura"] = MTH_DS_HunterSpellsDBC[19506]
t["Unleashed Fury"] = MTH_DS_HunterSpellsDBC[19616]
t["Unseen Path Pet Bonus"] = MTH_DS_HunterSpellsDBC[26176]
t["Untamed Trapper"] = MTH_DS_HunterSpellsDBC[51586]
t["Vicious Strikes"] = MTH_DS_HunterSpellsDBC[51584]
t["Viper Sting"] = MTH_DS_HunterSpellsDBC[3034]
t["Viper Strike"] = MTH_DS_HunterSpellsDBC[45660]
t["Volley"] = MTH_DS_HunterSpellsDBC[1510]
t["Web"] = MTH_DS_HunterSpellsDBC[36533]
t["Widow Bite"] = MTH_DS_HunterSpellsDBC[26226]
t["Wing Clip"] = MTH_DS_HunterSpellsDBC[2974]
t["Wyvern Sting"] = MTH_DS_HunterSpellsDBC[19386]
end
+224
View File
@@ -0,0 +1,224 @@
-- ds-hunter-spells.lua
-- Hunter spell data for TWoW 1.18.1 "Nightmares of Ursol"
-- Used by: ExpAmmo cycle tracker, HunterBook (future spell reference tab)
--
-- TODO markers:
-- SPELL_ID : exact spell ID not yet extracted from Spell.dbc (needs MPQ)
-- ICON : icon texture not yet confirmed from game files
-- VERIFY : value observed in patch notes but subject to change on PTR/live
-- PENDING : mechanic not yet verified against live server
if not MTH_DS then MTH_DS = {} end
-- ============================================================
-- BASELINE HUNTER SPELLS (available to all specs from 1.18.1)
-- ============================================================
MTH_DS_HunterSpells = {
["baseline"] = {
{
["name"] = "Steady Shot",
["addedIn"] = "1.18.1",
["note"] = "Moved from Marksmanship talent tree to baseline at level 20.",
["castTime"] = "1.5s", -- TODO VERIFY
["cooldown"] = "n/a",
["school"] = "Physical",
["icon"] = "Ability_Hunter_SteadyShot", -- TODO ICON confirm
},
{
["name"] = "Trueshot Aura",
["addedIn"] = "1.18.1",
["note"] = "Moved from Marksmanship capstone to baseline. All hunters now get this.",
["castTime"] = "Instant",
["cooldown"] = "n/a",
["school"] = "Physical",
["icon"] = "Ability_Trueshot", -- TODO ICON confirm
},
{
["name"] = "Aspect of the Viper",
["addedIn"] = "1.18.1",
["note"] = "New baseline aspect learned at level 56. Regenerates mana while dealing reduced damage.",
["castTime"] = "Instant",
["cooldown"] = "n/a",
["school"] = "Nature",
["icon"] = "Spell_Nature_Poison", -- TODO ICON confirm
},
{
["name"] = "Volley",
["addedIn"] = "1.18.1",
["note"] = "Changed from channelled to a 3-second cast AoE. Now a proper cast-bar spell.",
["castTime"] = "3s", -- TODO VERIFY
["cooldown"] = "n/a",
["school"] = "Physical",
["icon"] = "Ability_Whirlwind", -- TODO ICON confirm
},
},
-- ============================================================
-- MARKSMANSHIP TALENTS (1.18.1 changes)
-- ============================================================
["marksmanship"] = {
{
["name"] = "Aimed Shot",
["addedIn"] = "1.18.1",
["note"] = "Now has a visible 2s cast bar. Triggers ExpAmmo cycle start (resets to FIRE state).",
["castTime"] = "2s",
["cooldown"] = "26s", -- TODO VERIFY vs Spell.dbc
["school"] = "Physical",
["icon"] = "Inv_Spear_07", -- TODO ICON confirm
["expAmmoTrigger"] = true, -- resets ExpAmmo cycle to state 1 (FIRE)
},
{
["name"] = "Lock and Load",
["addedIn"] = "1.18.1",
["talentRow"] = 7, -- MM capstone row
["note"] = "10s proc buff. Triggered by Aimed Shot crit or trap activation (TODO VERIFY exact condition).",
["castTime"] = "Instant",
["cooldown"] = "n/a",
["duration"] = "10s", -- TODO VERIFY
["school"] = "Physical",
["type"] = "buff_proc",
["icon"] = "Ability_Hunter_LockAndLoad", -- TODO ICON confirm
},
{
["name"] = "Rapid Fire",
["note"] = "15% haste, 15s duration, 5min CD. Unchanged in 1.18.1 baseline values.",
["castTime"] = "Instant",
["cooldown"] = "5min",
["duration"] = "15s",
["school"] = "Physical",
["type"] = "buff",
["icon"] = "Ability_Hunter_RapidKilling",
},
},
-- ============================================================
-- BEAST MASTERY TALENTS (1.18.1 changes)
-- ============================================================
["beastmastery"] = {
{
["name"] = "Bestial Wrath",
["note"] = "Returned to BM tree. 18s buff, 90s CD (1.5min). Pet immune to fear/CC during.",
["castTime"] = "Instant",
["cooldown"] = "90s",
["duration"] = "18s", -- TODO VERIFY
["school"] = "Physical",
["type"] = "buff",
["icon"] = "Ability_Druid_Ferociousbite", -- TODO ICON confirm
},
{
["name"] = "Kill Command",
["addedIn"] = "1.18.1",
["talentRow"] = 7, -- BM capstone row
["note"] = "8s CD, triggered on crit. Commands pet to inflict bonus damage on next hit.",
["castTime"] = "Instant",
["cooldown"] = "8s",
["school"] = "Physical",
["icon"] = "Ability_Hunter_KillCommand", -- TODO ICON confirm
},
{
["name"] = "Scent of Blood",
["addedIn"] = "1.18.1",
["talentRow"] = 5,
["note"] = "8s pet rage proc buff. Triggers when Bestial Wrath is active (TODO VERIFY exact condition).",
["castTime"] = "Instant (proc)",
["cooldown"] = "n/a",
["duration"] = "8s", -- TODO VERIFY
["school"] = "Physical",
["type"] = "buff_proc",
["icon"] = "Ability_Warrior_BloodFrenzy", -- TODO ICON confirm
},
{
["name"] = "Baited Shot",
["removedIn"] = "1.18.1",
["note"] = "REMOVED. Replaced by Kill Command as the BM capstone. Any saved data for this spell can be cleared.",
["deprecated"] = true,
},
},
-- ============================================================
-- SURVIVAL TALENTS (1.18.1 changes)
-- ============================================================
["survival"] = {
{
["name"] = "Alone Against the World",
["addedIn"] = "1.18.1",
["talentRow"] = 2,
["note"] = "New 2-point talent. Bonus to pet/self when no group members nearby (TODO VERIFY exact bonus values).",
["type"] = "passive",
["icon"] = "Ability_Hunter_AloneAgainstTheWorld", -- TODO ICON confirm
},
{
["name"] = "Lacerate",
["addedIn"] = "1.18.1",
["note"] = "40% Attack Power bleed, +15% from all bleed sides. (TODO VERIFY: rank values from Spell.dbc).",
["castTime"] = "Instant",
["school"] = "Physical",
["type"] = "dot",
["icon"] = "Ability_Hunter_Lacerate", -- TODO ICON confirm
},
},
-- ============================================================
-- EXPERIMENTAL AMMUNITION (new 1.18.1 mechanic)
-- States cycle: FIRE -> ARCANE -> NATURE -> (loop)
-- Aimed Shot resets cycle to FIRE each time it is fired.
-- ============================================================
["expAmmoStates"] = {
{
["state"] = 1,
["label"] = "Fire Ammo",
["school"] = "Fire",
["duration"] = "60s", -- TODO VERIFY from server
["consumeSpell"] = "Multi-Shot", -- TODO VERIFY spell that consumes this state
["icon"] = "Spell_Fire_FlameBolt", -- TODO ICON confirm
["buffName"] = nil, -- TODO: exact buff name from Spell.dbc
},
{
["state"] = 2,
["label"] = "Arcane Ammo",
["school"] = "Arcane",
["duration"] = "60s", -- TODO VERIFY
["consumeSpell"] = "Arcane Shot", -- TODO VERIFY
["icon"] = "Spell_Arcane_ArcaneMissiles", -- TODO ICON confirm
["buffName"] = nil, -- TODO: exact buff name from Spell.dbc
},
{
["state"] = 3,
["label"] = "Nature Ammo",
["school"] = "Nature",
["duration"] = "60s", -- TODO VERIFY
["consumeSpell"] = "Serpent Sting", -- TODO VERIFY
["icon"] = "Spell_Nature_NatureBolt", -- TODO ICON confirm
["buffName"] = nil, -- TODO: exact buff name from Spell.dbc
},
},
}
+5
View File
@@ -137,6 +137,11 @@ local MTH_DS_AtlasSources = {
[9426] = {["boss"] = true, },
[9456] = {["boss"] = true, },
[9487] = {["boss"] = true, },
[42198] = {["crafted"] = true, },
[42199] = {["crafted"] = true, },
[42200] = {["crafted"] = true, },
[42201] = {["crafted"] = true, },
[42202] = {["crafted"] = true, },
}
for itemId, sourceMap in pairs(MTH_DS_AtlasSources) do
+26
View File
@@ -6308,6 +6308,13 @@ MTH_DS_ItemOrigins = {
["level"] = 25,
["classification"] = 0,
},
{
["id"] = 62906,
["name"] = "Elendon Truebough",
["tag"] = "Weapons",
["level"] = 44,
["classification"] = 0,
},
},
},
},
@@ -11066,6 +11073,12 @@ MTH_DS_ItemOrigins = {
["level"] = 40,
["classification"] = 0,
},
{
["id"] = 62843,
["name"] = "Ornala",
["level"] = 53,
["classification"] = 0,
},
},
},
},
@@ -11747,6 +11760,19 @@ MTH_DS_ItemOrigins = {
["level"] = 25,
["classification"] = 0,
},
{
["id"] = 62906,
["name"] = "Elendon Truebough",
["tag"] = "Weapons",
["level"] = 44,
["classification"] = 0,
},
{
["id"] = 62843,
["name"] = "Ornala",
["level"] = 53,
["classification"] = 0,
},
},
},
},
+45
View File
@@ -1121,6 +1121,51 @@ MTH_DS_AmmoItems = {
[13219] = 0,
},
},
[42198] = {
["name"] = "Bright Wood Arrows",
["level"] = 35,
["reqlevel"] = 30,
["icon"] = "INV_Ammo_Arrow_02",
["subtype"] = "arrow",
["dps"] = 10.5,
["crafted"] = true,
},
[42199] = {
["name"] = "Shade Wood Arrows",
["level"] = 45,
["reqlevel"] = 40,
["icon"] = "INV_Ammo_Arrow_02",
["subtype"] = "arrow",
["dps"] = 14.5,
["crafted"] = true,
},
[42200] = {
["name"] = "Smooth Ironfeather Arrows",
["level"] = 55,
["reqlevel"] = 50,
["icon"] = "INV_Ammo_Arrow_02",
["subtype"] = "arrow",
["dps"] = 17.5,
["crafted"] = true,
},
[42201] = {
["name"] = "Starfeather Arrows",
["level"] = 60,
["reqlevel"] = 55,
["icon"] = "INV_Ammo_Arrow_02",
["subtype"] = "arrow",
["dps"] = 19,
["crafted"] = true,
},
[42202] = {
["name"] = "Enchanted Thorium Shells",
["level"] = 60,
["reqlevel"] = 55,
["icon"] = "INV_Ammo_Bullet_03",
["subtype"] = "bullet",
["dps"] = 19,
["crafted"] = true,
},
}
MTH_DS_Items = MTH_DS_AmmoItems
+125 -117
View File
@@ -2,120 +2,128 @@ if not MTH_DS then MTH_DS = {} end
MTH_DS_MinimapSizes = {
[0] = { 0, 0 },
[1] = { 4925, 3283.34 },
[3] = { 2487.5, 1658.34 },
[4] = { 3350, 2233.3 },
[8] = { 2293.75, 1529.17 },
[10] = { 2700, 1800.03 },
[11] = { 4135.42, 2756.25 },
[12] = { 3470.84, 2314.62 },
[14] = { 5287.5, 3525 },
[15] = { 5250, 3500 },
[16] = { 5070.84, 3381.25 },
[17] = { 10133.34, 6756.25 },
[25] = { 711.56, 468.68 },
[28] = { 4300, 2866.67 },
[33] = { 6381.25, 4254.1 },
[36] = { 2800, 1866.67 },
[38] = { 2758.33, 1839.58 },
[40] = { 3500, 2333.3 },
[41] = { 2500, 1666.63 },
[44] = { 2170.84, 1447.9 },
[45] = { 3600, 2400 },
[46] = { 2929.16, 1952.08 },
[47] = { 3850, 2566.67 },
[51] = { 2231.25, 1487.5 },
[85] = { 4518.75, 3012.5 },
[130] = { 4200, 2800 },
[139] = { 3870.83, 2581.25 },
[141] = { 5091.66, 3393.7 },
[148] = { 6550, 4366.66 },
[209] = { 381, 254 },
[215] = { 5137.5, 3425 },
[267] = { 3200, 2133.33 },
[331] = { 5766.67, 3843.75 },
[357] = { 6950, 4633.33 },
[361] = { 5750, 3833.33 },
[400] = { 4400, 2933.33 },
[405] = { 4495.83, 2997.91 },
[406] = { 6018, 4010 },
[408] = { 3092.1, 2047 },
[409] = { 2901.45, 1915.9 },
[440] = { 6900, 4600 },
[490] = { 3700, 2466.66 },
[491] = { 736.45, 490.96 },
[493] = { 2308.33, 1539.59 },
[616] = { 3206, 2142 },
[618] = { 7100, 4733.33 },
[717] = { 378.15, 252.1 },
[718] = { 572.78, 381.85 },
[719] = { 1221.87, 806.42 },
[721] = { 769.67, 513.11 },
[722] = { 881, 589 },
[796] = { 203.66, 135.04 },
[876] = { 828, 541 },
[1176] = { 1383.33, 922.91 },
[1337] = { 893.67, 595.78 },
[1377] = { 3483.33, 2322.92 },
[1477] = { 695.03, 463.35 },
[1497] = { 959.37, 640.11 },
[1519] = { 1737.5, 1158.34 },
[1537] = { 790.63, 527.61 },
[1581] = { 449.89, 300 },
[1583] = { 886.84, 591.23 },
[1584] = { 1985, 1268 },
[1637] = { 1402.61, 935.42 },
[1638] = { 1043.75, 695.83 },
[1657] = { 1058.33, 705.71 },
[1941] = { 1348.24, 888.09 },
[1977] = { 2120.83, 1414.6 },
[2017] = { 1185.35, 789.86 },
[2040] = { 1468, 976 },
[2057] = { 320.05, 213.37 },
[2100] = { 2112.09, 1410.89 },
[2159] = { 483.11, 322.08 },
[2257] = { 312, 208 },
[2366] = { 1085.86, 726.61 },
[2437] = { 738.86, 492.57 },
[2557] = { 1324, 869 },
[2597] = { 4237.5, 2825 },
[2677] = { 499.43, 332.95 },
[2717] = { 1495, 983 },
[3277] = { 1145.84, 764.58 },
[3358] = { 1756.25, 1170.83 },
[3428] = { 977.56, 651.7 },
[3429] = { 2512.5, 1675 },
[3456] = { 1991.69, 1318.42 },
[3457] = { 823, 549 },
[3478] = { 4139.02, 2946 },
[4012] = { 3159, 2108 },
[5023] = { 988.63, 1393.17 },
[5024] = { 1608, 1075 },
[5053] = { 1007.7, 671.8 },
[5077] = { 2643.21, 1751.16 },
[5086] = { 546.75, 391.9 },
[5087] = { 354.5, 234.74 },
[5097] = { 1273.1, 853.72 },
[5103] = { 752.12, 510.33 },
[5121] = { 3227, 2187 },
[5130] = { 1432, 977 },
[5135] = { 320.19, 213.46 },
[5136] = { 619.98, 413.32 },
[5138] = { 656.59, 434.97 },
[5144] = { 309, 208 },
[5147] = { 2777.54, 1851.69 },
[5148] = { 652.1, 439.67 },
[5153] = { 612.69, 408.46 },
[5163] = { 703.3, 468.87 },
[5179] = { 3666, 2442 },
[5204] = { 1271.99, 845.48 },
[5208] = { 1250.18, 837.44 },
[5225] = { 3082, 2061 },
[5536] = { 2472, 1665 },
[5557] = { 2800, 1868 },
[5561] = { 3098, 2068 },
[5581] = { 3241, 2157 },
[5601] = { 1634, 1073 },
[5602] = { 5387, 3584 },
[5628] = { 1980, 1350 },
}
[9] = { 5137.50, 3425.00 }, -- Mulgore
[11] = { 2487.50, 1658.34 }, -- Barrens
[15] = { 2170.84, 1447.90 }, -- Alterac
[19] = { 5287.50, 3525.00 }, -- BlastedLands
[20] = { 4518.75, 3012.50 }, -- Tirisfal
[21] = { 4200.00, 2800.00 }, -- Silverpine
[22] = { 2231.25, 1487.50 }, -- WesternPlaguelands
[23] = { 3870.83, 2581.25 }, -- EasternPlaguelands
[24] = { 3200.00, 2133.33 }, -- Hilsbrad
[26] = { 3850.00, 2566.67 }, -- Hinterlands
[27] = { 4925.00, 3283.34 }, -- DunMorogh
[29] = { 2929.16, 1952.08 }, -- BurningSteppes
[30] = { 3470.84, 2314.62 }, -- Elwynn
[32] = { 5091.66, 3393.70 }, -- DeadwindPass
[34] = { 2700.00, 1800.03 }, -- Duskwood
[35] = { 2293.75, 1529.17 }, -- LochModan
[37] = { 6381.25, 4254.10 }, -- Stranglethorn
[39] = { 4135.42, 2756.25 }, -- Westfall
[40] = { 10133.34, 6756.25 }, -- Wetlands
[41] = { 5250.00, 3500.00 }, -- Teldrassil
[42] = { 6550.00, 4366.66 }, -- Darkshore
[43] = { 5766.67, 3843.75 }, -- Ashenvale
[61] = { 4400.00, 2933.33 }, -- ThousandNeedles
[81] = { 6018.00, 4010.00 }, -- StonetalonMountains
[101] = { 4495.83, 2997.91 }, -- Desolace
[121] = { 6950.00, 4633.33 }, -- Feralas
[141] = { 2800.00, 1866.67 }, -- Dustwallow
[161] = { 6900.00, 4600.00 }, -- Tanaris
[181] = { 3600.00, 2400.00 }, -- Azshara
[182] = { 5750.00, 3833.33 }, -- Felwood
[201] = { 3700.00, 2466.66 }, -- UngoroCrater
[241] = { 2308.33, 1539.59 }, -- Moonglade
[261] = { 3483.33, 2322.92 }, -- Silithus
[281] = { 7100.00, 4733.33 }, -- Winterspring
[301] = { 1737.50, 1158.34 }, -- Stormwind
[321] = { 1402.61, 935.42 }, -- Orgrimmar
[341] = { 790.63, 527.61 }, -- Ironforge
[362] = { 1043.75, 695.83 }, -- ThunderBluff
[381] = { 1058.33, 705.71 }, -- Darnassus
[382] = { 959.37, 640.11 }, -- Undercity
[401] = { 4237.50, 2825.00 }, -- AlteracValley
[443] = { 1145.84, 764.58 }, -- WarsongGulch
[461] = { 1756.25, 1170.83 }, -- ArathiBasin
[500] = { 828.00, 541.00 }, -- pfQ876
[501] = { 3206.00, 2142.00 }, -- pfQ616
[502] = { 3159.00, 2108.00 }, -- pfQ4012
[503] = { 988.63, 1393.17 }, -- pfQ5023
[504] = { 2901.45, 1915.90 }, -- pfQ409
[505] = { 3092.10, 2047.00 }, -- pfQ408
[507] = { 3227.00, 2187.00 }, -- pfQ5121
[509] = { 1468.00, 976.00 }, -- pfQ2040
[510] = { 3666.00, 2442.00 }, -- pfQ5179
[511] = { 1608.00, 1075.00 }, -- pfQ5024
[512] = { 2472.00, 1665.00 }, -- pfQ5536
[513] = { 3082.00, 2061.00 }, -- pfQ5225
[514] = { 449.89, 300.00 }, -- pfQ1581
[516] = { 572.78, 381.85 }, -- pfQ718
[517] = { 2112.09, 1410.89 }, -- pfQ2100
[518] = { 769.67, 513.11 }, -- pfQ721
[519] = { 711.56, 468.68 }, -- pfQ25
[520] = { 203.66, 135.04 }, -- pfQ796
[521] = { 893.67, 595.78 }, -- pfQ1337
[522] = { 4139.02, 2946.00 }, -- pfQ3478
[523] = { 1348.24, 888.09 }, -- pfQ1941
[601] = { 1432.00, 977.00 }, -- pfQ5130
[603] = { 738.86, 492.57 }, -- pfQ2437
[605] = { 1383.33, 922.91 }, -- pfQ1176
[607] = { 695.03, 463.35 }, -- pfQ1477
[609] = { 1221.87, 806.42 }, -- pfQ719
[611] = { 378.15, 252.10 }, -- pfQ717
[617] = { 1495.00, 983.00 }, -- pfQ2717
[619] = { 2120.83, 1414.60 }, -- pfQ1977
[623] = { 1985.00, 1268.00 }, -- pfQ1584
[625] = { 2512.50, 1675.00 }, -- pfQ3429
[627] = { 483.11, 322.08 }, -- pfQ2159
[629] = { 886.84, 591.23 }, -- pfQ1583
[635] = { 499.43, 332.95 }, -- pfQ2677
[637] = { 656.59, 434.97 }, -- pfQ5138
[639] = { 881.00, 589.00 }, -- pfQ722
[641] = { 736.45, 490.96 }, -- pfQ491
[643] = { 619.98, 413.32 }, -- pfQ5136
[644] = { 320.19, 213.46 }, -- pfQ5135
[645] = { 612.69, 408.46 }, -- pfQ5153
[646] = { 703.30, 468.87 }, -- pfQ5163
[648] = { 320.05, 213.37 }, -- pfQ2057
[650] = { 381.00, 254.00 }, -- pfQ209
[652] = { 1185.35, 789.86 }, -- pfQ2017
[654] = { 977.56, 651.70 }, -- pfQ3428
[655] = { 2777.54, 1851.69 }, -- pfQ5147
[657] = { 619.00, 410.00 }, -- pfQ5723
[659] = { 312.00, 208.00 }, -- pfQ2257
[662] = { 1271.99, 845.48 }, -- pfQ5204
[663] = { 1085.86, 726.61 }, -- pfQ2366
[665] = { 1250.18, 837.44 }, -- pfQ5208
[667] = { 1991.69, 1318.42 }, -- pfQ3456
[668] = { 652.10, 439.67 }, -- pfQ5148
[670] = { 2643.21, 1751.16 }, -- pfQ5077
[672] = { 752.12, 510.33 }, -- pfQ5103
[674] = { 546.75, 391.90 }, -- pfQ5086
[676] = { 354.50, 234.74 }, -- pfQ5087
[678] = { 1273.10, 853.72 }, -- pfQ5097
[680] = { 1007.70, 671.80 }, -- pfQ5053
[682] = { 823.00, 549.00 }, -- pfQ3457
[683] = { 2800.00, 1868.00 }, -- pfQ5557
[684] = { 5387.00, 3584.00 }, -- pfQ5602
[685] = { 3098.00, 2068.00 }, -- pfQ5561
[686] = { 3241.00, 2157.00 }, -- pfQ5581
[689] = { 1324.00, 869.00 }, -- pfQ2557
[691] = { 1634.00, 1073.00 }, -- pfQ5601
[693] = { 1980.00, 1350.00 }, -- pfQ5628
[698] = { 1234.00, 829.00 }, -- pfQ5641
[699] = { 7856.00, 5241.00 }, -- pfQ5642
[700] = { 861.00, 607.00 }, -- pfQ1769
[701] = { 1347.00, 971.00 }, -- pfQ5640
[702] = { 1047.00, 694.00 }, -- pfQ5722
[704] = { 1011.00, 653.00 }, -- pfQ5734
[705] = { 440.00, 279.00 }, -- pfQ5731
[613] = { 1125.00, 740.00 },
[615] = { 893.67, 595.78 },
[621] = { 1919.00, 1250.00 },
[631] = { 1170.00, 785.00 },
[633] = { 2112.09, 1410.89 },
[696] = { 1425.00, 962.00 },
[5144] = { 309.00, 208.00 },
}
+140
View File
@@ -0,0 +1,140 @@
-- MetaHunt: Pet spell DBC acquire-method overlay (learnMethod, minRank, nextSpellId)
-- AUTO-GENERATED by .tools — do not edit by hand.
-- Source DBC(s): SkillLineAbility.dbc, SkillLine.dbc
-- Generated: 2026-03-20 13:57 UTC
-- This file patches MTH_DS_PetSpells.allSpells entries with DBC-authoritative
-- learnMethod, minRank, and nextSpellId values.
-- Load order: after ds-pet-spells.lua and ds-pet-spells-trainer.lua
if not MTH_DS_PetSpells or not MTH_DS_PetSpells.allSpells then return end
local _acquire = {
[1747] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[1748] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[1749] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[1750] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[1751] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[1853] = { learnMethod="unknown(1)", minRank=1, nextSpellId=0, skillLineId=261 },
[2975] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[2976] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[2977] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[2980] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[2981] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[2982] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[3666] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[3667] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[4195] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[4196] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[4197] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[4198] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[4199] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[4200] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[4201] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[4202] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[5048] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[5049] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[5149] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[7370] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[14922] = { learnMethod="unknown(1)", minRank=1, nextSpellId=0, skillLineId=261 },
[14923] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[14924] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[14925] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[14926] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[14927] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[16698] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[17254] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[17262] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[17263] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[17264] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[17265] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[17266] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[17267] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[17268] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[23100] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[23111] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[23112] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[23146] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[23149] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[23150] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[23163] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[23166] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[23167] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24424] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24440] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24441] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24451] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24454] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24455] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24463] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24464] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24475] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24476] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24477] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24478] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24490] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24494] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24495] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24508] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24509] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24510] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24511] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24512] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24513] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24514] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24515] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24516] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24547] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24556] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24557] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24558] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24559] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24560] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24561] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24562] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24580] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24581] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24582] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24584] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24588] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24589] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24599] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24607] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24608] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24609] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24631] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24632] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24641] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[24845] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[25013] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[25014] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[25015] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[25016] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[25017] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[25077] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[26065] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[26094] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[26184] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[26185] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[26186] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[26189] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[26190] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[26202] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[28343] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[46023] = { learnMethod="trainer", minRank=0, nextSpellId=0, skillLineId=261 },
[46303] = { learnMethod="trainer", minRank=0, nextSpellId=0, skillLineId=261 },
[46307] = { learnMethod="trainer", minRank=0, nextSpellId=0, skillLineId=261 },
[51154] = { learnMethod="trainer", minRank=0, nextSpellId=0, skillLineId=261 },
[51155] = { learnMethod="trainer", minRank=0, nextSpellId=0, skillLineId=261 },
[51157] = { learnMethod="trainer", minRank=0, nextSpellId=0, skillLineId=261 },
}
for _, spell in ipairs(MTH_DS_PetSpells.allSpells) do
local meta = _acquire[spell.id]
if meta then
spell.learnMethod = meta.learnMethod
spell.minRank = meta.minRank
spell.nextSpellId = meta.nextSpellId
spell.skillLineId = meta.skillLineId
end
end
+59 -19
View File
@@ -25,6 +25,7 @@ MTH_DS_PetSpells = {
"Strider Presence",
"Thunderstomp",
"Web",
"Pollen Burst",
},
["allSpells"] = {
{
@@ -1355,14 +1356,15 @@ MTH_DS_PetSpells = {
{
["ability"] = "Roar of Fortitude",
["castTime"] = "Instant",
["categoryCooldown"] = "180 seconds",
["cooldown"] = "n/a",
["cost"] = "None",
["description"] = "Increases max hitpoints of allies in your party by 200 for 120 sec",
["categoryCooldown"] = "n/a",
["cooldown"] = "120 seconds",
["cost"] = "50 mana",
["description"] = "Let out a fortifying roar, reducing damage taken by 8% and increasing all damage dealt by 3% for your party members within 30 yards for 12 sec.",
["effects"] = {
"(6) Apply Aura #34: Mod Increase Health Value: 200 Radius: 20 yards",
"(6) Apply Aura #87: Mod Dmg % Taken (127) Value: -8 Radius: 30 yards",
"(6) Apply Aura #79: Mod Dmg % (127) Value: 3 Radius: 30 yards",
},
["icon"] = "Ability_BullRush",
["icon"] = "Ability_Druid_ChallangingRoar",
["id"] = 36535,
["name"] = "Roar of Fortitude",
["range"] = "0 yards (Self Only)",
@@ -1803,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"] = {
@@ -3245,14 +3262,15 @@ MTH_DS_PetSpells = {
{
["ability"] = "Roar of Fortitude",
["castTime"] = "Instant",
["categoryCooldown"] = "180 seconds",
["cooldown"] = "n/a",
["cost"] = "None",
["description"] = "Increases max hitpoints of allies in your party by 200 for 120 sec",
["categoryCooldown"] = "n/a",
["cooldown"] = "120 seconds",
["cost"] = "50 mana",
["description"] = "Let out a fortifying roar, reducing damage taken by 8% and increasing all damage dealt by 3% for your party members within 30 yards for 12 sec.",
["effects"] = {
"(6) Apply Aura #34: Mod Increase Health Value: 200 Radius: 20 yards",
"(6) Apply Aura #87: Mod Dmg % Taken (127) Value: -8 Radius: 30 yards",
"(6) Apply Aura #79: Mod Dmg % (127) Value: 3 Radius: 30 yards",
},
["icon"] = "Ability_BullRush",
["icon"] = "Ability_Druid_ChallangingRoar",
["id"] = 36535,
["name"] = "Roar of Fortitude",
["range"] = "0 yards (Self Only)",
@@ -3742,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"] = {
@@ -4291,14 +4331,14 @@ MTH_DS_PetSpells = {
{
["ability"] = "Web",
["droppedId"] = 12023,
["keptId"] = 4167,
["keptId"] = 36533,
["mode"] = "lower-score",
["rankNumber"] = 0,
},
{
["ability"] = "Web",
["droppedId"] = 36533,
["keptId"] = 4167,
["droppedId"] = 4167,
["keptId"] = 36533,
["mode"] = "lower-score",
["rankNumber"] = 0,
},
+41
View File
@@ -0,0 +1,41 @@
-- MetaHunt: Stable slot prices
-- AUTO-GENERATED by .tools — do not edit by hand.
-- Source DBC(s): StableSlotPrices.dbc
-- Generated: 2026-03-20 13:57 UTC
if not MTH_DS then MTH_DS = {} end
MTH_DS_StableSlotPrices = {
[1] = {
["cost_copper"] = 500,
["cost_gold"] = 0,
["cost_silver"] = 5,
["cost_copper_remainder"] = 0,
["display"] = "0g 5s 0c",
["acquireMethod"] = "gold",
},
[2] = {
["cost_copper"] = 50000,
["cost_gold"] = 5,
["cost_silver"] = 0,
["cost_copper_remainder"] = 0,
["display"] = "5g 0s 0c",
["acquireMethod"] = "gold",
},
[3] = {
["cost_copper"] = 2500000,
["cost_gold"] = 250,
["cost_silver"] = 0,
["cost_copper_remainder"] = 0,
["display"] = "250g 0s 0c",
["acquireMethod"] = "gold",
},
[4] = {
["cost_copper"] = 0,
["cost_gold"] = 0,
["cost_silver"] = 0,
["cost_copper_remainder"] = 0,
["display"] = "Shop",
["acquireMethod"] = "shop",
},
}
+23493 -23493
View File
File diff suppressed because it is too large Load Diff
+223 -167
View File
File diff suppressed because it is too large Load Diff
+219 -219
View File
File diff suppressed because it is too large Load Diff
+97
View File
@@ -0,0 +1,97 @@
-- MetaHunt: Zone DBC enrichment (worldBounds, dbcAreaId, dbcMapId, dbcName)
-- AUTO-GENERATED by .tools — do not edit by hand.
-- Source DBC(s): WorldMapArea.dbc, AreaTable.dbc
-- Generated: 2026-03-20 13:57 UTC
if not MTH_DS_Zones then return end
-- WMA ID=301 map=0 'Stormwind'
if MTH_DS_Zones[1519] then
MTH_DS_Zones[1519]["dbcAreaId"] = 1519
MTH_DS_Zones[1519]["dbcMapId"] = 0
MTH_DS_Zones[1519]["dbcName"] = "Stormwind"
MTH_DS_Zones[1519]["worldBounds"] = {
["left"] = 1722.92,
["right"] = -14.58,
["top"] = -7995.8301,
["bottom"] = -9154.1699,
}
end
-- WMA ID=321 map=1 'Ogrimmar'
if MTH_DS_Zones[1637] then
MTH_DS_Zones[1637]["dbcAreaId"] = 1637
MTH_DS_Zones[1637]["dbcMapId"] = 1
MTH_DS_Zones[1637]["dbcName"] = "Ogrimmar"
MTH_DS_Zones[1637]["worldBounds"] = {
["left"] = -3680.6001,
["right"] = -5083.21,
["top"] = 2273.8799,
["bottom"] = 1338.46,
}
end
-- WMA ID=362 map=1 'ThunderBluff'
if MTH_DS_Zones[1638] then
MTH_DS_Zones[1638]["dbcAreaId"] = 1638
MTH_DS_Zones[1638]["dbcMapId"] = 1
MTH_DS_Zones[1638]["dbcName"] = "ThunderBluff"
MTH_DS_Zones[1638]["worldBounds"] = {
["left"] = 516.67,
["right"] = -527.08,
["top"] = -850.0,
["bottom"] = -1545.83,
}
end
-- WMA ID=381 map=1 'Darnassis'
if MTH_DS_Zones[1657] then
MTH_DS_Zones[1657]["dbcAreaId"] = 1657
MTH_DS_Zones[1657]["dbcMapId"] = 1
MTH_DS_Zones[1657]["dbcName"] = "Darnassis"
MTH_DS_Zones[1657]["worldBounds"] = {
["left"] = 2938.3601,
["right"] = 1880.03,
["top"] = 10238.3203,
["bottom"] = 9532.5898,
}
end
-- WMA ID=382 map=0 'Undercity'
if MTH_DS_Zones[1497] then
MTH_DS_Zones[1497]["dbcAreaId"] = 1497
MTH_DS_Zones[1497]["dbcMapId"] = 0
MTH_DS_Zones[1497]["dbcName"] = "Undercity"
MTH_DS_Zones[1497]["worldBounds"] = {
["left"] = 873.19,
["right"] = -86.18,
["top"] = 1877.95,
["bottom"] = 1237.84,
}
end
-- WMA ID=508 map=1 'AmaniAlor'
if MTH_DS_Zones[2041] then
MTH_DS_Zones[2041]["dbcAreaId"] = 2041
MTH_DS_Zones[2041]["dbcMapId"] = 1
MTH_DS_Zones[2041]["dbcName"] = "AmaniAlor"
MTH_DS_Zones[2041]["worldBounds"] = {
["left"] = 3112.0,
["right"] = 1599.0,
["top"] = 3437.0,
["bottom"] = 2434.0,
}
end
-- WMA ID=509 map=0 'AlahThalas'
if MTH_DS_Zones[2040] then
MTH_DS_Zones[2040]["dbcAreaId"] = 2040
MTH_DS_Zones[2040]["dbcMapId"] = 0
MTH_DS_Zones[2040]["dbcName"] = "AlahThalas"
MTH_DS_Zones[2040]["worldBounds"] = {
["left"] = -2169.0,
["right"] = -3637.0,
["top"] = 4907.0,
["bottom"] = 3931.0,
}
end
+2
View File
@@ -72,4 +72,6 @@ MTH_DS_ZoneNamesFallback = {
[5581] = "Northwind",
[5601] = "Dragonmaw Retreat",
[5602] = "Grim Reaches",
[5641] = "Windhorn Canyon",
[5642] = "Moonwhisper Coast",
}
File diff suppressed because it is too large Load Diff
+402
View File
@@ -0,0 +1,402 @@
-- MetaHunt: WMA (WorldMapArea.dbc) zone reference data
-- AUTO-GENERATED by .tools/migrate_wma.py — do not edit by hand.
-- Source: Haxxor WMA CSV (patch-9.mpq)
--
-- Provides MTH_MAP_PFQ_TO_WMA: pfQ AreaTable ID → WMA ID lookup
-- Used by map-markers.lua to translate GetCurrentMapAreaID() results
-- and by GetCurrentMapID() to return WMA zone IDs for coord matching.
--
-- Provides MTH_DS_WMAZones: WMA ID → worldBounds + pfqAreaId
-- Used for map overlay features without modifying the pfQ-keyed MTH_DS_Zones.
if not MTH_DS then MTH_DS = {} end
-- pfQ AreaTable ID → WMA ID runtime bridge
MTH_MAP_PFQ_TO_WMA = {
[1] = 27, -- DunMorogh
[3] = 17, -- Badlands
[4] = 19, -- BlastedLands
[8] = 38, -- SwampOfSorrows
[10] = 34, -- Duskwood
[11] = 40, -- Wetlands
[12] = 30, -- Elwynn
[14] = 4, -- Durotar
[15] = 141, -- Dustwallow
[16] = 181, -- Aszhara
[17] = 11, -- Barrens
[25] = 519, -- BlackrockMountain
[28] = 22, -- WesternPlaguelands
[33] = 37, -- Stranglethorn
[36] = 15, -- Alterac
[38] = 35, -- LochModan
[40] = 39, -- Westfall
[41] = 32, -- DeadwindPass
[44] = 36, -- Redridge
[45] = 16, -- Arathi
[46] = 29, -- BurningSteppes
[47] = 26, -- Hinterlands
[51] = 28, -- SearingGorge
[85] = 20, -- Tirisfal
[130] = 21, -- Silverpine
[139] = 23, -- EasternPlaguelands
[141] = 41, -- Teldrassil
[148] = 42, -- Darkshore
[209] = 650, -- ShadowfangKeep
[215] = 9, -- Mulgore
[267] = 24, -- Hilsbrad
[331] = 43, -- Ashenvale
[357] = 121, -- Feralas
[361] = 182, -- Felwood
[400] = 61, -- ThousandNeedles
[405] = 101, -- Desolace
[406] = 81, -- StonetalonMountains
[408] = 505, -- Gillijim
[409] = 504, -- Lapidis
[440] = 161, -- Tanaris
[490] = 201, -- UngoroCrater
[491] = 641, -- RazorfenKraul
[493] = 241, -- Moonglade
[616] = 501, -- Hyjal
[618] = 281, -- Winterspring
[717] = 611, -- TheStockade
[718] = 516, -- WailingCavernsEntrance
[719] = 609, -- BlackFathomDeeps
[721] = 518, -- GnomereganEntrance
[722] = 639, -- RazorfenDowns
[796] = 520, -- ScarletMonasteryEntrance
[876] = 500, -- GMIsland
[1176] = 605, -- ZulFarrak
[1337] = 521, -- UldamanEntrance
[1377] = 261, -- Silithus
[1477] = 607, -- TheTempleOfAtalHakkar
[1497] = 382, -- Undercity
[1519] = 301, -- Stormwind
[1537] = 341, -- Ironforge
[1581] = 514, -- DeadminesEntrance
[1583] = 629, -- BlackrockSpire
[1584] = 623, -- BlackrockDepths
[1637] = 321, -- Orgrimmar
[1638] = 362, -- ThunderBluff
[1657] = 381, -- Darnassus
[1769] = 700, -- TimbermawTunnels
[1941] = 523, -- CavernsOfTime
[1977] = 619, -- ZulGurub
[2017] = 652, -- Stratholme
[2040] = 509, -- AlahThalas
[2057] = 648, -- Scholomance
[2100] = 517, -- MaraudonEntrance
[2159] = 627, -- OnyxiasLair
[2257] = 659, -- DeeprunTram
[2366] = 663, -- BlackMorass2f
[2437] = 603, -- Ragefire
[2557] = 689, -- DireMaulEntrance
[2597] = 401, -- AlteracValley
[2677] = 635, -- BlackwingLair
[2717] = 617, -- MoltenCore
[3277] = 443, -- WarsongGulch
[3358] = 461, -- ArathiBasin
[3428] = 654, -- AhnQiraj
[3429] = 625, -- RuinsofAhnQiraj
[3456] = 667, -- Naxxramas
[3457] = 682, -- UpperKarazhan
[3478] = 522, -- AhnQirajEntrance
[4012] = 502, -- ScarletEnclave
[5023] = 503, -- Sunnyglade
[5024] = 511, -- Icepoint
[5053] = 680, -- Moomoo
[5077] = 670, -- CrescentGrove
[5086] = 674, -- KarazhanCrypt
[5087] = 676, -- StormwindVault
[5097] = 678, -- EmeraldSanctum
[5103] = 672, -- HateforgeQuarry
[5121] = 507, -- TelAbim
[5130] = 601, -- WinterVeilVale
[5135] = 644, -- ScarletMonastery2f
[5136] = 643, -- ScarletMonastery
[5138] = 637, -- TheDeadmines
[5147] = 655, -- AhnQiraj2f
[5148] = 668, -- Naxxramas2f
[5153] = 645, -- ScarletMonastery3f
[5163] = 646, -- ScarletMonastery4f
[5179] = 510, -- Gilneas
[5204] = 662, -- BlackMorass
[5208] = 665, -- GilneasCity
[5225] = 513, -- ThalassianHighlands
[5536] = 512, -- BlackstoneIsland
[5557] = 683, -- UpperKarazhan2f
[5561] = 685, -- Balor
[5581] = 686, -- Northwind
[5601] = 691, -- DragonmawRetreat
[5602] = 684, -- GrimReaches
[5628] = 693, -- StormwroughtRuins
[5640] = 701, -- TimbermawEntrance
[5641] = 698, -- WindhornCanyon
[5642] = 699, -- Moonwhisper
[5722] = 702, -- ThornGorge
[5723] = 657, -- Karazhan
[5731] = 705, -- WindhornCanyonEntrance
[5734] = 704, -- FrostmaneHollow
}
-- WMA ID → pfQ AreaTable ID (for fallback lookups)
MTH_MAP_WMA_TO_PFQ = {
[4] = 14, -- Durotar
[9] = 215, -- Mulgore
[11] = 17, -- Barrens
[15] = 36, -- Alterac
[16] = 45, -- Arathi
[17] = 3, -- Badlands
[19] = 4, -- BlastedLands
[20] = 85, -- Tirisfal
[21] = 130, -- Silverpine
[22] = 28, -- WesternPlaguelands
[23] = 139, -- EasternPlaguelands
[24] = 267, -- Hilsbrad
[26] = 47, -- Hinterlands
[27] = 1, -- DunMorogh
[28] = 51, -- SearingGorge
[29] = 46, -- BurningSteppes
[30] = 12, -- Elwynn
[32] = 41, -- DeadwindPass
[34] = 10, -- Duskwood
[35] = 38, -- LochModan
[36] = 44, -- Redridge
[37] = 33, -- Stranglethorn
[38] = 8, -- SwampOfSorrows
[39] = 40, -- Westfall
[40] = 11, -- Wetlands
[41] = 141, -- Teldrassil
[42] = 148, -- Darkshore
[43] = 331, -- Ashenvale
[61] = 400, -- ThousandNeedles
[81] = 406, -- StonetalonMountains
[101] = 405, -- Desolace
[121] = 357, -- Feralas
[141] = 15, -- Dustwallow
[161] = 440, -- Tanaris
[181] = 16, -- Aszhara
[182] = 361, -- Felwood
[201] = 490, -- UngoroCrater
[241] = 493, -- Moonglade
[261] = 1377, -- Silithus
[281] = 618, -- Winterspring
[301] = 1519, -- Stormwind
[321] = 1637, -- Orgrimmar
[341] = 1537, -- Ironforge
[362] = 1638, -- ThunderBluff
[381] = 1657, -- Darnassus
[382] = 1497, -- Undercity
[401] = 2597, -- AlteracValley
[443] = 3277, -- WarsongGulch
[461] = 3358, -- ArathiBasin
[500] = 876, -- GMIsland
[501] = 616, -- Hyjal
[502] = 4012, -- ScarletEnclave
[503] = 5023, -- Sunnyglade
[504] = 409, -- Lapidis
[505] = 408, -- Gillijim
[507] = 5121, -- TelAbim
[509] = 2040, -- AlahThalas
[510] = 5179, -- Gilneas
[511] = 5024, -- Icepoint
[512] = 5536, -- BlackstoneIsland
[513] = 5225, -- ThalassianHighlands
[514] = 1581, -- DeadminesEntrance
[516] = 718, -- WailingCavernsEntrance
[517] = 2100, -- MaraudonEntrance
[518] = 721, -- GnomereganEntrance
[519] = 25, -- BlackrockMountain
[520] = 796, -- ScarletMonasteryEntrance
[521] = 1337, -- UldamanEntrance
[522] = 3478, -- AhnQirajEntrance
[523] = 1941, -- CavernsOfTime
[601] = 5130, -- WinterVeilVale
[603] = 2437, -- Ragefire
[605] = 1176, -- ZulFarrak
[607] = 1477, -- TheTempleOfAtalHakkar
[609] = 719, -- BlackFathomDeeps
[611] = 717, -- TheStockade
[617] = 2717, -- MoltenCore
[619] = 1977, -- ZulGurub
[623] = 1584, -- BlackrockDepths
[625] = 3429, -- RuinsofAhnQiraj
[627] = 2159, -- OnyxiasLair
[629] = 1583, -- BlackrockSpire
[635] = 2677, -- BlackwingLair
[637] = 5138, -- TheDeadmines
[639] = 722, -- RazorfenDowns
[641] = 491, -- RazorfenKraul
[643] = 5136, -- ScarletMonastery
[644] = 5135, -- ScarletMonastery2f
[645] = 5153, -- ScarletMonastery3f
[646] = 5163, -- ScarletMonastery4f
[648] = 2057, -- Scholomance
[650] = 209, -- ShadowfangKeep
[652] = 2017, -- Stratholme
[654] = 3428, -- AhnQiraj
[655] = 5147, -- AhnQiraj2f
[657] = 5723, -- Karazhan
[659] = 2257, -- DeeprunTram
[662] = 5204, -- BlackMorass
[663] = 2366, -- BlackMorass2f
[665] = 5208, -- GilneasCity
[667] = 3456, -- Naxxramas
[668] = 5148, -- Naxxramas2f
[670] = 5077, -- CrescentGrove
[672] = 5103, -- HateforgeQuarry
[674] = 5086, -- KarazhanCrypt
[676] = 5087, -- StormwindVault
[678] = 5097, -- EmeraldSanctum
[680] = 5053, -- Moomoo
[682] = 3457, -- UpperKarazhan
[683] = 5557, -- UpperKarazhan2f
[684] = 5602, -- GrimReaches
[685] = 5561, -- Balor
[686] = 5581, -- Northwind
[689] = 2557, -- DireMaulEntrance
[691] = 5601, -- DragonmawRetreat
[693] = 5628, -- StormwroughtRuins
[698] = 5641, -- WindhornCanyon
[699] = 5642, -- Moonwhisper
[700] = 1769, -- TimbermawTunnels
[701] = 5640, -- TimbermawEntrance
[702] = 5722, -- ThornGorge
[704] = 5734, -- FrostmaneHollow
[705] = 5731, -- WindhornCanyonEntrance
}
-- WMA ID → worldBounds + pfqAreaId (for map overlay and minimap calibration)
MTH_DS_WMAZones = {
[4] = { pfqAreaId=14, mapId=1, left=-1962.50, right=-7250.00, top=1808.33, bottom=-1716.67, width=5287.50, height=3525.00 }, -- Durotar
[9] = { pfqAreaId=215, mapId=1, left=2047.92, right=-3089.58, top=-272.92, bottom=-3697.92, width=5137.50, height=3425.00 }, -- Mulgore
[11] = { pfqAreaId=17, mapId=1, left=2622.92, right=-7510.42, top=1612.50, bottom=-5143.75, width=10133.34, height=6756.25 }, -- Barrens
[13] = { pfqAreaId=0, mapId=1, left=17066.60, right=-19733.21, top=12799.90, bottom=-11733.30, width=36799.81, height=24533.20 }, -- Kalimdor
[14] = { pfqAreaId=0, mapId=0, left=16000.00, right=-19199.90, top=7466.60, bottom=-16000.00, width=35199.90, height=23466.60 }, -- Azeroth
[15] = { pfqAreaId=36, mapId=0, left=783.33, right=-2016.67, top=1500.00, bottom=-366.67, width=2800.00, height=1866.67 }, -- Alterac
[16] = { pfqAreaId=45, mapId=0, left=-866.67, right=-4466.67, top=-133.33, bottom=-2533.33, width=3600.00, height=2400.00 }, -- Arathi
[17] = { pfqAreaId=3, mapId=0, left=-2079.17, right=-4566.67, top=-5889.58, bottom=-7547.92, width=2487.50, height=1658.34 }, -- Badlands
[19] = { pfqAreaId=4, mapId=0, left=-1241.67, right=-4591.67, top=-10566.67, bottom=-12800.00, width=3350.00, height=2233.33 }, -- BlastedLands
[20] = { pfqAreaId=85, mapId=0, left=3033.33, right=-1485.42, top=3837.50, bottom=825.00, width=4518.75, height=3012.50 }, -- Tirisfal
[21] = { pfqAreaId=130, mapId=0, left=3450.00, right=-750.00, top=1666.67, bottom=-1133.33, width=4200.00, height=2800.00 }, -- Silverpine
[22] = { pfqAreaId=28, mapId=0, left=416.67, right=-3883.33, top=3366.67, bottom=500.00, width=4300.00, height=2866.67 }, -- WesternPlaguelands
[23] = { pfqAreaId=139, mapId=0, left=-2185.42, right=-6056.25, top=3800.00, bottom=1218.75, width=3870.83, height=2581.25 }, -- EasternPlaguelands
[24] = { pfqAreaId=267, mapId=0, left=1066.67, right=-2133.33, top=400.00, bottom=-1733.33, width=3200.00, height=2133.33 }, -- Hilsbrad
[26] = { pfqAreaId=47, mapId=0, left=-1575.00, right=-5425.00, top=1466.67, bottom=-1100.00, width=3850.00, height=2566.67 }, -- Hinterlands
[27] = { pfqAreaId=1, mapId=0, left=1802.08, right=-3122.92, top=-3877.08, bottom=-7160.42, width=4925.00, height=3283.34 }, -- DunMorogh
[28] = { pfqAreaId=51, mapId=0, left=-322.92, right=-2554.17, top=-6100.00, bottom=-7587.50, width=2231.25, height=1487.50 }, -- SearingGorge
[29] = { pfqAreaId=46, mapId=0, left=-266.67, right=-3195.83, top=-7031.25, bottom=-8983.33, width=2929.16, height=1952.08 }, -- BurningSteppes
[30] = { pfqAreaId=12, mapId=0, left=1535.42, right=-1935.42, top=-7939.58, bottom=-10254.17, width=3470.84, height=2314.59 }, -- Elwynn
[32] = { pfqAreaId=41, mapId=0, left=-833.33, right=-3333.33, top=-9866.67, bottom=-11533.33, width=2500.00, height=1666.66 }, -- DeadwindPass
[34] = { pfqAreaId=10, mapId=0, left=833.33, right=-1866.67, top=-9716.67, bottom=-11516.67, width=2700.00, height=1800.00 }, -- Duskwood
[35] = { pfqAreaId=38, mapId=0, left=-1993.75, right=-4752.08, top=-4487.50, bottom=-6327.08, width=2758.33, height=1839.58 }, -- LochModan
[36] = { pfqAreaId=44, mapId=0, left=-1570.83, right=-3741.67, top=-8575.00, bottom=-10022.92, width=2170.84, height=1447.92 }, -- Redridge
[37] = { pfqAreaId=33, mapId=0, left=2220.83, right=-4160.42, top=-11168.75, bottom=-15422.92, width=6381.25, height=4254.17 }, -- Stranglethorn
[38] = { pfqAreaId=8, mapId=0, left=-2222.92, right=-4516.67, top=-9620.83, bottom=-11150.00, width=2293.75, height=1529.17 }, -- SwampOfSorrows
[39] = { pfqAreaId=40, mapId=0, left=3016.67, right=-483.33, top=-9400.00, bottom=-11733.33, width=3500.00, height=2333.33 }, -- Westfall
[40] = { pfqAreaId=11, mapId=0, left=-389.58, right=-4525.00, top=-2147.92, bottom=-4904.17, width=4135.42, height=2756.25 }, -- Wetlands
[41] = { pfqAreaId=141, mapId=1, left=3814.58, right=-1277.08, top=11831.25, bottom=8437.50, width=5091.66, height=3393.75 }, -- Teldrassil
[42] = { pfqAreaId=148, mapId=1, left=2941.67, right=-3608.33, top=8333.33, bottom=3966.67, width=6550.00, height=4366.66 }, -- Darkshore
[43] = { pfqAreaId=331, mapId=1, left=1700.00, right=-4066.67, top=4672.92, bottom=829.17, width=5766.67, height=3843.75 }, -- Ashenvale
[61] = { pfqAreaId=400, mapId=1, left=-433.33, right=-4833.33, top=-3966.67, bottom=-6900.00, width=4400.00, height=2933.33 }, -- ThousandNeedles
[81] = { pfqAreaId=406, mapId=1, left=3949.00, right=-2069.00, top=3441.00, bottom=-569.00, width=6018.00, height=4010.00 }, -- StonetalonMountains
[101] = { pfqAreaId=405, mapId=1, left=4233.33, right=-262.50, top=452.08, bottom=-2545.83, width=4495.83, height=2997.91 }, -- Desolace
[121] = { pfqAreaId=357, mapId=1, left=5441.67, right=-1508.33, top=-2366.67, bottom=-7000.00, width=6950.00, height=4633.33 }, -- Feralas
[141] = { pfqAreaId=15, mapId=1, left=-975.00, right=-6225.00, top=-2033.33, bottom=-5533.33, width=5250.00, height=3500.00 }, -- Dustwallow
[161] = { pfqAreaId=440, mapId=1, left=-218.75, right=-7118.75, top=-5875.00, bottom=-10475.00, width=6900.00, height=4600.00 }, -- Tanaris
[181] = { pfqAreaId=16, mapId=1, left=-3277.08, right=-8347.92, top=5341.67, bottom=1960.42, width=5070.84, height=3381.25 }, -- Aszhara
[182] = { pfqAreaId=361, mapId=1, left=1641.67, right=-4108.33, top=7133.33, bottom=3300.00, width=5750.00, height=3833.33 }, -- Felwood
[201] = { pfqAreaId=490, mapId=1, left=533.33, right=-3166.67, top=-5966.67, bottom=-8433.33, width=3700.00, height=2466.66 }, -- UngoroCrater
[241] = { pfqAreaId=493, mapId=1, left=-1381.25, right=-3689.58, top=8491.67, bottom=6952.08, width=2308.33, height=1539.59 }, -- Moonglade
[261] = { pfqAreaId=1377, mapId=1, left=2537.50, right=-945.83, top=-5958.33, bottom=-8281.25, width=3483.33, height=2322.92 }, -- Silithus
[281] = { pfqAreaId=618, mapId=1, left=-316.67, right=-7416.67, top=8533.33, bottom=3800.00, width=7100.00, height=4733.33 }, -- Winterspring
[301] = { pfqAreaId=1519, mapId=0, left=1722.92, right=-14.58, top=-7995.83, bottom=-9154.17, width=1737.50, height=1158.34 }, -- Stormwind
[321] = { pfqAreaId=1637, mapId=1, left=-3680.60, right=-5083.21, top=2273.88, bottom=1338.46, width=1402.61, height=935.42 }, -- Orgrimmar
[341] = { pfqAreaId=1537, mapId=0, left=-713.59, right=-1504.22, top=-4569.24, bottom=-5096.85, width=790.63, height=527.61 }, -- Ironforge
[362] = { pfqAreaId=1638, mapId=1, left=516.67, right=-527.08, top=-850.00, bottom=-1545.83, width=1043.75, height=695.83 }, -- ThunderBluff
[381] = { pfqAreaId=1657, mapId=1, left=2938.36, right=1880.03, top=10238.32, bottom=9532.59, width=1058.33, height=705.73 }, -- Darnassus
[382] = { pfqAreaId=1497, mapId=0, left=873.19, right=-86.18, top=1877.95, bottom=1237.84, width=959.37, height=640.11 }, -- Undercity
[401] = { pfqAreaId=2597, mapId=30, left=1781.25, right=-2456.25, top=1085.42, bottom=-1739.58, width=4237.50, height=2825.00 }, -- AlteracValley
[443] = { pfqAreaId=3277, mapId=489, left=2041.67, right=895.83, top=1627.08, bottom=862.50, width=1145.84, height=764.58 }, -- WarsongGulch
[461] = { pfqAreaId=3358, mapId=529, left=1858.33, right=102.08, top=1508.33, bottom=337.50, width=1756.25, height=1170.83 }, -- ArathiBasin
[500] = { pfqAreaId=876, mapId=1, left=16748.00, right=15920.00, top=16525.00, bottom=15984.00, width=828.00, height=541.00 }, -- GMIsland
[501] = { pfqAreaId=616, mapId=1, left=-1080.00, right=-4286.00, top=6125.00, bottom=3983.00, width=3206.00, height=2142.00 }, -- Hyjal
[502] = { pfqAreaId=4012, mapId=0, left=-4050.00, right=-7209.00, top=3087.00, bottom=979.00, width=3159.00, height=2108.00 }, -- ScarletEnclave
[503] = { pfqAreaId=5023, mapId=27, left=1013.00, right=-435.00, top=1823.00, bottom=858.00, width=1448.00, height=965.00 }, -- Sunnyglade
[504] = { pfqAreaId=409, mapId=0, left=4933.33, right=2031.88, top=-11043.48, bottom=-12959.42, width=2901.45, height=1915.94 }, -- Lapidis
[505] = { pfqAreaId=408, mapId=0, left=4436.22, right=1344.12, top=-12528.71, bottom=-14575.72, width=3092.10, height=2047.01 }, -- Gillijim
[507] = { pfqAreaId=5121, mapId=1, left=-5179.00, right=-8406.00, top=-7184.00, bottom=-9371.00, width=3227.00, height=2187.00 }, -- TelAbim
[509] = { pfqAreaId=2040, mapId=0, left=-2169.00, right=-3637.00, top=4907.00, bottom=3931.00, width=1468.00, height=976.00 }, -- AlahThalas
[510] = { pfqAreaId=5179, mapId=0, left=3253.00, right=-413.00, top=-359.00, bottom=-2801.00, width=3666.00, height=2442.00 }, -- Gilneas
[511] = { pfqAreaId=5024, mapId=1, left=-5595.00, right=-7203.00, top=14405.00, bottom=13330.00, width=1608.00, height=1075.00 }, -- Icepoint
[512] = { pfqAreaId=5536, mapId=1, left=-6274.00, right=-8746.00, top=799.00, bottom=-866.00, width=2472.00, height=1665.00 }, -- BlackstoneIsland
[513] = { pfqAreaId=5225, mapId=0, left=-1005.00, right=-4087.00, top=4952.00, bottom=2891.00, width=3082.00, height=2061.00 }, -- ThalassianHighlands
[514] = { pfqAreaId=1581, mapId=0, left=1793.18, right=1343.29, top=-11054.94, bottom=-11354.86, width=449.89, height=299.92 }, -- DeadminesEntrance
[516] = { pfqAreaId=718, mapId=1, left=-1900.87, right=-2473.65, top=-488.64, bottom=-870.49, width=572.78, height=381.85 }, -- WailingCavernsEntrance
[517] = { pfqAreaId=2100, mapId=1, left=3208.00, right=2384.00, top=-1096.00, bottom=-1646.00, width=824.00, height=550.00 }, -- MaraudonEntrance
[518] = { pfqAreaId=721, mapId=0, left=1027.64, right=456.45, top=-4806.32, bottom=-5185.46, width=571.19, height=379.14 }, -- GnomereganEntrance
[519] = { pfqAreaId=25, mapId=0, left=-760.94, right=-1472.50, top=-7327.82, bottom=-7796.50, width=711.56, height=468.68 }, -- BlackrockMountain
[520] = { pfqAreaId=796, mapId=0, left=-659.96, right=-863.62, top=2947.37, bottom=2812.33, width=203.66, height=135.04 }, -- ScarletMonasteryEntrance
[521] = { pfqAreaId=1337, mapId=0, left=-2747.15, right=-3310.46, top=-5953.41, bottom=-6329.51, width=563.31, height=376.10 }, -- UldamanEntrance
[522] = { pfqAreaId=3478, mapId=1, left=3932.67, right=-206.35, top=-8005.20, bottom=-10951.25, width=4139.02, height=2946.05 }, -- AhnQirajEntrance
[523] = { pfqAreaId=1941, mapId=1, left=-3695.85, right=-5044.09, top=-8023.09, bottom=-8911.18, width=1348.24, height=888.09 }, -- CavernsOfTime
[601] = { pfqAreaId=5130, mapId=813, left=1916.00, right=484.00, top=-2332.00, bottom=-3309.00, width=1432.00, height=977.00 }, -- WinterVeilVale
[603] = { pfqAreaId=2437, mapId=389, left=452.87, right=-285.99, top=39.62, bottom=-452.95, width=738.86, height=492.57 }, -- Ragefire
[605] = { pfqAreaId=1176, mapId=209, left=1625.00, right=241.67, top=2052.08, bottom=1129.17, width=1383.33, height=922.91 }, -- ZulFarrak
[607] = { pfqAreaId=1477, mapId=109, left=442.76, right=-252.27, top=-255.17, bottom=-718.52, width=695.03, height=463.35 }, -- TheTempleOfAtalHakkar
[609] = { pfqAreaId=719, mapId=48, left=485.41, right=-736.46, top=-107.91, bottom=-914.33, width=1221.87, height=806.42 }, -- BlackFathomDeeps
[611] = { pfqAreaId=717, mapId=34, left=189.82, right=-188.33, top=220.63, bottom=-31.47, width=378.15, height=252.10 }, -- TheStockade
[613] = { pfqAreaId=721, mapId=90, left=863.00, right=-262.00, top=-245.00, bottom=-985.00, width=1125.00, height=740.00 }, -- Gnomeregan
[615] = { pfqAreaId=1337, mapId=70, left=645.12, right=-248.55, top=204.31, bottom=-391.47, width=893.67, height=595.78 }, -- Uldaman
[617] = { pfqAreaId=2717, mapId=409, left=-11.00, right=-1506.00, top=1439.00, bottom=456.00, width=1495.00, height=983.00 }, -- MoltenCore
[619] = { pfqAreaId=1977, mapId=309, left=-612.50, right=-2733.33, top=-11225.00, bottom=-12639.58, width=2120.83, height=1414.58 }, -- ZulGurub
[621] = { pfqAreaId=2557, mapId=429, left=1007.00, right=-912.00, top=955.00, bottom=-295.00, width=1919.00, height=1250.00 }, -- DireMaul
[623] = { pfqAreaId=1584, mapId=230, left=875.00, right=-1110.00, top=1513.00, bottom=245.00, width=1985.00, height=1268.00 }, -- BlackrockDepths
[625] = { pfqAreaId=3429, mapId=509, left=3035.42, right=522.92, top=-8233.33, bottom=-9908.33, width=2512.50, height=1675.00 }, -- RuinsofAhnQiraj
[627] = { pfqAreaId=2159, mapId=249, left=155.00, right=-366.00, top=86.00, bottom=-262.00, width=521.00, height=348.00 }, -- OnyxiasLair
[629] = { pfqAreaId=1583, mapId=229, left=10.59, right=-876.25, top=304.40, bottom=-286.83, width=886.84, height=591.23 }, -- BlackrockSpire
[631] = { pfqAreaId=718, mapId=43, left=790.00, right=-380.00, top=215.00, bottom=-570.00, width=1170.00, height=785.00 }, -- WailingCaverns
[633] = { pfqAreaId=2100, mapId=349, left=485.41, right=-1626.68, top=1199.76, bottom=-211.13, width=2112.09, height=1410.89 }, -- Maraudon
[635] = { pfqAreaId=2677, mapId=469, left=-645.00, right=-1422.00, top=-7255.00, bottom=-7760.00, width=777.00, height=505.00 }, -- BlackwingLair
[637] = { pfqAreaId=5138, mapId=36, left=-322.94, right=-979.53, top=36.93, bottom=-398.04, width=656.59, height=434.97 }, -- TheDeadmines
[639] = { pfqAreaId=722, mapId=129, left=1271.00, right=390.00, top=2683.00, bottom=2094.00, width=881.00, height=589.00 }, -- RazorfenDowns
[641] = { pfqAreaId=491, mapId=47, left=2058.92, right=1322.47, top=2349.64, bottom=1858.68, width=736.45, height=490.96 }, -- RazorfenKraul
[643] = { pfqAreaId=5136, mapId=189, left=1567.97, right=947.99, top=2030.18, bottom=1616.86, width=619.98, height=413.32 }, -- ScarletMonastery
[644] = { pfqAreaId=5135, mapId=189, left=-162.27, right=-482.46, top=307.37, bottom=93.91, width=320.19, height=213.46 }, -- ScarletMonastery2f
[645] = { pfqAreaId=5153, mapId=189, left=50.27, right=-562.42, top=2009.10, bottom=1600.64, width=612.69, height=408.46 }, -- ScarletMonastery3f
[646] = { pfqAreaId=5163, mapId=189, left=1743.99, right=1040.69, top=1281.29, bottom=812.42, width=703.30, height=468.87 }, -- ScarletMonastery4f
[648] = { pfqAreaId=2057, mapId=289, left=251.41, right=-68.64, top=317.70, bottom=104.33, width=320.05, height=213.37 }, -- Scholomance
[650] = { pfqAreaId=209, mapId=33, left=2436.00, right=2055.00, top=-61.00, bottom=-315.00, width=381.00, height=254.00 }, -- ShadowfangKeep
[652] = { pfqAreaId=2017, mapId=329, left=-2639.83, right=-3825.18, top=4163.90, bottom=3374.04, width=1185.35, height=789.86 }, -- Stratholme
[654] = { pfqAreaId=3428, mapId=531, left=2515.63, right=1538.07, top=-8051.80, bottom=-8703.50, width=977.56, height=651.70 }, -- AhnQiraj
[655] = { pfqAreaId=5147, mapId=531, left=2915.62, right=138.08, top=-7659.29, bottom=-9510.98, width=2777.54, height=1851.69 }, -- AhnQiraj2f
[657] = { pfqAreaId=5723, mapId=532, left=-1546.00, right=-2165.00, top=-10789.00, bottom=-11199.00, width=619.00, height=410.00 }, -- Karazhan
[659] = { pfqAreaId=2257, mapId=369, left=2623.50, right=2311.50, top=95.50, bottom=-112.50, width=312.00, height=208.00 }, -- DeeprunTram
[662] = { pfqAreaId=5204, mapId=269, left=7338.47, right=6066.48, top=-1247.55, bottom=-2093.03, width=1271.99, height=845.48 }, -- BlackMorass
[663] = { pfqAreaId=2366, mapId=269, left=7651.85, right=6565.99, top=-1500.47, bottom=-2227.08, width=1085.86, height=726.61 }, -- BlackMorass2f
[665] = { pfqAreaId=5208, mapId=815, left=3290.32, right=2040.14, top=-760.72, bottom=-1598.16, width=1250.18, height=837.44 }, -- GilneasCity
[667] = { pfqAreaId=3456, mapId=533, left=-2375.27, right=-4366.96, top=3656.70, bottom=2338.28, width=1991.69, height=1318.42 }, -- Naxxramas
[668] = { pfqAreaId=5148, mapId=533, left=-4869.36, right=-5521.46, top=3816.56, bottom=3376.89, width=652.10, height=439.67 }, -- Naxxramas2f
[670] = { pfqAreaId=5077, mapId=802, left=1207.72, right=-1435.49, top=882.98, bottom=-868.18, width=2643.21, height=1751.16 }, -- CrescentGrove
[672] = { pfqAreaId=5103, mapId=808, left=-3024.33, right=-3776.45, top=-7888.28, bottom=-8398.61, width=752.12, height=510.33 }, -- HateforgeQuarry
[674] = { pfqAreaId=5086, mapId=800, left=-1302.51, right=-1849.26, top=-10916.17, bottom=-11308.14, width=546.75, height=391.97 }, -- KarazhanCrypt
[676] = { pfqAreaId=5087, mapId=35, left=314.34, right=-40.16, top=89.30, bottom=-145.44, width=354.50, height=234.74 }, -- StormwindVault
[678] = { pfqAreaId=5097, mapId=807, left=3939.10, right=2666.00, top=3519.72, bottom=2666.00, width=1273.10, height=853.72 }, -- EmeraldSanctum
[680] = { pfqAreaId=5053, mapId=801, left=17296.30, right=16288.62, top=17158.52, bottom=16486.73, width=1007.68, height=671.79 }, -- Moomoo
[682] = { pfqAreaId=3457, mapId=814, left=-1378.00, right=-2201.00, top=-10887.00, bottom=-11436.00, width=823.00, height=549.00 }, -- UpperKarazhan
[683] = { pfqAreaId=5557, mapId=814, left=-1483.00, right=-4283.00, top=-5800.00, bottom=-7668.00, width=2800.00, height=1868.00 }, -- UpperKarazhan2f
[684] = { pfqAreaId=5602, mapId=0, left=-2072.00, right=-7459.00, top=-2904.00, bottom=-6488.00, width=5387.00, height=3584.00 }, -- GrimReaches
[685] = { pfqAreaId=5561, mapId=0, left=4728.00, right=1630.00, top=-7577.00, bottom=-9645.00, width=3098.00, height=2068.00 }, -- Balor
[686] = { pfqAreaId=5581, mapId=0, left=2258.00, right=-983.00, top=-6327.00, bottom=-8484.00, width=3241.00, height=2157.00 }, -- Northwind
[689] = { pfqAreaId=2557, mapId=1, left=2014.00, right=690.00, top=-3401.00, bottom=-4270.00, width=1324.00, height=869.00 }, -- DireMaulEntrance
[691] = { pfqAreaId=5601, mapId=816, left=-3428.00, right=-5062.00, top=-5599.00, bottom=-6672.00, width=1634.00, height=1073.00 }, -- DragonmawRetreat
[693] = { pfqAreaId=5628, mapId=818, left=-3365.00, right=-5345.00, top=-5424.00, bottom=-6774.00, width=1980.00, height=1350.00 }, -- StormwroughtRuins
[694] = { pfqAreaId=0, mapId=0, left=0.00, right=0.00, top=0.00, bottom=0.00, width=0.00, height=0.00 }, -- World
[696] = { pfqAreaId=5640, mapId=819, left=-2991.00, right=-4416.00, top=-7195.00, bottom=-8157.00, width=1425.00, height=962.00 }, -- Timbermaw
[698] = { pfqAreaId=5641, mapId=820, left=-3184.00, right=-4418.00, top=-7163.00, bottom=-7992.00, width=1234.00, height=829.00 }, -- WindhornCanyon
[699] = { pfqAreaId=5642, mapId=1, left=-1354.00, right=-9210.00, top=10204.00, bottom=4963.00, width=7856.00, height=5241.00 }, -- Moonwhisper
[700] = { pfqAreaId=1769, mapId=1, left=-1862.00, right=-2723.00, top=7395.00, bottom=6788.00, width=861.00, height=607.00 }, -- TimbermawTunnels
[701] = { pfqAreaId=5640, mapId=1, left=-4646.00, right=-5993.00, top=5175.00, bottom=4204.00, width=1347.00, height=971.00 }, -- TimbermawEntrance
[702] = { pfqAreaId=5722, mapId=821, left=2105.00, right=1058.00, top=2522.00, bottom=1828.00, width=1047.00, height=694.00 }, -- ThornGorge
[704] = { pfqAreaId=5734, mapId=822, left=-3157.00, right=-4168.00, top=-7453.00, bottom=-8106.00, width=1011.00, height=653.00 }, -- FrostmaneHollow
[705] = { pfqAreaId=5731, mapId=1, left=-3231.00, right=-3671.00, top=-5053.00, bottom=-5332.00, width=440.00, height=279.00 }, -- WindhornCanyonEntrance
}
-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
+13 -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"/>
@@ -15,6 +14,11 @@
<Include file="..\api\options-chronometer.lua"/>
<Include file="..\api\options-credits.lua"/>
<Include file="..\api\options-autobuy.lua"/>
<Include file="..\api\options-autoquest.lua"/>
<Include file="..\api\options-icu.lua"/>
<Include file="..\api\options-expammo.lua"/>
<Include file="..\api\profile.lua"/>
<Include file="..\api\options-profiles.lua"/>
<Include file="..\api\options-shell.lua"/>
<Include file="..\api\options-zbuttons.lua"/>
<Include file="..\api\options-feedomatic.lua"/>
@@ -27,11 +31,19 @@
<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"/>
<Include file="..\api\pets-service-vitals.lua"/>
<Include file="..\api\pets-service-runaway.lua"/>
<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"/>
+9
View File
@@ -20,4 +20,13 @@ MetaHunt Module Loader
<!-- AutoBuy Module - ENABLED -->
<Include file="..\modules\autobuy\loader.xml"/>
<!-- Auto Quest Module - ENABLED -->
<Include file="..\modules\autoquest\loader.xml"/>
<!-- 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"] = "震荡射击",
+44 -11
View File
@@ -9,6 +9,7 @@ local Engine = MTH_AutoBuyEngine
local AB_AMMO_BY_NAME = nil
local AB_FOOD_DIET_BY_ITEM_ID = nil
local function AB_ParseItemIdFromLink(link)
if type(link) ~= "string" then
return nil
@@ -589,21 +590,46 @@ local function AB_EnsureRuleList(bucket)
return bucket
end
local function AB_DeepCopy(value)
if type(value) ~= "table" then
return value
end
local copy = {}
for key, entry in pairs(value) do
copy[key] = AB_DeepCopy(entry)
end
return copy
end
function Engine:GetConfigStore()
if MTH and MTH.GetModuleSavedVariables then
local store = MTH:GetModuleSavedVariables("autobuy")
if type(store) == "table" then
return store
if MTH and MTH.GetModuleCharSavedVariables then
local charStore = MTH:GetModuleCharSavedVariables("autobuy")
if type(charStore) == "table" then
if MTH.GetModuleSavedVariables then
local accountStore = MTH:GetModuleSavedVariables("autobuy")
if type(accountStore) == "table" and next(charStore) == nil and next(accountStore) ~= nil then
for key, value in pairs(accountStore) do
charStore[key] = AB_DeepCopy(value)
end
end
end
return charStore
end
end
MTH_AutoBuy_Saved = MTH_AutoBuy_Saved or {}
return MTH_AutoBuy_Saved
if MTH and MTH.GetModuleSavedVariables then
local accountStore = MTH:GetModuleSavedVariables("autobuy")
if type(accountStore) == "table" then
return accountStore
end
end
self._transientStore = self._transientStore or {}
return self._transientStore
end
function Engine:EnsureDefaults()
local store = self:GetConfigStore()
if store.enabled == nil then
store.enabled = true
store.enabled = false
end
if type(store.food) ~= "table" then
store.food = {}
@@ -618,13 +644,13 @@ function Engine:EnsureDefaults()
store.petFood = {}
end
if store.food.enabled == nil then
store.food.enabled = true
store.food.enabled = false
end
if store.ammo.enabled == nil then
store.ammo.enabled = true
store.ammo.enabled = false
end
if store.projectiles.enabled == nil then
store.projectiles.enabled = true
store.projectiles.enabled = false
end
if store.petFood.enabled == nil then
store.petFood.enabled = false
@@ -1229,6 +1255,7 @@ local function AB_AnnouncePurchases(actions)
end
function Engine:OnMerchantEvent(eventName)
if eventName == "MERCHANT_SHOW" then
self._merchantSessionExecuted = false
elseif eventName == "MERCHANT_UPDATE" and self._merchantSessionExecuted then
@@ -1243,6 +1270,11 @@ function Engine:OnMerchantEvent(eventName)
local foodPlan = self:BuildFoodPlan(snapshot)
local ammoPlan = self:BuildAmmoPlan(snapshot)
for i = 1, table.getn(foodPlan.debugLines or {}) do
end
for i = 1, table.getn(ammoPlan.debugLines or {}) do
end
local totalRequestedStacks = 0
if table.getn(foodPlan.actions or {}) > 0 then
local _, requestedStacks = self:ExecutePlan(foodPlan)
@@ -1256,13 +1288,14 @@ function Engine:OnMerchantEvent(eventName)
local _, requestedStacks = self:ExecutePlan(ammoPlan)
totalRequestedStacks = totalRequestedStacks + requestedStacks
if requestedStacks > 0 then
AB_MoveAmmoStacksToSpecialBags(300)
local moved = AB_MoveAmmoStacksToSpecialBags(300)
AB_AnnouncePurchases(ammoPlan.actions)
end
end
if totalRequestedStacks > 0 then
self._merchantSessionExecuted = true
else
end
end
+33 -42
View File
@@ -6,7 +6,7 @@
local MTH_AutoBuy = {
name = "autobuy",
enabled = false,
version = "1.0.4",
version = "1.4.1",
events = {
"VARIABLES_LOADED",
"MERCHANT_SHOW",
@@ -24,46 +24,31 @@ 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
end
frame:RegisterEvent("VARIABLES_LOADED")
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 == "VARIABLES_LOADED" then
engine:Init()
local store = engine:EnsureDefaults()
AB_Trace("bridge VARIABLES_LOADED store.enabled=" .. tostring(store and store.enabled)
.. " projectiles.enabled=" .. tostring(store and store.projectiles and store.projectiles.enabled))
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
@@ -75,6 +60,31 @@ local function AB_EnsureBridgeFrame(moduleRef)
return frame
end
local function AB_SetBridgeActive(active)
local frame = MTH_AutoBuy._bridgeFrame
if active then
if not frame then
frame = AB_EnsureBridgeFrame(MTH_AutoBuy)
end
if frame then
frame:RegisterEvent("MERCHANT_SHOW")
frame:RegisterEvent("MERCHANT_UPDATE")
frame:RegisterEvent("MERCHANT_CLOSED")
MTH_AutoBuy._bridgeActive = true
else
MTH_AutoBuy._bridgeActive = false
end
return
end
if frame then
frame:UnregisterEvent("MERCHANT_SHOW")
frame:UnregisterEvent("MERCHANT_UPDATE")
frame:UnregisterEvent("MERCHANT_CLOSED")
end
MTH_AutoBuy._bridgeActive = false
end
AB_Engine = function()
return MTH_AutoBuyEngine
end
@@ -83,15 +93,12 @@ function MTH_AutoBuy:init()
local engine = AB_Engine()
if not engine then
AB_Log("init failed: engine missing", "error")
AB_Trace("init failed: engine missing")
return
end
engine:Init()
AB_EnsureBridgeFrame(self)
self.initialized = true
AB_Trace("init done")
AB_SetBridgeActive(self.enabled and true or false)
if self.enabled then
AB_Log("module initialized", "debug")
end
end
@@ -101,53 +108,38 @@ function MTH_AutoBuy:setEnabled(enabled)
end
local engine = AB_Engine()
if not engine then
AB_Trace("setEnabled ignored: engine missing")
return
end
local store = engine:EnsureDefaults()
store.enabled = enabled and true or false
AB_EnsureBridgeFrame(self)
AB_Trace("setEnabled store.enabled=" .. tostring(store.enabled))
AB_SetBridgeActive(store.enabled and true or false)
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_EnsureBridgeFrame(self)
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))
AB_SetBridgeActive(self.enabled and true or false)
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
@@ -155,7 +147,6 @@ function MTH_AutoBuy:onEvent(event)
end
function MTH_AutoBuy:cleanup()
AB_Log("module cleanup", "debug")
end
MTH:RegisterModule("autobuy", MTH_AutoBuy)
+6
View File
@@ -0,0 +1,6 @@
<!--
MetaHunt Auto Quest Module Loader
-->
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<Include file="module.lua"/>
</Ui>
+952
View File
@@ -0,0 +1,952 @@
------------------------------------------------------
-- MetaHunt: Quest Automation Module
-- SHIFT-rightclick automation for specific quest flows
------------------------------------------------------
local MTH_AutoQuest = {
name = "autoquest",
enabled = false,
version = "1.4.1",
events = {
"GOSSIP_SHOW",
"QUEST_GREETING",
"QUEST_DETAIL",
"QUEST_PROGRESS",
"QUEST_COMPLETE",
},
initialized = false,
}
local QA_SCORPOK_QUEST_ID = 2586
local QA_SCORPOK_QUEST_TITLE = "Salt of the Scorpok"
local QA_SCORPOK_NPC_ID = 7505
local QA_SCORPOK_NPC_NAME = "Bloodmage Drazial"
local QA_SISSIES_QUEST_ID = 7342
local QA_SISSIES_QUEST_TITLE = "Arrows Are For Sissies"
local QA_SISSIES_NPC_ID = 14183
local QA_SISSIES_NPC_NAME = "Artilleryman Sheldonore"
local QA_ITEM_SCORPOK_PINCER_ID = 8393
local QA_ITEM_BLASTED_BOAR_LUNG_ID = 8392
local QA_ITEM_VULTURE_GIZZARD_ID = 8396
local QA_ITEM_GROUND_SCORPOK_ASSAY_ID = 8412
local QA_ITEM_SCORPOK_PINCER_NAME = "Scorpok Pincer"
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_TooltipHooked = false
local QA_TooltipPollFrame = nil
local QA_TooltipRefreshFrame = nil
local function QA_Debug(message)
return
end
local function QA_CopyValue(value)
if type(value) ~= "table" then
return value
end
local copy = {}
for key, entry in pairs(value) do
copy[key] = QA_CopyValue(entry)
end
return copy
end
local function QA_GetOptionsStore()
if type(MTH_CharSavedVariables) ~= "table" then
MTH_CharSavedVariables = {}
end
if type(MTH_CharSavedVariables.autoquest) ~= "table" then
MTH_CharSavedVariables.autoquest = {}
end
local store = MTH_CharSavedVariables.autoquest
if store._migrated ~= true then
if type(MTH_CharSavedVariables.questautomation) == "table" then
local legacyStore = MTH_CharSavedVariables.questautomation
if legacyStore.scorpokDrazial ~= nil and store.scorpokDrazial == nil then
store.scorpokDrazial = legacyStore.scorpokDrazial and true or false
end
if legacyStore.scorpokTooltip ~= nil and store.scorpokTooltip == nil then
store.scorpokTooltip = legacyStore.scorpokTooltip and true or false
end
end
local legacyChar = nil
local legacyAccount = nil
if MTH and MTH.GetModuleCharSavedVariables then
legacyChar = MTH:GetModuleCharSavedVariables("autoquest")
if type(legacyChar) ~= "table" then
legacyChar = MTH:GetModuleCharSavedVariables("questautomation")
end
end
if MTH and MTH.GetModuleSavedVariables then
legacyAccount = MTH:GetModuleSavedVariables("autoquest")
if type(legacyAccount) ~= "table" then
legacyAccount = MTH:GetModuleSavedVariables("questautomation")
end
end
if type(legacyChar) == "table" and legacyChar.scorpokDrazial ~= nil and store.scorpokDrazial == nil then
store.scorpokDrazial = legacyChar.scorpokDrazial and true or false
elseif type(legacyAccount) == "table" and legacyAccount.scorpokDrazial ~= nil and store.scorpokDrazial == nil then
store.scorpokDrazial = legacyAccount.scorpokDrazial and true or false
elseif store.scorpokDrazial == nil and MTH and MTH.GetModuleCharSavedVariables then
local autobuyStore = MTH:GetModuleCharSavedVariables("autobuy")
if type(autobuyStore) == "table"
and type(autobuyStore.questAutomation) == "table"
and autobuyStore.questAutomation.scorpokDrazial ~= nil then
store.scorpokDrazial = autobuyStore.questAutomation.scorpokDrazial and true or false
end
end
store._migrated = true
end
if store.scorpokDrazial == nil then
store.scorpokDrazial = false
end
if store.arrowsForSissies == nil then
store.arrowsForSissies = false
end
if store.scorpokTooltip == nil then
store.scorpokTooltip = false
end
if MTH and MTH.GetModuleCharSavedVariables then
local moduleStore = MTH:GetModuleCharSavedVariables("autoquest")
if type(moduleStore) == "table" then
moduleStore.scorpokDrazial = store.scorpokDrazial and true or false
moduleStore.arrowsForSissies = store.arrowsForSissies and true or false
moduleStore.scorpokTooltip = store.scorpokTooltip and true or false
end
end
return store
end
function MTH_AutoQuest:GetStore()
return QA_GetOptionsStore()
end
function MTH_AutoQuest:SetScorpokDrazialEnabled(enabled)
local store = QA_GetOptionsStore()
store.scorpokDrazial = enabled and true or false
if MTH and MTH.GetModuleCharSavedVariables then
local moduleStore = MTH:GetModuleCharSavedVariables("autoquest")
if type(moduleStore) == "table" then
moduleStore.scorpokDrazial = store.scorpokDrazial and true or false
end
end
QA_Debug("option write: scorpokDrazial=" .. tostring(store.scorpokDrazial and true or false))
end
function MTH_AutoQuest:SetArrowsForSissiesEnabled(enabled)
local store = QA_GetOptionsStore()
store.arrowsForSissies = enabled and true or false
if MTH and MTH.GetModuleCharSavedVariables then
local moduleStore = MTH:GetModuleCharSavedVariables("autoquest")
if type(moduleStore) == "table" then
moduleStore.arrowsForSissies = store.arrowsForSissies and true or false
end
end
QA_Debug("option write: arrowsForSissies=" .. tostring(store.arrowsForSissies and true or false))
end
function MTH_AutoQuest:SetScorpokTooltipEnabled(enabled)
local store = QA_GetOptionsStore()
store.scorpokTooltip = enabled and true or false
if MTH and MTH.GetModuleCharSavedVariables then
local moduleStore = MTH:GetModuleCharSavedVariables("autoquest")
if type(moduleStore) == "table" then
moduleStore.scorpokTooltip = store.scorpokTooltip and true or false
end
end
QA_Debug("option write: scorpokTooltip=" .. tostring(store.scorpokTooltip and true or false))
end
local function QA_IsScorpokAutomationEnabled()
local store = QA_GetOptionsStore()
return store and store.scorpokDrazial and true or false
end
local function QA_IsArrowsForSissiesAutomationEnabled()
local store = QA_GetOptionsStore()
return store and store.arrowsForSissies and true or false
end
local function QA_IsScorpokTooltipEnabled()
local store = QA_GetOptionsStore()
return store and store.scorpokTooltip and true or false
end
local function QA_NormalizeText(value)
local text = string.lower(tostring(value or ""))
text = string.gsub(text, "[^%a%d]", "")
return text
end
local function QA_IsTitleMatch(title, expectedTitle)
if type(title) ~= "string" or title == "" then
return false
end
return QA_NormalizeText(title) == QA_NormalizeText(expectedTitle)
end
local function QA_IsScorpokTitle(title)
return QA_IsTitleMatch(title, QA_SCORPOK_QUEST_TITLE)
end
local function QA_IsArrowsForSissiesTitle(title)
return QA_IsTitleMatch(title, QA_SISSIES_QUEST_TITLE)
end
local function QA_IsQuestContext(title, expectedQuestId, titleMatcher)
if type(GetQuestID) == "function" then
local questId = tonumber(GetQuestID())
if questId and questId == expectedQuestId then
return true
end
end
if type(titleMatcher) == "function" then
return titleMatcher(title)
end
return false
end
local function QA_IsScorpokQuestContext(title)
return QA_IsQuestContext(title, QA_SCORPOK_QUEST_ID, QA_IsScorpokTitle)
end
local function QA_IsArrowsForSissiesQuestContext(title)
return QA_IsQuestContext(title, QA_SISSIES_QUEST_ID, QA_IsArrowsForSissiesTitle)
end
local function QA_GetNpcIdFromGuid(guid)
if type(guid) ~= "string" or guid == "" then
return nil
end
local _, _, _, _, _, npcId = string.find(guid, "^Creature%-%d+%-%d+%-%d+%-%d+%-(%d+)%-%x+$")
if npcId then
return tonumber(npcId)
end
if string.sub(guid, 1, 2) == "0x" and string.len(guid) >= 12 then
local hex = string.sub(guid, 9, 12)
local parsed = tonumber(hex, 16)
if parsed and parsed > 0 then
return parsed
end
end
return nil
end
local function QA_ColorByThreshold(count, threshold)
local value = math.floor(tonumber(count) or 0)
if value <= 0 then
return "|cffff0000" .. tostring(value) .. "|r"
end
local modFn = nil
if type(rawget) == "function" and type(math) == "table" then
modFn = rawget(math, "mod") or rawget(math, "fmod")
elseif type(math) == "table" then
modFn = math.fmod
end
if threshold > 0 and type(modFn) == "function" and modFn(value, threshold) == 0 then
return "|cff00ff00" .. tostring(value) .. "|r"
end
return "|cffffa500" .. tostring(value) .. "|r"
end
local function QA_ColorAssay(count)
local value = math.floor(tonumber(count) or 0)
if value > 0 then
return "|cff66ccff" .. tostring(value) .. "|r"
end
return tostring(value)
end
local function QA_MatchesItem(itemId, itemName, expectedId, expectedName)
if expectedId and tonumber(itemId) == tonumber(expectedId) then
return true
end
if type(itemName) == "string" and type(expectedName) == "string" and QA_NormalizeText(itemName) == QA_NormalizeText(expectedName) then
return true
end
return false
end
local function QA_CountItemInBags(expectedId, expectedName)
local getContainerNumSlots = _G and _G["GetContainerNumSlots"] or nil
local getContainerItemLink = _G and _G["GetContainerItemLink"] or nil
local getContainerItemInfo = _G and _G["GetContainerItemInfo"] or nil
local getItemInfo = _G and _G["GetItemInfo"] or nil
if type(getContainerNumSlots) ~= "function" or type(getContainerItemLink) ~= "function" then
return 0
end
local total = 0
for bag = 0, 4 do
local slots = math.floor(tonumber(getContainerNumSlots(bag)) or 0)
for slot = 1, slots do
local link = getContainerItemLink(bag, slot)
if type(link) == "string" and link ~= "" then
local _, _, itemIdStr = string.find(link, "item:(%d+)")
local itemId = tonumber(itemIdStr)
local itemName = nil
if type(getItemInfo) == "function" then
itemName = getItemInfo(link)
end
if QA_MatchesItem(itemId, itemName, expectedId, expectedName) then
local texture, stackCount = nil, nil
if type(getContainerItemInfo) == "function" then
texture, stackCount = getContainerItemInfo(bag, slot)
end
total = total + math.floor(tonumber(stackCount) or 1)
end
end
end
end
return total
end
local function QA_TooltipTrace(message, force)
return
end
local function QA_GetTooltipNpc(tooltip)
if not tooltip or type(tooltip.GetUnit) ~= "function" then
return nil, nil, nil, nil
end
local ok, unitA, unitB = pcall(function()
return tooltip:GetUnit()
end)
local unit = nil
if ok then
if type(unitB) == "string" and unitB ~= "" then
unit = unitB
elseif type(unitA) == "string" and unitA ~= "" then
unit = unitA
end
end
if (type(unit) ~= "string" or unit == "") and type(UnitExists) == "function" and UnitExists("mouseover") then
unit = "mouseover"
end
if type(unit) ~= "string" or unit == "" then
local left1 = (type(getglobal) == "function" and getglobal("GameTooltipTextLeft1")) or (_G and _G["GameTooltipTextLeft1"])
local title = left1 and left1.GetText and left1:GetText() or nil
if type(title) == "string" and title ~= "" then
return nil, title, nil, title
end
return nil, nil, nil, nil
end
local guid = UnitGUID and UnitGUID(unit) or nil
local npcId = QA_GetNpcIdFromGuid(guid)
local name = UnitName and UnitName(unit) or nil
local title = nil
if (type(name) ~= "string" or name == "") then
local left1 = (type(getglobal) == "function" and getglobal("GameTooltipTextLeft1")) or (_G and _G["GameTooltipTextLeft1"])
title = left1 and left1.GetText and left1:GetText() or nil
if type(title) == "string" and title ~= "" then
name = title
end
end
return npcId, name, unit, title
end
local function QA_TooltipAlreadyExtended(tooltip)
if not tooltip or type(tooltip.NumLines) ~= "function" then
return false
end
local lines = math.floor(tonumber(tooltip:NumLines()) or 0)
for i = 1, lines do
local line = (type(getglobal) == "function" and getglobal("GameTooltipTextLeft" .. tostring(i))) or (_G and _G["GameTooltipTextLeft" .. tostring(i)])
local text = line and line.GetText and line:GetText() or nil
if type(text) == "string" and QA_NormalizeText(text) == QA_NormalizeText("Quest Automation") then
return true
end
end
return false
end
local function QA_IsTooltipDrazial(tooltip)
local npcId, npcName = QA_GetTooltipNpc(tooltip)
if tonumber(npcId) == QA_SCORPOK_NPC_ID then
return true
end
if type(npcName) == "string" and npcName ~= "" and QA_NormalizeText(npcName) == QA_NormalizeText(QA_SCORPOK_NPC_NAME) then
return true
end
return false
end
local function QA_AppendDrazialTooltip(tooltip, sourceTag)
sourceTag = tostring(sourceTag or "unknown")
if not (MTH_AutoQuest and MTH_AutoQuest.enabled) then
QA_TooltipTrace("skip: module disabled source=" .. sourceTag)
return
end
if not QA_IsScorpokAutomationEnabled() or not QA_IsScorpokTooltipEnabled() then
QA_TooltipTrace("skip: option disabled source=" .. sourceTag)
return
end
local npcId, npcName, unitToken, title = QA_GetTooltipNpc(tooltip)
local isMatch = tonumber(npcId) == QA_SCORPOK_NPC_ID
if not isMatch and type(npcName) == "string" and npcName ~= "" and QA_NormalizeText(npcName) == QA_NormalizeText(QA_SCORPOK_NPC_NAME) then
isMatch = true
end
if not isMatch then
QA_TooltipTrace("skip: npc mismatch source=" .. sourceTag
.. " unit=" .. tostring(unitToken)
.. " id=" .. tostring(npcId)
.. " name='" .. tostring(npcName or "") .. "'"
.. " title='" .. tostring(title or "") .. "'")
return
end
if QA_TooltipAlreadyExtended(tooltip) then
QA_TooltipTrace("skip: already-extended source=" .. sourceTag)
return
end
local pincerCount = QA_CountItemInBags(QA_ITEM_SCORPOK_PINCER_ID, QA_ITEM_SCORPOK_PINCER_NAME)
local gizzardCount = QA_CountItemInBags(QA_ITEM_VULTURE_GIZZARD_ID, QA_ITEM_VULTURE_GIZZARD_NAME)
local lungCount = QA_CountItemInBags(QA_ITEM_BLASTED_BOAR_LUNG_ID, QA_ITEM_BLASTED_BOAR_LUNG_NAME)
local assayCount = QA_CountItemInBags(QA_ITEM_GROUND_SCORPOK_ASSAY_ID, QA_ITEM_GROUND_SCORPOK_ASSAY_NAME)
tooltip:AddLine(" ")
tooltip:AddLine("|cffffff00Quest Automation|r")
tooltip:AddLine("Scorpok Pincer: " .. QA_ColorByThreshold(pincerCount, 3), 1, 1, 1)
tooltip:AddLine("Vulture Gizzard: " .. QA_ColorByThreshold(gizzardCount, 2), 1, 1, 1)
tooltip:AddLine("Blasted Boar Lung: " .. QA_ColorByThreshold(lungCount, 1), 1, 1, 1)
tooltip:AddLine("Ground Scorpok Assay: " .. QA_ColorAssay(assayCount), 1, 1, 1)
tooltip:AddLine("Spam SHIFT-click!", 0.7, 0.9, 1)
tooltip:AddLine("Recipes: 3x Pincer, 2x Gizzard, 1x Lung", 0.7, 0.7, 0.7)
tooltip:Show()
QA_TooltipTrace("added: source=" .. sourceTag
.. " id=" .. tostring(npcId)
.. " name='" .. tostring(npcName or "") .. "'", true)
end
local function QA_EnsureTooltipHook()
if QA_TooltipHooked then
return
end
if not GameTooltip then
return
end
local hasGetScript = type(GameTooltip.GetScript) == "function"
local hasSetScript = type(GameTooltip.SetScript) == "function"
local hasHookScript = type(GameTooltip.HookScript) == "function"
local onTooltipSetUnitExists = false
local onShowExists = false
if hasGetScript then
local okSetUnit, existingSetUnit = pcall(function()
return GameTooltip:GetScript("OnTooltipSetUnit")
end)
onTooltipSetUnitExists = okSetUnit and type(existingSetUnit) == "function"
local okShow, existingShow = pcall(function()
return GameTooltip:GetScript("OnShow")
end)
onShowExists = okShow and type(existingShow) == "function"
end
if hasHookScript and onTooltipSetUnitExists then
pcall(function()
GameTooltip:HookScript("OnTooltipSetUnit", function()
QA_AppendDrazialTooltip(this, "hook:setunit")
end)
end)
elseif hasSetScript and hasGetScript and onTooltipSetUnitExists then
local okGetPrev, previous = pcall(function()
return GameTooltip:GetScript("OnTooltipSetUnit")
end)
if okGetPrev then
pcall(function()
GameTooltip:SetScript("OnTooltipSetUnit", function()
if type(previous) == "function" then
previous()
end
QA_AppendDrazialTooltip(this, "script:setunit")
end)
end)
end
end
if hasHookScript and onShowExists then
pcall(function()
GameTooltip:HookScript("OnShow", function()
QA_AppendDrazialTooltip(this, "hook:show")
end)
end)
elseif hasSetScript and hasGetScript and onShowExists then
local okGetPrevShow, previousShow = pcall(function()
return GameTooltip:GetScript("OnShow")
end)
if okGetPrevShow then
pcall(function()
GameTooltip:SetScript("OnShow", function()
if type(previousShow) == "function" then
previousShow()
end
QA_AppendDrazialTooltip(this, "script:show")
end)
end)
end
end
QA_TooltipHooked = true
end
local function QA_EnsureTooltipPoller()
if QA_TooltipPollFrame then
return
end
local frame = CreateFrame("Frame", "MTH_AQTooltipPoll")
if not frame then
return
end
frame._mthElapsed = 0
frame:SetScript("OnUpdate", function()
this._mthElapsed = (this._mthElapsed or 0) + (arg1 or 0)
if this._mthElapsed < 0.20 then
return
end
this._mthElapsed = 0
if not (MTH_AutoQuest and MTH_AutoQuest.enabled) then
return
end
if GameTooltip and type(GameTooltip.IsVisible) == "function" and GameTooltip:IsVisible() then
QA_AppendDrazialTooltip(GameTooltip, "poll")
end
end)
QA_TooltipPollFrame = frame
end
local function QA_GetInteractedNpcId()
local getGuid = _G and _G["UnitGUID"] or nil
if type(getGuid) ~= "function" then
return nil
end
local npcGuid = getGuid("npc")
local npcId = QA_GetNpcIdFromGuid(npcGuid)
if npcId then
return npcId
end
return QA_GetNpcIdFromGuid(getGuid("target"))
end
local function QA_GetInteractedNpcName()
local getName = _G and _G["UnitName"] or nil
if type(getName) ~= "function" then
return nil
end
local name = getName("npc")
if type(name) == "string" and name ~= "" then
return name
end
name = getName("target")
if type(name) == "string" and name ~= "" then
return name
end
return nil
end
local function QA_IsInteractedNpc(expectedNpcId, expectedNpcName)
local npcId = QA_GetInteractedNpcId()
if tonumber(npcId) == tonumber(expectedNpcId) then
return true
end
local npcName = QA_GetInteractedNpcName()
if type(npcName) == "string" and npcName ~= "" and QA_NormalizeText(npcName) == QA_NormalizeText(expectedNpcName) then
return true
end
return false
end
local function QA_IsScorpokNpc()
return QA_IsInteractedNpc(QA_SCORPOK_NPC_ID, QA_SCORPOK_NPC_NAME)
end
local function QA_IsArrowsForSissiesNpc()
return QA_IsInteractedNpc(QA_SISSIES_NPC_ID, QA_SISSIES_NPC_NAME)
end
local function QA_RefreshMouseoverTooltip()
local tooltip = _G and _G["GameTooltip"] or GameTooltip
if not tooltip then
return
end
if type(UnitExists) == "function" and UnitExists("mouseover") and type(tooltip.SetUnit) == "function" then
tooltip:SetUnit("mouseover")
if type(tooltip.Show) == "function" then
tooltip:Show()
end
end
end
local function QA_RequestTooltipRefresh(delaySeconds)
delaySeconds = tonumber(delaySeconds) or 0.10
if not QA_TooltipRefreshFrame then
QA_TooltipRefreshFrame = CreateFrame("Frame", "MTH_AQTooltipRefresh")
if not QA_TooltipRefreshFrame then
return
end
QA_TooltipRefreshFrame:SetScript("OnUpdate", function()
local frameRef = this or QA_TooltipRefreshFrame
if not frameRef then
return
end
frameRef._elapsed = (frameRef._elapsed or 0) + (arg1 or 0)
if frameRef._elapsed < (frameRef._delay or 0.10) then
return
end
frameRef._elapsed = 0
frameRef._delay = 0
frameRef:SetScript("OnUpdate", nil)
QA_RefreshMouseoverTooltip()
end)
end
QA_TooltipRefreshFrame._delay = delaySeconds
QA_TooltipRefreshFrame._elapsed = 0
QA_TooltipRefreshFrame:SetScript("OnUpdate", QA_TooltipRefreshFrame:GetScript("OnUpdate"))
end
local QA_PROFILE_SCORPOK = {
key = "scorpokDrazial",
label = "Scorpok",
optionCheck = QA_IsScorpokAutomationEnabled,
npcCheck = QA_IsScorpokNpc,
titleCheck = QA_IsScorpokTitle,
questContextCheck = QA_IsScorpokQuestContext,
lastActionField = "_scorpokLastActionAt",
refreshTooltipOnComplete = true,
}
local QA_PROFILE_SISSIES = {
key = "arrowsForSissies",
label = "ArrowsForSissies",
optionCheck = QA_IsArrowsForSissiesAutomationEnabled,
npcCheck = QA_IsArrowsForSissiesNpc,
titleCheck = QA_IsArrowsForSissiesTitle,
questContextCheck = QA_IsArrowsForSissiesQuestContext,
lastActionField = "_sissiesLastActionAt",
refreshTooltipOnComplete = false,
}
local QA_LastActionAt = {}
local function QA_GetLastActionAt(profile)
local key = type(profile) == "table" and tostring(profile.lastActionField or "") or ""
if key == "" then
return 0
end
return tonumber(QA_LastActionAt[key]) or 0
end
local function QA_SetLastActionAt(profile, value)
local key = type(profile) == "table" and tostring(profile.lastActionField or "") or ""
if key == "" then
return
end
QA_LastActionAt[key] = tonumber(value) or 0
end
local function QA_CanRunProfile(profile)
if type(profile) ~= "table" then
return false
end
if type(profile.optionCheck) ~= "function" or not profile.optionCheck() then
QA_Debug("[" .. tostring(profile.label or "AutoQuest") .. "] skip: option disabled")
return false
end
if type(IsShiftKeyDown) ~= "function" or not IsShiftKeyDown() then
QA_Debug("[" .. tostring(profile.label or "AutoQuest") .. "] skip: SHIFT not held")
return false
end
local npcOk = type(profile.npcCheck) == "function" and profile.npcCheck() or false
if not npcOk then
QA_Debug("[" .. tostring(profile.label or "AutoQuest") .. "] skip: NPC mismatch (id=" .. tostring(QA_GetInteractedNpcId()) .. ", name='" .. tostring(QA_GetInteractedNpcName() or "") .. "')")
end
return npcOk
end
local function QA_FindGossipQuestIndex(rawEntries, count, profile)
if type(rawEntries) ~= "table" or tonumber(count) == nil or tonumber(count) <= 0 then
return nil
end
if type(profile) ~= "table" or type(profile.titleCheck) ~= "function" then
return nil
end
local entryCount = math.floor(tonumber(count) or 0)
local tupleSize = math.floor(table.getn(rawEntries) / entryCount)
if tupleSize < 1 then
tupleSize = 1
end
for i = 1, entryCount do
local title = tostring(rawEntries[((i - 1) * tupleSize) + 1] or "")
if profile.titleCheck(title) then
return i
end
end
if entryCount == 1 and type(profile.npcCheck) == "function" and profile.npcCheck() then
QA_Debug("[" .. tostring(profile.label or "AutoQuest") .. "] gossip fallback: single quest entry -> selecting index 1")
return 1
end
return nil
end
local function QA_FindQuestGreetingIndex(isActive, profile)
local getCount = isActive and _G and _G["GetNumActiveQuests"] or _G and _G["GetNumAvailableQuests"]
local getTitle = isActive and _G and _G["GetActiveTitle"] or _G and _G["GetAvailableTitle"]
if type(getCount) ~= "function" or type(getTitle) ~= "function" or type(profile) ~= "table" or type(profile.titleCheck) ~= "function" then
return nil
end
local count = math.floor(tonumber(getCount()) or 0)
if count <= 0 then
return nil
end
for i = 1, count do
local title = tostring(getTitle(i) or "")
if profile.titleCheck(title) then
return i
end
end
if count == 1 and type(profile.npcCheck) == "function" and profile.npcCheck() then
QA_Debug("[" .. tostring(profile.label or "AutoQuest") .. "] quest greeting fallback: single quest entry -> selecting index 1")
return 1
end
return nil
end
local function QA_GetFrameText(frame)
if type(frame) ~= "table" then
return ""
end
if type(frame.GetText) == "function" then
return tostring(frame:GetText() or "")
end
if type(frame.GetName) == "function" then
local name = frame:GetName()
if type(name) == "string" and name ~= "" then
local textRegion = _G and _G[name .. "Text"] or nil
if textRegion and type(textRegion.GetText) == "function" then
return tostring(textRegion:GetText() or "")
end
end
end
return ""
end
local function QA_ClickGossipTitleButtonFallback(profile)
if type(profile) ~= "table" or type(profile.titleCheck) ~= "function" then
return false
end
for i = 1, 40 do
local button = _G and _G["GossipTitleButton" .. tostring(i)] or nil
if button then
local visible = true
if type(button.IsVisible) == "function" then
visible = button:IsVisible() and true or false
end
if visible then
local title = QA_GetFrameText(button)
if profile.titleCheck(title) then
QA_Debug("action: GossipTitleButton" .. tostring(i) .. " click title='" .. tostring(title) .. "'")
if type(button.Click) == "function" then
button:Click()
return true
end
local onClick = type(button.GetScript) == "function" and button:GetScript("OnClick") or nil
if type(onClick) == "function" then
onClick(button)
return true
end
end
end
end
end
return false
end
local function QA_RunProfileAutomation(eventName, profile)
if type(profile) ~= "table" then
return
end
QA_Debug("[" .. tostring(profile.label or "AutoQuest") .. "] event=" .. tostring(eventName))
if not QA_CanRunProfile(profile) then
return
end
local getTimeFn = _G and _G["GetTime"] or nil
if eventName == "GOSSIP_SHOW" and type(getTimeFn) == "function" then
local now = tonumber(getTimeFn()) or 0
local last = QA_GetLastActionAt(profile)
if (now - last) < 0.05 then
QA_Debug("skip: throttled dt=" .. tostring(now - last))
return
end
end
if eventName == "QUEST_GREETING" then
local selectAvailableQuest = _G and _G["SelectAvailableQuest"] or nil
local selectActiveQuest = _G and _G["SelectActiveQuest"] or nil
local availableIndex = QA_FindQuestGreetingIndex(false, profile)
if availableIndex and type(selectAvailableQuest) == "function" then
QA_Debug("action: SelectAvailableQuest (compat) index=" .. tostring(availableIndex))
selectAvailableQuest()
QA_SetLastActionAt(profile, type(getTimeFn) == "function" and getTimeFn() or 0)
return
end
local activeIndex = QA_FindQuestGreetingIndex(true, profile)
if activeIndex and type(selectActiveQuest) == "function" then
QA_Debug("action: SelectActiveQuest (compat) index=" .. tostring(activeIndex))
selectActiveQuest()
QA_SetLastActionAt(profile, type(getTimeFn) == "function" and getTimeFn() or 0)
return
end
for i = 1, 40 do
local button = _G and _G["QuestTitleButton" .. tostring(i)] or nil
if button and (type(button.IsVisible) ~= "function" or button:IsVisible()) then
local title = QA_GetFrameText(button)
if profile.titleCheck(title) then
QA_Debug("action: QuestTitleButton" .. tostring(i) .. " click title='" .. tostring(title) .. "'")
if type(button.Click) == "function" then
button:Click()
QA_SetLastActionAt(profile, type(getTimeFn) == "function" and getTimeFn() or 0)
return
end
end
end
end
QA_Debug("quest greeting: no action taken")
return
end
if eventName == "GOSSIP_SHOW" then
local getNumGossipAvailableQuests = _G and _G["GetNumGossipAvailableQuests"] or nil
local getGossipAvailableQuests = _G and _G["GetGossipAvailableQuests"] or nil
local selectGossipAvailableQuest = _G and _G["SelectGossipAvailableQuest"] or nil
QA_Debug("api available: numAvail=" .. tostring(type(getNumGossipAvailableQuests) == "function")
.. " getAvail=" .. tostring(type(getGossipAvailableQuests) == "function")
.. " selAvail=" .. tostring(type(selectGossipAvailableQuest) == "function"))
if type(getNumGossipAvailableQuests) == "function" and type(getGossipAvailableQuests) == "function" and type(selectGossipAvailableQuest) == "function" then
local availableCount = math.floor(tonumber(getNumGossipAvailableQuests()) or 0)
QA_Debug("gossip available count=" .. tostring(availableCount))
if availableCount > 0 then
local availableIndex = QA_FindGossipQuestIndex({ getGossipAvailableQuests() }, availableCount, profile)
if availableIndex then
QA_Debug("action: SelectGossipAvailableQuest index=" .. tostring(availableIndex))
selectGossipAvailableQuest(availableIndex)
QA_SetLastActionAt(profile, type(getTimeFn) == "function" and getTimeFn() or 0)
return
end
QA_Debug("gossip available: no Scorpok match")
end
end
local getNumGossipActiveQuests = _G and _G["GetNumGossipActiveQuests"] or nil
local getGossipActiveQuests = _G and _G["GetGossipActiveQuests"] or nil
local selectGossipActiveQuest = _G and _G["SelectGossipActiveQuest"] or nil
QA_Debug("api active: numActive=" .. tostring(type(getNumGossipActiveQuests) == "function")
.. " getActive=" .. tostring(type(getGossipActiveQuests) == "function")
.. " selActive=" .. tostring(type(selectGossipActiveQuest) == "function"))
if type(getNumGossipActiveQuests) == "function" and type(getGossipActiveQuests) == "function" and type(selectGossipActiveQuest) == "function" then
local activeCount = math.floor(tonumber(getNumGossipActiveQuests()) or 0)
QA_Debug("gossip active count=" .. tostring(activeCount))
if activeCount > 0 then
local activeIndex = QA_FindGossipQuestIndex({ getGossipActiveQuests() }, activeCount, profile)
if activeIndex then
QA_Debug("action: SelectGossipActiveQuest index=" .. tostring(activeIndex))
selectGossipActiveQuest(activeIndex)
QA_SetLastActionAt(profile, type(getTimeFn) == "function" and getTimeFn() or 0)
return
end
QA_Debug("gossip active: no Scorpok match")
end
end
if QA_ClickGossipTitleButtonFallback(profile) then
QA_SetLastActionAt(profile, type(getTimeFn) == "function" and getTimeFn() or 0)
return
end
QA_Debug("gossip: no action taken")
return
end
local title = type(GetTitleText) == "function" and tostring(GetTitleText() or "") or ""
QA_Debug("quest frame title='" .. tostring(title) .. "'")
if type(profile.questContextCheck) ~= "function" or not profile.questContextCheck(title) then
QA_Debug("skip: quest context mismatch")
return
end
if eventName == "QUEST_DETAIL" then
if type(AcceptQuest) == "function" then
QA_Debug("action: AcceptQuest")
AcceptQuest()
QA_SetLastActionAt(profile, type(getTimeFn) == "function" and getTimeFn() or 0)
end
elseif eventName == "QUEST_PROGRESS" then
if type(CompleteQuest) == "function" then
if type(IsQuestCompletable) ~= "function" or IsQuestCompletable() then
QA_Debug("action: CompleteQuest")
CompleteQuest()
QA_SetLastActionAt(profile, type(getTimeFn) == "function" and getTimeFn() or 0)
else
QA_Debug("skip: quest not completable yet")
end
end
elseif eventName == "QUEST_COMPLETE" then
if type(GetQuestReward) == "function" then
local choices = type(GetNumQuestChoices) == "function" and math.floor(tonumber(GetNumQuestChoices()) or 0) or 0
if choices <= 1 then
QA_Debug("action: GetQuestReward(1)")
GetQuestReward(1)
QA_SetLastActionAt(profile, type(getTimeFn) == "function" and getTimeFn() or 0)
if profile.refreshTooltipOnComplete then
QA_RequestTooltipRefresh(0.10)
end
else
QA_Debug("skip: multiple reward choices=" .. tostring(choices))
end
end
end
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_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)
.. ", option scorpokTooltip=" .. tostring(store and store.scorpokTooltip and true or false))
end
function MTH_AutoQuest:setEnabled(enabled)
self.enabled = enabled and true or false
end
function MTH_AutoQuest:onEvent(eventName)
if eventName == "GOSSIP_SHOW"
or eventName == "QUEST_GREETING"
or eventName == "QUEST_DETAIL"
or eventName == "QUEST_PROGRESS"
or eventName == "QUEST_COMPLETE" then
QA_Debug("onEvent=" .. tostring(eventName))
QA_RunProfileAutomation(eventName, QA_PROFILE_SCORPOK)
QA_RunProfileAutomation(eventName, QA_PROFILE_SISSIES)
end
end
function MTH_AutoQuest:cleanup()
-- no-op: direct event frame removed in Phase 2
end
MTH:RegisterModule("autoquest", MTH_AutoQuest)
+20 -6
View File
@@ -28,6 +28,8 @@ function MTH_ChronometerHunterInstallData(engine)
engine:AddTimer(engine.SPELL, BS["Flare"], 30, 0, 0, 0, { cr = "YELLOW", rc = true })
engine:AddTimer(engine.SPELL, BS["Hunter's Mark"], 120, 1, 0, 0, { cr = "RED", rc = true })
engine:AddTimer(engine.SPELL, BS["Immolation Trap"], 60, 0, 0, 0, { gr = 1, cr = "ORANGE", ea = { [BS["Immolation Trap Effect"]] = 60 } })
engine:AddTimer(engine.SPELL, BS["Aimed Shot"], 20, 0, 0, 0, { cr = "RED", rc = true, d = { tn = BS["Swiftshot"], tb = -2, ts = -2 } }) -- base 20s; Swiftshot reduces by 2s/rank (max -6s at rank 3)
engine:AddTimer(engine.SPELL, BS["Kill Command"], 12, 0, 0, 0, { cr = "RED", rc = true })
engine:AddTimer(engine.SPELL, BS["Rapid Fire"], 15, 0, 1, 1, { cr = "RED" })
engine:AddTimer(engine.SPELL, BS["Scare Beast"], 10, 1, 0, 0, { cr = "BLUE", rc = true, d = { rs = 5 } })
engine:AddTimer(engine.SPELL, BS["Scatter Shot"], 4, 1, 0, 0, { cr = "MAROON" })
@@ -38,14 +40,26 @@ function MTH_ChronometerHunterInstallData(engine)
engine:AddTimer(engine.SPELL, BS["Wyvern Sting"], 12, 1, 0, 0, { gr = 2, cr = "MAROON", rc = true })
engine:AddTimer(engine.SPELL, BS["Feed Pet"], 20, 0, 0, 0, { cr = "MAROON" })
engine:AddTimer(engine.EVENT, BS["Explosive Trap Effect"], 20, 1, 0, 1, { gr = 1, cr = "ORANGE", a = 1, xn = BS["Explosive Trap"] })
engine:AddTimer(engine.EVENT, BS["Freezing Trap Effect"], 10, 1, 0, 1, { gr = 1, cr = "CYAN", a = 1, d = { rs = 5, tn = BS["Clever Traps"], tb = 0.15, tp = 1 }, xn = BS["Freezing Trap"] })
engine:AddTimer(engine.EVENT, BS["Frost Trap Aura"], 30, 0, 0, 0, { gr = 1, cr = "CYAN", a = 1, d = { tn = BS["Clever Traps"], tb = 0.15, tp = 1 }, xn = BS["Frost Trap"] })
engine:AddTimer(engine.EVENT, BS["Immolation Trap Effect"], 15, 1, 0, 1, { gr = 1, cr = "ORANGE", a = 1, xn = BS["Immolation Trap"] })
-- Trap effects: no "a=1" here — primed by the SPELL timer's "ea" chain (cast hook sets timer.v
-- and timer.t). Using "a=1" would trigger on any hunter's affliction message since
-- CHAT_MSG_SPELL_PERIODIC_CREATURE_BUFFS never embeds a caster name.
engine:AddTimer(engine.EVENT, BS["Explosive Trap Effect"], 20, 1, 0, 1, { gr = 1, cr = "ORANGE", xn = BS["Explosive Trap"] })
engine:AddTimer(engine.EVENT, BS["Freezing Trap Effect"], 10, 1, 0, 1, { gr = 1, cr = "CYAN", d = { rs = 5, tn = BS["Clever Traps"], tb = 0.15, tp = 1 }, xn = BS["Freezing Trap"] })
engine:AddTimer(engine.EVENT, BS["Frost Trap Aura"], 30, 0, 0, 0, { gr = 1, cr = "CYAN", d = { tn = BS["Clever Traps"], tb = 0.15, tp = 1 }, xn = BS["Frost Trap"] })
engine:AddTimer(engine.EVENT, BS["Immolation Trap Effect"], 15, 1, 0, 1, { gr = 1, cr = "ORANGE", xn = BS["Immolation Trap"] })
engine:AddTimer(engine.EVENT, BS["Improved Concussive Shot"], 3, 1, 0, 0, { a = 1, cr = "BLUE", xn = BS["Concussive Shot"] })
engine:AddTimer(engine.EVENT, BS["Improved Wing Clip"], 5, 1, 0, 0, { a = 1, cr = "YELLOW", xn = BS["Wing Clip"] })
engine:AddTimer(engine.EVENT, BS["Improved Wing Clip"], 5, 1, 0, 0, { a = 1, cr = "MAROON", xn = BS["Wing Clip"] })
engine:AddTimer(engine.EVENT, BS["Lock and Load"], 10, 0, 1, 1, { a = 1, cr = "MAROON", tx = "Interface\\Icons\\ability_hunter_lockandload" }) -- 10s (tooltip confirmed; expires on Aimed Shot cast)
engine:AddTimer(engine.EVENT, BS["Scent of Blood"], 8, 0, 1, 1, { a = 1, cr = "RED" })
engine:AddTimer(engine.EVENT, BS["Piercing Shot"], 8, 1, 0, 1, { a = 1, cr = "PURPLE", tx = "Interface\\Icons\\Ability_Hunter_DisarmingShot" })
engine:AddTimer(engine.EVENT, BS["Quick Shots"], 12, 0, 1, 1, { a = 1, cr = "MAROON", tx = "Interface\\Icons\\Ability_Hunter_Quickshot" })
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" })
+365 -31
View File
@@ -60,6 +60,34 @@ local latins = { I = 1, II = 2, III = 3, IV = 4, V = 5, VI = 6, VII = 7, VIII =
local BAR_GROUP = "MTHChronometer"
local PARSER_OWNER = "MTHChronometerHunter"
local function MTH_CHRON_FormatBarPosition(profile)
local bp = profile and profile.barposition
if type(bp) ~= "table" then
return "<none>"
end
return tostring(bp.point or "nil")
.. "/" .. tostring(bp.relativePoint or "nil")
.. " @(" .. tostring(bp.x) .. "," .. tostring(bp.y) .. ")"
end
local function MTH_CHRON_DescribePoint(frame)
if not (frame and frame.GetPoint) then
return "<no-frame>"
end
local point, relTo, relPoint, x, y = frame:GetPoint()
local relName = "nil"
if type(relTo) == "table" and relTo.GetName then
relName = tostring(relTo:GetName() or "<anon>")
elseif relTo then
relName = tostring(relTo)
end
return tostring(point or "nil")
.. "->" .. relName
.. ":" .. tostring(relPoint or "nil")
.. " @(" .. tostring(x) .. "," .. tostring(y) .. ")"
end
local COLOR_NAME_MAP = {
white = { 1.0, 1.0, 1.0 },
black = { 0.0, 0.0, 0.0 },
@@ -143,15 +171,70 @@ local function shallowCopy(source)
return dest
end
local function deepCopyValue(value)
if type(value) ~= "table" then
return value
end
local copy = {}
for key, entry in pairs(value) do
copy[key] = deepCopyValue(entry)
end
return copy
end
local function ensureProfile()
if not MTH or not MTH.GetModuleSavedVariables then
if not MTH or not MTH.GetModuleCharSavedVariables then
return shallowCopy(DEFAULTS)
end
local store = MTH:GetModuleSavedVariables("chronometer")
local store = MTH:GetModuleCharSavedVariables("chronometer")
if type(store) ~= "table" then
return shallowCopy(DEFAULTS)
end
if type(store.profile) ~= "table"
and type(MTH_CharSavedVariables) == "table"
and type(MTH_CharSavedVariables.chronometer) == "table"
then
local legacyChar = MTH_CharSavedVariables.chronometer
if type(legacyChar.profile) == "table" then
store.profile = deepCopyValue(legacyChar.profile)
elseif type(legacyChar.barposition) == "table" then
store.profile = { barposition = deepCopyValue(legacyChar.barposition) }
end
end
if type(store.profile) ~= "table"
and type(MTH_SavedVariables) == "table"
and type(MTH_SavedVariables.chronometer) == "table"
then
local legacyAccount = MTH_SavedVariables.chronometer
if type(legacyAccount.profile) == "table" then
store.profile = deepCopyValue(legacyAccount.profile)
elseif type(legacyAccount.barposition) == "table" then
store.profile = { barposition = deepCopyValue(legacyAccount.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)
end
end
if type(store.profile) ~= "table" then
store.profile = {}
end
if type(store.profile.barposition) ~= "table" and type(store.barposition) == "table" then
store.profile.barposition = deepCopyValue(store.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)
elseif type(accountStore.profile) == "table" and type(accountStore.profile.barposition) == "table" then
store.profile.barposition = deepCopyValue(accountStore.profile.barposition)
end
end
end
local profile = store.profile
for key, value in pairs(DEFAULTS) do
@@ -178,9 +261,61 @@ local function ensureProfile()
profile.disabledSpells.HUNTER = {}
end
return profile
end
local function MTH_CHRON_PersistAnchorPosition(self, reason)
if not (self and self.anchor and self.anchor.GetPoint) then
return false
end
if type(self.profile) ~= "table" then
return false
end
if type(self.profile.barposition) ~= "table" then
self.profile.barposition = {}
end
local point, _, relPoint, x, y = self.anchor:GetPoint()
x = tonumber(x)
y = tonumber(y)
if not (point and relPoint and x and y) then
return false
end
x = math.floor(x + 0.5)
y = math.floor(y + 0.5)
self.profile.barposition.point = point
self.profile.barposition.relativePoint = relPoint
self.profile.barposition.x = x
self.profile.barposition.y = y
return true
end
local function MTH_CHRON_RestoreAnchorFromProfile(self, reason)
if not (self and self.anchor and self.anchor.ClearAllPoints and self.anchor.SetPoint) then
return false
end
if type(self.profile) ~= "table" then
return false
end
local bp = self.profile.barposition
if type(bp) ~= "table" then
return false
end
local px = tonumber(bp.x)
local py = tonumber(bp.y)
local ppoint = tostring(bp.point or "")
local prelpoint = tostring(bp.relativePoint or "")
if not (px and py and ppoint ~= "") then
return false
end
if prelpoint == "" then
prelpoint = ppoint
end
self.anchor:ClearAllPoints()
self.anchor:SetPoint(ppoint, UIParent, prelpoint, px, py)
return true
end
local function MTH_CHRON_IsPersistedEnabled()
if MTH and MTH.IsModuleEnabled then
return MTH:IsModuleEnabled("chronometer", true) and true or false
@@ -252,22 +387,92 @@ local function parseRankNumber(text)
return 0
end
local function resolvePlayerClassBucket(profile)
profile = profile or {}
local buckets = profile.disabledSpells
if type(buckets) ~= "table" then
return "HUNTER"
end
local localizedClass, classToken = UnitClass("player")
local candidates = {
classToken,
localizedClass,
string.upper(tostring(classToken or "")),
string.upper(tostring(localizedClass or "")),
"HUNTER",
}
for i = 1, table.getn(candidates) do
local key = tostring(candidates[i] or "")
if key ~= "" and type(buckets[key]) == "table" then
return key
end
end
return tostring(classToken or localizedClass or "HUNTER")
end
local function normalizeTimerName(name)
local normalized = tostring(name or "")
normalized = string.gsub(normalized, "^%s+", "")
normalized = string.gsub(normalized, "%s+$", "")
normalized = string.gsub(normalized, "%s+", " ")
normalized = string.lower(normalized)
return normalized
end
local function isTimerDisabled(profile, timerClass, timerName)
local disabledSpells = profile and profile.disabledSpells
if type(disabledSpells) ~= "table" then
return false, "no-disabled-table"
end
local classBucket = disabledSpells[timerClass]
if type(classBucket) ~= "table" then
return false, "no-class-bucket"
end
if classBucket[timerName] ~= nil then
return true, "exact"
end
local wanted = normalizeTimerName(timerName)
if wanted == "" then
return false, "empty-name"
end
for key, value in pairs(classBucket) do
if value ~= nil and normalizeTimerName(key) == wanted then
return true, "normalized"
end
end
return false, "miss"
end
local function getActionSpell(slot)
if GetActionText(slot) or not HasAction(slot) then
return nil, 0
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
@@ -353,6 +558,9 @@ function Chronometer:OnEnable()
self.profile = ensureProfile()
self:SetCandyBarGroupGrowth(BAR_GROUP, self.profile.growup and true or false)
self:SetCandyBarGroupVerticalSpacing(BAR_GROUP, self.profile.spacing or 0)
if self.anchor then
end
MTH_CHRON_RestoreAnchorFromProfile(self, "OnEnable")
self.groups = {}
self.timers = {}
@@ -372,14 +580,18 @@ function Chronometer:OnEnable()
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_AURA_GONE_SELF", function(event, info) self:SPELL_FADE(event, info) end)
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_AURA_GONE_OTHER", function(event, info) self:SPELL_FADE(event, info) end)
-- Registered events:
-- SELF_BUFFS / SELF_DAMAGE → our own procs & self-damage (source == ParserLib_SELF confirmed)
-- CREATURE_DAMAGE → DOT ticks on creatures ("from your X" embeds source; filtered in SPELL_PERIODIC)
-- CREATURE_BUFFS → debuff APPLICATION on creatures ("X is afflicted by Y"; no source embedded).
-- Needed for non-ticking debuffs: Poisonous/Enchanted Ammunition, Piercing Shot.
-- Foreign-bar risk is mitigated in SPELL_PERIODIC by requiring victim == target
-- when source is nil and no confirmed cast record exists.
-- FRIENDLYPLAYER_*, HOSTILEPLAYER_* are other players' events → intentionally NOT registered.
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE", function(event, info) self:SPELL_PERIODIC(event, info) end)
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_PERIODIC_CREATURE_BUFFS", function(event, info) self:SPELL_PERIODIC(event, info) end)
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_BUFFS", function(event, info) self:SPELL_PERIODIC(event, info) end)
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE", function(event, info) self:SPELL_PERIODIC(event, info) end)
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS", function(event, info) self:SPELL_PERIODIC(event, info) end)
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE", function(event, info) self:SPELL_PERIODIC(event, info) end)
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS", function(event, info) self:SPELL_PERIODIC(event, info) end)
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_DAMAGE", function(event, info) self:SPELL_PERIODIC(event, info) end)
local enableRoM = false
for _, timer in pairs(self.timers[self.SPELL] or {}) do
@@ -430,16 +642,37 @@ function Chronometer:OnEnable()
self:RegisterEvent("SPELLCAST_INTERRUPTED")
self:RegisterEvent("SPELLCAST_START")
self:RegisterEvent("SPELLCAST_STOP")
self:RegisterEvent("UNIT_AURA")
self:RegisterEvent("PLAYER_DEAD")
self:RegisterEvent("VARIABLES_LOADED")
self:RegisterEvent("PLAYER_ENTERING_WORLD")
self:RegisterEvent("PLAYER_LOGOUT")
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)
end
function Chronometer:VARIABLES_LOADED()
self.profile = ensureProfile()
MTH_CHRON_RestoreAnchorFromProfile(self, "VARIABLES_LOADED")
end
function Chronometer:PLAYER_ENTERING_WORLD()
self.profile = ensureProfile()
MTH_CHRON_RestoreAnchorFromProfile(self, "PLAYER_ENTERING_WORLD")
end
function Chronometer:PLAYER_LOGOUT()
MTH_CHRON_PersistAnchorPosition(self, "PLAYER_LOGOUT")
end
function Chronometer:OnDisable()
if not self._mth_enabled then
return
end
if self.anchor then
end
MTH_CHRON_PersistAnchorPosition(self, "OnDisable")
self._mth_enabled = false
if self.bars then
@@ -543,9 +776,12 @@ function Chronometer:StartTimer(timer, name, target, rank, durmod)
end
end
local _, class = UnitClass("player")
local timerClass = timer.x.cl == nil and class or timer.x.cl
if self.profile.disabledSpells[timerClass] and self.profile.disabledSpells[timerClass][name] ~= nil then
local timerClass = timer.x.cl
if timerClass == nil then
timerClass = resolvePlayerClassBucket(self.profile)
end
local blocked, reason = isTimerDisabled(self.profile, timerClass, name)
if blocked then
return
end
@@ -928,7 +1164,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
@@ -938,31 +1174,37 @@ function Chronometer:CreateAnchor(text, r, g, b)
local px = tonumber(self.profile and self.profile.barposition and self.profile.barposition.x)
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 "")
if px ~= nil and py ~= nil then
if ppoint == "" then
ppoint = "TOPLEFT"
end
if prelpoint == "" then
prelpoint = ppoint
end
frame:ClearAllPoints()
frame:SetPoint("TOPLEFT", UIParent, "TOPLEFT", px, py)
frame:SetPoint(ppoint, UIParent, prelpoint, px, py)
else
frame:SetPoint("CENTER", UIParent, "CENTER", 0, 50)
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
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
@@ -971,12 +1213,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.x = x
self.owner.profile.barposition.y = y
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)
@@ -1022,9 +1266,11 @@ function Chronometer:SPELL_FADE(event, info)
if not self.profile.fadeonfade then return end
if info.skill == BS["Banish"] then return end
if info.type == "fade" then
-- Only act when the fade is on the player or on their target, not on other players
if info.victim == ParserLib_SELF then
return self:KillBar(info.skill)
end
if info.source and info.source ~= ParserLib_SELF then return end
return self:KillBar(info.skill, info.victim)
end
end
@@ -1032,8 +1278,20 @@ end
function Chronometer:SPELL_PERIODIC(event, info)
local aura, rank, unit, isgain
-- Primary filter: explicit non-self source → always ignore (other player's confirmed event)
if info.source and info.source ~= ParserLib_SELF then return end
-- Secondary filter: creature/friendly periodic events do NOT embed a caster name,
-- so info.source is nil even for other players' effects. Track whether this event
-- was provably ours (SELF_BUFFS / SELF_DAMAGE events give source == ParserLib_SELF).
local sourceIsConfirmedSelf = (info.source == ParserLib_SELF)
if info.type == "buff" then
isgain = 1
-- Buff gained by someone other than self with no source confirmation → another player's proc
if not sourceIsConfirmedSelf and info.victim ~= nil and info.victim ~= ParserLib_SELF then
return
end
elseif info.type == "debuff" then
isgain = nil
elseif not info.isDOT then
@@ -1061,7 +1319,27 @@ function Chronometer:SPELL_PERIODIC(event, info)
if timer.k.t then
if not unit then unit = UnitName("player") end
if timer.k.s then
if timer.t and timer.t ~= unit then return end
-- Selforselect: four-tier confirmation.
-- 1. Confirmed cast record (timer.t set by CastSpell hook): require target match.
-- 2. Confirmed self source (SELF_BUFFS / SELF_DAMAGE events, source == SELF): accept.
-- 3. Cast-window confirmed (timer.v fresh, timer.t nil): untargeted spells like traps
-- where the caster is known but the trigger victim cannot be predicted in advance.
-- Accept unconditionally — the mob that walked into the trap may not be our target.
-- 4. Source unconfirmed AND no cast record (CREATURE_BUFFS affliction messages never
-- embed a caster name): fall back to target-check. This lets non-ticking debuffs
-- (Poisonous Ammo, Enchanted Ammo, Piercing Shot…) show when applied to our current
-- target, while blocking the same debuff on a mob we are NOT targeting (i.e. another
-- player's debuff on a different dummy).
if timer.t then
if timer.t ~= unit then return end
elseif sourceIsConfirmedSelf then
-- source confirmed → accept unconditionally
elseif timer.v and timer.v > GetTime() then
-- cast-window confirmed, timer.t nil → untargeted cast (e.g. trap placed on ground)
-- victim unknown at cast time; accept whatever mob triggered it
elseif not UnitExists("target") or unit ~= UnitName("target") then
return
end
else
if not UnitExists("target") or unit ~= UnitName("target") then return end
end
@@ -1102,6 +1380,62 @@ function Chronometer:PLAYER_DEAD()
end
end
function Chronometer:UNIT_AURA(unit)
local auraUnit = unit
if (not auraUnit or auraUnit == "") and type(arg1) == "string" then
auraUnit = arg1
end
if auraUnit ~= "pet" then
return
end
if not (UnitExists and UnitExists("pet")) then
return
end
local feedSpell = BS and BS["Feed Pet"] or "Feed Pet"
local timer = self.timers and self.timers[self.SPELL] and self.timers[self.SPELL][feedSpell]
if not timer then
return
end
local hasFeedAura = false
if type(UnitBuff) == "function" then
for i = 1, 32 do
local texture = UnitBuff("pet", i)
if not texture then
break
end
local tex = tostring(texture)
if string.find(tex, "Ability_Hunter_BeastTraining", 1, true)
or string.find(tex, "INV_Misc_Fork&Knife", 1, true)
then
hasFeedAura = true
break
end
end
end
if not hasFeedAura then
return
end
local petName = UnitName and UnitName("pet") or "none"
if not petName or petName == "" then
petName = "none"
end
for i = 1, 20 do
local bar = self.bars and self.bars[i]
if bar and bar.id and bar.name == feedSpell then
if bar.target == petName or bar.target == "none" then
return
end
end
end
self:StartTimer(timer, feedSpell, petName)
end
function Chronometer:UseAction(slot, clicked, onself)
local name, rank = getActionSpell(slot)
if name 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 $"
+3 -1
View File
@@ -1,11 +1,13 @@
local MTH_ChronometerModule = {
name = "chronometer",
enabled = true,
version = "1.0.4",
version = "1.4.1",
events = {},
initialized = false,
}
local function MTH_CHRON_ApplyRuntimeEnabledState(enabled)
if not MTH_ChronometerHunter then
return
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>
File diff suppressed because it is too large Load Diff
+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
+47 -70
View File
@@ -6,7 +6,7 @@
local MTH_FeedOMatic = {
name = "feedomatic",
enabled = false,
version = "1.0.4",
version = "1.4.1",
events = {
"VARIABLES_LOADED",
"MERCHANT_SHOW",
@@ -32,13 +32,13 @@ 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
return
end
if not (MTH and MTH.IsModuleEnabled and MTH:IsModuleEnabled("feedomatic", true)) then
if not (MTH and MTH.IsModuleEnabled and MTH:IsModuleEnabled("feedomatic", false)) then
return
end
if type(FOM_OnUpdate) == "function" then
@@ -47,7 +47,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 +112,6 @@ local function MTH_FeedOMatic_CallLegacyOnEvent(evt, a1, a2, a3, a4, a5, a6, a7,
event = oldEvent
arg1, arg2, arg3, arg4, arg5 = oldArg1, oldArg2, oldArg3, oldArg4, oldArg5
arg6, arg7, arg8, arg9 = oldArg6, oldArg7, oldArg8, oldArg9
if not ok and MTH and MTH.DebugPrint then
MTH:DebugPrint("FeedOMatic legacy OnEvent call failed for event: " .. tostring(evt))
end
end
local function MTH_FeedOMatic_EnsureVariablesLoaded(module)
@@ -137,53 +133,36 @@ local function MTH_FeedOMatic_SyncSavedVariables()
moduleStore.legacy = {}
end
if type(FOM_Config) == "table" then
moduleStore.legacy.FOM_Config = FOM_Config
elseif type(moduleStore.legacy.FOM_Config) == "table" then
FOM_Config = moduleStore.legacy.FOM_Config
local function bindLegacyTable(globalName)
local legacyValue = moduleStore.legacy[globalName]
local globalValue = _G and _G[globalName] or nil
if type(legacyValue) ~= "table" then
if type(globalValue) == "table" then
legacyValue = globalValue
else
legacyValue = {}
end
moduleStore.legacy[globalName] = legacyValue
end
if _G then
_G[globalName] = legacyValue
end
end
if type(FOM_FoodQuality) == "table" then
moduleStore.legacy.FOM_FoodQuality = FOM_FoodQuality
elseif type(moduleStore.legacy.FOM_FoodQuality) == "table" then
FOM_FoodQuality = moduleStore.legacy.FOM_FoodQuality
end
if type(FOM_AddedFoods) == "table" then
moduleStore.legacy.FOM_AddedFoods = FOM_AddedFoods
elseif type(moduleStore.legacy.FOM_AddedFoods) == "table" then
FOM_AddedFoods = moduleStore.legacy.FOM_AddedFoods
end
if type(FOM_RemovedFoods) == "table" then
moduleStore.legacy.FOM_RemovedFoods = FOM_RemovedFoods
elseif type(moduleStore.legacy.FOM_RemovedFoods) == "table" then
FOM_RemovedFoods = moduleStore.legacy.FOM_RemovedFoods
end
if type(FOM_Cooking) == "table" then
moduleStore.legacy.FOM_Cooking = FOM_Cooking
elseif type(moduleStore.legacy.FOM_Cooking) == "table" then
FOM_Cooking = moduleStore.legacy.FOM_Cooking
end
if type(FOM_QuestFood) == "table" then
moduleStore.legacy.FOM_QuestFood = FOM_QuestFood
elseif type(moduleStore.legacy.FOM_QuestFood) == "table" then
FOM_QuestFood = moduleStore.legacy.FOM_QuestFood
end
if type(FOM_LocaleInfo) == "table" then
moduleStore.legacy.FOM_LocaleInfo = FOM_LocaleInfo
elseif type(moduleStore.legacy.FOM_LocaleInfo) == "table" then
FOM_LocaleInfo = moduleStore.legacy.FOM_LocaleInfo
end
bindLegacyTable("FOM_Config")
bindLegacyTable("FOM_FoodQuality")
bindLegacyTable("FOM_AddedFoods")
bindLegacyTable("FOM_RemovedFoods")
bindLegacyTable("FOM_Cooking")
bindLegacyTable("FOM_QuestFood")
bindLegacyTable("FOM_LocaleInfo")
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
@@ -214,10 +193,8 @@ local function MTH_FeedOMatic_DebugMerchantScan(sourceEvent)
if type(rows) == "table" then
dietCount = table.getn(rows)
end
MTH_FeedOMatic_Log("diet " .. tostring(dietName) .. ": " .. tostring(dietCount) .. " item(s)", "debug")
if type(rows) == "table" then
for _, row in rows do
MTH_FeedOMatic_Log(" - [" .. tostring(row.id) .. "] " .. tostring(row.name) .. " (slot=" .. tostring(row.index) .. ")", "debug")
end
end
end
@@ -225,31 +202,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 MTH_FeedOMatic_MerchantProbeFrame then
MTH_FeedOMatic_MerchantProbeFrame = CreateFrame("Frame", "MTH_FeedOMatic_MerchantProbeFrame")
MTH_FeedOMatic_MerchantProbeFrame:SetScript("OnEvent", function()
if not enabled then
if MTH_FOM_MerchantProbe then
MTH_FOM_MerchantProbe:UnregisterEvent("MERCHANT_SHOW")
MTH_FOM_MerchantProbe:UnregisterEvent("MERCHANT_UPDATE")
end
return
end
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
if enabled then
MTH_FeedOMatic_MerchantProbeFrame:RegisterEvent("MERCHANT_SHOW")
MTH_FeedOMatic_MerchantProbeFrame:RegisterEvent("MERCHANT_UPDATE")
MTH_FeedOMatic_Log("merchant probe enabled", "debug")
else
MTH_FeedOMatic_MerchantProbeFrame:UnregisterEvent("MERCHANT_SHOW")
MTH_FeedOMatic_MerchantProbeFrame:UnregisterEvent("MERCHANT_UPDATE")
end
MTH_FOM_MerchantProbe:RegisterEvent("MERCHANT_SHOW")
MTH_FOM_MerchantProbe:RegisterEvent("MERCHANT_UPDATE")
end
function MTH_FeedOMatic:init()
@@ -290,8 +267,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
@@ -300,8 +277,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
@@ -329,8 +306,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");
File diff suppressed because it is too large Load Diff
+320
View File
@@ -0,0 +1,320 @@
<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/">
<Script file="MTH_ICU.lua"/>
<!-- The main ICU frame -->
<Frame name="ICU_Frame">
<Scripts>
<OnLoad>
ICU_OnLoad();
</OnLoad>
<OnEvent>
ICU_OnEvent(event);
</OnEvent>
</Scripts>
</Frame>
<!-- A button template, to be inherited by each button -->
<Frame name="ICU_PopupButtonTemplate" virtual="true">
<Backdrop bgFile="Interface\Tooltips\UI-Tooltip-Background" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
<BackgroundInsets>
<AbsInset left="5" right="5" top="5" bottom="5"/>
</BackgroundInsets>
<TileSize>
<AbsValue val="16"/>
</TileSize>
<EdgeSize>
<AbsValue val="16"/>
</EdgeSize>
</Backdrop>
<Size>
<AbsDimension x="30" y="28"/>
</Size>
<Layers>
<Layer level="ARTWORK">
<Texture name="$parentButtonPortraitIcon">
<Size>
<AbsDimension x="20" y="20"/>
</Size>
<Anchors>
<Anchor point="LEFT" relativePoint="LEFT">
<Offset>
<AbsDimension x="5" y="0"/>
</Offset>
</Anchor>
</Anchors>
</Texture>
<Texture name="$parentButtonClassIcon">
<Size>
<AbsDimension x="20" y="20"/>
</Size>
<Anchors>
<Anchor point="LEFT" relativePoint="RIGHT" relativeTo="$parentButtonPortraitIcon">
<Offset>
<AbsDimension x="0" y="0"/>
</Offset>
</Anchor>
</Anchors>
</Texture>
<Texture name="$parentButtonRankIcon">
<Size>
<AbsDimension x="20" y="20"/>
</Size>
<Anchors>
<Anchor point="RIGHT" relativePoint="LEFT">
<Offset>
<AbsDimension x="0" y="0"/>
</Offset>
</Anchor>
</Anchors>
</Texture>
</Layer>
</Layers>
<Frames>
<Button name="$parentButton" frameStrata="ARTWORK">
<Size>
<AbsDimension x="20" y="20"/>
</Size>
<Anchors>
<Anchor point="LEFT" relativePoint="LEFT">
<Offset>
<AbsDimension x="5" y="0"/>
</Offset>
</Anchor>
</Anchors>
<HighlightTexture file="Interface\QuestFrame\UI-QuestTitleHighlight" alphaMode="ADD" setAllPoints="true" hidden="true"/>
<NormalText inherits="GameFontNormal">
<Anchors>
<Anchor point="LEFT" relativePoint="RIGHT" relativeTo="$parentClassIcon">
<Offset>
<AbsDimension x="5" y="0"/>
</Offset>
</Anchor>
</Anchors>
</NormalText>
<HighlightText inherits="GameFontHighlight">
<Anchors>
<Anchor point="LEFT" relativePoint="RIGHT" relativeTo="$parentClassIcon">
<Offset>
<AbsDimension x="5" y="0"/>
</Offset>
</Anchor>
</Anchors>
</HighlightText>
<DisabledText inherits="GameFontDisable">
<Anchors>
<Anchor point="LEFT" relativePoint="RIGHT" relativeTo="$parentClassIcon">
<Offset>
<AbsDimension x="5" y="0"/>
</Offset>
</Anchor>
</Anchors>
</DisabledText>
<Scripts>
<OnClick>
ICU_ButtonClick();
</OnClick>
</Scripts>
</Button>
<StatusBar name="$parentButtonBar" inherits="TextStatusBar" frameStrata="ARTWORK">
<Size>
<AbsDimension x="20" y="20"/>
</Size>
<Anchors>
<Anchor point="LEFT" relativePoint="RIGHT" relativeTo="$parentButtonClassIcon">
<Offset>
<AbsDimension x="0" y="0"/>
</Offset>
</Anchor>
</Anchors>
<BarTexture file="Interface\TargetingFrame\UI-TargetingFrame-BarFill"/>
<BarColor r="0" g="0.8" b="0" a="0.7" />
<!--
<Layers>
<Layer level="OVERLAY">
<FontString name="$parentLeftText" inherits="GameFontNormal" text="">
<Anchors>
<Anchor point="LEFT" relativePoint="LEFT">
<Offset>
<AbsDimension x="10" y="1"/>
</Offset>
</Anchor>
</Anchors>
</FontString>
<FontString name="$parentRightText" inherits="GameFontNormal" text="">
<Anchors>
<Anchor point="RIGHT" relativePoint="RIGHT">
<Offset>
<AbsDimension x="-10" y="1"/>
</Offset>
</Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
!-->
<Scripts>
<OnLoad>
this:SetMinMaxValues(0, 100);
</OnLoad>
</Scripts>
</StatusBar>
<StatusBar name="$parentButtonBGBar" inherits="TextStatusBar" frameStrata="ARTWORK">
<Size>
<AbsDimension x="20" y="20"/>
</Size>
<Anchors>
<Anchor point="TOP" relativePoint="TOP" relativeTo="$parentButtonBar">
<Offset>
<AbsDimension x="0" y="0"/>
</Offset>
</Anchor>
</Anchors>
<BarTexture file="Interface\TargetingFrame\UI-TargetingFrame-BarFill"/>
<BarColor r="0" g="0.8" b="0" a="0.25"/>
</StatusBar>
</Frames>
<Scripts>
<OnLoad>
this:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b);
this:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b);
</OnLoad>
</Scripts>
</Frame>
<!-- The popup menu -->
<Frame name="ICU_Popup" toplevel="true" frameStrata="DIALOG" enableMouse="true" hidden="true" parent="UIParent">
<Anchors>
<Anchor point="TOPRIGHT" relativeTo="MinimapCluster" relativePoint="BOTTOMRIGHT"/>
</Anchors>
<!--
<Backdrop bgFile="Interface\Tooltips\UI-Tooltip-Background" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
<BackgroundInsets>
<AbsInset left="5" right="5" top="5" bottom="5"/>
</BackgroundInsets>
<TileSize>
<AbsValue val="16"/>
</TileSize>
<EdgeSize>
<AbsValue val="16"/>
</EdgeSize>
</Backdrop>
!-->
<!-- Within the popup menu, the clickable buttons which to display text -->
<Frames>
<!-- Button 1 -->
<Frame name="ICU_PopupButton1" inherits="ICU_PopupButtonTemplate" id="1" ICU_DATA="">
<Anchors>
<Anchor point="TOP">
<Offset>
<AbsDimension x="0" y="-6"/>
</Offset>
</Anchor>
</Anchors>
</Frame>
<!-- Frame 2 -->
<Frame name="ICU_PopupButton2" inherits="ICU_PopupButtonTemplate" id="2" ICU_DATA="">
<Anchors>
<Anchor point="TOP" relativeTo="ICU_PopupButton1" relativePoint="BOTTOM">
<Offset>
<AbsDimension x="0" y="4"/>
</Offset>
</Anchor>
</Anchors>
</Frame>
<!-- Frame 3 -->
<Frame name="ICU_PopupButton3" inherits="ICU_PopupButtonTemplate" id="3" ICU_DATA="">
<Anchors>
<Anchor point="TOP" relativeTo="ICU_PopupButton2" relativePoint="BOTTOM">
<Offset>
<AbsDimension x="0" y="4"/>
</Offset>
</Anchor>
</Anchors>
</Frame>
<!-- Frame 4 -->
<Frame name="ICU_PopupButton4" inherits="ICU_PopupButtonTemplate" id="4" ICU_DATA="">
<Anchors>
<Anchor point="TOP" relativeTo="ICU_PopupButton3" relativePoint="BOTTOM">
<Offset>
<AbsDimension x="0" y="4"/>
</Offset>
</Anchor>
</Anchors>
</Frame>
<!-- Frame 5 -->
<Frame name="ICU_PopupButton5" inherits="ICU_PopupButtonTemplate" id="5" ICU_DATA="">
<Anchors>
<Anchor point="TOP" relativeTo="ICU_PopupButton4" relativePoint="BOTTOM">
<Offset>
<AbsDimension x="0" y="4"/>
</Offset>
</Anchor>
</Anchors>
</Frame>
<!-- Frame 6 -->
<Frame name="ICU_PopupButton6" inherits="ICU_PopupButtonTemplate" id="6" ICU_DATA="">
<Anchors>
<Anchor point="TOP" relativeTo="ICU_PopupButton5" relativePoint="BOTTOM">
<Offset>
<AbsDimension x="0" y="4"/>
</Offset>
</Anchor>
</Anchors>
</Frame>
<!-- Frame 7 -->
<Frame name="ICU_PopupButton7" inherits="ICU_PopupButtonTemplate" id="7" ICU_DATA="">
<Anchors>
<Anchor point="TOP" relativeTo="ICU_PopupButton6" relativePoint="BOTTOM">
<Offset>
<AbsDimension x="0" y="4"/>
</Offset>
</Anchor>
</Anchors>
</Frame>
<!-- Frame 8 -->
<Frame name="ICU_PopupButton8" inherits="ICU_PopupButtonTemplate" id="8" ICU_DATA="">
<Anchors>
<Anchor point="TOP" relativeTo="ICU_PopupButton7" relativePoint="BOTTOM">
<Offset>
<AbsDimension x="0" y="4"/>
</Offset>
</Anchor>
</Anchors>
</Frame>
<!-- Frame 9 -->
<Frame name="ICU_PopupButton9" inherits="ICU_PopupButtonTemplate" id="9" ICU_DATA="">
<Anchors>
<Anchor point="TOP" relativeTo="ICU_PopupButton8" relativePoint="BOTTOM">
<Offset>
<AbsDimension x="0" y="4"/>
</Offset>
</Anchor>
</Anchors>
</Frame>
<!-- Frame 10 -->
<Frame name="ICU_PopupButton10" inherits="ICU_PopupButtonTemplate" id="10" ICU_DATA="">
<Anchors>
<Anchor point="TOP" relativeTo="ICU_PopupButton9" relativePoint="BOTTOM">
<Offset>
<AbsDimension x="0" y="4"/>
</Offset>
</Anchor>
</Anchors>
</Frame>
</Frames>
<Scripts>
<OnLoad>
<!-- This makes the dialog look like a normal tooltip -->
this:SetBackdropBorderColor( TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b );
this:SetBackdropColor( TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b );
</OnLoad>
<OnUpdate>
ICU_Popup_OnUpdate();
</OnUpdate>
</Scripts>
</Frame>
<Frame name="ICU_UpdateFrame" hidden="true">
<Scripts>
<OnUpdate>
ICU_MouseOverUpdate();
</OnUpdate>
</Scripts>
</Frame>
</Ui>
+8
View File
@@ -0,0 +1,8 @@
<!--
ICU Module Loader
Loads ICU runtime and wraps it in the MetaHunt module system.
-->
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<Include file="MTH_ICU.xml"/>
<Include file="module.lua"/>
</Ui>

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