52 Commits

Author SHA1 Message Date
DuvelCorp 99e7eaa5d2 Merge master into 2.0 (keep 2.0 content)
# Conflicts:
#	README.MD
2026-09-14 22:57:27 +02:00
DuvelCorp 44288cd635 Use 2.0.0 to allow patch releases 2026-09-14 22:49:40 +02:00
DuvelCorp 7d974f24b5 Set 2.0 release date 2026-09-14 22:39:25 +02:00
DuvelCorp 3de8088bbb Bump version to 2.0 2026-09-14 22:38:06 +02:00
DuvelCorp 7a0f03ba6f Remove temporary fx load-path debug tracer 2026-09-14 22:38:06 +02:00
DuvelCorp 0aaa49f33b Dedup trainer craft scans to prevent open freeze 2026-09-14 22:38:06 +02:00
DuvelCorp 4770724f60 Prefix pet level-up spell list with 'It can learn' 2026-09-14 18:53:49 +02:00
DuvelCorp acac6ace6f NPC Finder: fix zone-ID mislabel and show all zones per NPC
- Translate the coord's WMA id to pfQuest before the MTH_DS_Zones parent
  lookup, so e.g. Feralas (WMA 121) no longer collides with pfQ 121
  (Raven Hill) and prints its parent 'Duskwood'
- Zone column now lists every distinct zone an NPC occupies, smallest-map
  first, so border/capital NPCs read 'Darnassus, Teldrassil' etc. (sort
  key matches)
2026-09-14 18:38:10 +02:00
DuvelCorp 772299baf3 Use Black Hole celebration for max pet loyalty (6) 2026-09-13 11:00:45 +02:00
DuvelCorp eed6624611 Update SmartAmmo changelog wording 2026-09-13 10:52:14 +02:00
DuvelCorp 71c84e1704 Silence trainer scan noise during Growl auto-teach
- Add MTH_Growl_IsTeaching(); the beast-training scanner skips its chat
  feedback while auto-teach is driving the trainer window (no more
  'scan did not run yet' spam on tame)
- Confirm the taught rank in chat: 'Auto-taught Growl (Rank N) to <pet>.'
2026-09-13 10:46:23 +02:00
DuvelCorp dec41628ad Fix pet loyalty notifications and trim level-up copy
- Loyalty now read from MTH_GetCurrentPetInfo().loyaltyLevel; GetPetLoyalty()
  returns a localized name (not a number), so the loyalty branch never fired
- Listen for UNIT_HAPPINESS so a loyalty gain actually triggers the check
- Drop redundant 'has leveled up' / 'is now <rank>' openers from pet and
  loyalty lines, keeping the sarcasm
2026-09-13 09:54:04 +02:00
DuvelCorp d1d2660c2c Point bestiary link to Codeberg Pages 2026-09-12 22:19:39 +02:00
DuvelCorp 3bdc66770b Point update URL to Codeberg 2026-09-12 22:10:55 +02:00
DuvelCorp 67ed20532b Fix Hunter's Book animation SetPoint errors
The window's first anchor is relative to a child, so SlideIn/SlideOut's
position tween threw '<child> is dependent on this' every tick. Switch the
open/close/tab/search animations to alpha-only fades (matches the option labels).
2026-09-12 12:08:54 +02:00
DuvelCorp f3af63926e Fix SmartAmmo low-ammo warnings
- Remove infinite tail-recursion threshold getter that hard-froze the client on /rl
- Fix double-count (equipped ammo was added to the bag total)
- Make it true crossing-only: warn only when the total drops across the threshold, never on cold baseline or restock
- Use the shaking warning banner instead of a celebration, with sarcastic chat/banner copy
- Options: fix legacy EditBox HasFocus error on the threshold input; move Pet Auto-Teach to the right column
2026-09-12 12:08:54 +02:00
DuvelCorp 22232364b1 Add SmartAmmo low-ammo alerts 2026-09-12 10:57:27 +02:00
DuvelCorp af31acdb3e Refresh zCraft after profession ranks 2026-09-12 08:05:44 +02:00
DuvelCorp 402cd8aef0 Balance animation options layout 2026-09-11 15:29:01 +02:00
DuvelCorp aa99ee01cc Update Credits copy 2026-09-11 15:20:03 +02:00
DuvelCorp c862cdb25e Refine level-up notifications 2026-09-11 15:10:23 +02:00
DuvelCorp 0fb52f67ff Add trainer planning and level-up tracking 2026-09-11 13:45:58 +02:00
DuvelCorp aeaddd9558 Unify all option panels through MTH_Layout engine; restore realm gate 2026-09-11 00:05:38 +02:00
DuvelCorp ccf9d8b0b6 Unify all option panels through MTH_Layout engine
Add reusable MTH_Layout box/column layout engine (api/options-layout.lua) and route every option panel through it (shell, general, zButtons per-button config, zBar, chronometer, auto buy, profiles, credits, etc.) for consistent, auto-sizing layout. Restore realm gate. README note.
2026-09-10 23:59:37 +02:00
DuvelCorp adb21c336c Add Growl pet auto-teach feature and smooth zButton expand animation
- New Pet Auto-Teach: on tame, offer to teach Growl at the best rank via the
  Beast Training window (Craft API DoCraft), with a General options checkbox
  and /mth growl on|off|status toggle (on by default).
- De-stagger zButton flyout bars and zBar so buttons fade in together.
- Update CHANGELOG.
2026-09-10 21:20:56 +02:00
DuvelCorp 64e449481f zBar: revert to plain staggered fade + no init animation (SlideIn caused client micro-freezes on reload) 2026-09-10 17:48:46 +02:00
DuvelCorp 2863adff1c zBar: play assemble animation on init/reload, not only on checkbox toggle 2026-09-10 17:45:33 +02:00
DuvelCorp 0609d487a7 zBar: speed up assemble animation further 2026-09-10 17:44:18 +02:00
DuvelCorp e7cf99facb zBar: speed up assemble animation 2026-09-10 17:43:04 +02:00
DuvelCorp c5a42d8d2a zBar: cap stagger window + shorter slide so a full bar assembles quickly 2026-09-10 17:42:07 +02:00
DuvelCorp 3233ebff7f Strengthen subtle animations: pin scale-pops, window slide-in/out with overshoot, panel slides, zButton cascade, zBar springy stagger 2026-09-10 17:21:10 +02:00
DuvelCorp 66ff1ae711 Animations: wire remaining effects + 2-column options panel
- Shared FX toolkit in fx-celebrate.lua: mth_shake tween type, MTH_FX_Flash
  glow helper, MTH_CelebrateRunaway red shaking banner, gold title reset.
- pet.runaway, pet.happiness, pet.loyaltyup, pet.feed
- combat.ammoswap, combat.rangedswap, combat.mmprocs
- ui.zbar, ui.stable, ui.minimapping, ui.optionsclose
- book.model, book.search
- Options > Animations panel now uses a balanced two-column layout.
- CHANGELOG Animations entry expanded (no version bump).
2026-09-10 16:38:14 +02:00
DuvelCorp d2fb04f7c5 Animations: add option-tab fade, minimap pulse, zButton expand fade, map-pin drop-in; wire BeastTraining debug log to /mth err 2026-09-10 16:05:36 +02:00
DuvelCorp 566f3f4925 Add Animations feature + fix own-pet tooltip false positive
Animations (all optional, default on, per-effect toggles):
- New Options -> Animations tab with master switch + per-effect checkboxes
- Embed PizzaSauce animation library (api/PizzaSauce.lua)
- fx-celebrate: firework + banner when a pet learns a new ability/rank,
  triggered from core-scan-beasttraining pet-learn hook
- Hunter's Book: slide + fade on open, results list fade on tab switch
- Options window: slide + fade on open
- All new user-facing strings keyed (ANIM_* namespace)

Fixes:
- Tooltip #19: own-pet lines (mood/loyalty/happiness/XP) no longer
  misfire on party members / targets. Removed the fragile title-name
  fallback in MTH_TT_IsTooltipOnOwnPet; ownership now requires positive
  unit identity.

Temp (remove before release): /mth fx and /mth fxdebug diagnostics.
2026-09-10 14:41:26 +02:00
DuvelCorp 060fc555fe Unify SavedVariables into two clean roots with one-time migration
Collapse all legacy/scattered SavedVariables from absorbed standalone
addons (FeedOMatic, ZHunter/zButtons, SmartAmmo, AutoQuest, AntiDaze,
AutoStrip, MinimapButton) into two persisted globals with clean per-module
nesting under .modules.<id>.

Migration engine (api/savedvariables.lua):
- MTH_SV_EnsureSchema() with schemaVersion gate, idempotent + non-destructive
- Per-module migrators map old locations -> canonical nested stores
- Driven by an ADDON_LOADED("MetaHunt") handler so it runs only AFTER WoW
  loads the real SavedVariables (fixes announce-every-session bug that ran
  the migration on empty pre-load defaults)
- One-time player-facing chat announce, gated on real legacy data present

Framework/config:
- core-framework InitSavedVariables no longer runs migration at file-load;
  unconditionally strips account-root module aliases
- config.lua stops re-creating root[module] aliases (was duplicating data
  onto disk); canonical store stays under .modules.<id>

Modules:
- feedomatic: bind FOM_* globals as runtime aliases into nested store,
  drop persisted duplicates and .legacy blob
- zhunter: ZHunterMod_Saved re-pointed at .modules.zhunter
- smartammo/autoquest/antidaze/autostrip/minimapbutton read nested store

TOC trimmed to MTH_SavedVariables + MTH_CharSavedVariables only.
Profiles unchanged (already snapshot/apply the nested .modules structure).

Also includes pet-scan refinements: canonical pet key derivation and
level-independent pet signature (schema v3).
2026-09-09 22:52:48 +02:00
DuvelCorp de6621f41f Add requirements.txt for the .tools Python venv
Freezes the dev toolchain (numpy, pandas, pillow, mpyq, odfpy, asarPy,
defusedxml, python-dateutil, six) so any machine rebuilds .venv from this
instead of copying the 176MB virtualenv. Supports the multi-machine workflow:
clone via git, then 'python -m venv .venv && .venv/bin/pip install -r requirements.txt'.
2026-09-09 16:58:19 +02:00
DuvelCorp da645fa53c Remove empty placeholder init XMLs
init/env.xml, compat.xml, libs.xml, skins.xml contained only comments (no
frames, scripts, or includes) and were loaded as no-ops at startup. Deleted the
files and their MetaHunt.toc include lines. init/ now holds only the three real
XMLs (api, localization, modules).
2026-09-09 16:33:39 +02:00
DuvelCorp 9379668e4b Remove dead code: SmartPet module + orphaned DBC datastores
SmartPet (module never wired into modules.xml) removed entirely — server-side
pet AI on TWoW/OctoWoW makes it useless. Deleted modules/smartpet/ and
api/options-smartpet.lua.

Orphaned .tools DBC extracts (2026-03-20) never loaded via TOC, superseded by
the curated/pfQuest datastores that ship:
- data/ds-zones-dbc.lua, data/ds-zones-new-dbc.lua (bounds come from ds-zones-wma)
- data/ds-stable-prices.lua (never wired to any UI)
- data/ds-pet-spells-acquire.lua (learnMethod set at runtime by init.lua + trainer)

Also removed api/beastlore-scan.lua (8-line deprecation stub) and its
init/api.xml include. No shipped references or player-facing behaviour affected.
2026-09-09 16:29:15 +02:00
DuvelCorp c55cd66ffc Fix realm-gate disable for book/options, pet-rank learning, unique beasts
- Hunter's Book and options window now show a full 'DISABLED.' overlay on realm-gated realms instead of rendering content
- Pet training: new rank of an already-known ability is now recognised, announced, and reflected in tooltips
- Restore curated 'unique appearance' beast allowlist (34 beasts) in data/init.lua
- CHANGELOG updated
2026-09-08 01:25:51 +02:00
DuvelCorp 38e080261c Fix rank-0 pet spell icons (Bubble Barrier, Charge); finalize 1.5.2 changelog; ignore Copilot data 2026-09-06 21:59:29 +02:00
DuvelCorp 04e04a4853 1.5.2 2026-04-08 09:36:17 +02:00
DuvelCorp 6c007f2c17 1.5.1 2026-04-04 23:43:46 +02:00
DuvelCorp c4911ef9fe 1.5.1 2026-04-04 23:38:34 +02:00
Duvel 99268643eb Merge pull request #17 from DuvelCorp/v1.4.2
1.5.0
2026-03-31 21:26:45 +02:00
DuvelCorp 51d4c785fa 1.5.0 2026-03-31 21:25:35 +02:00
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
129 changed files with 80045 additions and 73255 deletions
+9
View File
@@ -9,3 +9,12 @@
tools/ tools/
LOC*.md LOC*.md
LOCALIZATION*.md LOCALIZATION*.md
# Dev tooling — Python generators, data processing scripts, scratch files
# Not part of the addon, never ship this
.tools/
# GitHub Copilot — never sync Copilot data to GitHub
.copilot/
.github/copilot-instructions.md
**/copilot-*
+5
View File
@@ -118,4 +118,9 @@
<Binding name="ZPet 6" header="ZPetHeader"> <Binding name="ZPet 6" header="ZPetHeader">
zButtonPet_KeyBinding(6) zButtonPet_KeyBinding(6)
</Binding> </Binding>
<!-- ExpAmmo Action Button -->
<Binding name="ExpAmmo Action" header="ExpAmmoHeader">
MTH_EA_CastActionSpell();
</Binding>
</Ui> </Ui>
+232
View File
@@ -2,6 +2,238 @@
All notable changes to MetaHunt will be documented in this file. All notable changes to MetaHunt will be documented in this file.
## [2.0.0] - 2026-09-14
Resurrecting on Octowow.
### Added
- **SmartAmmo — low-ammo alerts**: SmartAmmo can now monitor the total matching arrows or bullets in your bags and equipped slot: Enable the option, choose a warning threshold (default: 200), and MetaHunt will ouput a chat and centered-screen alert when you cross it. Sarcasms are always enabled ofc.
- **New spell datastore**: Metahunt now ships a datastore of all twow 1.18.1 hunter and pet spells. You can browse them with new pages in the Book.
- **Trainer cost planning**: Added shared Hunter and Pet Trainer spell cost planning for spells available now, at the next training level, and still remaining.
- **Hunter and Pet spell scanning**: MetaHunt now reads your Hunter and Pet spellbooks to mark every spell rank you already know. When you visit a Hunter or Pet Trainer, it also records which ranks you can learn now and which ones are still unavailable.
- **Level-up tracking**: Added Hunter, Pet, and Pet Loyalty level-up notifications. When you hit level, you can have a summary in the chat of all new spells available and their costs.
- **Pet Auto-Teach — Growl**: When you tame a new beast, MetaHunt now offers to teach it Growl at the best rank for the pet's level. Click **Yes** and it opens the Beast Training window, teaches the ability and closes the window automatically.
- **Animations**: MetaHunt now plays optional animations that add fun and make the interface feel alive. Powered by the great PizzaSauce library of [Pizzahawaii](https://codeberg.org/Pizzahawaii/) ! Thank you man.
- **zTrack — Find Fish**: Added new spell Find Fish to zTrack.
- **DDOSers Realm Blocking**: The Addon will auto-disable itself on all Mafia's realms, we know who they are.
### Changed
- **Complete revamp of SavedVariables**: All your settings now live in one tidy place instead of being scattered around. A migration happens the first time you log in, and you shouldn't lose any settings.
- **Polished options**: The option panel had become messy over time and it finally received some attention to look cleaner and coherent with same styling everywhere and better arrangement.
- **Book navigation**: The book navigation is no longer done with horizontal tabs, but with a left vertical menu just like in the options. This allow more pages and fix the tab overfloading issues on some resolutions and UI scales.
- **Web database links**: All links in the data that were pointing on old Turtle WoW DB have been replaced to point on the OctoWoW DB.
- **Addon's code repository change**: The addon repo is now hosted on Codeberg and the "update available" link will point on the new location.
### Fixed
- **NPC Finder — zone accuracy**: Fixed NPCs that could show a wrong zone (e.g. Feathermoon vendors in Feralas were labelled "Duskwood") caused by a zone-ID mix-up. NPCs that stand on a zone border or in a capital city now list **all** the zones they appear on, most precise first — e.g. "Darnassus, Teldrassil" or "Alterac, Hilsbrad".
- **zCraft — profession ranks**: zCraft now refreshes after learning a profession rank, without requiring a UI reload.
- **Tooltips — your pet's info showing on other players**: In a party, mousing over or targeting another player could wrongly show YOUR pet's details (mood, loyalty, happiness, XP) on their tooltip. The own-pet detection no longer misfires on other units.
- **zCraft — Smelting**: Smelting was previously not appearing in zCraft because incorrectly hardcoded as "Mining", this is fixed.
- **Wrong icons on pet spells**: Fixed the Rank 0 of Bubble Barrier and Charge that had incorrect icons.
- **Fixed tracking data in Book's Stables**: Fixed an issue with pets' GUID returned by the server, that was not really unique and caused issues with pets' tracking, notably when you had several pets with the same name. Taming info was disapearing, amongst other weirdness. A migration is done on first load to fix your existing data.
- **FeedOMatic — "Set Key" button error**: Clicking **Set Key** in Options → Feed-o-Matic threw an error and wouldn't let you bind a feed key. It now works again.
- **FeedOMatic — pet left hungry when bags were full**: When your bags were full, FeedOMatic could grab a random tiny stack (like a single meat your pet won't eat) to free a bag slot, so your pet stayed hungry. It now always picks food your pet can actually eat.
- **Tooltips — "Not learned yet" hint missing on pet abilities**: When you tamed a new beast, hovering one of its abilities on your pet bar no longer showed the red "Not learned yet, keep going !" reminder, even for abilities you hadn't learned. The hint is back and now correctly shows for any ability rank your hunter hasn't learned yet.
- **Pet training — new rank of a known ability not registered**: When you trained a higher rank of an ability you already had (e.g. learning Charge Rank 2 after Rank 1), MetaHunt treated it as already known — so the "Pet ability learned and recorded" message never appeared and the pet-bar tooltip kept saying "Not learned yet" for the new rank. Learning a new rank is now recognised on its own, announces correctly, and the tooltip immediately reflects the rank you just learned.
- **Hunter's Book — fixed "Unique" beasts**: Restored a curated list of the one-of-a-kind beasts (Humar the Pridelord, Darksaber, Shar'lan, Frostsaber Pride Watcher, Mazzranache, Moonfeather, Azurebeak, Old Cliff Jumper, Rotting Agam'ar, Razzashi Serpent, Magmalash Scorpid, Spirit Fox and more).
## [1.5.1] - 2026-04-01
### Added
- **zAmmo — bag ammo labels**: Quality-colored short-name labels overlaid on ammo items in bags and bank, with optional heat-colored DPS damage indicator. Three new checkboxes General options: bag labels, bank labels, and damage display. Works with both default bags and pfUI bags.
- **MM Widget — immunity detection**: The MM Widget keybind now detects when the current target is immune to a damage school (Fire / Nature / Arcane) and automatically skips the useless shot, casting Aimed or Steady Shot instead. Immunity is detected in two ways: reactively via combat log "immune" messages, and proactively via hardcoded creature-type rules (e.g. Elementals are always flagged Nature-immune on target). Immune cells show a red border.
- **MM Widget — rotation toggles**: Three new checkboxes in Options → ExpAmmo → Rotation let you permanently disable individual shots (Multi-Shot, Serpent Sting, Arcane Shot) — unchecked shots are skipped during their proc window.
### Fixed
- **Zone mapping — 22 beasts with raw zone IDs**: Fixed 8 unmapped zone IDs (Blackrock Depths, Molten Core, Wailing Caverns, Maraudon, Sunnyglade, Icepoint Rock, Winter Veil Vale, Gilneas City, Karazhan) that caused 22 beasts to display as "Zone xxx" instead of their proper zone name in the Hunter Book.
- **MM Widget — IsStateBlocked nil error**: Moved `MTH_EA_IsStateBlocked` definition before its first use, fixing a nil function call on load.
## [1.5.0] - 2026-03-31
### Changed
- **Beast spawn coordinates — custom-zone remap**: Reworked 63 beast coordinate sets in the datastore. The largest changes move placeholder vanilla-zone coords onto the proper Turtle WoW custom zones for **Grim Reaches**, **Northwind**, and **Balor**, and clean up prairie beasts that were incorrectly spilling into **Thunder Bluff**.
- **Beast spawn coordinates — Stonetalon recalibration**: Recalibrated several **Stonetalon Mountains** beast spawn clusters to match the Twow updated zone bounds and improve marker placement consistency.
- **MM Widget — ClickThrough **: The whole widget is now clickable through, and ALT-drag to reposition it is thus no longer possible
- **MM Widget — clearer bottom-cell display**: The bottom cell now shows the cooldown of the current shot you would use, and only shows `USE` when that shot is ready.
- **Feed-O-Matic — NO-BUFF quarantine disabled**: The mechanism that permanently blacklisted a food item per pet family after a failed feed (no buff detected within 2.5 s) has been disabled. Foods are no longer quarantined on a no-buff timeout — the feed will simply retry without banning the item. This avoids false positives caused by server lag or client timeouts.
### Added
- **zCraft — new zButton for crafting professions**: A new `zCraft` button (Options → zButtons → zCraft) scans your spellbook and shows a child button for each crafting profession you know — Alchemy, Blacksmithing, Cooking, Disenchanting, Enchanting, Engineering, Fishing, First Aid, Herbalism, Jewelcrafting, Leatherworking, Mining (Smelting), Skinning, Survival, and Tailoring. Clicking a child opens the corresponding tradeskill window. Disabled by default.
- **zBar — unified draggable toolbar for all zButtons**: A new "zBar" tab (Options → zButtons → zBar) groups all enabled zButton bars into a single draggable anchor. Alt-drag the zBar to move the whole group together. Options: Horizontal or Vertical layout; which side children pop out (Up/Down for horizontal, Left/Right for vertical); spacing between buttons; anchor icon size; per-bar include/exclude toggle and drag-to-reorder. Disabled by default — existing layouts are untouched until you enable it.
- **zAspect / zTrack — Smart Parent**: A new "Smart Parent" checkbox in Options → zButtons → zAspect and zTrack. When enabled, the parent button automatically toggles between your 1st and 2nd spell: if the 1st is active, the parent shows the 2nd, and vice-versa. Perfect for PvP — put Track Hidden and Track Humanoids as your first two tracking spells, and the parent always shows the one you're *not* currently using. When disabled, the parent always shows the 1st spell. Enabled by default.
- **zPet — Smart Parent**: A new "Smart Parent" checkbox in Options → zButtons → zPet. When enabled, the parent button dynamically changes based on your pet's state: Revive Pet if dead, Mend Pet if hurt, Feed Pet if unhappy, or your default spell otherwise. When disabled, the parent always shows the 1st spell. Enabled by default.
- **zMounts / zCompanions — Random Parent**: A new "Random Parent" checkbox in Options → zButtons → zMounts and zCompanions. When enabled, a random child mount is picked as the parent icon on each login and after each use. Disabled by default.
- **zMounts — AQ Mount Filter**: A new "AQ Mount Filter" checkbox in Options → zButtons → zMounts. When enabled, Qiraji mounts are hidden outside AQ40, and only Qiraji mounts are shown inside AQ40. Enabled by default.
- **zAspect — NamPower support**: The Aspect button now listens to `NamPower` aura events when available, so aspect changes update faster and without the old polling delay.
- **MM Widget — NamPower support**: The widget now uses `NamPower` aura updates when available, which makes ammo proc tracking faster and more reliable. It still falls back to the old tracking method if NamPower is not installed.
- **MM Widget — keybind **: The MM Widget options panel now has a "Set Key" button. Press any key (with optional ALT/CTRL/SHIFT modifiers), or mouse button/wheel to bind the MM Widget action, turning it into a One-button rotation feature.
- **MM Widget — Quiver no-clip option**: New "Use Quiver no-clip casting" checkbox in the MM Widget options. When enabled and Quiver is installed, Aimed Shot, Steady Shot, and Multi-Shot of the Action bind are routed through Quiver's no-clip system to avoid clipping your auto-shot swing timer.
- **MM Widget — Anchor **: Since the widget is now clickable-through, a new anchor system was implemented to move the widget: toggling the anchor button in the options shows 4 drag handles (top, bottom, left, right) outside the widget edge. Any handle can be dragged to reposition the widget.
- **Hunter Book — Families stats modifiers**: The Families page now also shows `Health`, `Damage`, and `Armor` modifiers for every family. Those modifier values can be shown as raw multipliers or as `%` with a footer checkbox, and are color-coded for easier reading. Additionaly the footer recap now also shows the total number of families, and the Named/Coord counts are plugged on the new accurate server's data instead of old pfQuest data.
- **FOM — keybind**: The Feed-O-Matic "Set Key" button in options now also accepts modifiers and mouse.
- **/mth food command**: Added a new slash command to inspect Feed-O-Matic state, clear one item ban, or reset all food bans and quarantines.
- **AntiDaze — NamPower support**: Daze detection now uses NamPower's aura events when available, replacing chat-message parsing for faster and more reliable aspect cancellation.
- **Chronometer — NamPower support**: Timer bars now start and stop instantly via NamPower aura events when available. Bar durations are read directly from the NamPower aura array instead of guessed from spell data.
- **Feed-O-Matic — NamPower support**: Mount and buff detection (Shadowmeld, Feign Death, eating, etc.) now uses the NamPower aura array when available, replacing the old tooltip-scraping method.
### Fixed
- **Pet lifecycle — duplicate / ghost pets in Stable**: Reworked pet identity with GUID-based matching and fixed stable tracking so the same pet is no longer split into multiple rows after tame, rename, stable swap, or `/reload`. Old stale pets should also stop reappearing in the Stable tab as fake active/stabled entries.
- **Pet lifecycle — wrong taming info shown on old pets**: Fixed several cases where MetaHunt could invent tame data for pets that were only seen later through stable swaps or normal pet refreshes. Taming info is now only recorded from a real tame event, so old pets should no longer show fake tame location / tame date data.
- **Hunter Book — Stable tab now trusts real stable slots**: The Stable tab now shows the current pet plus the pets that are actually in your stable slots, instead of trusting stale saved slot data from older sessions.
- **Hunter Book — pet training kept on the correct pet**: Fixed cases where a pet could keep its row but lose its Training section after lifecycle repairs or row merges. Training/spellbook data now stays attached to the right pet.
- **Hunter Book — stable feed item icons**: Fixed broken food icons in the Stable tab feed summary, including cases where foods like `Roasted Quail` were showing as a red square or question mark.
- **Minimap markers moving with the player since 1.4.0**: Reworked minimap marker zone resolution and calibration so pins stay anchored to world locations again instead of sliding with player movement. The minimap path now uses exact current-zone lookup in pfQuest space, while the world map keeps the WMA-aware logic introduced for 1.18.1 zone support.
- **Chronometer — "Grow bars upward" forgotten after `/reload`**: Checking the "Grow bars upward" option and reloading the UI would revert the bars back to growing downward, even though the checkbox still appeared checked. Fixed.
- **Chronometer — bars appear below the anchor instead of above when "Grow bars upward" is enabled**: Even with "Grow bars upward" checked, the bars were still spawning a bit below the anchor and stacking upward from there, because of an existing issue in old `CandyBar` library. Fixed — bars now correctly start at the anchor and grow upward.
- **Chronometer — Aimed Shot tracker removed**: Chronometer no longer tracks Aimed Shot cooldowns. The MM Widget now owns that behavior, so Chronometer keeps its hunter timers focused on the remaining spell and proc bars.
- **MM Widget — Quiver no-clip covers Multi-Shot**: The Quiver no-clip option now explicitly includes Multi-Shot alongside Aimed Shot and Steady Shot.
- **MM Widget — stale NamPower proc state**: The widget now resyncs from the NamPower aura array after aura events, so it no longer keeps showing a proc after the debuff is gone.
- **Quiver no-clip macro — Steady Shot castbar not showing**: If you were using Quiver and its no-clip macro for your shots, the Quiver castbar for those shots would not appear with MetaHunt enabled. Fixed — the castbar now appears correctly when using the macro.
## [1.4.1] - 2026-03-24
### Added
- **zTrack**: added `Find Trees` in tracking spells.
### Changed
- **Performance — MM Widget / ExpAmmo**: Replaced tooltip-based aura scanning (which triggered hidden tooltip rendering on every aura check) with direct texture/icon comparison via `UnitBuff`/`UnitDebuff`. Added a 150 ms throttle on `PLAYER_AURAS_CHANGED`, which fires 1050+ times per second in combat. Lock and Load detection and ammo cycle recovery after `/reload` both updated to use the new zero-cost texture path.
- **Performance — event frame leaks**: Some addon features were registering WoW game events at load time and never unregistering them when the feature was disabled from the options, causing their `OnEvent` handlers to fire even with the option turned off. Fixed for: Auto-Strip (`PLAYER_REGEN_*`), Smart Ammo (`SPELLCAST_*`, `START/STOP_AUTOREPEAT_SPELL`), and some zButton frames (Pet, Mounts, Track, Trap, Toys). Events are now properly registered on enable and unregistered on disable.
- **Chronometer — trap effects from other hunters**: Trap effect bars (`Immolation Trap Effect`, `Explosive Trap Effect`, `Freezing Trap Effect`, `Frost Trap Aura`) were incorrectly appearing when another hunter's trap triggered on a mob you were also targeting. .
- **Version check broadcast interval**: Increased update notification broadcast cooldown from 10 minutes to 1 hour to reduce channel traffic now that the user base has grown.
- **Profile snapshot memory**: The auto `BuildSnapshot` (called on every `/reload` via `PLAYER_LOGOUT`) was deep-copying `feedomatic.legacy`, a large migration table mirroring `FOM_Cooking`, `FOM_QuestFood`, `FOM_AddedFoods` etc. These are runtime/operational blobs, not user configuration settings, and should never be included in a profile. Introduced `MTH_Profile_CopyModules` which skips `legacy` and `history` keys, reducing the snapshot allocation from ~1 MB to a few KB.
## [1.4.0] - 2026-03-23
### TWoW 1.18.1 "Nightmares of Ursol" support
### Added
- **Complete beast database overhaul**: The MTH beast datastore was reconstructed from scratch, based on a full (beasts) 1.18.1 extract from live DB, gladly provided by Twow staff (thanks @Haaxor!). The beast data should now be 100% accurate, and notably all spawn coordinates, respawn times and pet abilities learnable on all beasts in the world.
- The **Beast Lore** page of the Book now allows to filter the list by Zone. Additionally, the beast inspector has been reformatted and all meaningful beast's data is displayed nicely.
- All new and missing **Beasts Models** have been (re)scanned from MPQ game files and are viewable in the Beast Lore page, and browsable in the Family Model viewer.
- Added **MM widget** module: tracks the new 3-state of Experimental ammo cycle (Fire → Nature → Arcane), the Lock and Load procs, and has a dynamic cell that shows either Aimed Shot availability or the right shot to use following the current Experimental ammo proc if any. Lets Make MM great again.
- Added new **Moth** pet family.
- Added new **Pollen Burst** pet ability.
- Added new **Aspect of the Viper** in zAspect.
- Added new **Chronometer** timer entries for 1.18.1 spells:
- Aimed Shot cooldown tracker
- Kill Command cooldown tracker
- Lock and Load proc timer
- Scent of Blood proc timer
- Added new **Chronometer** target debuff entries for the Experimental Ammo states:
- Poisonous Ammunition: 15s armor-reduction debuff (triggered when Serpent Sting consumes the Poisonous state)
- Enchanted Ammunition: 6s magic-resist debuff (triggered when Arcane Shot consumes the Enchanted state)
- Added the **5 new craftable ammo** to the Projectiles book's page, SmartAmmo and zAmmo button:
- **Bright Wood Arrows** (Survival: Journeyman, req. 30) — 10.5 DPS
- **Shade Wood Arrows** (Survival: Expert, req. 40) — 14.5 DPS
- **Smooth Ironfeather Arrows** (Survival: Artisan, req. 50) — 17.5 DPS
- **Starfeather Arrows** (Survival: Artisan, req. 55) — 19 DPS
- **Enchanted Thorium Shells** (Engineering: Artisan, req. 55) — 19 DPS
- Added new hunter-friends NPC of **Moonwhisper Coast** to the NPC Finder Book's page:
- **Hula Swiftmane** — Horde Stable Master (66.1, 39.1)
- **Ornala** — Horde Ammo Vendor (67.3, 37.1) — sells Arrows and Slugs
- **Badel Wildlance** — Alliance Stable Master (59.0, 24.9)
- **Elendon Truebough** — Alliance Ammo Vendor (58.8, 24.5) — sells Arrows
### Fixed
- Fixed **Chronometer** bars appearing from nearby players' combat events (e.g. another hunter's traps or DoTs triggering your own bars).
- Fixed the list in Families Book's page, that wasn't tall enough to display all families (wolves were hidden).
## [1.3.0] - 2026-03-19 ## [1.3.0] - 2026-03-19
+9
View File
@@ -2,6 +2,15 @@
-- MetaHunt: Hunter's Toolkit Addon -- MetaHunt: Hunter's Toolkit Addon
-- A unified addon merging FeedOMatic, Tooltips, and zhunter -- A unified addon merging FeedOMatic, Tooltips, and zhunter
-- Modern modular architecture with legacy addon support -- Modern modular architecture with legacy addon support
--
-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-- TARGET RUNTIME: LUA 5.0 (WoW 1.12 / Twow 1.18)
-- - 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 -- Initialize beast database after all files loaded
+8 -10
View File
@@ -1,27 +1,21 @@
## Interface: 11200 ## Interface: 11200
## Version: 1.3.0 ## Version: 2.0.0
## Title: MetaHunt - |cff00ff00Hunter ## Title: MetaHunt - |cff00ff00Hunter
## Author: Metasploit and his Copilot ;) ## 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. ## Notes: Unified addon suite for huntards making old addons compatible with Twow, and adding an arsenal of never seen Hunter's utilities.
## SavedVariables: MTH_SavedVariables, FOM_Config, FOM_FoodQuality, FOM_AddedFoods, FOM_RemovedFoods, FOM_Cooking, FOM_QuestFood, FOM_LocaleInfo ## SavedVariables: MTH_SavedVariables
## SavedVariablesPerCharacter: ZHunterMod_Saved, MTH_CharSavedVariables ## SavedVariablesPerCharacter: MTH_CharSavedVariables
data\init.lua data\init.lua
Bindings.xml Bindings.xml
## Load initialization and core framework ## Load initialization and core framework
init\env.xml
init\compat.xml
init\libs.xml
init\api.xml init\api.xml
api\core-feed-tracking.lua api\core-feed-tracking.lua
init\skins.xml
## Load database files (DB files must come before data\init.lua) ## Load database files (DB files must come before data\init.lua)
data\ds-beasts.lua data\ds-beasts.lua
data\ds-beasts-extra.lua
data\ds-beasts-html-enrichment.lua
data\ds-vendors.lua data\ds-vendors.lua
data\ds-vendors-extra.lua data\ds-vendors-extra.lua
data\ds-items.lua data\ds-items.lua
@@ -31,6 +25,7 @@ data\ds-bags.lua
data\ds-items-atlas-sources.lua data\ds-items-atlas-sources.lua
data\ds-zones.lua data\ds-zones.lua
data\ds-zones-fallback.lua data\ds-zones-fallback.lua
data\ds-zones-wma.lua
data\ds-minimap-sizes.lua data\ds-minimap-sizes.lua
data\ds-unit-coords.lua data\ds-unit-coords.lua
data\ds-creature-coords.lua data\ds-creature-coords.lua
@@ -39,6 +34,9 @@ data\ds-beasts-families-dbc.lua
data\ds-beasts-skins.lua data\ds-beasts-skins.lua
data\ds-pet-spells.lua data\ds-pet-spells.lua
data\ds-pet-spells-trainer.lua data\ds-pet-spells-trainer.lua
data\ds-hunter-spells.lua
data\ds-hunter-spells-dbc.lua
data\ds-trainer-catalog.lua
data\ds-racial.lua data\ds-racial.lua
data\init.lua data\init.lua
+61 -25
View File
@@ -1,26 +1,43 @@
# MetaHunt # MetaHunt
MetaHunt is a Turtle WoW Hunter toolkit. MetaHunt is a Twow Hunter toolkit.
It is a modern modular addon that combines never-seen features, and improves/fixes a few old hunter add-ons.
It provides a bunch useful tools for all huntards, wheter they are still levelling, lone-wolf, or HL raider. It is a modern modular addon compiling brand new features, and improving a few old hunter add-ons that were broken or disfunctional with twow.
It provides a bunch of useful functionalities for all huntards, wheter they are still levelling, lone-wolf, or HL raider.
You play the best class, now you have the best companion for it (and its not your pet).
### Important notes
- Altough Nampower isn't mandatory for most of the features, it is a must-have for some, and certainly the MM Widget.
- If you were using old versions of Feed-O-Matic, ICU, zHunterMod and HunterHelper, you dont need those with MetaHunt and you SHOULD disable them to avoid conflicts.
- MetaHunt is fully compatible with QUIVER <3, and is not a replacement for it.
- The addon purposely disable itself on all Capycraft/Ravencraft Realms, following the DDOS attacks they conducted on other servers.
## Twow Data ## Twow Data
MetaHunt ships with large up-to-date datastores for all Turtle WOW data a hunter needs : MetaHunt ships with large twow datastores, and notably:
- 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 families, their abilities and their diet
- All pet abilities ranks - All pet abilities ranks
- All Ammo (Arrows and Bullets) - All hunter and pet spells
- All Stable, Hunter and Pet Masters - All Ammo and ranged weapons
- All Hunter/Pet/Stable Masters
- All Ammo vendors - All Ammo vendors
Online bestiary is also available here https://duvelcorp.codeberg.page/MetaHunt-Web/
## Smart Ammo ## Smart Ammo
- Auto swap your main ammo to junk ammo for shots that doesn't scale on damage, and re-equip you main ammo right away. Don't waste your Doomshots on nothing anymore, save money ! - Auto swap your main ammo to junk ammo for shots that doesn't scale on damage, and re-equip you main ammo right away. Don't waste your Doomshots on nothing anymore, save money !
- Auto equip another ammo instantly during combat whenever you get out of your current ammo. - 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. - Auto equip the right ammo when you swap from a box/xbow to a gun and the other way around.
## MM Widget
1.18.1 weapon of mass-destruction. NAMPOWER MANDATORY. The widget is composed of 5 cells, tracking the 3-state of Experimental ammo cycle (Fire → Nature → Arcane), the Lock & Load procs, and a smart dynamic cell. The smart cell shows either Aimed Shot availability or the right shot to use following the current Experimental ammo proc if any, and it bypasses the proc spell if your current target is immune to the school. You can bind the smart cell to turn the widget into a very efficient one-button rotation. Lets make MM great again !
## Tooltips ## Tooltips
@@ -29,50 +46,69 @@ MetaHunt ships with large up-to-date datastores for all Turtle WOW data a hunter
- Vendors : Shows if a vendor sells Arrows and/or Bullets. - 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. - 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 Old zHunterMod addon on steroids. Fully compatible with Twow, and enhanced with new functionalities
- zAmmo : Track all your ammo Live and swap them in a click or bind - zAmmo : Track all your ammo Live and swap them in a click or bind
- 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 - zPet : Intelligent collapsible pet Bar regrouping all pet spells and providing a main button that always swap on the right spell to use depending on the situation
- zTrack: All your trackings in one collapsible bar - zTrack: All your trackings in one collapsible bar
- zAspect: All your trackings in one collapsible bar - zAspect: All your aspects in one collapsible bar
- zRanged : All the ranged weampons currently in your bags to swap them fast - zRanged : All the ranged weapons currently in your bags to swap them fast
- But also zMounts, zToys and zCompanions. - But also zCrafts, zMounts, zToys and zCompanions.
Additionally, a special _zBar_ can regroup all the zButtons above into a single bar for people that like clean and ordered stuff.
## Feed-O-Matic ## Feed-O-Matic
Now compatible with Turtle, feed your pet with one key bind, it will always pickup the best food for your pet. Now compatible with Twow, feed your pet with one key bind, it will always pickup the best food for your pet, according to your preferences.
## Chronometer ## Chronometer
The good old Chronometer addon, purged of non-necessary things and reconfigured for a pure hunter use. The good old Chronometer addon, purged of non-necessary things and reconfigured for a pure hunter usage.
## ICU (I see you) ## 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. This old addon is mostly a PVP weapon used to display targets' enhanced information when you click on your minimap tracked things. MetaHunt offers many more customization options for it.
## Ammo labels
You can display some labels on all ammo in your bags/bank to instantly see which of those damn same arrows icons really are.
## Auto-Buy ## Auto-Buy
Define your own simple rules for auto buy ammo and pet food when you open a vendor. Define your own simple rules for auto buy ammo and pet food when you open a vendor. You can also make a food-auto-buy planning based on all your stabled pet's diets.
## Auto-Quest ## Auto-Quest
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. 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 Blasted Land mobs involved.
## Efficient Scanners
The add-on smartly scan your character and pet tabs, spellbook, beast training, stable and learning masters windows. This is done automatically and fast in the background, and allows to enrich the static datastore with YOUR own data, and to provide meaningfull dynamic information following the context. It also scan your Pet's state constantly, but with minimal ressources.
## Animations
Metahunt is the first add-on that uses the great PizzaSauce library of [Pizzahawaii](https://codeberg.org/Pizzahawaii/). This adds fun and makes the interface feel alive.
## The Great Book of Huntards ## 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. The Book is the main GUI of MetaHunt, it allows to 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 - Search Hunter-related NPCs (Beasts, vendors, masters) and open the map to locate them like in pfQuest.
- Track all info about the pet abilities that you know and don't know yet - Beast model Viewer : Display the model of all beasts in the world. You can also browse all skin models of the same family to find the best-looking pet according to your tastes.
- Track all info about your stabled pets - Instantly find where the beasts having a specific ability are lying and plan ahead your tracking sessions.
- Keep an history of all your pets after you abandon them or if they run away. - Get a full understanding of all Pet families.
- Display meaningfully the pet abilities that you know and don't know yet
- Get your stable's state everywhere and track all possible information about all your pets
- Keep a detailed history of all the pets you abandonned or those that ran away.
- Browse all twow 1.18.1 hunter and pet spells. Get the status of what you already learned or not, and a detailed cost planning.
## Tune it the way you like
Metahunt option panel is huge. There are hundreds of settings you can configure in order to tweak it like a boss so it fits YOUR preferences.
Its modular architecture also allows you to completely disable any module in order to decrease the addon's memory footprint and background processes.
## What it will never do. ### The world is yours. Happy Hunting Fellas !
The goal of MetaHunt is to offer hunter's features that are not covered by existing Twow addons, and in others https://github.com/SabineWren/Quiver .
There's no point to reinvent the wheel, except is the wheel is broken. Nothing will work better than Quiver for auto-shot, castbar and tranqshot mgt. MetaHunt is build to work alongside of Quiver and is complementary to it, and it will remain like this unless Quiver turns to be not upgraded anymore.
+62 -7
View File
@@ -10,10 +10,23 @@ local function AntiDaze_GetSaved()
if type(MTH_CharSavedVariables) ~= "table" then if type(MTH_CharSavedVariables) ~= "table" then
MTH_CharSavedVariables = {} MTH_CharSavedVariables = {}
end end
if type(MTH_CharSavedVariables.antiDaze) ~= "table" then local store
MTH_CharSavedVariables.antiDaze = {} if MTH and MTH.GetModuleCharSavedVariables then
store = MTH:GetModuleCharSavedVariables("antidaze")
end end
return MTH_CharSavedVariables.antiDaze if type(store) ~= "table" then
if type(MTH_CharSavedVariables.modules) ~= "table" then
MTH_CharSavedVariables.modules = {}
end
if type(MTH_CharSavedVariables.modules.antidaze) ~= "table" then
MTH_CharSavedVariables.modules.antidaze = {}
end
store = MTH_CharSavedVariables.modules.antidaze
end
if type(store.settings) ~= "table" then
store.settings = {}
end
return store.settings
end end
local function AntiDaze_IsEnabled() local function AntiDaze_IsEnabled()
@@ -25,16 +38,21 @@ local function AntiDaze_RegisterCombatEvents()
if not AntiDazeFrame then if not AntiDazeFrame then
return return
end end
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE") if MTH and MTH.nampower then
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE") AntiDazeFrame:RegisterEvent("DEBUFF_ADDED_SELF")
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE") else
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PARTY_DAMAGE") AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE")
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE")
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PARTY_DAMAGE")
end
end end
local function AntiDaze_UnregisterCombatEvents() local function AntiDaze_UnregisterCombatEvents()
if not AntiDazeFrame then if not AntiDazeFrame then
return return
end end
AntiDazeFrame:UnregisterEvent("DEBUFF_ADDED_SELF")
AntiDazeFrame:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE") AntiDazeFrame:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE")
AntiDazeFrame:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE") AntiDazeFrame:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE")
AntiDazeFrame:UnregisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE") AntiDazeFrame:UnregisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
@@ -87,10 +105,31 @@ local function AntiDaze_ResolvePlayerFromMessage(msg)
return nil return nil
end end
local AntiDaze_DazeSpellIdCache = {} -- [spellId] = true/false
local function AntiDaze_IsDazeSpellId(spellId)
local cached = AntiDaze_DazeSpellIdCache[spellId]
if cached ~= nil then return cached end
if type(GetSpellRecField) ~= "function" then return false end
local name = GetSpellRecField(spellId, "name")
if name and (name == "Daze" or name == "Dazed") then
AntiDaze_DazeSpellIdCache[spellId] = true
return true
end
AntiDaze_DazeSpellIdCache[spellId] = false
return false
end
local function AntiDaze_CancelRelevantAspect(isSelfDazed) local function AntiDaze_CancelRelevantAspect(isSelfDazed)
local packName = _G["ZHUNTER_ASPECT_PACK"] or "Aspect of the Pack" local packName = _G["ZHUNTER_ASPECT_PACK"] or "Aspect of the Pack"
local cheetahName = _G["ZHUNTER_ASPECT_CHEETAH"] or "Aspect of the Cheetah" local cheetahName = _G["ZHUNTER_ASPECT_CHEETAH"] or "Aspect of the Cheetah"
-- Always use GetPlayerBuff + tooltip scan for cancellation.
-- The aura-array slot index from GetUnitField does NOT map 1:1
-- to the sequential buff index that CancelPlayerBuff expects
-- (empty slots create gaps), so we cannot use the NamPower
-- array for the cancel call. NamPower is still used for daze
-- DETECTION via DEBUFF_ADDED_SELF in the event handler.
for i = 0, 32 do for i = 0, 32 do
if GetPlayerBuff(i, "HELPFUL") < 0 then if GetPlayerBuff(i, "HELPFUL") < 0 then
break break
@@ -118,6 +157,12 @@ if AntiDazeFrame then
AntiDazeFrame:RegisterEvent("VARIABLES_LOADED") AntiDazeFrame:RegisterEvent("VARIABLES_LOADED")
AntiDazeFrame:SetScript("OnEvent", function() AntiDazeFrame:SetScript("OnEvent", function()
if event == "VARIABLES_LOADED" then if event == "VARIABLES_LOADED" then
-- Class gate: only hunters use AntiDaze.
if MTH and MTH.IsClassGateBlocked and MTH:IsClassGateBlocked() then
AntiDazeFrame:UnregisterAllEvents()
AntiDazeFrame:SetScript("OnEvent", nil)
return
end
if AntiDaze_IsEnabled() then if AntiDaze_IsEnabled() then
AntiDaze_RegisterCombatEvents() AntiDaze_RegisterCombatEvents()
else else
@@ -126,6 +171,16 @@ if AntiDazeFrame then
return return
end end
-- NamPower path: DEBUFF_ADDED_SELF fires with spellId in arg3
if event == "DEBUFF_ADDED_SELF" then
local spellId = tonumber(arg3)
if spellId and AntiDaze_IsDazeSpellId(spellId) then
AntiDaze_CancelRelevantAspect(true)
end
return
end
-- Legacy path: parse chat messages
local player = AntiDaze_ResolvePlayerFromMessage(arg1) local player = AntiDaze_ResolvePlayerFromMessage(arg1)
if player then if player then
local you = _G["ZHUNTER_YOU"] or "You" local you = _G["ZHUNTER_YOU"] or "You"
+37 -9
View File
@@ -33,10 +33,20 @@ function AutoStrip_GetSaved()
if type(MTH_CharSavedVariables) ~= "table" then if type(MTH_CharSavedVariables) ~= "table" then
MTH_CharSavedVariables = {} MTH_CharSavedVariables = {}
end end
if type(MTH_CharSavedVariables.autoStrip) ~= "table" then local store
MTH_CharSavedVariables.autoStrip = {} if MTH and MTH.GetModuleCharSavedVariables then
store = MTH:GetModuleCharSavedVariables("autostrip")
end end
return MTH_CharSavedVariables.autoStrip if type(store) ~= "table" then
if type(MTH_CharSavedVariables.modules) ~= "table" then
MTH_CharSavedVariables.modules = {}
end
if type(MTH_CharSavedVariables.modules.autostrip) ~= "table" then
MTH_CharSavedVariables.modules.autostrip = {}
end
store = MTH_CharSavedVariables.modules.autostrip
end
return store
end end
local AUTO_STRIP_ORDER = {16, 17, 18, 5, 7, 1, 3, 10, 8, 6, 9} local AUTO_STRIP_ORDER = {16, 17, 18, 5, 7, 1, 3, 10, 8, 6, 9}
@@ -150,6 +160,10 @@ function AutoStrip_SetAutoStripToggle(enabled, silent)
if enabled then if enabled then
saved["autostrip"] = 1 saved["autostrip"] = 1
AutoStrip_On = 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 if AutoStripDisplay and AutoStripDisplay:IsVisible() and AutoStripDisplayAutoCast then
AutoStripDisplayAutoCast:Show() AutoStripDisplayAutoCast:Show()
end end
@@ -159,6 +173,10 @@ function AutoStrip_SetAutoStripToggle(enabled, silent)
else else
saved["autostrip"] = nil saved["autostrip"] = nil
AutoStrip_On = nil AutoStrip_On = nil
if AutoStrip_Frame then
AutoStrip_Frame:UnregisterEvent("PLAYER_REGEN_DISABLED")
AutoStrip_Frame:UnregisterEvent("PLAYER_REGEN_ENABLED")
end
if AutoStripDisplayAutoCast then if AutoStripDisplayAutoCast then
AutoStripDisplayAutoCast:Hide() AutoStripDisplayAutoCast:Hide()
end end
@@ -252,11 +270,19 @@ function AutoStrip_UnequipAll(weaponsOnly)
end end
local function AutoStrip_OnEvent() local function AutoStrip_OnEvent()
AutoStrip_Trace("OnEvent evt=" .. tostring(event or "") if AutoStrip_TraceEnabled then
.. " runtime=" .. tostring(AutoStrip_On and 1 or 0) AutoStrip_Trace("OnEvent evt=" .. tostring(event or "")
.. " wasInCombat=" .. tostring(AutoStrip_WasInCombat and 1 or 0) .. " runtime=" .. tostring(AutoStrip_On and 1 or 0)
.. " playerInCombat=" .. tostring((type(UnitAffectingCombat) == "function" and UnitAffectingCombat("player")) 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 if event == "VARIABLES_LOADED" then
-- Class gate: only hunters use AutoStrip.
if MTH and MTH.IsClassGateBlocked and MTH:IsClassGateBlocked() then
AutoStrip_Frame:UnregisterAllEvents()
AutoStrip_Frame:SetScript("OnEvent", nil)
return
end
local saved = AutoStrip_GetSaved() local saved = AutoStrip_GetSaved()
AutoStrip_Trace("VARIABLES_LOADED saved.autostrip=" .. tostring(saved["autostrip"] and 1 or 0) AutoStrip_Trace("VARIABLES_LOADED saved.autostrip=" .. tostring(saved["autostrip"] and 1 or 0)
.. " saved.display=" .. tostring(saved["display"] and 1 or 0)) .. " saved.display=" .. tostring(saved["display"] and 1 or 0))
@@ -318,9 +344,11 @@ end
AutoStrip_Frame = CreateFrame("Frame", "MTH_AutoStripEvent") AutoStrip_Frame = CreateFrame("Frame", "MTH_AutoStripEvent")
if AutoStrip_Frame then 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("VARIABLES_LOADED")
AutoStrip_Frame:RegisterEvent("PLAYER_REGEN_DISABLED")
AutoStrip_Frame:RegisterEvent("PLAYER_REGEN_ENABLED")
AutoStrip_Frame:RegisterEvent("PLAYER_ENTERING_WORLD") AutoStrip_Frame:RegisterEvent("PLAYER_ENTERING_WORLD")
AutoStrip_Frame:RegisterEvent("PLAYER_LOGOUT") AutoStrip_Frame:RegisterEvent("PLAYER_LOGOUT")
AutoStrip_Frame:SetScript("OnEvent", AutoStrip_OnEvent) AutoStrip_Frame:SetScript("OnEvent", AutoStrip_OnEvent)
+1347
View File
File diff suppressed because it is too large Load Diff
+463
View File
@@ -0,0 +1,463 @@
-- ============================================================
-- Bag Ammo Labels — standalone overlay for bag & bank ammo slots
-- Uses globals from zButtonAmmo.lua:
-- zButtonAmmo_ShortArrowLabels, zButtonAmmo_ShortBulletLabels,
-- zButtonAmmo_GetQualityColor, zButtonAmmo_GetColorGradient,
-- zButtonAmmo_GetKnownAmmoTypes
-- Uses DPS data from MTH_DS_AmmoItems[id].dps
-- ============================================================
-- Saved variable cache (per-character, stored in "general" module)
local cache = {} -- bagAmmoLabels, bankAmmoLabels, bagAmmoDamage
local function GetStore()
return MTH and MTH.GetModuleCharSavedVariables and MTH:GetModuleCharSavedVariables("general")
end
local function GetCached(key, default)
if cache[key] == nil then
local store = GetStore()
if type(store) == "table" and store[key] ~= nil then
cache[key] = store[key] and true or false
else
cache[key] = default or false
end
end
return cache[key]
end
local function SetCached(key, enabled)
cache[key] = enabled and true or false
local store = GetStore()
if type(store) == "table" then
store[key] = cache[key]
end
end
-- Public getters/setters
function MTH_BagAmmoLabels_GetEnabled()
return GetCached("bagAmmoLabels")
end
function MTH_BagAmmoLabels_SetEnabled(enabled)
SetCached("bagAmmoLabels", enabled)
MTH_BagAmmoLabels_RefreshAll()
end
function MTH_BankAmmoLabels_GetEnabled()
return GetCached("bankAmmoLabels")
end
function MTH_BankAmmoLabels_SetEnabled(enabled)
SetCached("bankAmmoLabels", enabled)
MTH_BagAmmoLabels_RefreshAllBank()
end
function MTH_BagAmmoDamage_GetEnabled()
return GetCached("bagAmmoDamage")
end
function MTH_BagAmmoDamage_SetEnabled(enabled)
SetCached("bagAmmoDamage", enabled)
MTH_BagAmmoLabels_RefreshAll()
MTH_BagAmmoLabels_RefreshAllBank()
end
-- Heat color: red (7.5 DPS) → yellow → green (20.5 DPS); below 7.5 = grey
local DPS_MIN = 7.5
local DPS_MAX = 20.5
local function GetDpsHeatColor(dps)
if not dps then return 1, 1, 1 end
if dps < DPS_MIN then return 0.6, 0.6, 0.6 end
local perc = (dps - DPS_MIN) / (DPS_MAX - DPS_MIN)
return zButtonAmmo_GetColorGradient(perc)
end
-- Look up DPS from MTH_DS_AmmoItems by numeric item ID
local function GetAmmoDps(numericId)
if not MTH_DS_AmmoItems then return nil end
local entry = MTH_DS_AmmoItems[numericId]
return entry and entry.dps
end
-- Detect whether pfUI bags are active
local function HasPfUI()
return pfUI and pfUI.bags and pfUI.bag and true or false
end
-- Is this bagID a bank bag?
local BANK_BAGS = { [-1] = true, [5] = true, [6] = true, [7] = true, [8] = true, [9] = true, [10] = true, [11] = true }
local function IsBankBag(bagID)
return BANK_BAGS[bagID] or false
end
-- Apply or clear labels on a single bag-slot button
local function UpdateButton(button, bagID, slot, showName, showDmg)
if not button then return end
-- Lazy-create name label (TOPLEFT)
if not button._mthAmmoLabel then
button._mthAmmoLabel = button:CreateFontString(nil, "OVERLAY")
button._mthAmmoLabel:SetPoint("TOPLEFT", button, "TOPLEFT", 1, -1)
button._mthAmmoLabel:SetFont("Fonts\\ARIALN.ttf", 9, "OUTLINE")
button._mthAmmoLabel:Hide()
end
-- Lazy-create damage label (CENTER, large + bold)
if not button._mthAmmoDmgLabel then
button._mthAmmoDmgLabel = button:CreateFontString(nil, "OVERLAY")
button._mthAmmoDmgLabel:SetPoint("CENTER", button, "CENTER", 0, 0)
button._mthAmmoDmgLabel:SetFont("Fonts\\ARIALN.ttf", 10, "THICKOUTLINE")
button._mthAmmoDmgLabel:Hide()
end
local nameLabel = button._mthAmmoLabel
local dmgLabel = button._mthAmmoDmgLabel
if not showName and not showDmg then
nameLabel:Hide()
dmgLabel:Hide()
return
end
local link = GetContainerItemLink(bagID, slot)
if not link then
nameLabel:Hide()
dmgLabel:Hide()
return
end
local _, _, id = string.find(link, "Hitem:(%d+)")
if not id then
nameLabel:Hide()
dmgLabel:Hide()
return
end
local numericId = tonumber(id)
local itemName, _, itemQuality, _, _, _, _, _, itemSlot = GetItemInfo(id)
if not itemName then
nameLabel:Hide()
dmgLabel:Hide()
return
end
local isAmmo = (itemSlot == "INVTYPE_AMMO")
if not isAmmo then
local knownAmmo = zButtonAmmo_GetKnownAmmoTypes and zButtonAmmo_GetKnownAmmoTypes()
isAmmo = knownAmmo and knownAmmo[itemName]
end
if not isAmmo then
nameLabel:Hide()
dmgLabel:Hide()
return
end
-- Name label
if showName then
local shortText = zButtonAmmo_ShortArrowLabels[itemName]
or zButtonAmmo_ShortBulletLabels[itemName]
if shortText then
local cr, cg, cb = zButtonAmmo_GetQualityColor(itemQuality)
nameLabel:SetTextColor(cr, cg, cb, 1)
nameLabel:SetText(shortText)
nameLabel:Show()
else
nameLabel:Hide()
end
else
nameLabel:Hide()
end
-- Damage label
if showDmg then
local dps = GetAmmoDps(numericId)
if dps then
local cr, cg, cb = GetDpsHeatColor(dps)
dmgLabel:SetTextColor(cr, cg, cb, 1)
dmgLabel:SetText(dps)
dmgLabel:Show()
else
dmgLabel:Hide()
end
else
dmgLabel:Hide()
end
end
-- Clear both labels on a button
local function ClearButton(button)
if not button then return end
if button._mthAmmoLabel then button._mthAmmoLabel:Hide() end
if button._mthAmmoDmgLabel then button._mthAmmoDmgLabel:Hide() end
end
-- Forward declaration
local RefreshAllVisibleFrames
-- Refresh labels on a single bag (works for any bagID)
local function RefreshBag(bagID, showName, showDmg)
local numSlots = GetContainerNumSlots(bagID)
if numSlots == 0 then return end
-- pfUI path
if HasPfUI() then
local pfBag = pfUI.bags[bagID]
if not pfBag or not pfBag.slots then return end
for slot = 1, numSlots do
local slotData = pfBag.slots[slot]
local button = slotData and slotData.frame
if button then
if showName or showDmg then
UpdateButton(button, bagID, slot, showName, showDmg)
else
ClearButton(button)
end
end
end
return
end
-- Default UI path
-- Main bank (bagID -1): buttons are BankFrameItem1..28
if bagID == -1 then
for slot = 1, numSlots do
local button = getglobal("BankFrameItem" .. slot)
if button then
if showName or showDmg then
UpdateButton(button, -1, slot, showName, showDmg)
else
ClearButton(button)
end
end
end
return
end
-- All other bags (0-4 and 5-11): search all ContainerFrames
for ci = 1, 13 do
local frame = getglobal("ContainerFrame" .. ci)
if frame and frame:IsVisible() and frame:GetID() == bagID then
local frameName = frame:GetName()
-- Visual button i = API slot (numSlots - i + 1)
for i = 1, numSlots do
local button = getglobal(frameName .. "Item" .. i)
local apiSlot = numSlots - i + 1
if button then
if showName or showDmg then
UpdateButton(button, bagID, apiSlot, showName, showDmg)
else
ClearButton(button)
end
end
end
-- Clear stale labels on any buttons beyond numSlots
for slot = numSlots + 1, 24 do
local button = getglobal(frameName .. "Item" .. slot)
if not button then break end
ClearButton(button)
end
return
end
end
end
-- Public: refresh all inventory bags (0-4)
function MTH_BagAmmoLabels_RefreshAll()
RefreshAllVisibleFrames()
end
-- Public: refresh all bank bags (-1, 5-11)
function MTH_BagAmmoLabels_RefreshAllBank()
RefreshAllVisibleFrames()
end
-- Refresh all visible default UI ContainerFrames (frame-centric: no bagID lookup)
RefreshAllVisibleFrames = function()
local bagShowName = MTH_BagAmmoLabels_GetEnabled()
local bankShowName = MTH_BankAmmoLabels_GetEnabled()
local showDmg = MTH_BagAmmoDamage_GetEnabled()
if HasPfUI() then
-- pfUI: refresh via pfUI bag structure
for bagID = 0, 4 do
RefreshBag(bagID, bagShowName, showDmg)
end
local bankBags = { -1, 5, 6, 7, 8, 9, 10, 11 }
for _, bagID in ipairs(bankBags) do
RefreshBag(bagID, bankShowName, showDmg)
end
return
end
-- Default UI: iterate all visible ContainerFrames
for ci = 1, 13 do
local cf = getglobal("ContainerFrame" .. ci)
if cf and cf:IsVisible() then
local bagID = cf:GetID()
local showName = IsBankBag(bagID) and bankShowName or bagShowName
local numSlots = GetContainerNumSlots(bagID)
local fname = cf:GetName()
-- Visual button i = API slot (numSlots - i + 1)
for i = 1, numSlots do
local button = getglobal(fname .. "Item" .. i)
local apiSlot = numSlots - i + 1
if button then
if showName or showDmg then
UpdateButton(button, bagID, apiSlot, showName, showDmg)
else
ClearButton(button)
end
end
end
for slot = numSlots + 1, 24 do
local button = getglobal(fname .. "Item" .. slot)
if not button then break end
ClearButton(button)
end
end
end
-- Bank bag -1: BankFrameItem buttons
if bankShowName or showDmg then
local numSlots = GetContainerNumSlots(-1)
for slot = 1, numSlots do
local button = getglobal("BankFrameItem" .. slot)
if button then
UpdateButton(button, -1, slot, bankShowName, showDmg)
end
end
end
end
-- Event-driven refresh: coalesce with a short delay
do
local frame = CreateFrame("Frame", "MTH_BagAmmoLabelsFrame", UIParent)
local needsRefresh = false
local elapsed = 0
frame:RegisterEvent("BAG_UPDATE")
frame:RegisterEvent("PLAYERBANKSLOTS_CHANGED")
frame:RegisterEvent("PLAYERBANKBAGSLOTS_CHANGED")
frame:RegisterEvent("BANKFRAME_OPENED")
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:SetScript("OnEvent", function()
if event == "PLAYER_ENTERING_WORLD" then
-- After login/reload, delay 1s for item cache to populate
elapsed = -0.95
needsRefresh = true
frame:Show()
return
end
if event == "BANKFRAME_OPENED" then
if MTH_BankAmmoLabels_GetEnabled() or MTH_BagAmmoDamage_GetEnabled() then
needsRefresh = true
if not frame:IsShown() then
elapsed = 0
frame:Show()
end
end
return
end
if event == "PLAYERBANKSLOTS_CHANGED" or event == "PLAYERBANKBAGSLOTS_CHANGED" then
if MTH_BankAmmoLabels_GetEnabled() or MTH_BagAmmoDamage_GetEnabled() then
needsRefresh = true
if not frame:IsShown() then
elapsed = 0
frame:Show()
end
end
return
end
-- BAG_UPDATE
local bagID = arg1
if not bagID then return end
if IsBankBag(bagID) then
if not MTH_BankAmmoLabels_GetEnabled() and not MTH_BagAmmoDamage_GetEnabled() then return end
else
if not MTH_BagAmmoLabels_GetEnabled() and not MTH_BagAmmoDamage_GetEnabled() then return end
end
needsRefresh = true
if not frame:IsShown() then
elapsed = 0
frame:Show()
end
end)
frame:SetScript("OnUpdate", function()
elapsed = elapsed + (arg1 or 0.016)
if elapsed < 0.05 then return end
needsRefresh = false
frame:Hide()
RefreshAllVisibleFrames()
end)
frame:Hide()
-- Hook ContainerFrame OnShow: trigger a refresh when a bag becomes visible
if not HasPfUI() then
for ci = 1, 13 do
local cf = getglobal("ContainerFrame" .. ci)
if cf then
local origOnShow = cf:GetScript("OnShow")
cf:SetScript("OnShow", function()
if origOnShow then origOnShow() end
needsRefresh = true
if not frame:IsShown() then
elapsed = 0
frame:Show()
end
end)
end
end
end
-- Post-hook ContainerFrame_Update for default UI (synchronous update)
if type(ContainerFrame_Update) == "function" then
local origCFU = ContainerFrame_Update
ContainerFrame_Update = function(cf)
origCFU(cf)
if cf and cf:IsVisible() then
local bagID = cf:GetID()
local showDmg = MTH_BagAmmoDamage_GetEnabled()
local showName
if IsBankBag(bagID) then
showName = MTH_BankAmmoLabels_GetEnabled()
else
showName = MTH_BagAmmoLabels_GetEnabled()
end
if showName or showDmg then
local numSlots = GetContainerNumSlots(bagID)
local fname = cf:GetName()
-- Visual button i = API slot (numSlots - i + 1)
for i = 1, numSlots do
local button = getglobal(fname .. "Item" .. i)
local apiSlot = numSlots - i + 1
if button then
UpdateButton(button, bagID, apiSlot, showName, showDmg)
end
end
for slot = numSlots + 1, 24 do
local button = getglobal(fname .. "Item" .. slot)
if not button then break end
ClearButton(button)
end
end
else
-- Frame hidden: clear all labels
if cf then
local fname = cf:GetName()
if fname then
for slot = 1, 24 do
local button = getglobal(fname .. "Item" .. slot)
if not button then break end
ClearButton(button)
end
end
end
end
end
end
end
+4 -1
View File
@@ -38,7 +38,10 @@ local function MTH_ConfigEnsureStore(module_name)
root.modules[module_name] = legacy root.modules[module_name] = legacy
end end
root[module_name] = root.modules[module_name] -- NOTE: we intentionally do NOT mirror root[module_name] = root.modules[module_name].
-- That old alias made WoW serialize a full DUPLICATE of every module store at the
-- account root on disk. The canonical store is root.modules[module_name] only.
root[module_name] = nil
MTH_SavedVariables = root MTH_SavedVariables = root
return root.modules[module_name] return root.modules[module_name]
end end
+16 -4
View File
@@ -1,5 +1,5 @@
MTH_CONST = MTH_CONST or {} MTH_CONST = MTH_CONST or {}
MTH_CONST.version = MTH_CONST.version or "1.3.0" MTH_CONST.version = MTH_CONST.version or "2.0.0"
MTH_CONST.WEAPON_TYPES = { MTH_CONST.WEAPON_TYPES = {
BOWS = "Bows", BOWS = "Bows",
@@ -24,6 +24,7 @@ MTH_CONST.AMMO = {
ICETHREADED = "Ice Threaded Bullet", ICETHREADED = "Ice Threaded Bullet",
THORIUM = "Thorium Shells", THORIUM = "Thorium Shells",
ROCKSHARD = "Rockshard Pellets", ROCKSHARD = "Rockshard Pellets",
CRAFTEDTHORIUM = "Enchanted Thorium Shells",
CANNONBALL = "Miniature Cannon Balls", CANNONBALL = "Miniature Cannon Balls",
}, },
ARROWS = { ARROWS = {
@@ -32,16 +33,19 @@ MTH_CONST.AMMO = {
RAZOR = "Razor Arrow", RAZOR = "Razor Arrow",
FEATHER = "Feathered Arrow", FEATHER = "Feathered Arrow",
PRECISION = "Precision Arrow", PRECISION = "Precision Arrow",
BRIGHT = "Bright Wood Arrows",
JAGGED = "Jagged Arrow", JAGGED = "Jagged Arrow",
SHADE = "Shade Wood Arrows",
ICETHREADED = "Ice Threaded Arrow", ICETHREADED = "Ice Threaded Arrow",
SMOOTH = "Smooth Ironfeather Arrows",
THORIUM = "Thorium Headed Arrow", THORIUM = "Thorium Headed Arrow",
STAR = "Starfeather Arrows",
DOOMSHOT = "Doomshot", DOOMSHOT = "Doomshot",
}, },
JUNKSHOTS = { JUNKSHOTS = {
SERPENT = "Serpent Sting", SERPENT = "Serpent Sting",
SCORPID = "Scorpid Sting", SCORPID = "Scorpid Sting",
VIPER = "Viper Sting", VIPER = "Viper Sting",
ARCANE = "Arcane Shot",
CONCUSSIVE = "Concussive Shot", CONCUSSIVE = "Concussive Shot",
DISTRACTING = "Distracting Shot", DISTRACTING = "Distracting Shot",
BAITED = "Baited Shot", BAITED = "Baited Shot",
@@ -69,6 +73,7 @@ BULLETS_ICETHREADED = MTH_CONST.AMMO.BULLETS.ICETHREADED
BULLETS_THORIUM = MTH_CONST.AMMO.BULLETS.THORIUM BULLETS_THORIUM = MTH_CONST.AMMO.BULLETS.THORIUM
BULLETS_ROCKSHARD = MTH_CONST.AMMO.BULLETS.ROCKSHARD BULLETS_ROCKSHARD = MTH_CONST.AMMO.BULLETS.ROCKSHARD
BULLETS_CANNONBALL = MTH_CONST.AMMO.BULLETS.CANNONBALL BULLETS_CANNONBALL = MTH_CONST.AMMO.BULLETS.CANNONBALL
BULLETS_CRAFTEDTHORIUM = MTH_CONST.AMMO.BULLETS.CRAFTEDTHORIUM
ARROWS_ROUGH = MTH_CONST.AMMO.ARROWS.ROUGH ARROWS_ROUGH = MTH_CONST.AMMO.ARROWS.ROUGH
ARROWS_SHARP = MTH_CONST.AMMO.ARROWS.SHARP 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_PRECISION = MTH_CONST.AMMO.ARROWS.PRECISION
ARROWS_JAGGED = MTH_CONST.AMMO.ARROWS.JAGGED ARROWS_JAGGED = MTH_CONST.AMMO.ARROWS.JAGGED
ARROWS_ICETHREADED = MTH_CONST.AMMO.ARROWS.ICETHREADED 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_THORIUM = MTH_CONST.AMMO.ARROWS.THORIUM
ARROWS_STAR = MTH_CONST.AMMO.ARROWS.STAR
ARROWS_DOOMSHOT = MTH_CONST.AMMO.ARROWS.DOOMSHOT ARROWS_DOOMSHOT = MTH_CONST.AMMO.ARROWS.DOOMSHOT
JUNKSHOT_SERPENT = MTH_CONST.AMMO.JUNKSHOTS.SERPENT JUNKSHOT_SERPENT = MTH_CONST.AMMO.JUNKSHOTS.SERPENT
JUNKSHOT_SCORPID = MTH_CONST.AMMO.JUNKSHOTS.SCORPID JUNKSHOT_SCORPID = MTH_CONST.AMMO.JUNKSHOTS.SCORPID
JUNKSHOT_VIPER = MTH_CONST.AMMO.JUNKSHOTS.VIPER JUNKSHOT_VIPER = MTH_CONST.AMMO.JUNKSHOTS.VIPER
JUNKSHOT_ARCANE = MTH_CONST.AMMO.JUNKSHOTS.ARCANE
JUNKSHOT_CONCUSSIVE = MTH_CONST.AMMO.JUNKSHOTS.CONCUSSIVE JUNKSHOT_CONCUSSIVE = MTH_CONST.AMMO.JUNKSHOTS.CONCUSSIVE
JUNKSHOT_DISTRACTING = MTH_CONST.AMMO.JUNKSHOTS.DISTRACTING JUNKSHOT_DISTRACTING = MTH_CONST.AMMO.JUNKSHOTS.DISTRACTING
JUNKSHOT_BAITED = MTH_CONST.AMMO.JUNKSHOTS.BAITED JUNKSHOT_BAITED = MTH_CONST.AMMO.JUNKSHOTS.BAITED
@@ -105,6 +113,7 @@ MTH_AMMO_BULLETS = {
BULLETS_ICETHREADED, BULLETS_ICETHREADED,
BULLETS_THORIUM, BULLETS_THORIUM,
BULLETS_ROCKSHARD, BULLETS_ROCKSHARD,
BULLETS_CRAFTEDTHORIUM,
BULLETS_CANNONBALL, BULLETS_CANNONBALL,
} }
@@ -114,9 +123,13 @@ MTH_AMMO_ARROWS = {
ARROWS_RAZOR, ARROWS_RAZOR,
ARROWS_FEATHER, ARROWS_FEATHER,
ARROWS_PRECISION, ARROWS_PRECISION,
ARROWS_BRIGHT,
ARROWS_JAGGED, ARROWS_JAGGED,
ARROWS_SHADE,
ARROWS_ICETHREADED, ARROWS_ICETHREADED,
ARROWS_SMOOTH,
ARROWS_THORIUM, ARROWS_THORIUM,
ARROWS_STAR,
ARROWS_DOOMSHOT, ARROWS_DOOMSHOT,
} }
@@ -124,7 +137,6 @@ MTH_AMMO_JUNKSHOTS = {
JUNKSHOT_SERPENT, JUNKSHOT_SERPENT,
JUNKSHOT_SCORPID, JUNKSHOT_SCORPID,
JUNKSHOT_VIPER, JUNKSHOT_VIPER,
JUNKSHOT_ARCANE,
JUNKSHOT_CONCUSSIVE, JUNKSHOT_CONCUSSIVE,
JUNKSHOT_DISTRACTING, JUNKSHOT_DISTRACTING,
JUNKSHOT_BAITED, JUNKSHOT_BAITED,
+8 -1
View File
@@ -167,7 +167,7 @@ local function MTH_FEED_IndexBagFoods()
return true return true
end end
local function MTH_FEED_HasPetFeedBuff() function MTH_FEED_HasPetFeedBuff()
if type(UnitBuff) ~= "function" then if type(UnitBuff) ~= "function" then
return false return false
end end
@@ -1385,6 +1385,13 @@ function MTH_FEED_FinalizeAttempt(attemptId, payload)
if petKey ~= "" then if petKey ~= "" then
MTH_FEED_Runtime.sessionFeedsByPetId[petKey] = (tonumber(MTH_FEED_Runtime.sessionFeedsByPetId[petKey]) or 0) + 1 MTH_FEED_Runtime.sessionFeedsByPetId[petKey] = (tonumber(MTH_FEED_Runtime.sessionFeedsByPetId[petKey]) or 0) + 1
end end
if type(MTH_FX_Flash) == "function" and type(MTH_FX_IsEnabled) == "function"
and MTH_FX_IsEnabled("pet.feed") then
local target = getglobal("zButtonPet")
if target then
MTH_FX_Flash(target, 0.35, 1.00, 0.35, 0.5)
end
end
else else
petRow.totals.rejected = petRow.totals.rejected + 1 petRow.totals.rejected = petRow.totals.rejected + 1
if reason == "no-buff" then if reason == "no-buff" then
+114 -45
View File
@@ -1,5 +1,5 @@
MTH = MTH or { MTH = MTH or {
version = "1.3.0", version = "2.0.0",
name = "MetaHunt", name = "MetaHunt",
modules = {}, modules = {},
config = {}, config = {},
@@ -7,13 +7,29 @@ MTH = MTH or {
} }
local MTH_CHAT_PREFIX = "|cFFFFFFFF[|r|cFFD14A4AMeta|r|cFFABD473Hunt|r|cFFFFFFFF]|r " local MTH_CHAT_PREFIX = "|cFFFFFFFF[|r|cFFD14A4AMeta|r|cFFABD473Hunt|r|cFFFFFFFF]|r "
local MTH_NON_HUNTER_BLOCK_MESSAGE = "You are not a hunter and you are not allowed here. Disable the add-on for this character." local MTH_NON_HUNTER_BLOCK_MESSAGE = "You are not a hunter. Disable the add-on for this character."
local MTH_REALM_BLOCK_MESSAGE = "Zero support for Ravencraft/Capybara mafia's realms. Consider joining Octowow."
local MTH_BLOCKED_REALMS = {
["Medivh"] = true,
["\230\176\184\230\173\140\232\141\146\233\135\142"] = true,
["\211\192\184\232\187\196\210\176"] = true,
["\229\159\186\232\181\171\231\186\179\230\150\175"] = true,
["\187\249\186\213\196\201\203\185"] = true,
["\231\190\164\230\152\159\231\155\134\229\156\176"] = true,
["\200\186\208\199\197\232\181\216"] = true,
}
local MTH_MESSAGE_DEFAULTS = { local MTH_MESSAGE_DEFAULTS = {
initModulesLoaded = false, initModulesLoaded = false,
initSarcasticWelcome = true, initSarcasticWelcome = true,
petHungry = false, petHungry = false,
beastTrainingScan = true, beastTrainingScan = true,
spellbookScan = false, spellbookScan = false,
trainerScan = true,
hunterLevelUp = true,
hunterLevelQuiet = false,
petLevelUp = true,
petLoyaltyUp = true,
petRanAway = true, petRanAway = true,
mapMarkers = true, mapMarkers = true,
stableScan = true, stableScan = true,
@@ -189,6 +205,62 @@ function MTH:ApplyClassGate(_source)
return blocked return blocked
end end
function MTH:GetCurrentRealmName()
local realm = nil
if type(GetRealmName) == "function" then
realm = GetRealmName()
end
if (realm == nil or realm == "") and type(GetCVar) == "function" then
realm = GetCVar("realmName")
end
return realm
end
function MTH:IsRealmGateBlocked()
return self and self._realmGateBlocked and true or false
end
function MTH:CheckRealmGate()
-- TEMP TESTING: allow Ravencraft client access while HunterBook layout is reviewed.
if true then
return false
end
-- Realm name is only reliable once logged in; an empty result never blocks.
local realm = self:GetCurrentRealmName()
if type(realm) == "string" and realm ~= "" then
self._currentRealm = realm
if MTH_BLOCKED_REALMS[realm] then
self._realmGateBlocked = true
end
end
return self:IsRealmGateBlocked()
end
function MTH:AnnounceRealmGateBlocked()
if self._realmGateAnnounced then
return
end
local sink = MTH_TryOutputClassGateMessage(MTH_REALM_BLOCK_MESSAGE)
if sink == "chat" or sink == "uierrors" or sink == "print" then
self._realmGateAnnounced = true
end
end
function MTH:ApplyRealmGate(_source)
if self:IsHardBlocked() then
return true
end
if self:CheckRealmGate() then
self:AnnounceRealmGateBlocked()
if self.ShutdownForNonHunter then
self:ShutdownForNonHunter(_source or "realm-gate")
end
return true
end
return false
end
MTH:ApplyClassGate("startup") MTH:ApplyClassGate("startup")
if not MTH._classGateLifecycleFrame then if not MTH._classGateLifecycleFrame then
@@ -202,6 +274,11 @@ if not MTH._classGateLifecycleFrame then
MTH._classGateAnnounced = nil MTH._classGateAnnounced = nil
MTH._classGateAnnouncePending = nil MTH._classGateAnnouncePending = nil
end end
if MTH and MTH.ApplyRealmGate and MTH:ApplyRealmGate(event) then
this:UnregisterAllEvents()
this:SetScript("OnEvent", nil)
return
end
if MTH and MTH.ApplyClassGate and MTH:ApplyClassGate(event) and MTH._classGateAnnouncedChat then if MTH and MTH.ApplyClassGate and MTH:ApplyClassGate(event) and MTH._classGateAnnouncedChat then
this:UnregisterAllEvents() this:UnregisterAllEvents()
this:SetScript("OnEvent", nil) this:SetScript("OnEvent", nil)
@@ -266,15 +343,6 @@ if type(MTH_CharSavedVariables.modules) ~= "table" then
MTH_CharSavedVariables.modules = {} MTH_CharSavedVariables.modules = {}
end 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 function MTH_IsPetUiFrameName(frameName)
local name = tostring(frameName or "") local name = tostring(frameName or "")
if name == "" then if name == "" then
@@ -423,7 +491,6 @@ end
local function MTH_GetPersistedModuleEnabled(name, defaultEnabled) local function MTH_GetPersistedModuleEnabled(name, defaultEnabled)
if type(name) ~= "string" or name == "" then 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 return defaultEnabled and true or false
end end
@@ -461,7 +528,6 @@ local function MTH_GetPersistedModuleEnabled(name, defaultEnabled)
local resolved = hardDefault and true or false local resolved = hardDefault and true or false
MTH_CharSavedVariables.moduleStates[resolvedName] = resolved MTH_CharSavedVariables.moduleStates[resolvedName] = resolved
MTH_CharSavedVariables.modules[resolvedName].enabled = resolved MTH_CharSavedVariables.modules[resolvedName].enabled = resolved
MTH_ModuleStateDebug("read " .. tostring(name) .. " seeded char default=" .. tostring(resolved))
return resolved return resolved
end end
end end
@@ -472,7 +538,6 @@ local function MTH_GetPersistedModuleEnabled(name, defaultEnabled)
local key = candidates[i] local key = candidates[i]
if MTH_CharSavedVariables.moduleStates[key] ~= nil then if MTH_CharSavedVariables.moduleStates[key] ~= nil then
local resolved = MTH_CharSavedVariables.moduleStates[key] and true or false local resolved = MTH_CharSavedVariables.moduleStates[key] and true or false
MTH_ModuleStateDebug("read " .. tostring(name) .. " from char.moduleStates." .. tostring(key) .. "=" .. tostring(resolved))
MTH_CharSavedVariables.moduleStates[resolvedName] = resolved MTH_CharSavedVariables.moduleStates[resolvedName] = resolved
return resolved return resolved
end end
@@ -484,7 +549,6 @@ local function MTH_GetPersistedModuleEnabled(name, defaultEnabled)
local key = candidates[i] local key = candidates[i]
if MTH_SavedVariables.moduleStates[key] ~= nil then if MTH_SavedVariables.moduleStates[key] ~= nil then
local resolved = MTH_SavedVariables.moduleStates[key] and true or false local resolved = MTH_SavedVariables.moduleStates[key] and true or false
MTH_ModuleStateDebug("read " .. tostring(name) .. " from moduleStates." .. tostring(key) .. "=" .. tostring(resolved))
MTH_CharSavedVariables.moduleStates[resolvedName] = resolved MTH_CharSavedVariables.moduleStates[resolvedName] = resolved
if type(MTH_CharSavedVariables.modules[resolvedName]) ~= "table" then if type(MTH_CharSavedVariables.modules[resolvedName]) ~= "table" then
MTH_CharSavedVariables.modules[resolvedName] = {} MTH_CharSavedVariables.modules[resolvedName] = {}
@@ -500,7 +564,6 @@ local function MTH_GetPersistedModuleEnabled(name, defaultEnabled)
local key = candidates[i] local key = candidates[i]
if type(MTH_SavedVariables.modules[key]) == "table" and MTH_SavedVariables.modules[key].enabled ~= nil then 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 local resolved = MTH_SavedVariables.modules[key].enabled and true or false
MTH_ModuleStateDebug("read " .. tostring(name) .. " from modules." .. tostring(key) .. ".enabled=" .. tostring(resolved))
MTH_CharSavedVariables.moduleStates[resolvedName] = resolved MTH_CharSavedVariables.moduleStates[resolvedName] = resolved
if type(MTH_CharSavedVariables.modules[resolvedName]) ~= "table" then if type(MTH_CharSavedVariables.modules[resolvedName]) ~= "table" then
MTH_CharSavedVariables.modules[resolvedName] = {} MTH_CharSavedVariables.modules[resolvedName] = {}
@@ -515,7 +578,6 @@ local function MTH_GetPersistedModuleEnabled(name, defaultEnabled)
local key = candidates[i] local key = candidates[i]
if type(MTH_SavedVariables[key]) == "table" and MTH_SavedVariables[key].enabled ~= nil then if type(MTH_SavedVariables[key]) == "table" and MTH_SavedVariables[key].enabled ~= nil then
local resolved = MTH_SavedVariables[key].enabled and true or false local resolved = MTH_SavedVariables[key].enabled and true or false
MTH_ModuleStateDebug("read " .. tostring(name) .. " from legacy-top-level." .. tostring(key) .. ".enabled=" .. tostring(resolved))
MTH_CharSavedVariables.moduleStates[resolvedName] = resolved MTH_CharSavedVariables.moduleStates[resolvedName] = resolved
if type(MTH_CharSavedVariables.modules[resolvedName]) ~= "table" then if type(MTH_CharSavedVariables.modules[resolvedName]) ~= "table" then
MTH_CharSavedVariables.modules[resolvedName] = {} MTH_CharSavedVariables.modules[resolvedName] = {}
@@ -525,7 +587,6 @@ local function MTH_GetPersistedModuleEnabled(name, defaultEnabled)
end end
end end
MTH_ModuleStateDebug("read " .. tostring(name) .. " no-persisted-value fallback-default=" .. tostring(defaultEnabled and true or false))
return defaultEnabled and true or false return defaultEnabled and true or false
end end
@@ -555,7 +616,6 @@ local function MTH_SetPersistedModuleEnabled(name, enabled)
enabled = enabled and true or false enabled = enabled and true or false
MTH_CharSavedVariables.moduleStates[name] = enabled MTH_CharSavedVariables.moduleStates[name] = enabled
MTH_CharSavedVariables.modules[name].enabled = enabled MTH_CharSavedVariables.modules[name].enabled = enabled
MTH_ModuleStateDebug("write " .. tostring(name) .. " enabled=" .. tostring(enabled))
end end
function MTH:InitEventRouter() function MTH:InitEventRouter()
@@ -669,7 +729,6 @@ function MTH:RegisterModule(name, module)
end end
if self.modules[name] then if self.modules[name] then
if self.debug then print("|cFFFFAA00MetaHunt|r: Module '" .. name .. "' already registered") end
return false return false
end end
@@ -684,8 +743,6 @@ function MTH:RegisterModule(name, module)
local initialEnabled = MTH_GetPersistedModuleEnabled(name, defaultEnabled) local initialEnabled = MTH_GetPersistedModuleEnabled(name, defaultEnabled)
module.enabled = initialEnabled and true or false module.enabled = initialEnabled and true or false
MTH_ModuleStateDebug("register " .. tostring(name) .. " default=" .. tostring(defaultEnabled) .. " initial=" .. tostring(module.enabled))
self.modules[name] = module self.modules[name] = module
self:RegisterModuleEvents(name, module.events) self:RegisterModuleEvents(name, module.events)
@@ -709,7 +766,6 @@ function MTH:RegisterModule(name, module)
self:_SetModuleEventSubscriptions(name, true) self:_SetModuleEventSubscriptions(name, true)
end end
if self.debug then print("|cFF00AA00MetaHunt|r: Module '" .. name .. "' registered") end
return true return true
end end
@@ -774,7 +830,6 @@ function MTH:SetModuleEnabled(name, enabled)
if self.SetConfig then if self.SetConfig then
self:SetConfig(name, "enabled", enabled) self:SetConfig(name, "enabled", enabled)
end end
MTH_ModuleStateDebug("request " .. tostring(name) .. " no-op runtime=" .. tostring(module.enabled) .. " persisted-synced")
return true return true
end end
@@ -796,7 +851,6 @@ function MTH:SetModuleEnabled(name, enabled)
if self.SetConfig then if self.SetConfig then
self:SetConfig(name, "enabled", enabled) self:SetConfig(name, "enabled", enabled)
end end
MTH_ModuleStateDebug("request " .. tostring(name) .. " applied runtime=" .. tostring(module.enabled))
return true return true
end end
@@ -1035,6 +1089,12 @@ function MTH:Log(msg, severity)
return chatText return chatText
end end
function MTH:DebugPrint(msg)
if self.debug then
self:Print("[DEBUG] " .. tostring(msg), "debug")
end
end
function MTH:Print(msg, severity) function MTH:Print(msg, severity)
return self:Log(msg, severity) return self:Log(msg, severity)
end end
@@ -1083,12 +1143,6 @@ function MTH:SetMessageEnabled(key, enabled)
return true return true
end end
function MTH:DebugPrint(msg)
if self.debug then
self:Print("[DEBUG] " .. tostring(msg), "debug")
end
end
function MTH_Log(message, severity) function MTH_Log(message, severity)
if MTH and MTH.Log then if MTH and MTH.Log then
return MTH:Log(message, severity) return MTH:Log(message, severity)
@@ -1143,8 +1197,6 @@ function MTH:InitSavedVariables()
if not MTH_SavedVariables then if not MTH_SavedVariables then
MTH_SavedVariables = { MTH_SavedVariables = {
modules = {}, modules = {},
feedomatic = {},
zhunter = {},
} }
end end
@@ -1154,12 +1206,6 @@ function MTH:InitSavedVariables()
if not MTH_SavedVariables.moduleStates then if not MTH_SavedVariables.moduleStates then
MTH_SavedVariables.moduleStates = {} MTH_SavedVariables.moduleStates = {}
end end
if not MTH_SavedVariables.modules.feedomatic then
MTH_SavedVariables.modules.feedomatic = MTH_SavedVariables.feedomatic or {}
end
if not MTH_SavedVariables.modules.zhunter then
MTH_SavedVariables.modules.zhunter = MTH_SavedVariables.zhunter or {}
end
if type(MTH_SavedVariables.messages) ~= "table" then if type(MTH_SavedVariables.messages) ~= "table" then
MTH_SavedVariables.messages = {} MTH_SavedVariables.messages = {}
end end
@@ -1171,9 +1217,6 @@ function MTH:InitSavedVariables()
end end
end end
MTH_SavedVariables.feedomatic = MTH_SavedVariables.modules.feedomatic
MTH_SavedVariables.zhunter = MTH_SavedVariables.modules.zhunter
if not MTH_CharSavedVariables then if not MTH_CharSavedVariables then
MTH_CharSavedVariables = {} MTH_CharSavedVariables = {}
end end
@@ -1227,6 +1270,24 @@ function MTH:InitSavedVariables()
end end
end end
end end
-- NOTE: the legacy/scattered SavedVariables consolidation (MTH_SV_EnsureSchema)
-- is intentionally NOT called here. InitSavedVariables also runs at file-load time
-- (before WoW has loaded the SV file from disk), when the roots are still empty
-- defaults. Running the migration there operated on throwaway tables and made it
-- re-announce every session. The migration is now driven from an ADDON_LOADED
-- handler in api/savedvariables.lua, which fires once the real saved data exists.
-- Account-root module aliases must NEVER persist: the canonical stores live under
-- .modules.<id>. feedomatic/zhunter used to be mirrored here (config.lua alias +
-- InitSavedVariables seeds), which made WoW serialize a full DUPLICATE on disk.
-- Strip them unconditionally on every load — even after the one-shot migration gate
-- has closed — so any stray copy re-created by older code, or already sitting in an
-- on-disk file from a previous version, is removed.
if type(MTH_SavedVariables) == "table" then
MTH_SavedVariables.feedomatic = nil
MTH_SavedVariables.zhunter = nil
end
end end
function MTH:GetModuleSavedVariables(name) function MTH:GetModuleSavedVariables(name)
@@ -1270,12 +1331,10 @@ function MTH:ApplyPersistedModuleStates(source)
end end
local persistedEnabled = MTH_GetPersistedModuleEnabled(name, defaultEnabled) 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 if module.enabled ~= persistedEnabled then
local ok, err = self:SetModuleEnabled(name, persistedEnabled) local ok, err = self:SetModuleEnabled(name, persistedEnabled)
if not ok then if not ok then
MTH_ModuleStateDebug("bootstrap apply failed " .. tostring(name) .. ": " .. tostring(err))
end end
else else
MTH_SetPersistedModuleEnabled(name, persistedEnabled) MTH_SetPersistedModuleEnabled(name, persistedEnabled)
@@ -1336,7 +1395,17 @@ function MTH:AnnounceLoadComplete()
moduleList = table.concat(enabled, ", ") moduleList = table.concat(enabled, ", ")
end end
self:Print("Check ignition: OK! Version " .. tostring(self.version or "unknown") .. " loaded.") local namPowerStatus = ""
if type(GetNampowerVersion) == "function" then
local maj, min = GetNampowerVersion()
maj = tonumber(maj) or 0
min = tonumber(min) or 0
if maj > 2 or (maj == 2 and min >= 40) then
namPowerStatus = " | NamPower detected"
self.nampower = true
end
end
self:Print("Check Ignition.... OK! Version " .. tostring(self.version or "unknown") .. " loaded." .. namPowerStatus)
if self:IsMessageEnabled("initModulesLoaded", false) then if self:IsMessageEnabled("initModulesLoaded", false) then
self:Print("Modules enabled : " .. moduleList .. ".") self:Print("Modules enabled : " .. moduleList .. ".")
end end
+386
View File
@@ -0,0 +1,386 @@
-- MetaHunt level tracker. All training facts come from MTH_DS_TrainerCatalog
-- through trainer-spell-planner.lua; this module only detects progression and
-- presents the resulting facts through chat and animations.
local MTH_LT_Frame = nil
local MTH_LT_PendingPetCheck = false
local MTH_LT_HunterLines = {
[1] = "You can now track beasts. Revolutionary: look for the things you intend to shoot.",
[2] = "The wilderness remains deeply unimpressed.",
[3] = "Still mostly leather, arrows, and misplaced confidence.",
[4] = "You have actual trainer lessons now. Try not to spend all your coin on repairs.",
[5] = "Your pet is doing its share. Please consider doing yours.",
[6] = "A hunter's mark: because shouting at the target was not precise enough.",
[7] = "Every pull is a plan until it becomes cardio.",
[8] = "Concussive Shot. Distance is now a tactical concept, not an accident.",
[9] = "Your arrows have begun their long argument with gravity.",
[10] = "You have a hawk aspect. The bird remains unavailable for comment.",
[11] = "A fine age for discovering that pets also require paperwork.",
[12] = "Mend Pet unlocked. Your pet has noticed this was overdue.",
[13] = "The wilderness has upgraded its complaints department.",
[14] = "Eagle Eye. Finally, a way to inspect trouble before walking into it.",
[15] = "You have survived another level through skill, luck, or a pet-shaped shield.",
[16] = "Traps and sharp things. Subtlety continues to be optional.",
[17] = "Your pet is still carrying the emotional weight of this partnership.",
[18] = "Multi-Shot. One target was apparently an unacceptable limitation.",
[19] = "Soon you may run faster. The enemies will take this personally.",
[20] = "Congratulations: escape now has a speed setting.",
[21] = "Fast enough to arrive at the next questionable decision sooner.",
[22] = "Your trainer has new bills and somehow they are all addressed to you.",
[23] = "The pet remains employed despite the working conditions.",
[24] = "Beast Lore. At last: research before violence. Briefly.",
[25] = "Halfway to better excuses, nowhere near fewer mistakes.",
[26] = "Rapid Fire. Your bow has entered its impatient phase.",
[27] = "Things are going well enough to become dangerous.",
[28] = "Your arrows now contain a measurable amount of intent.",
[29] = "Prepare for the hunter's most respected tactical maneuver.",
[30] = "Feign Death. Why win a fight when you can become administrative debris?",
[31] = "You are alive, which validates absolutely nothing.",
[32] = "Flare. Stealth has been downgraded to a suggestion.",
[33] = "The next trap is definitely part of the plan.",
[34] = "Explosive Trap. Negotiation has left the chat.",
[35] = "Your pet thinks this is all normal. That is worrying.",
[36] = "More damage, more maintenance, same heroic disregard for repair bills.",
[37] = "Keep going. The next disaster is probably already pathing toward you.",
[38] = "Your toolkit is becoming impressively hard to explain to civilized people.",
[39] = "One level until armor finally admits you have been taking hits.",
[40] = "Mail training. Leather got you this far; enemies kept putting holes in it.",
[41] = "You are now visibly better protected from consequences.",
[42] = "More Multi-Shot: because collateral damage needed a sequel.",
[43] = "Your pet has grown used to the sound of avoidable danger.",
[44] = "The trainer is delighted. Your coin pouch is less enthusiastic.",
[45] = "You are a seasoned hunter now, which means the mistakes are expensive.",
[46] = "Nature resistance: proof that the outdoors have been escalating.",
[47] = "Still alive. Still pointing the dangerous end away from yourself. Usually.",
[48] = "Your damage has matured into a fully funded public nuisance.",
[49] = "One more level until the trainer starts charging executive prices.",
[50] = "Your arrows have tenure now.",
[51] = "The wilderness would like a word. It has a queue.",
[52] = "Aimed Shot hits harder, which is your preferred form of diplomacy.",
[53] = "Your pet remains your most reliable party member. Make of that what you will.",
[54] = "Explosive Trap again. The floor continues to be a valid target.",
[55] = "Almost there. Try not to die to something with a tutorial tooltip.",
[56] = "Aspect of the Viper. Mana problems now have a bird-shaped solution.",
[57] = "The finish line is visible. So are several elite mobs. Choose carefully.",
[58] = "The final trainer bills are arriving with alarming confidence.",
[59] = "One last level. Your pet has begun drafting a victory speech.",
[60] = "You made it. The wildlife will remember this unfavorably.",
}
local MTH_LT_Priority = {
["Feign Death"] = 100,
["Mail"] = 95,
["Aspect of the Cheetah"] = 90,
["Rapid Fire"] = 88,
["Aspect of the Pack"] = 86,
["Explosive Trap"] = 84,
["Freezing Trap"] = 82,
["Flare"] = 80,
["Eagle Eye"] = 78,
["Multi-Shot"] = 76,
["Aimed Shot"] = 74,
["Aspect of the Viper"] = 72,
["Trueshot Aura"] = 70,
["Volley"] = 68,
["Track Hidden"] = 66,
}
local MTH_LT_PetLines = {
"It is now statistically better than your last pull.",
"The food budget has noticed.",
"Please do not celebrate by pulling three extra mobs.",
"It remains committed to solving problems with teeth.",
"The wilderness has filed no objection.",
"Your dependable colleague is carrying this team again.",
}
local MTH_LT_LoyaltyLines = {
[2] = "It tolerates your leadership as long as the food arrives on time.",
[3] = "Your authority has been acknowledged, reluctantly.",
[4] = "It has decided your plans are survivable often enough.",
[5] = "Please do not reward this trust with an elite pull.",
[6] = "Against all evidence, it trusts you completely.",
}
local function MTH_LT_Store()
if not MTH_CharSavedVariables then MTH_CharSavedVariables = {} end
if type(MTH_CharSavedVariables.levelTracker) ~= "table" then
MTH_CharSavedVariables.levelTracker = { petLine = 0, loyaltyLine = 0 }
end
return MTH_CharSavedVariables.levelTracker
end
local function MTH_LT_MessageEnabled(key, defaultValue)
return MTH and type(MTH.IsMessageEnabled) == "function" and MTH:IsMessageEnabled(key, defaultValue)
end
local function MTH_LT_FormatCost(copper)
local total = tonumber(copper) or 0
local gold = math.floor(total / 10000)
local silver = math.floor((total - gold * 10000) / 100)
local bronze = total - gold * 10000 - silver * 100
local parts = {}
if gold > 0 then table.insert(parts, "|cffffd100" .. gold .. "g|r") end
if silver > 0 or gold > 0 then table.insert(parts, "|cffc7c7cf" .. silver .. "s|r") end
table.insert(parts, "|cffeda55f" .. bronze .. "c|r")
return table.concat(parts, " ")
end
local function MTH_LT_NextLine(store, key, lines)
store[key] = (tonumber(store[key]) or 0) + 1
if store[key] > table.getn(lines) then store[key] = 1 end
return lines[store[key]]
end
local function MTH_LT_GetHeadline(plan)
local best = nil
local bestPriority = -1
for _, entry in ipairs(plan.rows or {}) do
local priority = MTH_LT_Priority[tostring(entry.row and entry.row.name or "")] or 0
if priority > bestPriority then best, bestPriority = entry, priority end
end
return best
end
local function MTH_LT_FormatRows(plan)
local labels = {}
for index, entry in ipairs(plan.rows or {}) do
local row = entry.row or {}
local label = tostring(row.name or "Unknown")
if row.rank and row.rank ~= "" then label = label .. " " .. tostring(row.rank) end
table.insert(labels, label)
end
return table.concat(labels, ", ")
end
local function MTH_LT_GrowlRankForLevel(level)
local rank = math.floor((tonumber(level) or 1) / 10) + 1
if rank < 1 then rank = 1 end
if rank > 7 then rank = 7 end
return rank
end
local function MTH_LT_BuildPetGrowlPlan(level, preview, honored)
if type(MTH_TrainerPlan_BuildLevel) ~= "function" then return nil end
local plan = MTH_TrainerPlan_BuildLevel("pet", level, { honored = honored and true or false, includeKnown = preview })
local growlPlan = {
rows = {},
unlearned = { count = 0, cost = 0 },
}
for _, entry in ipairs(plan.rows or {}) do
if tostring(entry.row and entry.row.name or "") == "Growl" then
table.insert(growlPlan.rows, entry)
growlPlan.unlearned.count = growlPlan.unlearned.count + 1
growlPlan.unlearned.cost = growlPlan.unlearned.cost + (tonumber(entry.price) or 0)
end
end
return growlPlan
end
local function MTH_LT_HandlePetGrowlLevel(petName, level)
if level < 10 or level > 60 or math.mod(level, 10) ~= 0 then return end
local rank = MTH_LT_GrowlRankForLevel(level)
local known = type(MTH_HT_GetSpellInfo) == "function" and MTH_HT_GetSpellInfo("Growl") or nil
if known and (tonumber(known.rankNum) or 0) >= rank then
if type(MTH_Growl_OfferForTamedPet) == "function" then
MTH_Growl_OfferForTamedPet(petName, level)
end
elseif MTH_LT_MessageEnabled("petLevelUp", true) then
MTH:Print(petName .. " can now learn Growl Rank " .. tostring(rank) .. ". Visit a Pet Trainer to learn it.")
end
end
local function MTH_LT_PrintSpellList(plan)
local line = ""
local isFirstLine = true
for _, entry in ipairs(plan.rows or {}) do
local row = entry.row or {}
local label = tostring(row.name or "Unknown")
if row.rank and row.rank ~= "" then label = label .. " " .. tostring(row.rank) end
if line == "" then
line = label
elseif string.len(line) + string.len(label) + 2 > 92 then
MTH:Print((isFirstLine and "New spells available: " or "And also: ") .. line)
isFirstLine = false
line = label
else
line = line .. ", " .. label
end
end
if line ~= "" then MTH:Print((isFirstLine and "New spells available: " or "And also: ") .. line) end
end
local function MTH_LT_AnnounceTraining(kind, level, eventKind, preview)
if type(MTH_TrainerPlan_BuildLevel) ~= "function" then return end
local base = MTH_TrainerPlan_BuildLevel(kind, level, { honored = false, includeKnown = preview })
local honored = MTH_TrainerPlan_BuildLevel(kind, level, { honored = true, includeKnown = preview })
if base.unlearned.count <= 0 then
if kind == "hunter" and MTH_LT_MessageEnabled("hunterLevelUp", true) then
MTH:Print("Congratz! You hit level " .. tostring(level) .. ".")
end
if preview then MTH:Print("Level test: no " .. kind .. " trainer spells in the 1.18.1 catalog at level " .. tostring(level) .. ".") end
return
end
local headline = MTH_LT_GetHeadline(base)
local headlineText = headline and headline.row and tostring(headline.row.name or "") or "New training"
if headline and headline.row and headline.row.rank and headline.row.rank ~= "" then headlineText = headlineText .. " " .. headline.row.rank end
local isLevelCap = kind == "hunter" and level == 60
if isLevelCap then headlineText = "The hunt is yours" end
local isPetGrowlMilestone = kind == "pet" and level >= 10 and level <= 60 and math.mod(level, 10) == 0
if isPetGrowlMilestone then headlineText = "Growl Rank " .. tostring(MTH_LT_GrowlRankForLevel(level)) end
if kind == "hunter" and not isLevelCap then headlineText = nil end
if MTH_LT_MessageEnabled(eventKind == "hunter" and "hunterLevelUp" or "petLevelUp", true) then
if kind == "hunter" then
MTH:Print("Congratz! You hit level " .. tostring(level) .. ".")
MTH:Print(MTH_LT_HunterLines[level] or "New training is available.")
MTH:Print("The spells you can learn will cost you " .. MTH_LT_FormatCost(base.unlearned.cost)
.. ", but only " .. MTH_LT_FormatCost(honored.unlearned.cost) .. " if you are Honored with the master.")
MTH_LT_PrintSpellList(base)
else
local displayPlan = isPetGrowlMilestone and MTH_LT_BuildPetGrowlPlan(level, preview) or base
local displayHonoredPlan = isPetGrowlMilestone and MTH_LT_BuildPetGrowlPlan(level, preview, true) or honored
if displayPlan and displayPlan.unlearned.count > 0 then
MTH:Print("It can learn " .. MTH_LT_FormatRows(displayPlan))
MTH:Print("Trainer cost: Base " .. MTH_LT_FormatCost(displayPlan.unlearned.cost) .. " | Honored " .. MTH_LT_FormatCost(displayHonoredPlan.unlearned.cost))
end
end
end
local isMilestone = kind == "hunter" and (level == 10 or level == 20 or level == 30 or level == 40 or level == 50 or level == 60)
local flag = isMilestone and "level.huntermilestone" or (kind == "hunter" and "level.hunter" or "level.pet")
if type(MTH_FX_IsEnabled) == "function" and MTH_FX_IsEnabled(flag) and type(MTH_Celebrate) == "function" then
local subtitle = kind == "hunter" and (MTH_LT_HunterLines[level] or "New training is available.") or "New pet training is available. The trainer wants coin."
if isMilestone and type(MTH_CelebrateBlackHole) == "function" then
MTH_CelebrateBlackHole(isLevelCap and headlineText or "Level " .. tostring(level), subtitle)
else
local title = headlineText and ("Level " .. tostring(level) .. " - " .. headlineText) or "Level " .. tostring(level)
MTH_Celebrate(title, subtitle, "fanfare", 5)
end
end
end
local function MTH_LT_HandleHunterLevel(level)
local store = MTH_LT_Store()
local previous = tonumber(store.hunterLevel)
store.hunterLevel = level
if previous and level > previous then
MTH_LT_AnnounceTraining("hunter", level, "hunter")
end
end
local function MTH_LT_GetPetState()
if type(UnitExists) ~= "function" or not UnitExists("pet") then return nil end
local level = type(UnitLevel) == "function" and tonumber(UnitLevel("pet")) or nil
local name = type(UnitName) == "function" and UnitName("pet") or "Your pet"
if not level or level <= 0 then return nil end
-- Loyalty is a localized name via GetPetLoyalty(); reuse the pet service which
-- parses it (plus UI fallbacks) into a numeric 1-6 level.
local loyalty = nil
if type(MTH_GetCurrentPetInfo) == "function" then
local info = MTH_GetCurrentPetInfo()
if type(info) == "table" then loyalty = tonumber(info.loyaltyLevel) or nil end
end
return { key = tostring(name or "pet"), name = tostring(name or "Your pet"), level = level, loyalty = loyalty }
end
local function MTH_LT_HandlePetState()
MTH_LT_PendingPetCheck = false
local state = MTH_LT_GetPetState()
if not state then return end
local store = MTH_LT_Store()
local pet = store.pet
if type(pet) ~= "table" or pet.key ~= state.key then
store.pet = state
return
end
if state.level > (tonumber(pet.level) or 0) then
local line = MTH_LT_NextLine(store, "petLine", MTH_LT_PetLines)
if MTH_LT_MessageEnabled("petLevelUp", true) then MTH:Print(state.name .. " reached level " .. state.level .. ". " .. line) end
MTH_LT_AnnounceTraining("pet", state.level, "pet")
MTH_LT_HandlePetGrowlLevel(state.name, state.level)
end
if state.loyalty and state.loyalty > 1 and state.loyalty > (tonumber(pet.loyalty) or 0) then
local line = MTH_LT_LoyaltyLines[state.loyalty] or "The food arrangement appears to be working."
if MTH_LT_MessageEnabled("petLoyaltyUp", true) then MTH:Print(state.name .. " reached Loyalty Level " .. state.loyalty .. ". " .. line) end
if type(MTH_FX_IsEnabled) == "function" and MTH_FX_IsEnabled("level.loyalty") then
local title = state.name .. " - Loyalty " .. state.loyalty
if state.loyalty == 6 and type(MTH_CelebrateBlackHole) == "function" then
MTH_CelebrateBlackHole(title, line)
elseif type(MTH_Celebrate) == "function" then
MTH_Celebrate(title, line, "fanfare")
end
end
end
store.pet = state
end
local function MTH_LT_RequestPetCheck()
if MTH_LT_PendingPetCheck or not MTH_LT_Frame then return end
MTH_LT_PendingPetCheck = true
MTH_LT_Frame.elapsed = 0
MTH_LT_Frame:SetScript("OnUpdate", function()
this.elapsed = (this.elapsed or 0) + (arg1 or 0)
if this.elapsed >= 0.35 then
this:SetScript("OnUpdate", nil)
MTH_LT_HandlePetState()
end
end)
end
function MTH_LevelTracker_Test(kind, level)
if kind == "hunter" or kind == "pet" then
local targetLevel = tonumber(level) or ((kind == "hunter") and (tonumber(UnitLevel("player")) or 1) or 1)
MTH:Print("Level test: previewing " .. kind .. " trainer spells at level " .. tostring(targetLevel) .. ".")
MTH_LT_AnnounceTraining(kind, targetLevel, kind, true)
return true
end
if kind == "loyalty" then
local state = MTH_LT_GetPetState()
local loyalty = tonumber(level) or (state and state.loyalty) or 1
if loyalty <= 1 then
MTH:Print("Pet Loyalty starts at level 1 when you tame a pet. Test levels 2 through 6.")
return false
end
local petName = state and state.name or "Your pet"
local line = MTH_LT_LoyaltyLines[loyalty] or "The food arrangement appears to be working."
MTH:Print(petName .. " reached Loyalty Level " .. tostring(loyalty) .. ". " .. line)
local title = petName .. " - Loyalty " .. tostring(loyalty)
if loyalty == 6 and type(MTH_CelebrateBlackHole) == "function" then
MTH_CelebrateBlackHole(title, line)
elseif type(MTH_Celebrate) == "function" then
MTH_Celebrate(title, line, "fanfare")
end
return true
end
return false
end
function MTH_LevelTracker_Initialize()
if MTH_LT_Frame then return end
MTH_LT_Frame = CreateFrame("Frame", "MTH_LevelTracker")
MTH_LT_Frame:RegisterEvent("PLAYER_ENTERING_WORLD")
MTH_LT_Frame:RegisterEvent("PLAYER_LEVEL_UP")
MTH_LT_Frame:RegisterEvent("UNIT_LEVEL")
MTH_LT_Frame:RegisterEvent("UNIT_PET")
MTH_LT_Frame:RegisterEvent("PET_BAR_UPDATE")
MTH_LT_Frame:RegisterEvent("UNIT_HAPPINESS")
MTH_LT_Frame:SetScript("OnEvent", function()
if event == "PLAYER_ENTERING_WORLD" then
local level = type(UnitLevel) == "function" and tonumber(UnitLevel("player")) or nil
if level then MTH_LT_Store().hunterLevel = level end
MTH_LT_RequestPetCheck()
elseif event == "PLAYER_LEVEL_UP" then
MTH_LT_HandleHunterLevel(tonumber(arg1) or tonumber(UnitLevel("player")) or 1)
elseif event == "UNIT_LEVEL" and arg1 == "pet" then
MTH_LT_RequestPetCheck()
elseif event == "UNIT_PET" and arg1 == "player" then
MTH_LT_RequestPetCheck()
elseif event == "PET_BAR_UPDATE" then
MTH_LT_RequestPetCheck()
elseif event == "UNIT_HAPPINESS" then
MTH_LT_RequestPetCheck()
end
end)
end
MTH_LevelTracker_Initialize()
+83 -4
View File
@@ -168,7 +168,10 @@ function MTH_PT_HasBeastTrainingRows()
end end
local function MTH_PT_DebugLog(line) local function MTH_PT_DebugLog(line)
return if line == nil then return end
if MTH_DebugFrame and type(MTH_DebugFrame.AddInfo) == "function" then
MTH_DebugFrame:AddInfo("[BeastTraining] " .. tostring(line))
end
end end
local function MTH_PT_IsScanMessageEnabled() local function MTH_PT_IsScanMessageEnabled()
@@ -335,6 +338,39 @@ local function MTH_PT_IsHunterTokenKnown(token)
return false return false
end end
-- Rank-strict variant of MTH_PT_IsHunterTokenKnown: only the EXACT token counts, with
-- no fall back to the base ability. Knowing Charge (Rank 1) must NOT make Charge (Rank 2)
-- look already-learned, otherwise learning a new rank is never recorded or announced.
local function MTH_PT_IsExactTokenKnown(token)
if token == nil or token == "" then
return false
end
local store = MTH_PT_GetStore()
if store.hunterKnownMap and store.hunterKnownMap[token] == true then
return true
end
if store.spellMap and store.spellMap[token] and store.spellMap[token].isKnown == true then
return true
end
return false
end
-- Authoritative, rank-strict check for whether the HUNTER has learned a pet ability at a
-- given rank. Consults both hunterKnownMap and spellMap, so callers (e.g. the tooltip hint)
-- stay correct even when a full Beast Training rescan has not yet rebuilt spellMap rows.
function MTH_IsPetAbilityKnownByHunter(abilityName, rankNumber)
local ability = MTH_PT_NormalizeName(abilityName)
if ability == "" then
return false
end
local token = MTH_PT_MakeTokenFromAbilityRank(ability, rankNumber)
if token == "" then
return false
end
return MTH_PT_IsExactTokenKnown(token)
end
local function MTH_PT_ProcessLearnSystemMessage(rawMessage) local function MTH_PT_ProcessLearnSystemMessage(rawMessage)
local message = tostring(rawMessage or "") local message = tostring(rawMessage or "")
if message == "" then return false end if message == "" then return false end
@@ -348,6 +384,10 @@ local function MTH_PT_ProcessLearnSystemMessage(rawMessage)
_, _, abilityName, rankText = string.find(message, "^[Yy]ou have learned a new spell:%s*(.-)%s*[Rr]ank%s*(%d+)%.?$") _, _, abilityName, rankText = string.find(message, "^[Yy]ou have learned a new spell:%s*(.-)%s*[Rr]ank%s*(%d+)%.?$")
end end
if not abilityName or not rankText then if not abilityName or not rankText then
-- Only trace messages that look like a learn line, to avoid flooding the log.
if MTH_PT_DebugLog and string.find(message, "[Ll]earned") then
MTH_PT_DebugLog("learn-msg parse FAILED: '" .. tostring(message) .. "'")
end
return false return false
end end
@@ -368,7 +408,7 @@ local function MTH_PT_ProcessLearnSystemMessage(rawMessage)
local token = MTH_PT_MakeTokenFromAbilityRank(abilityName, rankNumber) local token = MTH_PT_MakeTokenFromAbilityRank(abilityName, rankNumber)
if token == "" then return false end if token == "" then return false end
local alreadyKnown = MTH_PT_IsHunterTokenKnown(token) local alreadyKnown = MTH_PT_IsExactTokenKnown(token)
local changed = false local changed = false
if not alreadyKnown then if not alreadyKnown then
changed = MTH_PT_MarkHunterKnownToken(token) changed = MTH_PT_MarkHunterKnownToken(token)
@@ -379,6 +419,12 @@ local function MTH_PT_ProcessLearnSystemMessage(rawMessage)
petRecorded = MTH_PETS_RecordCurrentPetLearnedAbility(abilityName, rankNumber, "beasttraining:learn-msg") and true or false petRecorded = MTH_PETS_RecordCurrentPetLearnedAbility(abilityName, rankNumber, "beasttraining:learn-msg") and true or false
end end
if MTH_PT_DebugLog then
MTH_PT_DebugLog("learn-msg matched: ability='" .. tostring(abilityName) .. "' rank=" .. tostring(rankNumber)
.. " token='" .. tostring(token) .. "' alreadyKnown=" .. tostring(alreadyKnown)
.. " changed=" .. tostring(changed) .. " petRecorded=" .. tostring(petRecorded))
end
if changed or petRecorded then if changed or petRecorded then
if MTH_PT_DebugLog then if MTH_PT_DebugLog then
MTH_PT_DebugLog("learn-msg captured: source='you have learned a new spell' ability='" .. tostring(abilityName) .. "' rank=" .. tostring(rankNumber) .. " token='" .. tostring(token) .. "' changed=" .. tostring(changed) .. " petRecorded=" .. tostring(petRecorded)) MTH_PT_DebugLog("learn-msg captured: source='you have learned a new spell' ability='" .. tostring(abilityName) .. "' rank=" .. tostring(rankNumber) .. " token='" .. tostring(token) .. "' changed=" .. tostring(changed) .. " petRecorded=" .. tostring(petRecorded))
@@ -399,6 +445,9 @@ local function MTH_PT_ProcessLearnSystemMessage(rawMessage)
if changed then if changed then
MTH_PT_ApplyScan("learn-msg") MTH_PT_ApplyScan("learn-msg")
end end
if changed and type(MTH_CelebratePetAbility) == "function" then
MTH_CelebratePetAbility(label)
end
if type(MTH_PSP_RequestScan) == "function" then if type(MTH_PSP_RequestScan) == "function" then
MTH_PSP_RequestScan("train-learn-msg") MTH_PSP_RequestScan("train-learn-msg")
end end
@@ -623,13 +672,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)." 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 if MTH_PT_IsScanMessageEnabled() then
MTH:Print(msg) MTH:Print(msg)
MTH:Print("INFO: " .. msg, "debug")
end end
else 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)." 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 if MTH_PT_IsScanMessageEnabled() then
MTH:Print(msg) MTH:Print(msg)
MTH:Print("INFO: " .. msg, "debug")
end end
end end
end end
@@ -658,6 +705,24 @@ MTH_PT_IsPetBookVisible = function()
return false return false
end end
-- Cheap content signature of the trainer's craft rows, so repeated reads of the
-- same list (multiple CRAFT_UPDATE / open events) don't each pay for a full,
-- datastore-wide re-scan (that pile-up was freezing the client).
local MTH_PT_LastScanSignature = nil
local function MTH_PT_ComputeCraftSignature()
local getNumCrafts = MTH_PT_GetGlobal("GetNumCrafts")
local getCraftInfo = MTH_PT_GetGlobal("GetCraftInfo")
if type(getNumCrafts) ~= "function" or type(getCraftInfo) ~= "function" then return nil end
local total = tonumber(getNumCrafts()) or 0
if total <= 0 then return nil end
local parts = { tostring(total) }
for i = 1, total do
local name, sub, ctype = getCraftInfo(i)
table.insert(parts, tostring(name or "") .. "~" .. tostring(sub or "") .. "~" .. tostring(ctype or ""))
end
return table.concat(parts, "|")
end
local function MTH_PT_RunDoubleScan(triggerBase) local function MTH_PT_RunDoubleScan(triggerBase)
local triggerText = tostring(triggerBase or "") local triggerText = tostring(triggerBase or "")
local feedbackOnOpen = ( local feedbackOnOpen = (
@@ -667,6 +732,10 @@ local function MTH_PT_RunDoubleScan(triggerBase)
or triggerText == "beast-training-rows-open" or triggerText == "beast-training-rows-open"
or triggerText == "petbook-visible-poll" or triggerText == "petbook-visible-poll"
) )
-- Stay quiet while Growl auto-teach is driving the trainer window.
if feedbackOnOpen and type(MTH_Growl_IsTeaching) == "function" and MTH_Growl_IsTeaching() then
feedbackOnOpen = false
end
if not MTH_PT_ShouldScanNow() then if not MTH_PT_ShouldScanNow() then
if MTH_PT_DebugLog then if MTH_PT_DebugLog then
MTH_PT_DebugLog("scan skipped: no beast-training rows, trigger=" .. tostring(triggerBase or "")) MTH_PT_DebugLog("scan skipped: no beast-training rows, trigger=" .. tostring(triggerBase or ""))
@@ -677,9 +746,15 @@ local function MTH_PT_RunDoubleScan(triggerBase)
return false return false
end end
local signature = MTH_PT_ComputeCraftSignature()
if signature and signature == MTH_PT_LastScanSignature then
if MTH_PT_DebugLog then MTH_PT_DebugLog("scan skipped (unchanged signature): trigger=" .. triggerText) end
return true
end
local t0 = time() local t0 = time()
local ok, added, baselines, ranks = MTH_PT_ApplyScan(triggerText .. ":single") local ok, added, baselines, ranks = MTH_PT_ApplyScan(triggerText .. ":single")
local t1 = time() local t1 = time()
if ok and signature then MTH_PT_LastScanSignature = signature end
if MTH_PT_DebugLog then if MTH_PT_DebugLog then
MTH_PT_DebugLog("scan complete: trigger=" .. tostring(triggerBase or "") .. " ok=" .. tostring(ok and true or false) .. " dt=" .. tostring((t1 or 0) - (t0 or 0)) .. "s") MTH_PT_DebugLog("scan complete: trigger=" .. tostring(triggerBase or "") .. " ok=" .. tostring(ok and true or false) .. " dt=" .. tostring((t1 or 0) - (t0 or 0)) .. "s")
@@ -834,6 +909,10 @@ function MTH_PT_InitService()
MTH_PT_RunDoubleScanThrottled("beast-training-rows-present", 8) MTH_PT_RunDoubleScanThrottled("beast-training-rows-present", 8)
end end
if (not hasRows) and MTH_PT_TrainingRowsWerePresent then
-- Trainer closed: allow one fresh scan the next time it opens.
MTH_PT_LastScanSignature = nil
end
MTH_PT_TrainingRowsWerePresent = hasRows MTH_PT_TrainingRowsWerePresent = hasRows
MTH_PT_LastTrainingRowCount = rowCount MTH_PT_LastTrainingRowCount = rowCount
+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()
File diff suppressed because it is too large Load Diff
+251
View File
@@ -0,0 +1,251 @@
-- MetaHunt: one-time live trainer catalog collector.
-- Developer workflow: /mth trainerscan, then open a Hunter or Pet Trainer.
local MTH_TS_Frame
local MTH_TS_Armed = false
local MTH_TS_AutoArmed = false
local function MTH_TS_Log(text)
if MTH_DebugFrame and type(MTH_DebugFrame.AddInfo) == "function" then
MTH_DebugFrame:AddInfo("[TrainerScan] " .. tostring(text))
end
end
local function MTH_TS_Call(name, ...)
local fn = getglobal and getglobal(name) or (_G and _G[name])
if type(fn) ~= "function" then return nil, "missing" end
local ok, a, b, c, d, e, f, g = pcall(fn, unpack(arg))
if not ok then return nil, "error=" .. tostring(a) end
return { a, b, c, d, e, f, g }, nil
end
local function MTH_TS_Values(values)
if not values then return "" end
local parts = {}
local i = 1
while i <= table.getn(values) do
if values[i] ~= nil then
table.insert(parts, tostring(values[i]))
end
i = i + 1
end
return table.concat(parts, " | ")
end
local function MTH_TS_TargetName()
if type(UnitName) == "function" then
local name = UnitName("target")
if name and name ~= "" then return name end
end
local title = getglobal and getglobal("TrainerFrameTitleText") or nil
if title and title.GetText then return title:GetText() or "?" end
return "?"
end
local function MTH_TS_GetLiveStatusStore()
if type(MTH_CharSavedVariables) ~= "table" then MTH_CharSavedVariables = {} end
if type(MTH_CharSavedVariables.trainerLiveStatus) ~= "table" then
MTH_CharSavedVariables.trainerLiveStatus = {}
end
return MTH_CharSavedVariables.trainerLiveStatus
end
local function MTH_TS_GetCatalogKind(name, rank)
local catalog = MTH_DS_TrainerCatalog
if type(catalog) ~= "table" or type(catalog.byNameRank) ~= "table" then return nil end
local keySuffix = "\031" .. tostring(name or "") .. "\031" .. tostring(rank or "")
if catalog.byNameRank["hunter" .. keySuffix] then return "hunter" end
if catalog.byNameRank["pet" .. keySuffix] then return "pet" end
return nil
end
function MTH_GetTrainerLiveStatus(kind, name, rank)
local store = type(MTH_CharSavedVariables) == "table" and MTH_CharSavedVariables.trainerLiveStatus or nil
if type(store) ~= "table" then return nil end
return store[tostring(kind or "") .. "\031" .. tostring(name or "") .. "\031" .. tostring(rank or "")]
end
local function MTH_TS_CollectLiveStatuses()
local countValues = MTH_TS_Call("GetNumTrainerServices")
local total = countValues and tonumber(countValues[1]) or 0
if total <= 0 then return false end
local trainerName = MTH_TS_TargetName()
local store = MTH_TS_GetLiveStatusStore()
local observed = 0
for index = 1, total do
local info = MTH_TS_Call("GetTrainerServiceInfo", index)
local name = info and info[1] or nil
local rank = info and info[2] or ""
local status = info and info[3] or nil
local kind = MTH_TS_GetCatalogKind(name, rank)
if kind and (status == "available" or status == "unavailable" or status == "used") then
local key = kind .. "\031" .. tostring(name) .. "\031" .. tostring(rank)
store[key] = {
status = status,
observedAt = time(),
trainer = trainerName,
}
observed = observed + 1
end
end
if observed > 0 then
MTH_TS_Log("Live trainer overlay: " .. tostring(observed) .. " observed catalog row(s) from " .. tostring(trainerName) .. ".")
if MTH and MTH.Print and (not MTH.IsMessageEnabled or MTH:IsMessageEnabled("trainerScan", true)) then
MTH:Print("Trainer scan: " .. tostring(observed) .. " Hunter/Pet spell status" .. (observed == 1 and "" or "es") .. " observed.")
end
return true
end
return false
end
local function MTH_TS_Collect()
local countValues, countError = MTH_TS_Call("GetNumTrainerServices")
local total = countValues and tonumber(countValues[1]) or 0
if not total or total <= 0 then
MTH_TS_Log("No trainer services yet. GetNumTrainerServices=" .. tostring(MTH_TS_Values(countValues))
.. " " .. tostring(countError or ""))
return false
end
local trainerName = MTH_TS_TargetName()
local capture = {
trainer = trainerName,
capturedAt = time(),
services = {},
}
MTH_TRAINER_SCAN_LAST = capture
if type(MTH_CharSavedVariables) == "table" then
if type(MTH_CharSavedVariables.trainerCatalogScans) ~= "table" then
MTH_CharSavedVariables.trainerCatalogScans = {}
end
MTH_CharSavedVariables.trainerCatalogScans[trainerName] = capture
end
MTH_TS_Log("=== " .. tostring(capture.trainer) .. " | " .. tostring(total) .. " trainer services ===")
local index = 1
while index <= total do
local info, infoError = MTH_TS_Call("GetTrainerServiceInfo", index)
local icon, iconError = MTH_TS_Call("GetTrainerServiceIcon", index)
local cost, costError = MTH_TS_Call("GetTrainerServiceCost", index)
local level, levelError = MTH_TS_Call("GetTrainerServiceLevelReq", index)
local skill, skillError = MTH_TS_Call("GetTrainerServiceSkillReq", index)
local description, descriptionError = MTH_TS_Call("GetTrainerServiceDescription", index)
local reqCountValues = MTH_TS_Call("GetTrainerServiceNumAbilityReq", index)
local reqCount = reqCountValues and tonumber(reqCountValues[1]) or 0
local requirements = {}
local reqIndex = 1
while reqIndex <= reqCount do
local requirement, reqError = MTH_TS_Call("GetTrainerServiceAbilityReq", index, reqIndex)
table.insert(requirements, {
name = requirement and requirement[1] or nil,
flag = requirement and requirement[2] or nil,
error = reqError,
})
reqIndex = reqIndex + 1
end
local row = {
index = index,
name = info and info[1] or nil,
rank = info and info[2] or nil,
status = info and info[3] or nil,
expanded = info and info[4] or nil,
icon = icon and icon[1] or nil,
cost = cost and cost[1] or nil,
requiredLevel = level and level[1] or nil,
skillRequirement = skill,
description = description and description[1] or nil,
abilityRequirements = requirements,
}
table.insert(capture.services, row)
MTH_TS_Log(string.format("%02d name=%s | rank=%s | status=%s | expanded=%s | level=%s | cost=%s | icon=%s",
index, tostring(row.name), tostring(row.rank), tostring(row.status), tostring(row.expanded),
tostring(row.requiredLevel), tostring(row.cost), tostring(row.icon)))
if row.description and row.description ~= "" then
MTH_TS_Log(" description=" .. tostring(row.description))
end
if table.getn(requirements) > 0 then
local requirementText = {}
local requirementIndex = 1
while requirementIndex <= table.getn(requirements) do
local requirement = requirements[requirementIndex]
table.insert(requirementText, tostring(requirement.name)
.. " [flag=" .. tostring(requirement.flag) .. "]")
requirementIndex = requirementIndex + 1
end
MTH_TS_Log(" abilityRequirements=" .. table.concat(requirementText, ", "))
end
if skill and table.getn(skill) > 0 then
MTH_TS_Log(" skillRequirement=" .. MTH_TS_Values(skill))
end
if infoError or iconError or costError or levelError or skillError or descriptionError then
MTH_TS_Log(" API=" .. tostring(infoError or "ok") .. ", " .. tostring(iconError or "ok")
.. ", " .. tostring(costError or "ok") .. ", " .. tostring(levelError or "ok")
.. ", " .. tostring(skillError or "ok") .. ", " .. tostring(descriptionError or "ok"))
end
index = index + 1
end
MTH_TS_Log("=== Capture complete. " .. tostring(total)
.. " rows retained in MTH_TRAINER_SCAN_LAST and trainerCatalogScans. ===")
return true
end
local function MTH_TS_EnsureFrame()
if not MTH_TS_Frame then
MTH_TS_Frame = CreateFrame("Frame", "MTH_TrainerCatalogScanner")
MTH_TS_Frame:SetScript("OnEvent", function()
if event == "TRAINER_SHOW" then
MTH_TS_AutoArmed = true
this.elapsed = 0
this:SetScript("OnUpdate", function()
this.elapsed = (this.elapsed or 0) + (arg1 or 0)
if this.elapsed < 0.25 then return end
this:SetScript("OnUpdate", nil)
MTH_TS_CollectLiveStatuses()
end)
return
end
if event ~= "TRAINER_LIST_UPDATE" or not (MTH_TS_Armed or MTH_TS_AutoArmed) then return end
this.elapsed = 0
this:SetScript("OnUpdate", function()
this.elapsed = (this.elapsed or 0) + (arg1 or 0)
if this.elapsed < 0.25 then return end
this:SetScript("OnUpdate", nil)
local manualArmed = MTH_TS_Armed
MTH_TS_Armed = false
MTH_TS_AutoArmed = false
MTH_TS_CollectLiveStatuses()
if manualArmed and not MTH_TS_Collect() and MTH and MTH.Print then
MTH:Print("Trainer list was empty. Open the trainer again and run /mth trainerscan.")
end
end)
end)
MTH_TS_Frame:RegisterEvent("TRAINER_SHOW")
MTH_TS_Frame:RegisterEvent("TRAINER_LIST_UPDATE")
end
end
function MTH_CommandTrainerScan()
if MTH_DebugFrame and type(MTH_DebugFrame.Show) == "function" then MTH_DebugFrame:Show() end
local currentRows = MTH_TS_Call("GetNumTrainerServices")
if currentRows and (tonumber(currentRows[1]) or 0) > 0 then
MTH_TS_Collect()
if MTH and MTH.Print then
MTH:Print("Trainer catalog captured from the open trainer window.")
end
return
end
MTH_TS_EnsureFrame()
MTH_TS_Armed = true
if MTH and MTH.Print then
MTH:Print("Trainer catalog scan armed. Open a Hunter Trainer or Pet Trainer; results will appear in the debug window.")
end
end
MTH_TS_EnsureFrame()
+298 -50
View File
@@ -301,46 +301,6 @@ if type(MTH_CommandPetSpellScan) ~= "function" then
end end
end end
------------------------------------------------------
-- /mth diag — Runtime Diagnostics
------------------------------------------------------
function MTH_CommandDiag()
MTH:Print("======= MetaHunt Diagnostics =======", "debug")
-- 1. Lua version
local luaVer = _VERSION or "unknown"
MTH:Print("[DIAG] _VERSION = " .. tostring(luaVer), "debug")
MTH:Print("[DIAG] string.match = " .. tostring(type(string.match) == "function"), "debug")
MTH:Print("[DIAG] string.gfind = " .. tostring(type(string.gfind) == "function"), "debug")
-- 2. Active modules
local moduleCount = 0
local enabledModules = {}
if MTH.modules then
for name, mod in pairs(MTH.modules) do
moduleCount = moduleCount + 1
if mod.enabled then
table.insert(enabledModules, name)
end
end
end
MTH:Print("[DIAG] Registered modules: " .. tostring(moduleCount), "debug")
MTH:Print("[DIAG] Enabled: " .. table.concat(enabledModules, ", "), "debug")
-- 3. Event router state
local routerFrame = (MTH._eventRouter and MTH._eventRouter.frame) or nil
local routerEvents = (MTH._eventRouter and MTH._eventRouter.eventRefs) or {}
local eventCount = 0
for _ in pairs(routerEvents) do eventCount = eventCount + 1 end
MTH:Print("[DIAG] EventRouter frame: " .. tostring(routerFrame ~= nil) .. ", events: " .. tostring(eventCount), "debug")
-- 4. Debug frame initialized?
local dfInit = (MTH_DebugFrame and MTH_DebugFrame.initialized) and true or false
MTH:Print("[DIAG] DebugFrame initialized: " .. tostring(dfInit), "debug")
MTH:Print("======= End Diagnostics =======", "debug")
end
function SlashCmdList.MTH(msg, editbox) function SlashCmdList.MTH(msg, editbox)
if MTH and MTH.ApplyClassGate and MTH:ApplyClassGate("slash") then if MTH and MTH.ApplyClassGate and MTH:ApplyClassGate("slash") then
return return
@@ -352,24 +312,312 @@ function SlashCmdList.MTH(msg, editbox)
local lowerMsg = string.lower(msg) local lowerMsg = string.lower(msg)
local _, _, lowerCmd, lowerArg = string.find(lowerMsg, "^(%S+)%s*(.-)%s*$") local _, _, lowerCmd, lowerArg = string.find(lowerMsg, "^(%S+)%s*(.-)%s*$")
if msg == "" then if msg == "" then
MTH:Print("Available: /mth options, /mth book, /mth diag, /mth err") MTH:Print("Available: /mth options, /mth book, /mth food")
elseif lowerMsg == "options" then
MTH_CommandOptions()
elseif lowerMsg == "book" or lowerMsg == "hunterbook" then
MTH_CommandBook()
elseif lowerMsg == "peers" or lowerMsg == "who" then
MTH_CommandPeers()
elseif lowerMsg == "diag" then
MTH_CommandDiag()
elseif lowerMsg == "err" or lowerMsg == "errors" or lowerMsg == "debug" then elseif lowerMsg == "err" or lowerMsg == "errors" or lowerMsg == "debug" then
if MTH_DebugFrame and MTH_DebugFrame.Toggle then if MTH_DebugFrame and MTH_DebugFrame.Toggle then
MTH_DebugFrame:Toggle() MTH_DebugFrame:Toggle()
else else
MTH:Print("Debug frame is not available.") MTH:Print("Debug frame is not available.")
end end
elseif lowerMsg == "options" then
MTH_CommandOptions()
elseif lowerMsg == "book" or lowerMsg == "hunterbook" then
MTH_CommandBook()
elseif lowerMsg == "peers" or lowerMsg == "who" then
MTH_CommandPeers()
elseif lowerCmd == "trainscan" or lowerCmd == "petscan" then
if type(MTH_CommandTrainScan) == "function" then
MTH_CommandTrainScan()
else
MTH:Print("Train scan is not available.")
end
elseif lowerCmd == "trainerscan" then
if type(MTH_CommandTrainerScan) == "function" then
MTH_CommandTrainerScan()
else
MTH:Print("Trainer catalog scanner is not available.")
end
elseif lowerCmd == "spellscan" then
if type(MTH_HT_RescanSpellbook) ~= "function" then
MTH:Print("Hunter spellbook scanner is not available.")
return
end
MTH_HT_RescanSpellbook()
local mail = type(MTH_HT_GetSpellInfo) == "function" and MTH_HT_GetSpellInfo("Mail") or nil
if mail then
MTH:Print("Hunter spellbook scan: Mail found (rank=" .. tostring(mail.rank or "") .. ", slot=" .. tostring(mail.slot or "?") .. ").")
else
MTH:Print("Hunter spellbook scan: Mail was not returned by the client spellbook API.")
end
elseif lowerCmd == "leveltest" then
local _, _, kind, levelText = string.find(lowerArg or "", "^(%S+)%s*(%d*)")
if type(MTH_LevelTracker_Test) ~= "function" then
MTH:Print("Level tracker is not available.")
elseif not MTH_LevelTracker_Test(kind, tonumber(levelText)) then
MTH:Print("Use /mth leveltest hunter [level], pet [level], or loyalty [level]")
end
elseif lowerCmd == "fx" or lowerCmd == "fireworks" or lowerCmd == "celebrate" then
if type(MTH_CelebratePreview) == "function" then
local style = lowerArg or ""
if style == "" then style = "firework" end
if style == "help" or style == "list" then
MTH:Print("Celebrations: firework, blackhole, shockwave, shatter, all")
elseif not MTH_CelebratePreview(style) then
MTH:Print("Unknown celebration: " .. tostring(style))
MTH:Print("Use /mth fx help for: firework, blackhole, shockwave, shatter, all")
end
else
-- Self-diagnosing: report which piece failed to load so we can tell
-- a stale UI from a genuine load error.
MTH:Print("Celebration effect is not available. Diagnostics:")
MTH:Print(" PizzaSauce global: " .. type(PizzaSauce))
MTH:Print(" MTH_Celebrate: " .. type(MTH_Celebrate))
MTH:Print(" MTH_FX_IsEnabled: " .. type(MTH_FX_IsEnabled))
MTH:Print(" MTH_SetupAnimationsOptions: " .. type(MTH_SetupAnimationsOptions))
MTH:Print(" -> If these are 'nil', do /reload (your UI is running code from before the feature was added).")
end
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 == "dev" then
if lowerArg == "petmigrate" then
if type(MTH_CommandPetsMigrate) == "function" then
MTH_CommandPetsMigrate()
else
MTH:Print("Pet migration command unavailable.")
end
else
MTH:Print("Dev commands: /mth dev petmigrate")
end
elseif lowerCmd == "growl" then
if type(MTH_Growl_SetEnabled) ~= "function" or type(MTH_Growl_IsEnabled) ~= "function" then
MTH:Print("Growl auto-teach is not available.")
elseif lowerArg == "on" or lowerArg == "enable" then
MTH_Growl_SetEnabled(true)
MTH:Print("Growl auto-teach: |cff00ff00ON|r (offers Growl to newly tamed pets).")
elseif lowerArg == "off" or lowerArg == "disable" then
MTH_Growl_SetEnabled(false)
MTH:Print("Growl auto-teach: |cffff0000OFF|r.")
else
local state = MTH_Growl_IsEnabled() and "|cff00ff00ON|r" or "|cffff0000OFF|r"
MTH:Print("Growl auto-teach is " .. state .. ". Usage: /mth growl on | off")
end
elseif lowerCmd == "food" then
local sub = lowerArg
local function MTH_FoodItemLabel(itemId)
local id = tonumber(itemId)
if not id then return tostring(itemId) end
if type(GetItemInfo) == "function" then
local name = GetItemInfo(id)
if name and name ~= "" then
return "|cffffcc00" .. name .. "|r |cffaaaaaa(" .. id .. ")|r"
end
end
return "|cffaaaaaa" .. tostring(id) .. "|r"
end
if sub == "" or sub == "status" then
-- Show quarantine and exception state
local feedStore = type(MTH_CharSavedVariables) == "table" and MTH_CharSavedVariables.feedTracking or nil
local quarantine = feedStore and feedStore.fomQuarantine and feedStore.fomQuarantine.byFamily or nil
local exceptions = feedStore and feedStore.exceptions and feedStore.exceptions.byItemId or nil
MTH:Print("|cffffff00=== Feed-O-Matic Status ===|r")
-- Quarantine
local qCount = 0
if type(quarantine) == "table" then
for family, byItem in pairs(quarantine) do
if type(byItem) == "table" then
for itemId, _ in pairs(byItem) do
qCount = qCount + 1
MTH:Print(" |cffff8800Quarantine:|r " .. MTH_FoodItemLabel(itemId) .. " (family=" .. tostring(family) .. ")")
end
end
end
end
if qCount == 0 then MTH:Print(" Quarantine: |cff00ff00empty|r") end
-- Core exceptions
local eCount = 0
if type(exceptions) == "table" then
for itemId, _ in pairs(exceptions) do
if type(itemId) == "number" then -- skip string-key dupes
eCount = eCount + 1
MTH:Print(" |cffff4444Exception block:|r " .. MTH_FoodItemLabel(itemId))
end
end
end
if eCount == 0 then MTH:Print(" Exception blocks: |cff00ff00none|r") end
-- FOM_RemovedFoods
local rCount = 0
if type(FOM_RemovedFoods) == "table" then
for diet, list in pairs(FOM_RemovedFoods) do
if type(list) == "table" then
for _, itemId in ipairs(list) do
rCount = rCount + 1
MTH:Print(" |cffaaaaaaRemoved food:|r " .. MTH_FoodItemLabel(itemId) .. " (diet=" .. tostring(diet) .. ")")
end
end
end
end
if rCount == 0 then MTH:Print(" Removed foods: |cff00ff00none|r") end
MTH:Print("|cffaaaaaa/mth food unban <id>|r — unban one item |cffaaaaaa/mth food reset|r — clear all bans")
elseif string.find(sub, "^unban%s") or sub == "unban" then
local _, _, itemArg = string.find(lowerArg, "^unban%s+(%S+)")
local itemId = tonumber(itemArg)
if not itemId then
MTH:Print("|cffff4444Usage:|r /mth food unban <itemId>")
MTH:Print("Example: /mth food unban 8952")
elseif type(FOM_ClearItemBans) ~= "function" then
MTH:Print("|cffff4444Error:|r Feed-O-Matic not loaded.")
else
local label = MTH_FoodItemLabel(itemId)
FOM_ClearItemBans(itemId)
MTH:Print("|cff00ff00Unbanned " .. label .. "|r — removed from quarantine, exceptions and removed-foods list.")
end
elseif sub == "reset" then
local feedStore = type(MTH_CharSavedVariables) == "table" and MTH_CharSavedVariables.feedTracking or nil
if feedStore then
if feedStore.fomQuarantine then feedStore.fomQuarantine = { byFamily = {} } end
if feedStore.exceptions then feedStore.exceptions = { byItemId = {} } end
end
if type(FOM_RemovedFoods) == "table" then
for diet, _ in pairs(FOM_RemovedFoods) do FOM_RemovedFoods[diet] = {} end
end
if type(FOM_FEED_SCAN_CACHE) ~= "nil" then FOM_FEED_SCAN_CACHE = nil end
MTH:Print("|cff00ff00All Feed-O-Matic bans and quarantines cleared.|r")
else
MTH:Print("|cffffff00/mth food|r — show quarantine/ban status")
MTH:Print("|cffffff00/mth food unban <itemId>|r — remove one item from all bans")
MTH:Print("|cffffff00/mth food reset|r — clear ALL bans and quarantines")
end
elseif lowerCmd == "zbar" and lowerArg == "debug" then
-- Dump the live state of every zBar button to chat so we can see which one is the ghost.
local p = function(s) MTH:Print("[zBar] " .. tostring(s), "debug") end
local root = (type(MTH_ZH_GetSavedRoot) == "function") and MTH_ZH_GetSavedRoot() or ZHunterMod_Saved
local zs = root and root["_zbar"]
p("=== zBar debug ===")
p("enabled=" .. tostring(zs and zs.enabled) .. " anchor=" .. tostring(getglobal("MTH_ZBar_Anchor") ~= nil))
p("direction=" .. tostring(zs and zs.direction) .. " childexpand=" .. tostring(zs and zs.childexpand) .. " childarrange=" .. tostring(zs and zs.childarrange))
local allButtons = {
"zButtonAspect","zButtonAmmo","zButtonTrack","zButtonTrap",
"zButtonPet","zButtonRanged","zButtonMounts","zButtonCompanions","zButtonToys",
}
for _, bname in ipairs(allButtons) do
local btn = getglobal(bname)
local shown = btn and btn.IsShown and btn:IsShown()
local bsaved = root and type(root[bname]) == "table" and root[bname]
local enabled = bsaved and bsaved["enabled"]
local zvis = zs and zs.visible and zs.visible[bname]
local x, y, point = "?", "?", "?"
if btn and btn.GetPoint then
local pt, _, _, bx, by = btn:GetPoint()
point = tostring(pt)
x = tostring(bx and math.floor(bx + 0.5) or "?")
y = tostring(by and math.floor(by + 0.5) or "?")
end
local bmtname = btn and tostring(btn.name) or "nil"
p(bname .. ": shown=" .. tostring(shown) ..
" enabled=" .. tostring(enabled) ..
" zbarVisible=" .. tostring(zvis) ..
" btn.name=" .. bmtname ..
" anchor=(" .. point .. " " .. x .. "," .. y .. ")")
end
p("=== end ===")
else else
MTH:Print("Unknown command: " .. tostring(msg)) MTH:Print("Unknown command: " .. tostring(msg))
MTH:Print("Available: /mth options, /mth book, /mth diag, /mth err") MTH:Print("Available: /mth options, /mth book, /mth npcid, /mth food")
end end
end end
+818
View File
@@ -0,0 +1,818 @@
-- MetaHunt: center-screen celebration effect (message + firework burst).
-- Uses the embedded PizzaSauce animation library. Degrades to a silent no-op
-- when PizzaSauce or the WoW frame API are unavailable (e.g. offline tests).
-- Capture our own PizzaSauce instance now, before another addon that ships its
-- own copy can overwrite the global. This is the pattern the library requires.
local Sauce = PizzaSauce
-- Localization helper: resolve a key through MetaHunt's loc system, falling back
-- to the inline English default. Every user-facing string is keyed so the enUS
-- strings can be harvested later; the inline default is the working English.
local function MTH_FX_L(key, default)
if MTH and type(MTH.GetLocalization) == "function" then
return MTH:GetLocalization(key, default)
end
return default or key
end
-- ---------------------------------------------------------------------------
-- Animation settings (shared by the effect code and the options "Animations" tab)
-- ---------------------------------------------------------------------------
-- Sections shown, in order, in the options panel. `titleKey` localizes `title`.
MTH_FX_ANIM_SECTIONS = {
{ id = "pet", title = "Pet", titleKey = "ANIM_SECTION_PET" },
{ id = "level", title = "Leveling", titleKey = "ANIM_SECTION_LEVEL" },
{ id = "combat", title = "Combat", titleKey = "ANIM_SECTION_COMBAT" },
{ id = "book", title = "Book", titleKey = "ANIM_SECTION_BOOK" },
{ id = "ui", title = "Interface", titleKey = "ANIM_SECTION_UI" },
}
-- Every individual animation the addon can play. Each has a stable `key`, the
-- section it belongs to, a checkbox label, an optional tooltip, and a default.
-- labelKey/tooltipKey localize label/tooltip (resolved at render time).
-- Adding a new animation = one entry here + one MTH_FX_IsEnabled("key") guard.
MTH_FX_ANIM_DEFS = {
{ key = "level.hunter", section = "level", default = true,
label = "Celebrate Hunter training levels",
tooltip = "Show a banner and burst when a level unlocks Hunter trainer spells." },
{ key = "level.huntermilestone", section = "level", default = true,
label = "Celebrate Hunter milestones",
tooltip = "Use a large celebration at levels 10, 20, 30, 40, 50, and 60." },
{ key = "level.pet", section = "level", default = true,
label = "Celebrate pet training levels",
tooltip = "Show a banner when your current pet gains a level with Pet Trainer spells." },
{ key = "level.loyalty", section = "level", default = true,
label = "Celebrate pet loyalty gains",
tooltip = "Show a golden banner when your current pet gains a loyalty level." },
{ key = "pet.celebrate", section = "pet", default = true,
label = "Celebrate new pet abilities", labelKey = "ANIM_PET_CELEBRATE",
tooltip = "Play a firework and a banner in the middle of the screen when your pet learns a new ability or a new rank.",
tooltipKey = "ANIM_PET_CELEBRATE_TIP" },
{ key = "pet.runaway", section = "pet", default = true,
label = "Warn when a pet runs away", labelKey = "ANIM_PET_RUNAWAY",
tooltip = "Flash a red, shaking banner in the middle of the screen when one of your pets runs away for good.",
tooltipKey = "ANIM_PET_RUNAWAY_TIP" },
{ key = "pet.happiness", section = "pet", default = true,
label = "Flash zPet on happiness change", labelKey = "ANIM_PET_HAPPINESS",
tooltip = "Briefly flash the pet happiness area green when your pet cheers up, or red when it gets unhappy.",
tooltipKey = "ANIM_PET_HAPPINESS_TIP" },
{ key = "pet.loyaltyup", section = "pet", default = true,
label = "Glow zPet on loyalty gain", labelKey = "ANIM_PET_LOYALTYUP",
tooltip = "Give a short golden glow when your pet gains a loyalty level.",
tooltipKey = "ANIM_PET_LOYALTYUP_TIP" },
{ key = "pet.feed", section = "pet", default = true,
label = "Flash zPet when feeding your pet", labelKey = "ANIM_PET_FEED",
tooltip = "Give a short green flash when Feed-O-Matic feeds your pet.",
tooltipKey = "ANIM_PET_FEED_TIP" },
{ key = "combat.ammoswap", section = "combat", default = true,
label = "Flash zAmmo on Smart Ammo swap", labelKey = "ANIM_COMBAT_AMMOSWAP",
tooltip = "Briefly highlight the zAmmo button when Smart Ammo swaps your ammo.",
tooltipKey = "ANIM_COMBAT_AMMOSWAP_TIP" },
{ key = "combat.ammoout", section = "combat", default = true,
label = "Warn when current ammo runs out",
tooltip = "Show a red warning banner when Smart Ammo equips fallback ammunition." },
{ key = "combat.lowammo", section = "combat", default = true,
label = "Warn when total ammo is low",
tooltip = "Show a warning banner when your total arrows or bullets reach the Smart Ammo threshold." },
{ key = "combat.ammozero", section = "combat", default = true,
label = "Warn when all ammo is gone",
tooltip = "Show a sarcastic warning banner when you have no arrows or bullets left." },
{ key = "combat.rangedswap", section = "combat", default = true,
label = "Flash zRanged on weapon swap", labelKey = "ANIM_COMBAT_RANGEDSWAP",
tooltip = "Briefly highlight the zRanged button when you hot-swap a ranged weapon.",
tooltipKey = "ANIM_COMBAT_RANGEDSWAP_TIP" },
{ key = "combat.mmprocs", section = "combat", default = true,
label = "Pulse MM Widget procs", labelKey = "ANIM_COMBAT_MMPROCS",
tooltip = "Pulse an MM Widget cell when a Lock and Load or Experimental ammo proc lights up.",
tooltipKey = "ANIM_COMBAT_MMPROCS_TIP" },
{ key = "book.open", section = "book", default = true,
label = "Fade in the Hunter's Book", labelKey = "ANIM_BOOK_OPEN",
tooltip = "Fade the Hunter's Book window in when you open it.",
tooltipKey = "ANIM_BOOK_OPEN_TIP" },
{ key = "book.tabs", section = "book", default = true,
label = "Fade Hunter's Book results on tab changes", labelKey = "ANIM_BOOK_TABS",
tooltip = "Fade the results list back in when you switch tabs inside the Hunter's Book.",
tooltipKey = "ANIM_BOOK_TABS_TIP" },
{ key = "book.model", section = "book", default = true,
label = "Fade Hunter's Book beast models", labelKey = "ANIM_BOOK_MODEL",
tooltip = "Fade the beast model viewer in when it loads a new model.",
tooltipKey = "ANIM_BOOK_MODEL_TIP" },
{ key = "book.search", section = "book", default = true,
label = "Fade Hunter's Book search results", labelKey = "ANIM_BOOK_SEARCH",
tooltip = "Fade the results list when your search filter changes.",
tooltipKey = "ANIM_BOOK_SEARCH_TIP" },
{ key = "ui.options", section = "ui", default = true,
label = "Slide MetaHunt Options open", labelKey = "ANIM_UI_OPTIONS",
tooltip = "Slide and fade the MetaHunt options window in when you open it.",
tooltipKey = "ANIM_UI_OPTIONS_TIP" },
{ key = "ui.optiontabs", section = "ui", default = true,
label = "Fade MetaHunt Options tab changes", labelKey = "ANIM_UI_OPTIONTABS",
tooltip = "Fade the settings panel when you switch tabs inside the options window.",
tooltipKey = "ANIM_UI_OPTIONTABS_TIP" },
{ key = "ui.optionsclose", section = "ui", default = true,
label = "Fade MetaHunt windows closed", labelKey = "ANIM_UI_OPTIONSCLOSE",
tooltip = "Fade the options and Hunter's Book windows out when you close them.",
tooltipKey = "ANIM_UI_OPTIONSCLOSE_TIP" },
{ key = "ui.minimap", section = "ui", default = true,
label = "Pulse MetaHunt minimap button on click", labelKey = "ANIM_UI_MINIMAP",
tooltip = "Give the minimap button a quick pulse when you click it.",
tooltipKey = "ANIM_UI_MINIMAP_TIP" },
{ key = "ui.minimapping", section = "ui", default = true,
label = "Ping minimap when locating a target", labelKey = "ANIM_UI_MINIMAPPING",
tooltip = "Send a short ping ripple over the minimap when you locate a tracked NPC.",
tooltipKey = "ANIM_UI_MINIMAPPING_TIP" },
{ key = "ui.zbuttons", section = "ui", default = true,
label = "Fade zButton bars open", labelKey = "ANIM_UI_ZBUTTONS",
tooltip = "Fade the child buttons in when a collapsible zButton bar (pet, tracking, aspects...) expands.",
tooltipKey = "ANIM_UI_ZBUTTONS_TIP" },
{ key = "ui.zbar", section = "ui", default = true,
label = "Fade the zBar in", labelKey = "ANIM_UI_ZBAR",
tooltip = "Fade the unified zBar buttons in when the bar becomes active.",
tooltipKey = "ANIM_UI_ZBAR_TIP" },
{ key = "ui.stable", section = "ui", default = true,
label = "Flash Hunter's Book stable slots", labelKey = "ANIM_UI_STABLE",
tooltip = "Flash a stable slot in the Hunter's Book when a pet is deposited or withdrawn.",
tooltipKey = "ANIM_UI_STABLE_TIP" },
{ key = "ui.mappins", section = "ui", default = true,
label = "Fade World Map markers in", labelKey = "ANIM_UI_MAPPINS",
tooltip = "Fade the world map markers in when you locate a beast, vendor or master.",
tooltipKey = "ANIM_UI_MAPPINS_TIP" },
}
local function MTH_FX_Store()
if MTH and type(MTH.GetModuleSavedVariables) == "function" then
return MTH:GetModuleSavedVariables("animations")
end
return nil
end
local function MTH_FX_DefaultFor(key)
local i = 1
while i <= table.getn(MTH_FX_ANIM_DEFS) do
if MTH_FX_ANIM_DEFS[i].key == key then
return MTH_FX_ANIM_DEFS[i].default and true or false
end
i = i + 1
end
return true
end
function MTH_FX_AnimationsEnabled()
local store = MTH_FX_Store()
if not store or store.enabled == nil then
return true
end
return store.enabled and true or false
end
function MTH_FX_SetAnimationsEnabled(value)
local store = MTH_FX_Store()
if not store then return end
store.enabled = value and true or false
end
function MTH_FX_GetFlag(key)
local store = MTH_FX_Store()
if store and store.flags and store.flags[key] ~= nil then
return store.flags[key] and true or false
end
return MTH_FX_DefaultFor(key)
end
function MTH_FX_SetFlag(key, value)
local store = MTH_FX_Store()
if not store then return end
store.flags = store.flags or {}
store.flags[key] = value and true or false
end
-- Master gate every animation call site checks: false when animations are
-- globally disabled, or when this specific animation is turned off.
function MTH_FX_IsEnabled(key)
if not MTH_FX_AnimationsEnabled() then
return false
end
if key == nil then
return true
end
return MTH_FX_GetFlag(key)
end
-- Register a one-shot "ballistic" tween type: a spark that flies out from the
-- center along a velocity vector while gravity pulls it back down.
if Sauce and type(Sauce.RegisterType) == "function" and not Sauce.MTH_HasBallistic then
local ok, err = pcall(function()
Sauce.MTH_HasBallistic = true
Sauce:RegisterType("mth_ballistic", {
init = function(target, from)
return from or { 0, 0 }
end,
apply = function(target, from, to, t, tween)
local dur = tween.dur or 1
local elapsed = t * dur
local vx = tween.vx or 0
local vy = tween.vy or 0
local gravity = tween.gravity or 0
local x = from[1] + vx * elapsed
local y = from[2] + vy * elapsed - 0.5 * gravity * elapsed * elapsed
local anchor = tween.anchor or UIParent
target:ClearAllPoints()
target:SetPoint("CENTER", anchor, "CENTER", x, y)
end,
})
end)
end
-- Register a decaying "shake" tween type: oscillates the frame around its own
-- current anchor point, with the amplitude fading to zero over the tween so it
-- always lands back exactly where it started. Used for negative events.
if Sauce and type(Sauce.RegisterType) == "function" and not Sauce.MTH_HasShake then
local ok, err = pcall(function()
Sauce.MTH_HasShake = true
Sauce:RegisterType("mth_shake", {
init = function(target)
local p, rel, rp, x, y = target:GetPoint(1)
return { p or "CENTER", rel, rp or p or "CENTER", x or 0, y or 0 }
end,
apply = function(target, from, to, t, tween)
local mag = tween.magnitude or 8
local freq = tween.freq or 34
local decay = 1 - t
local offset = math.sin(t * freq) * mag * decay
target:ClearAllPoints()
target:SetPoint(from[1], from[2], from[3], (from[4] or 0) + offset, from[5] or 0)
end,
})
end)
end
if Sauce and type(Sauce.RegisterType) == "function" and not Sauce.MTH_HasSpiral then
local ok, err = pcall(function()
Sauce.MTH_HasSpiral = true
Sauce:RegisterType("mth_spiral", {
init = function(target, from)
return from or 0
end,
apply = function(target, from, to, t, tween)
local radius = (tween.radius or 0) * (1 - t)
local angle = (tween.startAngle or 0) + (tween.rotations or 1) * 2 * math.pi * t
target:ClearAllPoints()
target:SetPoint("CENTER", tween.anchor or UIParent, "CENTER",
(tween.cx or 0) + math.cos(angle) * radius,
(tween.cy or 0) + math.sin(angle) * radius)
end,
})
end)
end
-- Reusable flash: overlays a short additive colour glow on any frame/button and
-- fades it out. Safe on any frame that supports CreateTexture; silent no-op
-- otherwise. The overlay texture is cached on the frame for reuse.
function MTH_FX_Flash(frame, r, g, b, duration)
if not Sauce or not frame then
return
end
if type(frame.CreateTexture) ~= "function" then
return
end
local tex = frame.mthFxFlash
if not tex then
tex = frame:CreateTexture(nil, "OVERLAY")
tex:SetTexture("Interface\\Buttons\\CheckButtonHilight")
tex:SetBlendMode("ADD")
tex:SetAllPoints(frame)
frame.mthFxFlash = tex
end
tex:SetVertexColor(r or 1, g or 1, b or 1)
tex:SetAlpha(0)
tex:Show()
local dur = duration or 0.45
Sauce:Stop(tex)
Sauce:Sequence({
Sauce:FadeTo(tex, 0.85, dur * 0.35),
Sauce:FadeTo(tex, 0, dur * 0.65, "inQuad", {
onFinish = function() tex:Hide() end,
}),
})
end
local MTH_FX_NUM_SPARKS = 22
local MTH_FX_NUM_CONFETTI = 48
local MTH_FX_NUM_BLACKHOLE = 40
local MTH_FX_Container
local MTH_FX_MsgFrame
local MTH_FX_TitleFS
local MTH_FX_SubFS
local MTH_FX_Sparks = {}
local MTH_FX_Confetti = {}
local MTH_FX_BlackHole = {}
-- Palette for the firework sparks (r, g, b), warm celebratory tones.
local MTH_FX_Colors = {
{ 1.00, 0.82, 0.00 },
{ 1.00, 0.45, 0.10 },
{ 1.00, 0.20, 0.30 },
{ 0.35, 0.70, 1.00 },
{ 0.55, 1.00, 0.45 },
{ 0.85, 0.50, 1.00 },
}
local function MTH_FX_EnsureFrames()
if type(CreateFrame) ~= "function" then
return false
end
if MTH_FX_Container then
return true
end
MTH_FX_Container = CreateFrame("Frame", "MTH_FX_Container", UIParent)
MTH_FX_Container:SetWidth(1)
MTH_FX_Container:SetHeight(1)
MTH_FX_Container:SetPoint("CENTER", UIParent, "CENTER", 0, 40)
MTH_FX_Container:SetFrameStrata("FULLSCREEN_DIALOG")
MTH_FX_Container:Hide()
MTH_FX_MsgFrame = CreateFrame("Frame", "MTH_FX_Message", UIParent)
MTH_FX_MsgFrame:SetWidth(512)
MTH_FX_MsgFrame:SetHeight(90)
MTH_FX_MsgFrame:SetPoint("CENTER", UIParent, "CENTER", 0, 80)
MTH_FX_MsgFrame:SetFrameStrata("FULLSCREEN_DIALOG")
MTH_FX_TitleFS = MTH_FX_MsgFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalHuge")
MTH_FX_TitleFS:SetPoint("CENTER", MTH_FX_MsgFrame, "CENTER", 0, 14)
MTH_FX_TitleFS:SetTextColor(1, 0.82, 0)
MTH_FX_SubFS = MTH_FX_MsgFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge")
MTH_FX_SubFS:SetPoint("TOP", MTH_FX_TitleFS, "BOTTOM", 0, -8)
MTH_FX_SubFS:SetTextColor(1, 1, 1)
MTH_FX_MsgFrame:Hide()
local i = 1
while i <= MTH_FX_NUM_SPARKS do
local spark = MTH_FX_Container:CreateTexture(nil, "OVERLAY")
spark:SetTexture("Interface\\Cooldown\\star4")
spark:SetWidth(22)
spark:SetHeight(22)
spark:SetBlendMode("ADD")
spark:SetPoint("CENTER", MTH_FX_Container, "CENTER", 0, 0)
spark:Hide()
MTH_FX_Sparks[i] = spark
i = i + 1
end
i = 1
while i <= MTH_FX_NUM_CONFETTI do
local bit = MTH_FX_Container:CreateTexture(nil, "OVERLAY")
bit:SetTexture("Interface\\Buttons\\WHITE8X8")
bit:SetWidth(5)
bit:SetHeight(8)
bit:SetPoint("CENTER", MTH_FX_Container, "CENTER", 0, 0)
bit:Hide()
MTH_FX_Confetti[i] = bit
i = i + 1
end
i = 1
while i <= MTH_FX_NUM_BLACKHOLE do
local particle = MTH_FX_Container:CreateTexture(nil, "OVERLAY")
particle:SetTexture("Interface\\Cooldown\\star4")
particle:SetWidth(10)
particle:SetHeight(10)
particle:SetBlendMode("ADD")
particle:SetPoint("CENTER", MTH_FX_Container, "CENTER", 0, 0)
particle:Hide()
MTH_FX_BlackHole[i] = particle
i = i + 1
end
return true
end
-- Public entry point: play a full-screen celebration. `style` defaults to the
-- original firework burst and may be firework, victory, starfall, fanfare, or comet.
function MTH_Celebrate(title, subtitle, style, displaySeconds)
if not Sauce then
return
end
if not MTH_FX_EnsureFrames() then
return
end
style = string.lower(tostring(style or "firework"))
local fadeDelay = tonumber(displaySeconds) or 5
if fadeDelay < 0.5 then fadeDelay = 0.5 end
if style == "starfall" then
MTH_FX_TitleFS:SetTextColor(0.45, 0.80, 1.00)
else
MTH_FX_TitleFS:SetTextColor(1, 0.82, 0)
end
MTH_FX_TitleFS:SetText(title or "")
MTH_FX_SubFS:SetText(subtitle or "")
Sauce:Stop(MTH_FX_MsgFrame)
MTH_FX_MsgFrame:SetAlpha(0)
MTH_FX_MsgFrame:Show()
if style == "victory" or style == "fanfare" then
Sauce:Sequence({
Sauce:Group({
Sauce:SlideIn(MTH_FX_MsgFrame, "UP", 80, 0.36, "outBack", { defer = true }),
Sauce:Pulse(MTH_FX_MsgFrame, style == "victory" and 1.32 or 1.18, 0.52, { defer = true }),
}),
Sauce:FadeOut(MTH_FX_MsgFrame, 0.6, "inQuad", { delay = fadeDelay }),
})
else
Sauce:Sequence({
Sauce:Group({
Sauce:FadeTo(MTH_FX_MsgFrame, 1, 0.25),
Sauce:Pulse(MTH_FX_MsgFrame, 1.22, 0.5),
}),
Sauce:FadeOut(MTH_FX_MsgFrame, 0.6, "inQuad", { delay = fadeDelay }),
})
end
MTH_FX_Container:Show()
local colorCount = table.getn(MTH_FX_Colors)
local colorIdx = 0
local i = 1
while i <= MTH_FX_NUM_SPARKS do
local spark = MTH_FX_Sparks[i]
spark:SetTexture("Interface\\Cooldown\\star4")
spark:SetTexCoord(0, 1, 0, 1)
local startX, startY = 0, 0
local delay, gravity = 0, 300
local angle = (i / MTH_FX_NUM_SPARKS) * 2 * math.pi + (math.random() - 0.5) * 0.4
local speed = 150 + math.random() * 130
local vx = math.cos(angle) * speed
local vy = math.sin(angle) * speed + 70
local dur = 0.9 + math.random() * 0.4
if style == "victory" then
startX = ((i - math.floor(i / 2) * 2) == 0) and -130 or 130
speed = 100 + math.random() * 100
vx = math.cos(angle) * speed
vy = math.sin(angle) * speed + 105
elseif style == "starfall" then
startX = -230 + math.random() * 460
startY = 200 + math.random() * 100
vx = -35 + math.random() * 70
vy = -10 + math.random() * 25
gravity = 235
dur = 1.1 + math.random() * 0.5
elseif style == "fanfare" then
delay = ((i - 1) - math.floor((i - 1) / 3) * 3) * 0.18
speed = 110 + math.random() * 90
vx = math.cos(angle) * speed
vy = math.sin(angle) * speed + 90
elseif style == "comet" then
if i == 1 then
startY = -180
vx, vy, gravity, dur = 0, 430, 0, 0.52
else
delay = 0.5
startY = 45
speed = 130 + math.random() * 120
vx = math.cos(angle) * speed
vy = math.sin(angle) * speed + 70
end
end
colorIdx = colorIdx + 1
if colorIdx > colorCount then colorIdx = 1 end
local color = MTH_FX_Colors[colorIdx]
spark:SetVertexColor(color[1], color[2], color[3])
spark:SetAlpha(delay > 0 and 0 or 1)
spark:Show()
Sauce:Stop(spark)
Sauce:Group({
Sauce:Tween(spark, {
type = "mth_ballistic",
from = { startX, startY },
duration = dur,
easing = "linear",
delay = delay,
anchor = MTH_FX_Container,
dur = dur,
vx = vx,
vy = vy,
gravity = gravity,
}),
Sauce:FadeTo(spark, 1, 0.05, "outQuad", { delay = delay }),
Sauce:FadeTo(spark, 0, 0.45, "inQuad", {
delay = delay + dur - 0.45,
onFinish = function()
spark:Hide()
end,
}),
})
i = i + 1
end
end
local function MTH_FX_ShowPreviewBanner(title, subtitle, r, g, b)
MTH_FX_TitleFS:SetTextColor(r, g, b)
MTH_FX_TitleFS:SetText(title)
MTH_FX_SubFS:SetText(subtitle)
Sauce:Stop(MTH_FX_MsgFrame)
MTH_FX_MsgFrame:SetAlpha(0)
MTH_FX_MsgFrame:Show()
Sauce:Sequence({
Sauce:Group({
Sauce:FadeTo(MTH_FX_MsgFrame, 1, 0.18),
Sauce:Pulse(MTH_FX_MsgFrame, 1.18, 0.45),
}),
Sauce:FadeOut(MTH_FX_MsgFrame, 0.55, "inQuad", { delay = 5 }),
})
end
function MTH_CelebrateConfetti(title, subtitle)
if not Sauce or not MTH_FX_EnsureFrames() then return end
MTH_FX_ShowPreviewBanner(title or "Celebration!", subtitle or "Confetti", 1, 0.82, 0.15)
MTH_FX_Container:Show()
local count = table.getn(MTH_FX_Colors)
local i = 1
while i <= MTH_FX_NUM_CONFETTI do
local bit = MTH_FX_Confetti[i]
local color = MTH_FX_Colors[((i - 1) - math.floor((i - 1) / count) * count) + 1]
local size = 3 + math.random() * 5
local angle = math.random() * 2 * math.pi
local speed = 90 + math.random() * 210
local dur = 1.25 + math.random() * 0.7
bit:SetWidth(size)
bit:SetHeight(size * 1.6)
bit:SetVertexColor(color[1], color[2], color[3])
bit:SetAlpha(1)
bit:Show()
Sauce:Stop(bit)
Sauce:Group({
Sauce:Tween(bit, {
type = "mth_ballistic", from = { 0, 0 }, duration = dur,
easing = "linear", anchor = MTH_FX_Container, dur = dur,
vx = math.cos(angle) * speed, vy = math.sin(angle) * speed + 100, gravity = 255,
}),
Sauce:FadeTo(bit, 0, 0.35, "inQuad", {
delay = dur - 0.35, onFinish = function() bit:Hide() end,
}),
})
i = i + 1
end
end
function MTH_CelebrateBlackHole(title, subtitle)
if not Sauce or not MTH_FX_EnsureFrames() then return end
MTH_FX_ShowPreviewBanner(title or "Gravity Well", subtitle or "Black Hole", 0.68, 0.45, 1.00)
MTH_FX_Container:Show()
local colors = { { 0.45, 0.25, 1 }, { 0.30, 0.50, 1 }, { 0.80, 0.55, 1 } }
local i = 1
while i <= MTH_FX_NUM_BLACKHOLE do
local particle = MTH_FX_BlackHole[i]
local color = colors[((i - 1) - math.floor((i - 1) / 3) * 3) + 1]
local startAngle = math.random() * 2 * math.pi
local radius = 95 + math.random() * 145
local duration = 1.5 + math.random() * 1.0
local size = 7 + math.random() * 8
particle:SetWidth(size)
particle:SetHeight(size)
particle:SetVertexColor(color[1], color[2], color[3])
particle:SetAlpha(1)
particle:Show()
Sauce:Stop(particle)
Sauce:Group({
Sauce:Tween(particle, {
type = "mth_spiral", from = 0, to = 1, duration = duration,
easing = "inQuad", anchor = MTH_FX_Container, cx = 0, cy = 0,
radius = radius, startAngle = startAngle, rotations = 1.5 + math.random() * 2.0,
}),
Sauce:Size(particle, { size, size }, { 2, 2 }, duration, "inQuad"),
Sauce:FadeTo(particle, 0, duration * 0.3, "inQuad", {
delay = duration * 0.7, onFinish = function() particle:Hide() end,
}),
})
i = i + 1
end
end
function MTH_CelebrateShockwave(title, subtitle)
if not Sauce or not MTH_FX_EnsureFrames() then return end
MTH_FX_ShowPreviewBanner(title or "Impact!", subtitle or "Shockwave", 1, 0.62, 0.15)
MTH_FX_Container:Show()
local dotsPerWave = 16
local i = 1
while i <= MTH_FX_NUM_CONFETTI do
local dot = MTH_FX_Confetti[i]
local wave = math.floor((i - 1) / dotsPerWave) + 1
local pos = (i - 1) - math.floor((i - 1) / dotsPerWave) * dotsPerWave
local angle = pos / dotsPerWave * 2 * math.pi
local duration = 1.1 + wave * 0.18
local radius = 115 + wave * 52
local delay = (wave - 1) * 0.18
local intensity = 1 - (wave - 1) * 0.20
dot:SetWidth(5)
dot:SetHeight(5)
dot:SetVertexColor(1 * intensity, 0.65 * intensity, 0.18 * intensity)
dot:SetAlpha(0)
dot:Show()
Sauce:Stop(dot)
Sauce:Group({
Sauce:Tween(dot, {
type = "mth_ballistic", from = { 0, 0 }, duration = duration,
easing = "outQuad", delay = delay, anchor = MTH_FX_Container, dur = duration,
vx = math.cos(angle) * radius / duration, vy = math.sin(angle) * radius / duration, gravity = 0,
}),
Sauce:FadeTo(dot, 1, 0.04, "outQuad", { delay = delay }),
Sauce:FadeTo(dot, 0, 0.4, "inQuad", {
delay = delay + duration - 0.4, onFinish = function() dot:Hide() end,
}),
})
i = i + 1
end
end
function MTH_CelebrateShatter(title, subtitle)
if not Sauce or not MTH_FX_EnsureFrames() then return end
MTH_FX_ShowPreviewBanner(title or "Shattered!", subtitle or "Shatter", 0.70, 0.85, 1.00)
MTH_FX_Container:Show()
local grid = 4
local iconSize = 80
local fragmentSize = iconSize / grid
local i = 1
while i <= MTH_FX_NUM_SPARKS do
local spark = MTH_FX_Sparks[i]
if i <= grid * grid then
local row = math.floor((i - 1) / grid)
local col = (i - 1) - row * grid
local x = (col - grid / 2 + 0.5) * fragmentSize
local y = ((grid - 1 - row) - grid / 2 + 0.5) * fragmentSize
local duration = 1.1 + math.random() * 0.45
spark:SetTexture("Interface\\Icons\\Ability_Hunter_SniperShot")
spark:SetTexCoord(col / grid, (col + 1) / grid, row / grid, (row + 1) / grid)
spark:SetWidth(fragmentSize)
spark:SetHeight(fragmentSize)
spark:SetVertexColor(1, 1, 1)
spark:SetAlpha(1)
spark:Show()
Sauce:Stop(spark)
Sauce:Group({
Sauce:Tween(spark, {
type = "mth_ballistic", from = { x, y }, duration = duration,
easing = "linear", anchor = MTH_FX_Container, dur = duration,
vx = x * 4 + (math.random() - 0.5) * 90,
vy = y * 4 + 95 + math.random() * 80, gravity = 265,
}),
Sauce:FadeTo(spark, 0, 0.35, "inQuad", {
delay = duration - 0.35, onFinish = function() spark:Hide() end,
}),
})
end
i = i + 1
end
end
function MTH_CelebratePreview(style)
style = string.lower(tostring(style or "firework"))
if style == "all" then
if not Sauce then return end
local styles = { "firework", "blackhole", "shockwave", "shatter" }
local steps = {}
local i = 1
while i <= table.getn(styles) do
local previewStyle = styles[i]
table.insert(steps, Sauce:Run(function()
MTH_CelebratePreview(previewStyle)
end, { defer = true }))
table.insert(steps, Sauce:Delay(5.7))
i = i + 1
end
Sauce:Sequence(steps)
return true
end
if style == "blackhole" then
MTH_CelebrateBlackHole("Celebration Preview", "Black Hole")
return true
elseif style == "shockwave" then
MTH_CelebrateShockwave("Celebration Preview", "Shockwave")
return true
elseif style == "shatter" then
MTH_CelebrateShatter("Celebration Preview", "Shatter")
return true
end
local labels = {
firework = "Firework Burst",
}
if not labels[style] then return false end
MTH_Celebrate("Celebration Preview", labels[style], style)
return true
end
-- Convenience wrapper used when the hunter's pet learns a new ability/rank.
function MTH_CelebratePetAbility(label)
if not MTH_FX_IsEnabled("pet.celebrate") then
return
end
MTH_Celebrate(MTH_FX_L("ANIM_PET_LEARNED_TITLE", "New Pet Ability!"), label)
end
-- Dramatic negative banner: a red, shaking message shown when a pet runs away
-- for good. Gated on pet.runaway. Reuses the shared message frame.
function MTH_CelebrateRunaway(petName)
if not MTH_FX_IsEnabled("pet.runaway") then
return
end
if not Sauce then
return
end
if not MTH_FX_EnsureFrames() then
return
end
MTH_FX_TitleFS:SetTextColor(1, 0.15, 0.15)
MTH_FX_TitleFS:SetText(MTH_FX_L("ANIM_PET_RUNAWAY_TITLE", "Your pet ran away!"))
MTH_FX_SubFS:SetText(petName or "")
Sauce:Stop(MTH_FX_MsgFrame)
MTH_FX_MsgFrame:SetAlpha(0)
MTH_FX_MsgFrame:Show()
Sauce:Sequence({
Sauce:FadeTo(MTH_FX_MsgFrame, 1, 0.18),
Sauce:Tween(MTH_FX_MsgFrame, {
type = "mth_shake",
duration = 0.55,
easing = "linear",
magnitude = 11,
freq = 42,
}),
Sauce:FadeOut(MTH_FX_MsgFrame, 0.7, "inQuad", { delay = 5 }),
})
end
function MTH_CelebrateAmmoFallback(previousAmmo, newAmmo)
if not MTH_FX_IsEnabled("combat.ammoout") or not Sauce or not MTH_FX_EnsureFrames() then return end
MTH_FX_TitleFS:SetTextColor(1, 0.15, 0.15)
MTH_FX_TitleFS:SetText("Out of ammo!")
MTH_FX_SubFS:SetText("Equipped " .. tostring(newAmmo or "fallback ammunition"))
Sauce:Stop(MTH_FX_MsgFrame)
MTH_FX_MsgFrame:SetAlpha(0)
MTH_FX_MsgFrame:Show()
Sauce:Sequence({
Sauce:FadeTo(MTH_FX_MsgFrame, 1, 0.18),
Sauce:Tween(MTH_FX_MsgFrame, {
type = "mth_shake",
duration = 0.55,
easing = "linear",
magnitude = 11,
freq = 42,
}),
Sauce:FadeOut(MTH_FX_MsgFrame, 0.7, "inQuad", { delay = 5 }),
})
end
function MTH_CelebrateLowAmmo(ammoType, total)
if not MTH_FX_IsEnabled("combat.lowammo") or not Sauce or not MTH_FX_EnsureFrames() then return end
MTH_FX_TitleFS:SetTextColor(1, 0.55, 0.10)
MTH_FX_TitleFS:SetText("Running low on " .. tostring(ammoType or "ammo") .. "!")
MTH_FX_SubFS:SetText(tostring(total or 0) .. " left before you're reduced to harsh language.")
Sauce:Stop(MTH_FX_MsgFrame)
MTH_FX_MsgFrame:SetAlpha(0)
MTH_FX_MsgFrame:Show()
Sauce:Sequence({
Sauce:FadeTo(MTH_FX_MsgFrame, 1, 0.18),
Sauce:Tween(MTH_FX_MsgFrame, {
type = "mth_shake",
duration = 0.55,
easing = "linear",
magnitude = 11,
freq = 42,
}),
Sauce:FadeOut(MTH_FX_MsgFrame, 0.7, "inQuad", { delay = 5 }),
})
end
function MTH_CelebrateAmmoZero(ammoType)
if not MTH_FX_IsEnabled("combat.ammozero") or not Sauce or not MTH_FX_EnsureFrames() then return end
MTH_FX_TitleFS:SetTextColor(1, 0.15, 0.15)
MTH_FX_TitleFS:SetText("Out of " .. tostring(ammoType or "ammo") .. "!")
MTH_FX_SubFS:SetText("Your ranged weapon is now a very expensive club. Well done.")
Sauce:Stop(MTH_FX_MsgFrame)
MTH_FX_MsgFrame:SetAlpha(0)
MTH_FX_MsgFrame:Show()
Sauce:Sequence({
Sauce:FadeTo(MTH_FX_MsgFrame, 1, 0.18),
Sauce:Tween(MTH_FX_MsgFrame, {
type = "mth_shake",
duration = 0.55,
easing = "linear",
magnitude = 11,
freq = 42,
}),
Sauce:FadeOut(MTH_FX_MsgFrame, 0.7, "inQuad", { delay = 5 }),
})
end
+15 -27
View File
@@ -6,16 +6,16 @@
-- Uses MTH_DS_BeastSkins[familyName] generated by .tools/generators/gen_beast_skins.py -- Uses MTH_DS_BeastSkins[familyName] generated by .tools/generators/gen_beast_skins.py
-- Skin rendering: PlayerModel:SetUnit(CreatureDisplayInfo.ID) -- Skin rendering: PlayerModel:SetUnit(CreatureDisplayInfo.ID)
local function mv_log(msg) local MTH_MV_Sauce = PizzaSauce
if MTH_DebugFrame and type(MTH_DebugFrame.AddInfo) == "function" then
MTH_DebugFrame:AddInfo("[MV] " .. tostring(msg))
end
end
local function mv_err(msg) -- Fade a model frame in when a new skin is displayed, unless disabled.
if MTH_DebugFrame and type(MTH_DebugFrame.AddError) == "function" then local function MTH_MV_AnimateModel(model)
MTH_DebugFrame:AddError("[MV] " .. tostring(msg)) if not MTH_MV_Sauce or not model then return end
end if type(MTH_FX_IsEnabled) == "function" and not MTH_FX_IsEnabled("book.model") then return end
if type(model.SetAlpha) ~= "function" then return end
if not model:IsShown() then return end
model:SetAlpha(0)
MTH_MV_Sauce:FadeTo(model, 1, 0.30, "outQuad")
end end
local MTH_MV = { local MTH_MV = {
@@ -45,10 +45,8 @@ local MTH_MV = {
local function MTH_MV_ApplyModel(model, skinId) local function MTH_MV_ApplyModel(model, skinId)
if not model or not skinId then if not model or not skinId then
mv_err("ApplyModel: nil model or skinId model="..tostring(model).." id="..tostring(skinId))
return return
end end
mv_log("ApplyModel id="..tostring(skinId))
model:SetUnit(skinId) model:SetUnit(skinId)
model:SetPosition(0, 0, 0) model:SetPosition(0, 0, 0)
model:SetModelScale(1) model:SetModelScale(1)
@@ -65,6 +63,8 @@ local function MTH_MV_DisplayCurrent()
MTH_MV_ApplyModel(MTH_MV.previewModel, skin.id) MTH_MV_ApplyModel(MTH_MV.previewModel, skin.id)
MTH_MV_ApplyModel(MTH_MV.popupModel, skin.id) MTH_MV_ApplyModel(MTH_MV.popupModel, skin.id)
MTH_MV_AnimateModel(MTH_MV.previewModel)
MTH_MV_AnimateModel(MTH_MV.popupModel)
local indexStr = tostring(MTH_MV.index) .. " / " .. tostring(n) local indexStr = tostring(MTH_MV.index) .. " / " .. tostring(n)
if MTH_MV.popupSkinLabel then if MTH_MV.popupSkinLabel then
@@ -106,14 +106,11 @@ MTH_MV_BuildSkinBeastList = function(skinId)
end end
local function MTH_MV_Init() local function MTH_MV_Init()
if MTH_MV.inited then mv_log("Init: already done") return end if MTH_MV.inited then return end
mv_log("Init: starting")
local backdrop = getglobal("MTH_BOOK_DetailBackdrop") local backdrop = getglobal("MTH_BOOK_DetailBackdrop")
if not backdrop then if not backdrop then
mv_err("Init: MTH_BOOK_DetailBackdrop not found")
return return
end end
mv_log("Init: backdrop found, creating previewFrame")
MTH_MV.inited = true MTH_MV.inited = true
-- ---------------------------------------------------------------- -- ----------------------------------------------------------------
@@ -136,7 +133,6 @@ local function MTH_MV_Init()
MTH_MV.previewFrame:SetBackdropBorderColor(0.35, 0.35, 0.35, 0.8) MTH_MV.previewFrame:SetBackdropBorderColor(0.35, 0.35, 0.35, 0.8)
MTH_MV.previewFrame:Hide() MTH_MV.previewFrame:Hide()
mv_log("Init: previewFrame created, creating PlayerModel")
-- PlayerModel fills the inner area -- PlayerModel fills the inner area
MTH_MV.previewModel = CreateFrame("PlayerModel", nil, MTH_MV.previewFrame) MTH_MV.previewModel = CreateFrame("PlayerModel", nil, MTH_MV.previewFrame)
MTH_MV.previewModel:SetPoint("TOPLEFT", MTH_MV.previewFrame, "TOPLEFT", 3, -3) MTH_MV.previewModel:SetPoint("TOPLEFT", MTH_MV.previewFrame, "TOPLEFT", 3, -3)
@@ -158,7 +154,6 @@ local function MTH_MV_Init()
GameTooltip:Hide() GameTooltip:Hide()
end) end)
mv_log("Init: previewModel created, creating popup")
-- ---------------------------------------------------------------- -- ----------------------------------------------------------------
-- Popup frame (wider: left side = model viewer, right = beast list) -- Popup frame (wider: left side = model viewer, right = beast list)
-- ---------------------------------------------------------------- -- ----------------------------------------------------------------
@@ -343,7 +338,6 @@ local function MTH_MV_Init()
end end
MTH_MV.popup = popup MTH_MV.popup = popup
mv_log("Init: complete")
end end
-- --------------------------------------------------------------------------- -- ---------------------------------------------------------------------------
@@ -351,12 +345,10 @@ end
-- --------------------------------------------------------------------------- -- ---------------------------------------------------------------------------
function MTH_BOOK_ModelViewer_SetFamily(family, skinId) function MTH_BOOK_ModelViewer_SetFamily(family, skinId)
mv_log("SetFamily: "..tostring(family).." skinId="..tostring(skinId))
MTH_MV_Init() MTH_MV_Init()
if not MTH_MV.inited then mv_err("SetFamily: init failed") return end if not MTH_MV.inited then return end
local skins = MTH_DS_BeastSkins and type(MTH_DS_BeastSkins) == "table" and MTH_DS_BeastSkins[family] 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 if not skins or table.getn(skins) == 0 then
mv_log("SetFamily: no skins for "..tostring(family).." DS type="..type(MTH_DS_BeastSkins or false))
MTH_BOOK_ModelViewer_Clear() MTH_BOOK_ModelViewer_Clear()
return return
end end
@@ -380,7 +372,6 @@ function MTH_BOOK_ModelViewer_SetFamily(family, skinId)
MTH_BOOK_ModelViewer_Clear() MTH_BOOK_ModelViewer_Clear()
return return
end end
mv_log("SetFamily: "..table.getn(filteredSkins).." tameable skins (filtered from "..table.getn(skins)..")")
MTH_MV.family = family MTH_MV.family = family
MTH_MV.skins = filteredSkins MTH_MV.skins = filteredSkins
@@ -392,7 +383,6 @@ function MTH_BOOK_ModelViewer_SetFamily(family, skinId)
for i = 1, table.getn(filteredSkins) do for i = 1, table.getn(filteredSkins) do
if filteredSkins[i].id == skinId then if filteredSkins[i].id == skinId then
MTH_MV.index = i MTH_MV.index = i
mv_log("SetFamily: skinId "..skinId.." matched skin index "..i)
break break
end end
end end
@@ -409,7 +399,6 @@ function MTH_BOOK_ModelViewer_SetFamily(family, skinId)
end end
function MTH_BOOK_ModelViewer_Clear() function MTH_BOOK_ModelViewer_Clear()
mv_log("Clear")
if MTH_MV.previewFrame then MTH_MV.previewFrame:Hide() end if MTH_MV.previewFrame then MTH_MV.previewFrame:Hide() end
if not MTH_MV.keepPopup then if not MTH_MV.keepPopup then
if MTH_MV.popup and MTH_MV.popup:IsShown() then MTH_MV.popup:Hide() end if MTH_MV.popup and MTH_MV.popup:IsShown() then MTH_MV.popup:Hide() end
@@ -420,10 +409,9 @@ function MTH_BOOK_ModelViewer_Clear()
end end
function MTH_BOOK_ModelViewer_OpenPopup() function MTH_BOOK_ModelViewer_OpenPopup()
mv_log("OpenPopup skins="..table.getn(MTH_MV.skins))
MTH_MV_Init() MTH_MV_Init()
if not MTH_MV.popup then mv_err("OpenPopup: no popup frame") return end if not MTH_MV.popup then return end
if table.getn(MTH_MV.skins) == 0 then mv_log("OpenPopup: no skins") return end if table.getn(MTH_MV.skins) == 0 then return end
MTH_MV.popup:Show() MTH_MV.popup:Show()
MTH_MV.popup:Raise() MTH_MV.popup:Raise()
MTH_MV_DisplayCurrent() MTH_MV_DisplayCurrent()
+98 -8
View File
@@ -29,13 +29,31 @@ function MTH_BOOK_GetBeastZoneSummary(beast)
return "-" return "-"
end end
local seen = {} -- Count coords per WMA zone ID to find the dominant zone.
local zoneNames = {} local coordCount = {}
for i = 1, table.getn(beast.coords) do for i = 1, table.getn(beast.coords) do
local c = beast.coords[i] local c = beast.coords[i]
if c and c[3] and not seen[c[3]] then if c and c[3] then
seen[c[3]] = true local zid = c[3]
table.insert(zoneNames, MTH_BOOK_GetZoneName(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
end end
@@ -68,6 +86,69 @@ function MTH_BOOK_SplitAbilities(text)
return out return out
end 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) function MTH_BOOK_HasUsableAbilities(beast)
if not beast then return false end if not beast then return false end
local abilities = MTH_BOOK_SplitAbilities(beast.abilities) local abilities = MTH_BOOK_SplitAbilities(beast.abilities)
@@ -232,8 +313,11 @@ function MTH_BOOK_BeastMatches(beastId, beast)
end end
if MTH_BOOK_STATE.petInZoneOnly then if MTH_BOOK_STATE.petInZoneOnly then
local currentZoneId = MTH_BOOK_GetCurrentZoneId() -- Use the zone ID cached once per BuildResults call (avoids N calls to
if currentZoneId and not MTH_BOOK_BeastHasZoneId(beast, currentZoneId) then -- 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 return false
end end
end end
@@ -251,6 +335,12 @@ function MTH_BOOK_BeastMatches(beastId, beast)
end end
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 return true
end end
@@ -258,7 +348,7 @@ function MTH_BOOK_BeastSort(a, b)
local beasts = MTH_DS_Beasts local beasts = MTH_DS_Beasts
local ba = beasts and beasts[a] local ba = beasts and beasts[a]
local bb = beasts and beasts[b] 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 la = MTH_BOOK_ParseLevel(ba.lvl) or 0
local lb = MTH_BOOK_ParseLevel(bb.lvl) or 0 local lb = MTH_BOOK_ParseLevel(bb.lvl) or 0
if la ~= lb then return la < lb end if la ~= lb then return la < lb end
+157 -23
View File
@@ -2,13 +2,16 @@ if type(MTH_HUNTERBOOK_TABS) ~= "table" then MTH_HUNTERBOOK_TABS = {} end
MTH_HUNTERBOOK_TABS.families = { MTH_HUNTERBOOK_TABS.families = {
headerLabel = "Families", headerLabel = "Families",
columnLabels = { "Family", "Named", "Coords", "Abilities", "Diet" }, columnLabels = { "Family", "Named", "Coords", "Abilities", "Diet", "HP", "dmg", "Arm" },
columnLayout = { columnLayout = {
{ x = 10, width = 106, align = "LEFT" }, { x = 10, width = 100, align = "LEFT" },
{ x = 122, width = 52, align = "LEFT" }, { x = 114, width = 38, align = "LEFT" },
{ x = 176, width = 52, align = "LEFT" }, { x = 154, width = 38, align = "LEFT" },
{ x = 232, width = 364, align = "LEFT" }, { x = 196, width = 364, align = "LEFT" },
{ x = 532, width = 112, align = "LEFT" }, { x = 496, width = 102, align = "LEFT" },
{ x = 602, width = 30, align = "CENTER" },
{ x = 636, width = 36, align = "CENTER" },
{ x = 674, width = 38, align = "CENTER" },
}, },
} }
@@ -26,6 +29,28 @@ local function MTH_BOOKTAB_FamiliesTrim(value)
return text return text
end end
local function MTH_BOOKTAB_FormatFamilyStat(value)
local numeric = tonumber(value)
if not numeric then
return "-"
end
local colorPrefix = "|cFF4DA6FF"
if numeric < 1 then
colorPrefix = "|cFFFF4040"
elseif numeric > 1 then
colorPrefix = "|cFF40FF40"
end
local text = nil
if MTH_BOOK_STATE and MTH_BOOK_STATE.familyStatsPercent == true then
text = tostring(math.floor((numeric * 100) + 0.5)) .. "%"
return colorPrefix .. text .. "|r"
end
text = string.format("%.3f", numeric)
text = string.gsub(text, "(%..-)0+$", "%1")
text = string.gsub(text, "%.$", "")
return colorPrefix .. text .. "|r"
end
local function MTH_BOOKTAB_ShouldDisplayAllDiet(familyName) local function MTH_BOOKTAB_ShouldDisplayAllDiet(familyName)
local token = MTH_BOOKTAB_FamiliesSafeLower(MTH_BOOKTAB_FamiliesTrim(familyName)) local token = MTH_BOOKTAB_FamiliesSafeLower(MTH_BOOKTAB_FamiliesTrim(familyName))
return token == "bears" or token == "boars" return token == "bears" or token == "boars"
@@ -51,12 +76,41 @@ end
function MTH_BOOKTAB_BuildFamiliesRows() function MTH_BOOKTAB_BuildFamiliesRows()
local results = {} local results = {}
local families = MTH_DS_Families local families = MTH_DS_Families
local beasts = MTH_DS_Beasts
if type(families) ~= "table" then if type(families) ~= "table" then
return results return results
end end
local abilityCounts = {} local abilityCounts = {}
local canonicalMap = {} local canonicalMap = {}
local familyCounts = {}
if type(beasts) == "table" then
for _, beastRow in pairs(beasts) do
if type(beastRow) == "table" then
local familyName = MTH_BOOKTAB_FamiliesTrim(beastRow.family)
if familyName ~= "" then
local counts = familyCounts[familyName]
if not counts then
counts = { named = 0, coords = 0 }
familyCounts[familyName] = counts
end
counts.named = counts.named + 1
if type(beastRow.coords) == "table" then
for coordIndex = 1, table.getn(beastRow.coords) do
local coord = beastRow.coords[coordIndex]
if type(coord) == "table" then
if coord[1] ~= nil and coord[2] ~= nil then
counts.coords = counts.coords + 1
end
end
end
end
end
end
end
end
for _, familyRow in pairs(families) do for _, familyRow in pairs(families) do
if type(familyRow) == "table" and type(familyRow.abilities) == "table" then if type(familyRow) == "table" and type(familyRow.abilities) == "table" then
@@ -82,6 +136,7 @@ function MTH_BOOKTAB_BuildFamiliesRows()
for familyName, familyRow in pairs(families) do for familyName, familyRow in pairs(families) do
if type(familyRow) == "table" then if type(familyRow) == "table" then
local beastCounts = familyCounts[familyName] or nil
MTH_BOOKTAB_FamiliesTrace("BUILD " .. tostring(familyName) .. " icon=" .. tostring(familyRow.icon)) MTH_BOOKTAB_FamiliesTrace("BUILD " .. tostring(familyName) .. " icon=" .. tostring(familyRow.icon))
local abilities = {} local abilities = {}
local seen = {} local seen = {}
@@ -171,6 +226,10 @@ function MTH_BOOKTAB_BuildFamiliesRows()
end) end)
local dietText = "" local dietText = ""
local stats = type(familyRow.stats) == "table" and familyRow.stats or nil
local healthStat = stats and tonumber(stats.health) or nil
local damageStat = stats and tonumber(stats.damage) or nil
local armorStat = stats and tonumber(stats.armor) or nil
if MTH_BOOKTAB_ShouldDisplayAllDiet(familyName) then if MTH_BOOKTAB_ShouldDisplayAllDiet(familyName) then
dietText = "ALL" dietText = "ALL"
else else
@@ -178,7 +237,8 @@ function MTH_BOOKTAB_BuildFamiliesRows()
if type(familyRow.food) == "table" then if type(familyRow.food) == "table" then
for i = 1, table.getn(familyRow.food) do for i = 1, table.getn(familyRow.food) do
local food = MTH_BOOKTAB_FamiliesTrim(familyRow.food[i]) local food = MTH_BOOKTAB_FamiliesTrim(familyRow.food[i])
if food ~= "" then local token = MTH_BOOKTAB_FamiliesSafeLower(food)
if food ~= "" and token ~= "raw fish" and token ~= "raw meat" then
table.insert(dietItems, string.upper(string.sub(food, 1, 1)) .. string.sub(food, 2)) table.insert(dietItems, string.upper(string.sub(food, 1, 1)) .. string.sub(food, 2))
end end
end end
@@ -186,16 +246,22 @@ function MTH_BOOKTAB_BuildFamiliesRows()
return MTH_BOOKTAB_FamiliesSafeLower(a) < MTH_BOOKTAB_FamiliesSafeLower(b) return MTH_BOOKTAB_FamiliesSafeLower(a) < MTH_BOOKTAB_FamiliesSafeLower(b)
end) end)
end end
dietText = table.concat(dietItems, ", ") dietText = table.concat(dietItems, "|")
end end
table.insert(results, { table.insert(results, {
family = tostring(familyName), family = tostring(familyName),
icon = familyRow.icon or nil, icon = familyRow.icon or nil,
named = tonumber(familyRow.named) or 0, named = beastCounts and (tonumber(beastCounts.named) or 0) or 0,
coords = tonumber(familyRow.coords) or 0, coords = beastCounts and (tonumber(beastCounts.coords) or 0) or 0,
abilities = abilities, abilities = abilities,
dietText = dietText, dietText = dietText,
healthStat = healthStat,
healthText = MTH_BOOKTAB_FormatFamilyStat(healthStat),
damageStat = damageStat,
damageText = MTH_BOOKTAB_FormatFamilyStat(damageStat),
armorStat = armorStat,
armorText = MTH_BOOKTAB_FormatFamilyStat(armorStat),
}) })
end end
end end
@@ -253,31 +319,76 @@ function MTH_BOOKTAB_EnsureFamiliesUI()
ui.frame:SetPoint("BOTTOMRIGHT", listParent, "BOTTOMRIGHT", -6, 6) ui.frame:SetPoint("BOTTOMRIGHT", listParent, "BOTTOMRIGHT", -6, 6)
ui.frame:Hide() ui.frame:Hide()
local statsText = getglobal("MTH_BOOK_StatsText")
local statsParent = statsText and statsText:GetParent() or nil
if statsParent then
ui.percentCheckbox = CreateFrame("CheckButton", "MTH_BOOK_FamiliesPercentCheckbox", statsParent, "UICheckButtonTemplate")
ui.percentCheckbox:SetFrameStrata(statsParent:GetFrameStrata())
ui.percentCheckbox:SetFrameLevel((statsParent:GetFrameLevel() or 0) + 20)
ui.percentCheckbox:ClearAllPoints()
if statsText then
ui.percentCheckbox:SetPoint("LEFT", statsText, "RIGHT", 260, 0)
else
ui.percentCheckbox:SetPoint("TOPLEFT", statsParent, "TOPLEFT", 538, -526)
end
ui.percentCheckbox.label = ui.percentCheckbox:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.percentCheckbox.label:SetPoint("LEFT", ui.percentCheckbox, "RIGHT", 4, 0)
ui.percentCheckbox.label:SetTextColor(1.00, 0.82, 0.00)
ui.percentCheckbox.label:SetText("Show modifiers in %")
ui.percentCheckbox:SetScript("OnClick", function()
if not this then return end
MTH_BOOK_STATE.familyStatsPercent = this:GetChecked() == 1
MTH_BOOKTAB_RenderFamiliesList()
end)
ui.percentCheckbox:Hide()
end
ui.headerFamily = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall") ui.headerFamily = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerFamily:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 4, -2) ui.headerFamily:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 4, -2)
ui.headerFamily:SetTextColor(1.00, 0.82, 0.00) ui.headerFamily:SetTextColor(1.00, 0.82, 0.00)
ui.headerFamily:SetText("Family") ui.headerFamily:SetText("Family")
ui.headerNamed = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall") ui.headerNamed = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerNamed:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 122, -2) ui.headerNamed:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 114, -2)
ui.headerNamed:SetTextColor(1.00, 0.82, 0.00) ui.headerNamed:SetTextColor(1.00, 0.82, 0.00)
ui.headerNamed:SetText("Named") ui.headerNamed:SetText("Named")
ui.headerCoords = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall") ui.headerCoords = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerCoords:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 176, -2) ui.headerCoords:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 154, -2)
ui.headerCoords:SetTextColor(1.00, 0.82, 0.00) ui.headerCoords:SetTextColor(1.00, 0.82, 0.00)
ui.headerCoords:SetText("Coords") ui.headerCoords:SetText("Coords")
ui.headerAbilities = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall") ui.headerAbilities = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerAbilities:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 232, -2) ui.headerAbilities:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 196, -2)
ui.headerAbilities:SetTextColor(1.00, 0.82, 0.00) ui.headerAbilities:SetTextColor(1.00, 0.82, 0.00)
ui.headerAbilities:SetText("Abilities") ui.headerAbilities:SetText("Abilities")
ui.headerDiet = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall") ui.headerDiet = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerDiet:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 532, -2) ui.headerDiet:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 496, -2)
ui.headerDiet:SetTextColor(1.00, 0.82, 0.00) ui.headerDiet:SetTextColor(1.00, 0.82, 0.00)
ui.headerDiet:SetText("Diet") ui.headerDiet:SetText("Diet")
ui.headerHP = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerHP:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 602, -2)
ui.headerHP:SetWidth(30)
ui.headerHP:SetJustifyH("CENTER")
ui.headerHP:SetTextColor(1.00, 0.82, 0.00)
ui.headerHP:SetText("HP")
ui.headerDmg = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerDmg:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 636, -2)
ui.headerDmg:SetWidth(36)
ui.headerDmg:SetJustifyH("CENTER")
ui.headerDmg:SetTextColor(1.00, 0.82, 0.00)
ui.headerDmg:SetText("dmg")
ui.headerArm = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerArm:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 674, -2)
ui.headerArm:SetWidth(38)
ui.headerArm:SetJustifyH("CENTER")
ui.headerArm:SetTextColor(1.00, 0.82, 0.00)
ui.headerArm:SetText("Arm")
ui.rows = {} ui.rows = {}
for i = 1, 28 do for i = 1, 28 do
local row = CreateFrame("Frame", nil, ui.frame) local row = CreateFrame("Frame", nil, ui.frame)
@@ -298,24 +409,39 @@ function MTH_BOOKTAB_EnsureFamiliesUI()
row.family = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall") row.family = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.family:SetPoint("LEFT", row, "LEFT", 24, 0) row.family:SetPoint("LEFT", row, "LEFT", 24, 0)
row.family:SetWidth(88) row.family:SetWidth(82)
row.family:SetJustifyH("LEFT") row.family:SetJustifyH("LEFT")
row.named = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall") row.named = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.named:SetPoint("LEFT", row, "LEFT", 122, 0) row.named:SetPoint("LEFT", row, "LEFT", 114, 0)
row.named:SetWidth(52) row.named:SetWidth(38)
row.named:SetJustifyH("LEFT") row.named:SetJustifyH("LEFT")
row.coords = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall") row.coords = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.coords:SetPoint("LEFT", row, "LEFT", 176, 0) row.coords:SetPoint("LEFT", row, "LEFT", 154, 0)
row.coords:SetWidth(52) row.coords:SetWidth(38)
row.coords:SetJustifyH("LEFT") row.coords:SetJustifyH("LEFT")
row.diet = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall") row.diet = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.diet:SetPoint("LEFT", row, "LEFT", 532, 0) row.diet:SetPoint("LEFT", row, "LEFT", 496, 0)
row.diet:SetWidth(112) row.diet:SetWidth(102)
row.diet:SetJustifyH("LEFT") row.diet:SetJustifyH("LEFT")
row.hp = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.hp:SetPoint("LEFT", row, "LEFT", 602, 0)
row.hp:SetWidth(30)
row.hp:SetJustifyH("CENTER")
row.dmg = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.dmg:SetPoint("LEFT", row, "LEFT", 636, 0)
row.dmg:SetWidth(36)
row.dmg:SetJustifyH("CENTER")
row.arm = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.arm:SetPoint("LEFT", row, "LEFT", 674, 0)
row.arm:SetWidth(38)
row.arm:SetJustifyH("CENTER")
row.abilityButtons = {} row.abilityButtons = {}
for b = 1, 16 do for b = 1, 16 do
local button = CreateFrame("Button", nil, row) local button = CreateFrame("Button", nil, row)
@@ -379,9 +505,14 @@ function MTH_BOOKTAB_SetFamiliesUIVisible(visible)
if detailParent then detailParent:Hide() end if detailParent then detailParent:Hide() end
if detailText then detailText:Hide() end if detailText then detailText:Hide() end
if openMapButton then openMapButton:Hide() end if openMapButton then openMapButton:Hide() end
if ui.percentCheckbox then
ui.percentCheckbox:SetChecked(MTH_BOOK_STATE and MTH_BOOK_STATE.familyStatsPercent == true and 1 or nil)
ui.percentCheckbox:Show()
end
ui.frame:Show() ui.frame:Show()
else else
ui.frame:Hide() ui.frame:Hide()
if ui.percentCheckbox then ui.percentCheckbox:Hide() end
if listParent then listParent:Show() end if listParent then listParent:Show() end
if detailParent then detailParent:Show() end if detailParent then detailParent:Show() end
if detailText then detailText:Show() end if detailText then detailText:Show() end
@@ -411,9 +542,12 @@ function MTH_BOOKTAB_RenderFamiliesList()
rowFrame.named:SetText(tostring(row.named or 0)) rowFrame.named:SetText(tostring(row.named or 0))
rowFrame.coords:SetText(tostring(row.coords or 0)) rowFrame.coords:SetText(tostring(row.coords or 0))
rowFrame.diet:SetText(tostring(row.dietText or "-")) rowFrame.diet:SetText(tostring(row.dietText or "-"))
rowFrame.hp:SetText(MTH_BOOKTAB_FormatFamilyStat(row.healthStat))
rowFrame.dmg:SetText(MTH_BOOKTAB_FormatFamilyStat(row.damageStat))
rowFrame.arm:SetText(MTH_BOOKTAB_FormatFamilyStat(row.armorStat))
local offsetX = 232 local offsetX = 196
local abilityMaxRight = 526 local abilityMaxRight = 490
for b = 1, table.getn(rowFrame.abilityButtons) do for b = 1, table.getn(rowFrame.abilityButtons) do
local button = rowFrame.abilityButtons[b] local button = rowFrame.abilityButtons[b]
local ability = row.abilities and row.abilities[b] or nil local ability = row.abilities and row.abilities[b] or nil
+49 -2
View File
@@ -69,8 +69,14 @@ local function MTH_BOOKTAB_GetZoneAndSubzone(zoneId)
local zid = tonumber(zoneId) local zid = tonumber(zoneId)
if not zid then return "-", "-" end if not zid then return "-", "-" end
local zoneName = MTH_BOOK_GetZoneName(zid) local zoneName = MTH_BOOK_GetZoneName(zid)
local row = MTH_DS_Zones and (MTH_DS_Zones[zid] or MTH_DS_Zones[tostring(zid)]) -- Coord zone IDs are WMA; MTH_DS_Zones is keyed by pfQuest area IDs. Translate
if row and row.parent and tonumber(row.parent) and tonumber(row.parent) ~= zid then -- first, or WMA 121 (Feralas) collides with pfQ 121 (Raven Hill -> parent Duskwood).
local pfqId = zid
if type(MTH_MAP_WMA_TO_PFQ) == "table" then
pfqId = MTH_MAP_WMA_TO_PFQ[zid] or zid
end
local row = MTH_DS_Zones and (MTH_DS_Zones[pfqId] or MTH_DS_Zones[tostring(pfqId)])
if row and row.parent and tonumber(row.parent) and tonumber(row.parent) ~= pfqId then
local parentName = MTH_BOOK_GetZoneName(row.parent) local parentName = MTH_BOOK_GetZoneName(row.parent)
if parentName and string.sub(parentName, 1, 5) ~= "Zone " then if parentName and string.sub(parentName, 1, 5) ~= "Zone " then
return parentName, zoneName return parentName, zoneName
@@ -105,6 +111,47 @@ function MTH_BOOKTAB_GetNPCZoneSummary(vendor)
end end
MTH_BOOK_GetNPCZoneSummary = MTH_BOOKTAB_GetNPCZoneSummary MTH_BOOK_GetNPCZoneSummary = MTH_BOOKTAB_GetNPCZoneSummary
-- Zone column text: every distinct zone the NPC sits in, most-specific (smallest
-- map) first, so multi-location NPCs read e.g. "Darnassus, Teldrassil".
function MTH_BOOKTAB_GetNPCZoneDisplay(vendor)
if not vendor or not vendor.coords or table.getn(vendor.coords) == 0 then
return "-"
end
local seen = {}
local list = {}
for i = 1, table.getn(vendor.coords) do
local c = vendor.coords[i]
if c and c[3] then
local zoneName = MTH_BOOK_GetZoneName(c[3])
if zoneName and zoneName ~= "-" and not seen[zoneName] then
seen[zoneName] = true
local area = nil
local size = MTH_DS_MinimapSizes and (MTH_DS_MinimapSizes[tonumber(c[3])] or MTH_DS_MinimapSizes[c[3]])
if type(size) == "table" and tonumber(size[1]) and tonumber(size[2]) then
area = tonumber(size[1]) * tonumber(size[2])
end
table.insert(list, { name = zoneName, area = area, order = i })
end
end
end
local count = table.getn(list)
if count == 0 then return "-" end
-- Smallest map first; zones with no known size keep data order, at the end.
table.sort(list, function(x, y)
if x.area and y.area then
if x.area ~= y.area then return x.area < y.area end
return x.order < y.order
end
if x.area then return true end
if y.area then return false end
return x.order < y.order
end)
local names = {}
for i = 1, count do table.insert(names, list[i].name) end
return table.concat(names, ", ")
end
MTH_BOOK_GetNPCZoneDisplay = MTH_BOOKTAB_GetNPCZoneDisplay
function MTH_BOOK_NPCHasZoneId(vendor, zoneId) function MTH_BOOK_NPCHasZoneId(vendor, zoneId)
local zid = tonumber(zoneId) local zid = tonumber(zoneId)
if not zid or not vendor or not vendor.coords then if not zid or not vendor or not vendor.coords then
+110 -80
View File
@@ -547,7 +547,75 @@ MTH_BOOK_NormalizeStableFoodLink = function(itemId, itemLink, itemName)
return nil return nil
end end
local function MTH_BOOK_FindStableFoodBagIcon(itemId, itemName)
local getContainerNumSlots = (type(getglobal) == "function" and getglobal("GetContainerNumSlots")) or (_G and _G["GetContainerNumSlots"])
local getContainerItemLink = (type(getglobal) == "function" and getglobal("GetContainerItemLink")) or (_G and _G["GetContainerItemLink"])
local getContainerItemInfo = (type(getglobal) == "function" and getglobal("GetContainerItemInfo")) or (_G and _G["GetContainerItemInfo"])
if type(getContainerNumSlots) ~= "function" or type(getContainerItemLink) ~= "function" or type(getContainerItemInfo) ~= "function" then
return nil
end
local wantedItemId = tonumber(itemId)
local wantedName = string.lower(tostring(itemName or ""))
for bag = 0, 4 do
local slotCount = tonumber(getContainerNumSlots(bag)) or 0
for slot = 1, slotCount do
local link = getContainerItemLink(bag, slot)
if link then
local matched = false
if wantedItemId then
local _, _, parsedId = string.find(tostring(link), "item:(%d+)")
if tonumber(parsedId) == wantedItemId then
matched = true
end
end
if not matched and wantedName ~= "" then
local _, _, parsedName = string.find(tostring(link), "|h%[([^%]]+)%]|h")
if parsedName and string.lower(tostring(parsedName)) == wantedName then
matched = true
end
end
if matched then
local bagTexture = getContainerItemInfo(bag, slot)
if bagTexture and bagTexture ~= "" then
if type(bagTexture) == "string" and string.find(bagTexture, "\\", 1, true) then
return bagTexture
end
end
end
end
end
end
return nil
end
local function MTH_BOOK_ResolveStableFoodIcon(itemId, iconHint, itemName, itemLink) local function MTH_BOOK_ResolveStableFoodIcon(itemId, iconHint, itemName, itemLink)
local function resolveTextureCandidate(value)
if value == nil or value == "" then
return nil
end
if type(value) == "number" then
return nil
end
local text = tostring(value)
if text == "" then
return nil
end
if string.find(text, "|Hitem:", 1, true) or string.find(text, "^item:%d+") then
return nil
end
if string.find(text, "^table:", 1, true) then
return nil
end
if string.find(text, "\\", 1, true) then
return text
end
local asNumber = tonumber(text)
if asNumber then
return nil
end
return "Interface\\Icons\\" .. text
end
local linkText = tostring(itemLink or "") local linkText = tostring(itemLink or "")
local linkItemId = nil local linkItemId = nil
if linkText ~= "" then if linkText ~= "" then
@@ -559,104 +627,55 @@ local function MTH_BOOK_ResolveStableFoodIcon(itemId, iconHint, itemName, itemLi
local numericItemId = tonumber(itemId) or linkItemId local numericItemId = tonumber(itemId) or linkItemId
if linkText ~= "" and type(GetItemInfo) == "function" then
local _, _, _, _, _, _, _, _, _, linkTexture = GetItemInfo(linkText)
if linkTexture and linkTexture ~= "" then
if type(linkTexture) == "number" then
return linkTexture
end
if string.find(tostring(linkTexture), "\\", 1, true) then
return tostring(linkTexture)
end
local asNumber = tonumber(tostring(linkTexture))
if asNumber then
return asNumber
end
return "Interface\\Icons\\" .. tostring(linkTexture)
end
end
if type(iconHint) == "number" then
return iconHint
end
local iconHintText = tostring(iconHint or "")
if iconHintText ~= "" then
local numericHint = tonumber(iconHintText)
if numericHint then
return numericHint
end
end
local iconPath = iconHintText
if iconPath ~= "" then
if string.find(iconPath, "\\", 1, true) then
return iconPath
end
return "Interface\\Icons\\" .. iconPath
end
if numericItemId and type(GetItemInfo) == "function" then if numericItemId and type(GetItemInfo) == "function" then
local _, _, _, _, _, _, _, _, _, infoTexture = GetItemInfo(numericItemId) local _, _, _, _, _, _, _, _, _, infoTexture = GetItemInfo(numericItemId)
if infoTexture and infoTexture ~= "" then local resolvedInfoTexture = resolveTextureCandidate(infoTexture)
if type(infoTexture) == "number" then if resolvedInfoTexture then
return infoTexture return resolvedInfoTexture
end
if string.find(tostring(infoTexture), "\\", 1, true) then
return tostring(infoTexture)
end
local asNumber = tonumber(tostring(infoTexture))
if asNumber then
return asNumber
end
return "Interface\\Icons\\" .. tostring(infoTexture)
end end
end end
if numericItemId and type(GetItemIcon) == "function" then if numericItemId and type(GetItemIcon) == "function" then
local directIcon = GetItemIcon(numericItemId) local directIcon = GetItemIcon(numericItemId)
if directIcon and directIcon ~= "" then local resolvedDirectIcon = resolveTextureCandidate(directIcon)
if type(directIcon) == "number" then if resolvedDirectIcon then
return directIcon return resolvedDirectIcon
end end
if string.find(tostring(directIcon), "\\", 1, true) then end
return tostring(directIcon)
end local resolvedHintIcon = resolveTextureCandidate(iconHint)
local asNumber = tonumber(tostring(directIcon)) if resolvedHintIcon then
if asNumber then return resolvedHintIcon
return asNumber end
end
return "Interface\\Icons\\" .. tostring(directIcon) if linkText ~= "" and type(GetItemInfo) == "function" then
local _, _, _, _, _, _, _, _, _, linkTexture = GetItemInfo(linkText)
local resolvedLinkTexture = resolveTextureCandidate(linkTexture)
if resolvedLinkTexture then
return resolvedLinkTexture
end end
end end
if itemName and itemName ~= "" and type(GetItemIcon) == "function" then if itemName and itemName ~= "" and type(GetItemIcon) == "function" then
local namedIcon = GetItemIcon(itemName) local namedIcon = GetItemIcon(itemName)
if namedIcon and namedIcon ~= "" then local resolvedNamedIcon = resolveTextureCandidate(namedIcon)
if type(namedIcon) == "number" then if resolvedNamedIcon then
return namedIcon return resolvedNamedIcon
end
if string.find(tostring(namedIcon), "\\", 1, true) then
return tostring(namedIcon)
end
local asNumber = tonumber(tostring(namedIcon))
if asNumber then
return asNumber
end
return "Interface\\Icons\\" .. tostring(namedIcon)
end end
end end
if numericItemId and type(MTH_DS_Items) == "table" and type(MTH_DS_Items[numericItemId]) == "table" then if numericItemId and type(MTH_DS_Items) == "table" and type(MTH_DS_Items[numericItemId]) == "table" then
local dsIcon = tostring(MTH_DS_Items[numericItemId].icon or "") local resolvedDsIcon = resolveTextureCandidate(MTH_DS_Items[numericItemId].icon)
if dsIcon ~= "" then if resolvedDsIcon then
if string.find(dsIcon, "\\", 1, true) then return resolvedDsIcon
return dsIcon
end
return "Interface\\Icons\\" .. dsIcon
end end
end end
local bagIcon = MTH_BOOK_FindStableFoodBagIcon(numericItemId, itemName)
if bagIcon then
return bagIcon
end
return nil return nil
end end
@@ -1226,6 +1245,17 @@ function MTH_BOOKTAB_RenderStableCards()
local row = slotEntry and slotEntry.row or nil local row = slotEntry and slotEntry.row or nil
MTH_BOOK_DebugTrace("Render card#" .. tostring(i) .. " petId=" .. tostring(petId) .. " hasRow=" .. tostring(type(row) == "table")) MTH_BOOK_DebugTrace("Render card#" .. tostring(i) .. " petId=" .. tostring(petId) .. " hasRow=" .. tostring(type(row) == "table"))
-- Animations: flash the slot when its occupant changes (deposit /
-- withdraw / swap), but never on the first render of this card.
if card._stableInit and card._lastStablePetId ~= petId then
if type(MTH_FX_Flash) == "function" and type(MTH_FX_IsEnabled) == "function"
and MTH_FX_IsEnabled("ui.stable") then
MTH_FX_Flash(card, 1.00, 0.82, 0.20, 0.55)
end
end
card._lastStablePetId = petId
card._stableInit = true
card:ClearAllPoints() card:ClearAllPoints()
card:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 8, y) card:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 8, y)
card:SetPoint("TOPRIGHT", ui.frame, "TOPRIGHT", -8, y) card:SetPoint("TOPRIGHT", ui.frame, "TOPRIGHT", -8, y)
@@ -1566,7 +1596,7 @@ function MTH_BOOKTAB_RenderStableCards()
local abilitiesCount = MTH_BOOK_CountPetAbilitiesMap(row.abilities) local abilitiesCount = MTH_BOOK_CountPetAbilitiesMap(row.abilities)
local abilitiesText = MTH_BOOK_GetStableAbilitySummary(row.abilities) local abilitiesText = MTH_BOOK_GetStableAbilitySummary(row.abilities)
local legacyTameContext = type(row.tameContext) == "table" and row.tameContext or nil local legacyTameContext = type(row.tameContext) == "table" and row.tameContext or nil
local hasRecordedTame = row.tameRecorded == true local hasRecordedTame = type(MTH_PETS_HasVerifiedTameRecord) == "function" and MTH_PETS_HasVerifiedTameRecord(row) or (row.tameRecorded == true)
if not hasRecordedTame and legacyTameContext then if not hasRecordedTame and legacyTameContext then
if legacyTameContext.name or legacyTameContext.zone or legacyTameContext.timestamp then if legacyTameContext.name or legacyTameContext.zone or legacyTameContext.timestamp then
hasRecordedTame = true hasRecordedTame = true
+225
View File
@@ -0,0 +1,225 @@
if type(MTH_HUNTERBOOK_TABS) ~= "table" then MTH_HUNTERBOOK_TABS = {} end
local trainerColumns = {
{ x = 28, width = 150, align = "LEFT" },
{ x = 180, width = 62, align = "LEFT" },
{ x = 244, width = 43, align = "LEFT" },
{ x = 289, width = 80, align = "LEFT" },
{ x = 371, width = 105, align = "LEFT" },
{ x = 478, width = 74, align = "LEFT" },
}
MTH_HUNTERBOOK_TABS.hunterspells = {
headerLabel = "Hunter Spells",
columnLabels = { "Spell", "Rank", "Level", "Price", "Branch", "Status" },
columnLayout = trainerColumns,
}
MTH_HUNTERBOOK_TABS.petspells = {
headerLabel = "Pet Spells",
columnLabels = { "Spell", "Rank", "Level", "Price", "Branch", "Status" },
columnLayout = trainerColumns,
}
function MTH_BOOKTAB_IsTrainerSpellMode(mode)
return mode == "hunterspells" or mode == "petspells"
end
local function MTH_BOOKTAB_GetTrainerKind()
if MTH_BOOK_STATE and MTH_BOOK_STATE.mode == "petspells" then return "pet" end
return "hunter"
end
function MTH_BOOKTAB_FormatTrainerCost(copper)
local value = tonumber(copper) or 0
local gold = math.floor(value / 10000)
value = value - gold * 10000
local silver = math.floor(value / 100)
local bronze = value - silver * 100
local parts = {}
if gold > 0 then table.insert(parts, "|cffffd100" .. tostring(gold) .. "g|r") end
if silver > 0 or gold > 0 then table.insert(parts, "|cffc7c7cf" .. tostring(silver) .. "s|r") end
table.insert(parts, "|cffeda55f" .. tostring(bronze) .. "c|r")
return table.concat(parts, " ")
end
function MTH_BOOKTAB_GetTrainerDisplayCost(copper)
if type(MTH_TrainerPlan_GetPrice) == "function" then
return MTH_TrainerPlan_GetPrice({ trainerCost = copper }, MTH_BOOK_STATE and MTH_BOOK_STATE.trainerHonoredDiscount)
end
return tonumber(copper) or 0
end
function MTH_BOOKTAB_GetTrainerSpellStatus(row)
local kind = MTH_BOOKTAB_GetTrainerKind()
local playerLevel = MTH_BOOK_GetPlayerLevelValue and MTH_BOOK_GetPlayerLevelValue() or nil
local status = type(MTH_TrainerPlan_GetStatus) == "function" and MTH_TrainerPlan_GetStatus(kind, row, playerLevel) or "notyet"
if status == "known" then return "Known", "|cff40ff40" end
if status == "notlearned" then return "Not learned", "|cffffa500" end
return "Not yet learnable", "|cffff4040"
end
function MTH_BOOKTAB_BuildTrainerSpellResults()
local results = {}
local kind = MTH_BOOKTAB_GetTrainerKind()
local plan = type(MTH_TrainerPlan_Build) == "function" and MTH_TrainerPlan_Build(kind, {
playerLevel = MTH_BOOK_GetPlayerLevelValue and MTH_BOOK_GetPlayerLevelValue() or nil,
honored = MTH_BOOK_STATE and MTH_BOOK_STATE.trainerHonoredDiscount,
}) or nil
if type(plan) ~= "table" or type(plan.rows) ~= "table" then return results end
local search = MTH_BOOK_SafeLower(MTH_BOOK_STATE.search or "")
for _, planned in ipairs(plan.rows) do
local row = planned.row
local level = tonumber(row.requiredLevel) or 0
local haystack = tostring(row.name or "") .. " " .. tostring(row.rank or "") .. " " .. tostring(row.section or "") .. " " .. tostring(row.description or "")
if type(row.prerequisites) == "table" then
for _, prerequisite in ipairs(row.prerequisites) do
haystack = haystack .. " " .. tostring(prerequisite)
end
end
haystack = MTH_BOOK_SafeLower(haystack)
if (search == "" or string.find(haystack, search, 1, true) ~= nil)
and (not MTH_BOOK_STATE.minLevel or level >= MTH_BOOK_STATE.minLevel)
and (not MTH_BOOK_STATE.maxLevel or level <= MTH_BOOK_STATE.maxLevel)
and (type(MTH_TrainerPlan_MatchesQuick) ~= "function" or MTH_TrainerPlan_MatchesQuick(planned.status, MTH_BOOK_STATE.quick))
then
table.insert(results, row)
end
end
return results
end
function MTH_BOOKTAB_UpdateTrainerPlanPanel(detail)
local parent = detail and detail:GetParent()
if not parent then return end
if not parent.mthTrainerPlan then
local panel = CreateFrame("Frame", nil, parent)
panel:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, -238)
panel:SetWidth(136)
panel:SetHeight(128)
panel: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 } })
panel:SetBackdropColor(0.06, 0.06, 0.06, 1)
panel:SetBackdropBorderColor(0.28, 0.28, 0.28, 1)
panel.text = panel:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
panel.text:SetPoint("TOPLEFT", panel, "TOPLEFT", 7, -7)
panel.text:SetWidth(122)
panel.text:SetJustifyH("LEFT")
panel.text:SetJustifyV("TOP")
parent.mthTrainerPlan = panel
end
local plan = type(MTH_TrainerPlan_Build) == "function" and MTH_TrainerPlan_Build(MTH_BOOKTAB_GetTrainerKind(), {
playerLevel = MTH_BOOK_GetPlayerLevelValue and MTH_BOOK_GetPlayerLevelValue() or nil,
honored = MTH_BOOK_STATE.trainerHonoredDiscount,
}) or nil
if not plan then return end
local nextLevel = plan.nextLevel.level
local nextLabel = "Next"
if nextLevel then nextLabel = "At level " .. tostring(nextLevel) end
local function formatBlock(label, summary)
return "|cffffffff" .. label .. "|r\n"
.. "|cff4da6ff" .. tostring(summary.count) .. "|r |cff9a9a9aspells|r |cffffffff| |r"
.. MTH_BOOKTAB_FormatTrainerCost(summary.cost)
end
parent.mthTrainerPlan.text:SetText("|cffffd100Training Plan|r\n"
.. formatBlock("Now", plan.now)
.. "\n\n" .. formatBlock(nextLabel, plan.nextLevel)
.. "\n\n" .. formatBlock("Remaining", plan.remaining))
parent.mthTrainerPlan:Show()
end
function MTH_BOOKTAB_GetTrainerSpellTotal()
local catalog = MTH_DS_TrainerCatalog
local rows = catalog and catalog[MTH_BOOKTAB_GetTrainerKind()] or nil
return type(rows) == "table" and table.getn(rows) or 0
end
function MTH_BOOKTAB_TrainerSpellSort(a, b)
local al = tonumber(a and a.requiredLevel) or 0
local bl = tonumber(b and b.requiredLevel) or 0
if al ~= bl then return al < bl end
local an = MTH_BOOK_SafeLower(a and a.name)
local bn = MTH_BOOK_SafeLower(b and b.name)
if an ~= bn then return an < bn end
return MTH_BOOK_SafeLower(a and a.rank) < MTH_BOOK_SafeLower(b and b.rank)
end
function MTH_BOOKTAB_GetTrainerSpellSortKey(row, col)
if col == 1 then return MTH_BOOK_SafeLower(row and row.name) end
if col == 2 then return MTH_BOOK_SafeLower(row and row.rank) end
if col == 3 then return tonumber(row and row.requiredLevel) or 0 end
if col == 4 then return tonumber(row and row.trainerCost) or 0 end
if col == 5 then return MTH_BOOK_SafeLower(row and row.section) end
if col == 6 then
local status = MTH_BOOKTAB_GetTrainerSpellStatus(row)
return MTH_BOOK_SafeLower(status)
end
return nil
end
function MTH_BOOKTAB_GetTrainerSpellRowValues(row)
local requiredLevel = tonumber(row and row.requiredLevel) or 0
local playerLevel = MTH_BOOK_GetPlayerLevelValue and MTH_BOOK_GetPlayerLevelValue() or nil
local levelColor = (playerLevel and playerLevel >= requiredLevel) and "|cff40ff40" or "|cffff4040"
local status, statusColor = MTH_BOOKTAB_GetTrainerSpellStatus(row)
return {
tostring(row and row.name or "-"),
tostring(row and row.rank or "-"),
levelColor .. tostring(row and row.requiredLevel or "-") .. "|r",
MTH_BOOKTAB_FormatTrainerCost(MTH_BOOKTAB_GetTrainerDisplayCost(row and row.trainerCost)),
tostring(row and row.section or "-"),
statusColor .. status .. "|r",
}
end
function MTH_BOOKTAB_UpdateTrainerSpellDetail(detail, selected)
if not MTH_BOOKTAB_IsTrainerSpellMode(MTH_BOOK_STATE and MTH_BOOK_STATE.mode) then return false end
MTH_BOOKTAB_UpdateTrainerPlanPanel(detail)
if not selected then
MTH_BOOK_SetDetailText(detail, "|cFFFFD100Trainer Spells|r\n\nSelect a spell rank to inspect its trainer level, undiscounted cost, and prerequisites.")
return true
end
local lines = {}
table.insert(lines, "|cffffff00" .. tostring(selected.name or "Unknown") .. "|r")
if selected.rank and selected.rank ~= "" then table.insert(lines, "|cffaaaaaa" .. tostring(selected.rank) .. "|r") end
table.insert(lines, "")
table.insert(lines, "|cff9a9a9aBranch:|r |cffffffff" .. tostring(selected.section or "-") .. "|r")
local requiredLevel = tonumber(selected.requiredLevel) or 0
local playerLevel = MTH_BOOK_GetPlayerLevelValue and MTH_BOOK_GetPlayerLevelValue() or nil
local levelColor = (playerLevel and playerLevel >= requiredLevel) and "|cff40ff40" or "|cffff4040"
table.insert(lines, "|cff9a9a9aRequired Level:|r " .. levelColor .. tostring(selected.requiredLevel or "-") .. "|r")
local status, statusColor = MTH_BOOKTAB_GetTrainerSpellStatus(selected)
table.insert(lines, "|cff9a9a9aStatus:|r " .. statusColor .. status .. "|r")
table.insert(lines, "|cff9a9a9aBase Price:|r " .. MTH_BOOKTAB_FormatTrainerCost(selected.trainerCost))
table.insert(lines, "|cff9a9a9aHonored Price:|r " .. MTH_BOOKTAB_FormatTrainerCost(math.floor((tonumber(selected.trainerCost) or 0) * 0.90)))
if type(selected.prerequisites) == "table" and table.getn(selected.prerequisites) > 0 then
table.insert(lines, "")
table.insert(lines, "|cffffd100Prerequisites|r")
for _, prerequisite in ipairs(selected.prerequisites) do
table.insert(lines, " " .. tostring(prerequisite))
end
end
if selected.description and selected.description ~= "" then
table.insert(lines, "")
table.insert(lines, "|cffffd100Description|r")
table.insert(lines, "|cffffffff " .. tostring(selected.description) .. "|r")
end
local detailFrame = detail.mthTrainerDetailParent or detail:GetParent()
if not detail.mthTrainerIcon then
detail.mthTrainerIcon = detail:CreateTexture(nil, "ARTWORK")
detail.mthTrainerIcon:SetWidth(28)
detail.mthTrainerIcon:SetHeight(28)
end
detail.mthTrainerIcon:SetParent(detail)
detail.mthTrainerIcon:ClearAllPoints()
detail.mthTrainerIcon:SetPoint("TOPLEFT", detail, "TOPLEFT", 0, 0)
detail.mthTrainerIcon:SetTexture(MTH_BOOK_ResolveIconPath(selected.icon) or "Interface\\Icons\\INV_Misc_QuestionMark")
detail.mthTrainerIcon:Show()
if detail.SetTextInsets then detail:SetTextInsets(4, 4, 36, 4) end
if not detail.mthTrainerDetailScroll then
detail:ClearAllPoints()
detail:SetPoint("TOPLEFT", detailFrame, "TOPLEFT", 8, -42)
detail:SetWidth(detailFrame:GetWidth() - 16)
end
MTH_BOOK_SetDetailText(detail, table.concat(lines, "\n"))
return true
end
+925 -120
View File
File diff suppressed because it is too large Load Diff
+41 -4
View File
@@ -2,7 +2,7 @@
..\FrameXML\UI.xsd"> ..\FrameXML\UI.xsd">
<Frame name="MTH_BOOK_Template" toplevel="true" frameStrata="DIALOG" movable="true" enableMouse="true" hidden="true" parent="UIParent" virtual="true"> <Frame name="MTH_BOOK_Template" toplevel="true" frameStrata="DIALOG" movable="true" enableMouse="true" hidden="true" parent="UIParent" virtual="true">
<Size> <Size>
<AbsDimension x="760" y="560"/> <AbsDimension x="880" y="560"/>
</Size> </Size>
<Anchors> <Anchors>
<Anchor point="CENTER"/> <Anchor point="CENTER"/>
@@ -57,6 +57,14 @@
</Anchors> </Anchors>
</FontString> </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> </Layer>
</Layers> </Layers>
@@ -120,7 +128,7 @@
</Frame> </Frame>
<Frame name="$parentTabBar"> <Frame name="$parentTabBar">
<Size><AbsDimension x="728" y="24"/></Size> <Size><AbsDimension x="96" y="472"/></Size>
<Anchors> <Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="18" y="-44"/></Offset></Anchor> <Anchor point="TOPLEFT"><Offset><AbsDimension x="18" y="-44"/></Offset></Anchor>
</Anchors> </Anchors>
@@ -180,6 +188,18 @@
<Anchor point="LEFT" relativeTo="$parentSectionAmmoBags" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor> <Anchor point="LEFT" relativeTo="$parentSectionAmmoBags" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
</Anchors> </Anchors>
</Button> </Button>
<Button name="$parentSectionHunterSpells" inherits="TabButtonTemplate" text="Hunter Spells">
<Size><AbsDimension x="82" y="24"/></Size>
<Anchors>
<Anchor point="LEFT" relativeTo="$parentSectionPetHistory" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
</Anchors>
</Button>
<Button name="$parentSectionPetSpells" inherits="TabButtonTemplate" text="Pet Spells">
<Size><AbsDimension x="82" y="24"/></Size>
<Anchors>
<Anchor point="LEFT" relativeTo="$parentSectionHunterSpells" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
</Anchors>
</Button>
<EditBox name="$parentSearch" inherits="InputBoxTemplate"> <EditBox name="$parentSearch" inherits="InputBoxTemplate">
<Size><AbsDimension x="180" y="20"/></Size> <Size><AbsDimension x="180" y="20"/></Size>
@@ -227,6 +247,12 @@
</Anchors> </Anchors>
</CheckButton> </CheckButton>
<CheckButton name="$parentTrainerHonoredDiscount" inherits="UICheckButtonTemplate" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="618" y="-80"/></Offset></Anchor>
</Anchors>
</CheckButton>
<Button name="$parentPetBookScanButton" inherits="UIPanelButtonTemplate" text="Scan from Pet Book" hidden="true"> <Button name="$parentPetBookScanButton" inherits="UIPanelButtonTemplate" text="Scan from Pet Book" hidden="true">
<Size><AbsDimension x="130" y="20"/></Size> <Size><AbsDimension x="130" y="20"/></Size>
<Anchors> <Anchors>
@@ -265,7 +291,7 @@
</Frame> </Frame>
<CheckButton name="$parentPetInZoneOnly" inherits="UICheckButtonTemplate" hidden="true"> <CheckButton name="$parentPetInZoneOnly" inherits="UICheckButtonTemplate" hidden="true">
<Anchors> <Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="488" y="-112"/></Offset></Anchor> <Anchor point="TOPLEFT"><Offset><AbsDimension x="526" y="-106"/></Offset></Anchor>
</Anchors> </Anchors>
</CheckButton> </CheckButton>
<Button name="$parentShowAllOnMapButton" inherits="UIPanelButtonTemplate" text="Show all on map" hidden="true"> <Button name="$parentShowAllOnMapButton" inherits="UIPanelButtonTemplate" text="Show all on map" hidden="true">
@@ -298,8 +324,19 @@
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="232" y="-140"/></Offset></Anchor></Anchors> <Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="232" y="-140"/></Offset></Anchor></Anchors>
</CheckButton> </CheckButton>
<CheckButton name="$parentHideUnknown" inherits="UICheckButtonTemplate"> <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> </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"> <Frame name="$parentListSliderBackdrop">
<Size><AbsDimension x="22" y="322"/></Size> <Size><AbsDimension x="22" y="322"/></Size>
+2
View File
@@ -56,6 +56,8 @@ BINDING_HEADER_ZTrackHeader = MTH_L("BINDING_HEADER_ZTrackHeader", "[MetaHunt] Z
BINDING_HEADER_ZTrapHeader = MTH_L("BINDING_HEADER_ZTrapHeader", "[MetaHunt] ZTrap Buttons") BINDING_HEADER_ZTrapHeader = MTH_L("BINDING_HEADER_ZTrapHeader", "[MetaHunt] ZTrap Buttons")
BINDING_HEADER_ZPetHeader = MTH_L("BINDING_HEADER_ZPetHeader", "[MetaHunt] ZPet Buttons") BINDING_HEADER_ZPetHeader = MTH_L("BINDING_HEADER_ZPetHeader", "[MetaHunt] ZPet Buttons")
BINDING_HEADER_ZAmmoHeader = MTH_L("BINDING_HEADER_ZAmmoHeader", "[MetaHunt] ZAmmo Buttons") BINDING_HEADER_ZAmmoHeader = MTH_L("BINDING_HEADER_ZAmmoHeader", "[MetaHunt] ZAmmo Buttons")
BINDING_HEADER_ExpAmmoHeader = MTH_L("BINDING_HEADER_ExpAmmoHeader", "[MetaHunt] MM Widget")
BINDING_NAME_EXPAMMOACTION = MTH_L("BINDING_NAME_EXPAMMOACTION", "MM Widget Action")
function MTH:GetLocalization(key, default) function MTH:GetLocalization(key, default)
return MTH_L(key, default) return MTH_L(key, default)
+181 -48
View File
@@ -5,6 +5,7 @@ MTH_Map = {
activeSource = "focus", activeSource = "focus",
providers = {}, providers = {},
nodesByZone = {}, nodesByZone = {},
minimapNodesByZone = {},
worldPins = {}, worldPins = {},
minimapPins = {}, minimapPins = {},
zoneNameToId = {}, zoneNameToId = {},
@@ -12,6 +13,7 @@ MTH_Map = {
mapZoneCache = {}, mapZoneCache = {},
focusNodes = {}, focusNodes = {},
pendingZoneOpenId = nil, pendingZoneOpenId = nil,
pendingZoneOpenPfqId = nil,
pendingZoneOpenSourceId = nil, pendingZoneOpenSourceId = nil,
pendingZoneOpenAttempts = 0, pendingZoneOpenAttempts = 0,
pendingZoneOpenNextAttemptAt = 0, pendingZoneOpenNextAttemptAt = 0,
@@ -23,10 +25,37 @@ MTH_Map = {
nodeRevision = 0, nodeRevision = 0,
minimapForceRefreshInterval = 1.0, minimapForceRefreshInterval = 1.0,
verboseMiniReasons = false, verboseMiniReasons = false,
minimapTickActive = 0.20, minimapTickActive = 0.05,
minimapTickIdle = 0.75, minimapTickIdle = 0.75,
} }
-- Capture our PizzaSauce instance for the world map pin drop-in (guarded; may be nil).
local MTH_MAP_Sauce = PizzaSauce
local function MTH_Map_AnimatePin(pin)
if not MTH_MAP_Sauce or not pin then return end
if type(MTH_FX_IsEnabled) == "function" and not MTH_FX_IsEnabled("ui.mappins") then return end
if type(pin.SetAlpha) == "function" then pin:SetAlpha(0) end
-- Bouncy scale "pop": the pin overshoots its size and springs back while
-- fading in. Far more eye-catching than a plain alpha fade.
MTH_MAP_Sauce:Group({
MTH_MAP_Sauce:FadeIn(pin, 0.20, "outQuad", { defer = true }),
MTH_MAP_Sauce:Rubber(pin, 0.35, 0.55, { defer = true }),
})
end
-- Ping a minimap pin that just came into range. Only called for genuinely new
-- nodes (see UpdateMinimap), never every tick, to avoid strobing.
local function MTH_Map_PingMinimapPin(pin)
if not MTH_MAP_Sauce or not pin then return end
if type(MTH_FX_IsEnabled) == "function" and not MTH_FX_IsEnabled("ui.minimapping") then return end
if type(pin.SetAlpha) == "function" then pin:SetAlpha(0) end
-- Springy grow-in so a freshly-discovered node clearly "pops" onto the minimap.
MTH_MAP_Sauce:Group({
MTH_MAP_Sauce:FadeIn(pin, 0.20, "outQuad", { defer = true }),
MTH_MAP_Sauce:Rubber(pin, 0.30, 0.55, { defer = true }),
})
end
local MTH_MAP_MINIMAP_ZOOM = { local MTH_MAP_MINIMAP_ZOOM = {
[0] = { [0] = 300, [1] = 240, [2] = 180, [3] = 120, [4] = 80, [5] = 50 }, [0] = { [0] = 300, [1] = 240, [2] = 180, [3] = 120, [4] = 80, [5] = 50 },
[1] = { [0] = 466 + 2/3, [1] = 400, [2] = 333 + 1/3, [3] = 266 + 2/6, [4] = 200, [5] = 133 + 1/3 }, [1] = { [0] = 466 + 2/3, [1] = 400, [2] = 333 + 1/3, [3] = 266 + 2/6, [4] = 200, [5] = 133 + 1/3 },
@@ -60,11 +89,6 @@ local function MTH_Map_Mod(a, b)
return 0 return 0
end 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) local function MTH_Map_NormalizeName(name)
if not name then return "" end if not name then return "" end
@@ -126,16 +150,54 @@ local function MTH_Map_StripThePrefix(name)
return nil return nil
end end
local function MTH_Map_WmaToPfq(zoneId)
if type(MTH_MAP_WMA_TO_PFQ) == "table" then
return MTH_MAP_WMA_TO_PFQ[zoneId] or zoneId
end
return zoneId
end
local function MTH_Map_PfqToWma(pfqId)
if type(MTH_MAP_PFQ_TO_WMA) == "table" then
return MTH_MAP_PFQ_TO_WMA[pfqId] or pfqId
end
return pfqId
end
local function MTH_Map_GetMinimapSizes()
if pfDB and pfDB["minimap"] and MTH_DS_MinimapSizes then
if not MTH_Map._mergedMinimapSizes then
local merged = {}
for zoneId, size in pairs(pfDB["minimap"]) do
merged[zoneId] = size
end
for zoneId, size in pairs(MTH_DS_MinimapSizes) do
if merged[zoneId] == nil then
merged[zoneId] = size
end
end
MTH_Map._mergedMinimapSizes = merged
end
return MTH_Map._mergedMinimapSizes
elseif pfDB and pfDB["minimap"] then
return pfDB["minimap"]
end
return MTH_DS_MinimapSizes or {}
end
local function MTH_Map_GetZoneNameById(zoneId) local function MTH_Map_GetZoneNameById(zoneId)
local normalizedId = tonumber(zoneId) or zoneId local normalizedId = tonumber(zoneId) or zoneId
if not normalizedId then return nil end if not normalizedId then return nil end
if MTH_DS_ZoneNamesFallback and MTH_DS_ZoneNamesFallback[normalizedId] then -- Translate WMA→pfQ first so WMA IDs don't accidentally hit wrong pfQ entries.
return MTH_DS_ZoneNamesFallback[normalizedId] local pfqId = MTH_Map_WmaToPfq(normalizedId)
if MTH_DS_ZoneNamesFallback and MTH_DS_ZoneNamesFallback[pfqId] then
return MTH_DS_ZoneNamesFallback[pfqId]
end end
if not MTH_DS_Zones then return nil 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 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 return row.names.enUS or row.names[GetLocale()] or row.names.deDE or row.names.frFR
end end
@@ -327,7 +389,6 @@ function MTH_Map:BuildZoneLookup()
for _ in pairs(self.zoneNameNormToId) do for _ in pairs(self.zoneNameNormToId) do
count = count + 1 count = count + 1
end end
MTH_Map_Log("Zone lookup rebuilt: " .. tostring(count) .. " entries")
end end
function MTH_Map:GetMapIDByName(name) function MTH_Map:GetMapIDByName(name)
@@ -363,6 +424,8 @@ end
function MTH_Map:ResolveZoneHierarchy(zoneId) function MTH_Map:ResolveZoneHierarchy(zoneId)
local inputId = tonumber(zoneId) local inputId = tonumber(zoneId)
local pfqInputId = MTH_Map_WmaToPfq(inputId)
local inputIsWma = inputId and pfqInputId ~= inputId or false
local resolved = { local resolved = {
inputId = inputId, inputId = inputId,
zoneId = inputId, zoneId = inputId,
@@ -376,9 +439,9 @@ function MTH_Map:ResolveZoneHierarchy(zoneId)
return resolved return resolved
end end
local sizes = MTH_DS_MinimapSizes or (pfDB and pfDB["minimap"]) or {} local sizes = MTH_Map_GetMinimapSizes()
local visited = {} local visited = {}
local cursor = inputId local cursor = pfqInputId
local depth = 0 local depth = 0
while cursor and not visited[cursor] and depth < 8 do while cursor and not visited[cursor] and depth < 8 do
@@ -389,15 +452,17 @@ function MTH_Map:ResolveZoneHierarchy(zoneId)
end end
local parent = tonumber(row.parent or row.continent) local parent = tonumber(row.parent or row.continent)
local outputCursor = inputIsWma and MTH_Map_PfqToWma(cursor) or cursor
local outputParent = parent and (inputIsWma and MTH_Map_PfqToWma(parent) or parent) or nil
if parent and not resolved.continentId then if parent and not resolved.continentId then
resolved.continentId = parent resolved.continentId = outputParent
end end
if parent and parent > 0 and parent ~= cursor then if parent and parent > 0 and parent ~= cursor then
resolved.subzoneId = resolved.subzoneId or cursor resolved.subzoneId = resolved.subzoneId or outputCursor
resolved.parentId = parent resolved.parentId = outputParent
cursor = parent cursor = parent
resolved.zoneId = cursor resolved.zoneId = inputIsWma and MTH_Map_PfqToWma(cursor) or cursor
depth = depth + 1 depth = depth + 1
else else
break break
@@ -405,11 +470,17 @@ function MTH_Map:ResolveZoneHierarchy(zoneId)
end end
local renderZoneId = resolved.zoneId local renderZoneId = resolved.zoneId
local pfqRenderId = tonumber(cursor or pfqInputId)
local pfqParentId = resolved.parentId and MTH_Map_WmaToPfq(resolved.parentId) or nil
if not sizes[renderZoneId] then if not sizes[renderZoneId] then
if sizes[inputId] then if sizes[inputId] then
renderZoneId = inputId renderZoneId = inputId
elseif resolved.parentId and sizes[resolved.parentId] then elseif resolved.parentId and sizes[resolved.parentId] then
renderZoneId = resolved.parentId renderZoneId = resolved.parentId
elseif pfqRenderId and sizes[pfqRenderId] then
renderZoneId = pfqRenderId
elseif pfqParentId and sizes[pfqParentId] then
renderZoneId = pfqParentId
end end
end end
@@ -418,12 +489,13 @@ function MTH_Map:ResolveZoneHierarchy(zoneId)
return resolved return resolved
end end
function MTH_Map:GetCurrentMapID() function MTH_Map:GetCurrentMapID(space)
local continent = GetCurrentMapContinent and GetCurrentMapContinent() or nil local continent = GetCurrentMapContinent and GetCurrentMapContinent() or nil
local zone = GetCurrentMapZone and GetCurrentMapZone() or nil local zone = GetCurrentMapZone and GetCurrentMapZone() or nil
local zoneName = nil local zoneName = nil
local mapId = nil local mapId = nil
local worldMapShown = WorldMapFrame and WorldMapFrame.IsShown and WorldMapFrame:IsShown() or false local worldMapShown = WorldMapFrame and WorldMapFrame.IsShown and WorldMapFrame:IsShown() or false
local preferPfq = space == "pfq"
if continent and zone and continent > 0 and zone > 0 and GetMapZones then if continent and zone and continent > 0 and zone > 0 and GetMapZones then
if not self.mapZoneCache[continent] then if not self.mapZoneCache[continent] then
@@ -434,6 +506,9 @@ function MTH_Map:GetCurrentMapID()
if mapId then if mapId then
local resolved = self:ResolveZoneHierarchy(mapId) local resolved = self:ResolveZoneHierarchy(mapId)
local normalized = resolved.zoneId or mapId local normalized = resolved.zoneId or mapId
if not preferPfq then
normalized = MTH_Map_PfqToWma(normalized)
end
self.lastMapContext = string.format("continent=%s zone=%s mapZone='%s' real=nil mapId=%s normalized=%s", tostring(continent), tostring(zone), tostring(zoneName), tostring(mapId), tostring(normalized)) 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 return normalized
end end
@@ -449,6 +524,9 @@ function MTH_Map:GetCurrentMapID()
mapId = self:GetMapIDByName(real) mapId = self:GetMapIDByName(real)
local resolved = self:ResolveZoneHierarchy(mapId) local resolved = self:ResolveZoneHierarchy(mapId)
local normalized = resolved.zoneId or mapId local normalized = resolved.zoneId or mapId
if not preferPfq then
normalized = MTH_Map_PfqToWma(normalized)
end
self.lastMapContext = string.format("continent=%s zone=%s mapZone='%s' real='%s' mapId=%s normalized=%s", tostring(continent), tostring(zone), tostring(zoneName), tostring(real), tostring(mapId), tostring(normalized)) 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 return normalized
end end
@@ -458,6 +536,30 @@ function MTH_Map:GetCurrentMapID()
return nil return nil
end end
function MTH_Map:GetCurrentMinimapMapID()
local realZoneName = GetRealZoneText and GetRealZoneText() or nil
if realZoneName and realZoneName ~= "" then
local mapId = self:GetMapIDByName(realZoneName)
if mapId then
return mapId
end
end
local continent = GetCurrentMapContinent and GetCurrentMapContinent() or nil
local zone = GetCurrentMapZone and GetCurrentMapZone() or nil
if continent and zone and continent > 0 and zone > 0 and GetMapZones then
if not self.mapZoneCache[continent] then
self.mapZoneCache[continent] = { GetMapZones(continent) }
end
local zoneName = self.mapZoneCache[continent][zone]
if zoneName and zoneName ~= "" then
return self:GetMapIDByName(zoneName)
end
end
return nil
end
function MTH_Map:RegisterProvider(key, provider) function MTH_Map:RegisterProvider(key, provider)
if not key or type(key) ~= "string" then return false end if not key or type(key) ~= "string" then return false end
if type(provider) ~= "table" or type(provider.buildNodes) ~= "function" then return false end if type(provider) ~= "table" or type(provider.buildNodes) ~= "function" then return false end
@@ -467,6 +569,7 @@ end
local function MTH_Map_ClearPendingZoneOpen() local function MTH_Map_ClearPendingZoneOpen()
MTH_Map.pendingZoneOpenId = nil MTH_Map.pendingZoneOpenId = nil
MTH_Map.pendingZoneOpenPfqId = nil
MTH_Map.pendingZoneOpenSourceId = nil MTH_Map.pendingZoneOpenSourceId = nil
MTH_Map.pendingZoneOpenAttempts = 0 MTH_Map.pendingZoneOpenAttempts = 0
MTH_Map.pendingZoneOpenNextAttemptAt = 0 MTH_Map.pendingZoneOpenNextAttemptAt = 0
@@ -532,8 +635,12 @@ end
function MTH_Map:OpenWorldMapForZone(zoneId) function MTH_Map:OpenWorldMapForZone(zoneId)
local zid = tonumber(zoneId) local zid = tonumber(zoneId)
if not zid then return false end if not zid then return false end
local resolved = self:ResolveZoneHierarchy(zid) -- Translate WMA ID → pfQ so game map APIs (pfMap, SetMapZoom) work correctly.
local targetZoneId = tonumber(resolved.zoneId or zid) or zid 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 ToggleWorldMap then
if not WorldMapFrame or not WorldMapFrame:IsShown() then if not WorldMapFrame or not WorldMapFrame:IsShown() then
@@ -546,34 +653,39 @@ function MTH_Map:OpenWorldMapForZone(zoneId)
if pfMap and pfMap.SetMapByID then if pfMap and pfMap.SetMapByID then
pfMap:SetMapByID(targetZoneId) pfMap:SetMapByID(targetZoneId)
local currentAfterPF = self:GetCurrentMapID() local currentAfterPF = self:GetCurrentMapID()
if tonumber(currentAfterPF) == tonumber(targetZoneId) then if tonumber(currentAfterPF) == tonumber(targetWmaId) then
MTH_Map_ClearPendingZoneOpen() MTH_Map_ClearPendingZoneOpen()
return true return true
end end
end end
local zoomApplied = MTH_Map_ApplyWorldMapZone(targetZoneId, zid) local zoomApplied = MTH_Map_ApplyWorldMapZone(zid, zid)
if zoomApplied then if zoomApplied then
local currentAfterZoom = self:GetCurrentMapID() local currentAfterZoom = self:GetCurrentMapID()
if tonumber(currentAfterZoom) == tonumber(targetZoneId) then if tonumber(currentAfterZoom) == tonumber(targetWmaId) then
MTH_Map_ClearPendingZoneOpen() MTH_Map_ClearPendingZoneOpen()
return true return true
end end
end end
local nowMapId = self:GetCurrentMapID() local nowMapId = self:GetCurrentMapID()
if tonumber(nowMapId) == tonumber(targetZoneId) then if tonumber(nowMapId) == tonumber(targetWmaId) then
MTH_Map_ClearPendingZoneOpen() MTH_Map_ClearPendingZoneOpen()
return true return true
end end
self.pendingZoneOpenId = targetZoneId self.pendingZoneOpenId = targetWmaId
self.pendingZoneOpenSourceId = zid self.pendingZoneOpenPfqId = targetZoneId
self.pendingZoneOpenSourceId = pfqZid
self.pendingZoneOpenAttempts = 8 self.pendingZoneOpenAttempts = 8
self.pendingZoneOpenNextAttemptAt = (GetTime and GetTime() or 0) + 0.25 self.pendingZoneOpenNextAttemptAt = (GetTime and GetTime() or 0) + 0.25
return true return true
end end
function MTH_Map:GetFamilyColor(family)
return MTH_Map_GetFamilyColor(family)
end
function MTH_Map:FocusBeast(beastId, beast) function MTH_Map:FocusBeast(beastId, beast)
local id = tonumber(beastId) local id = tonumber(beastId)
if not id then return false end if not id then return false end
@@ -854,6 +966,7 @@ function MTH_Map:HideMinimapPins()
for i = 1, table.getn(self.minimapPins) do for i = 1, table.getn(self.minimapPins) do
if self.minimapPins[i] then self.minimapPins[i]:Hide() end if self.minimapPins[i] then self.minimapPins[i]:Hide() end
end end
self._miniShownSet = nil
end end
function MTH_Map:HideAllPins() function MTH_Map:HideAllPins()
@@ -864,6 +977,7 @@ end
function MTH_Map:RebuildNodes() function MTH_Map:RebuildNodes()
self:BuildZoneLookup() self:BuildZoneLookup()
self.nodesByZone = {} self.nodesByZone = {}
self.minimapNodesByZone = {}
self.nodeRevision = (tonumber(self.nodeRevision) or 0) + 1 self.nodeRevision = (tonumber(self.nodeRevision) or 0) + 1
self._lastWorldRenderKey = nil self._lastWorldRenderKey = nil
self._lastMiniState = nil self._lastMiniState = nil
@@ -877,20 +991,26 @@ function MTH_Map:RebuildNodes()
local node = allNodes[i] local node = allNodes[i]
if node and node.zoneId then if node and node.zoneId then
local sourceZoneId = tonumber(node.zoneId) local sourceZoneId = tonumber(node.zoneId)
local resolved = self:ResolveZoneHierarchy(sourceZoneId) local worldResolved = self:ResolveZoneHierarchy(sourceZoneId)
local zoneId = tonumber(resolved.zoneId or sourceZoneId) local worldZoneId = tonumber(worldResolved.zoneId or sourceZoneId)
if zoneId then local pfqSourceZoneId = MTH_Map_WmaToPfq(sourceZoneId)
local minimapZoneId = tonumber(pfqSourceZoneId)
if worldZoneId then
node.sourceZoneId = sourceZoneId node.sourceZoneId = sourceZoneId
node.zoneId = zoneId node.zoneId = worldZoneId
node.parentZoneId = resolved.parentId node.parentZoneId = worldResolved.parentId
node.subzoneId = resolved.subzoneId node.subzoneId = worldResolved.subzoneId
if not self.nodesByZone[zoneId] then self.nodesByZone[zoneId] = {} end node.minimapZoneId = minimapZoneId
table.insert(self.nodesByZone[zoneId], node) if not self.nodesByZone[worldZoneId] then self.nodesByZone[worldZoneId] = {} end
table.insert(self.nodesByZone[worldZoneId], node)
if minimapZoneId then
if not self.minimapNodesByZone[minimapZoneId] then self.minimapNodesByZone[minimapZoneId] = {} end
table.insert(self.minimapNodesByZone[minimapZoneId], node)
end
end end
end end
end end
MTH_Map_Log("Rebuilt nodes for source '" .. tostring(self.activeSource) .. "'")
end end
function MTH_Map:UpdateWorldMap() function MTH_Map:UpdateWorldMap()
@@ -949,6 +1069,7 @@ function MTH_Map:UpdateWorldMap()
pin:ClearAllPoints() pin:ClearAllPoints()
pin:SetPoint("CENTER", worldButton, "TOPLEFT", x, -y) pin:SetPoint("CENTER", worldButton, "TOPLEFT", x, -y)
pin:Show() pin:Show()
MTH_Map_AnimatePin(pin)
end end
end end
@@ -975,8 +1096,8 @@ function MTH_Map:UpdateMinimap()
return return
end end
local mapId = self:GetCurrentMapID() local mapId = self:GetCurrentMinimapMapID()
local nodes = mapId and self.nodesByZone[mapId] or nil local nodes = mapId and self.minimapNodesByZone[mapId] or nil
if not mapId or not nodes then if not mapId or not nodes then
self.lastMiniReason = "no-mapid-or-nodes" self.lastMiniReason = "no-mapid-or-nodes"
self._lastMiniState = nil self._lastMiniState = nil
@@ -1009,16 +1130,18 @@ function MTH_Map:UpdateMinimap()
and miniState.zoom == zoom and miniState.zoom == zoom
and miniState.nodeRevision == (self.nodeRevision or 0) and miniState.nodeRevision == (self.nodeRevision or 0)
then then
local dx = math.abs((xPlayer or 0) - (miniState.xPlayer or 0)) if xPlayer == miniState.xPlayer and yPlayer == miniState.yPlayer and now < (miniState.nextForceAt or 0) then
local dy = math.abs((yPlayer or 0) - (miniState.yPlayer or 0)) self.lastMiniReason = self.verboseMiniReasons and "throttled-still" or "throttle"
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 return
end end
end end
local minimapSizes = MTH_DS_MinimapSizes or (pfDB and pfDB["minimap"]) local minimapSizes = MTH_Map_GetMinimapSizes()
local mapSize = minimapSizes and minimapSizes[mapId] local mapSize = minimapSizes and minimapSizes[mapId]
if not mapSize then
local wmaId = MTH_Map_PfqToWma(mapId)
mapSize = wmaId and minimapSizes and minimapSizes[wmaId]
end
if not mapSize then if not mapSize then
self.lastMiniReason = self.verboseMiniReasons and "missing-minimap-size" or "missing-size" self.lastMiniReason = self.verboseMiniReasons and "missing-minimap-size" or "missing-size"
self._lastMiniState = nil self._lastMiniState = nil
@@ -1057,6 +1180,8 @@ function MTH_Map:UpdateMinimap()
local xDraw = Minimap:GetWidth() / xScale / 100 local xDraw = Minimap:GetWidth() / xScale / 100
local yDraw = Minimap:GetHeight() / yScale / 100 local yDraw = Minimap:GetHeight() / yScale / 100
local shown = 0 local shown = 0
local prevShown = self._miniShownSet or {}
local nowShown = {}
for i = 1, table.getn(nodes) do for i = 1, table.getn(nodes) do
local node = nodes[i] local node = nodes[i]
@@ -1078,9 +1203,16 @@ function MTH_Map:UpdateMinimap()
pin:ClearAllPoints() pin:ClearAllPoints()
pin:SetPoint("CENTER", Minimap, "CENTER", xPos, -yPos) pin:SetPoint("CENTER", Minimap, "CENTER", xPos, -yPos)
pin:Show() pin:Show()
local nodeKey = tostring(node.name or "") .. "@" .. tostring(node.x) .. "," .. tostring(node.y)
nowShown[nodeKey] = true
if not prevShown[nodeKey] then
MTH_Map_PingMinimapPin(pin)
end
end end
end end
end end
self._miniShownSet = nowShown
for i = shown + 1, table.getn(self.minimapPins) do for i = shown + 1, table.getn(self.minimapPins) do
if self.minimapPins[i] then self.minimapPins[i]:Hide() end if self.minimapPins[i] then self.minimapPins[i]:Hide() end
@@ -1227,7 +1359,6 @@ function MTH_Map:RegisterDefaultProviders()
end end
if unresolvedCount > 0 then if unresolvedCount > 0 then
MTH_Map_Log("drops source: " .. tostring(unresolvedCount) .. " drop-creature links have no loaded coordinates (sample IDs: " .. table.concat(unresolvedSamples, ", ") .. ")")
end end
return nodes return nodes
@@ -1253,6 +1384,9 @@ function MTH_Map:Init()
self.controller:SetScript("OnEvent", function(frame, eventName) self.controller:SetScript("OnEvent", function(frame, eventName)
eventName = eventName or event eventName = eventName or event
if eventName == "WORLD_MAP_UPDATE" and MTH_Map._syncingMapContext then
return
end
if eventName == "PLAYER_ENTERING_WORLD" or eventName == "ZONE_CHANGED" or eventName == "ZONE_CHANGED_NEW_AREA" or eventName == "MINIMAP_ZONE_CHANGED" then if eventName == "PLAYER_ENTERING_WORLD" or eventName == "ZONE_CHANGED" or eventName == "ZONE_CHANGED_NEW_AREA" or eventName == "MINIMAP_ZONE_CHANGED" then
if SetMapToCurrentZone and (not WorldMapFrame or not WorldMapFrame:IsShown()) then if SetMapToCurrentZone and (not WorldMapFrame or not WorldMapFrame:IsShown()) then
SetMapToCurrentZone() SetMapToCurrentZone()
@@ -1274,9 +1408,9 @@ function MTH_Map:Init()
if WorldMapFrame and WorldMapFrame:IsShown() then if WorldMapFrame and WorldMapFrame:IsShown() then
if pfMap and pfMap.SetMapByID then if pfMap and pfMap.SetMapByID then
pfMap:SetMapByID(MTH_Map.pendingZoneOpenId) pfMap:SetMapByID(MTH_Map.pendingZoneOpenPfqId or MTH_Map.pendingZoneOpenId)
end 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() local nowMapId = MTH_Map:GetCurrentMapID()
if tonumber(nowMapId) == tonumber(MTH_Map.pendingZoneOpenId) then if tonumber(nowMapId) == tonumber(MTH_Map.pendingZoneOpenId) then
MTH_Map_ClearPendingZoneOpen() MTH_Map_ClearPendingZoneOpen()
@@ -1295,15 +1429,15 @@ function MTH_Map:Init()
end end
if MTH_Map.enabled and MTH_Map.showMinimap then if MTH_Map.enabled and MTH_Map.showMinimap then
local mapId = MTH_Map:GetCurrentMapID() local mapId = MTH_Map:GetCurrentMinimapMapID()
if not mapId and (frame._mthMapRecoverAt or 0) <= now then if not mapId and (frame._mthMapRecoverAt or 0) <= now then
frame._mthMapRecoverAt = now + 2.0 frame._mthMapRecoverAt = now + 2.0
if SetMapToCurrentZone and (not WorldMapFrame or not WorldMapFrame:IsShown()) then if SetMapToCurrentZone and (not WorldMapFrame or not WorldMapFrame:IsShown()) then
SetMapToCurrentZone() SetMapToCurrentZone()
mapId = MTH_Map:GetCurrentMapID() mapId = MTH_Map:GetCurrentMinimapMapID()
end end
end end
local nodes = mapId and MTH_Map.nodesByZone and MTH_Map.nodesByZone[mapId] or nil local nodes = mapId and MTH_Map.minimapNodesByZone and MTH_Map.minimapNodesByZone[mapId] or nil
local hasNodes = nodes and table.getn(nodes) > 0 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)) frame._mthTick = now + (hasNodes and (tonumber(MTH_Map.minimapTickActive) or 0.20) or (tonumber(MTH_Map.minimapTickIdle) or 0.75))
if hasNodes then if hasNodes then
@@ -1318,5 +1452,4 @@ function MTH_Map:Init()
end) end)
end end
MTH_Map_Log("Map system initialized")
end end
+26 -3
View File
@@ -14,6 +14,19 @@ if not MTH_MinimapButton then
MTH_MinimapButton = {} MTH_MinimapButton = {}
end end
-- Capture our PizzaSauce instance for the click pulse (guarded; may be nil).
local MTH_MB_Sauce = PizzaSauce
local function MTH_MB_AnimateClick()
if not MTH_MB_Sauce then return end
if type(MTH_FX_IsEnabled) == "function" and not MTH_FX_IsEnabled("ui.minimap") then return end
local icon = MTH_MinimapButton and MTH_MinimapButton.icon
if not icon or type(icon.SetWidth) ~= "function" then return end
-- Reset to base size first so rapid clicks can't make the icon drift in size.
icon:SetWidth(20)
icon:SetHeight(20)
MTH_MB_Sauce:Pulse(icon, 1.35, 0.28)
end
local function MTH_MB_L(key, default) local function MTH_MB_L(key, default)
if MTH and MTH.GetLocalization then if MTH and MTH.GetLocalization then
return MTH:GetLocalization(key, default) return MTH:GetLocalization(key, default)
@@ -25,10 +38,19 @@ local function MTH_MB_GetStore()
if not MTH_CharSavedVariables then if not MTH_CharSavedVariables then
MTH_CharSavedVariables = {} MTH_CharSavedVariables = {}
end end
if type(MTH_CharSavedVariables.minimapButton) ~= "table" then local store
MTH_CharSavedVariables.minimapButton = {} if MTH and MTH.GetModuleCharSavedVariables then
store = MTH:GetModuleCharSavedVariables("minimapbutton")
end
if type(store) ~= "table" then
if type(MTH_CharSavedVariables.modules) ~= "table" then
MTH_CharSavedVariables.modules = {}
end
if type(MTH_CharSavedVariables.modules.minimapbutton) ~= "table" then
MTH_CharSavedVariables.modules.minimapbutton = {}
end
store = MTH_CharSavedVariables.modules.minimapbutton
end end
local store = MTH_CharSavedVariables.minimapButton
if store.angle == nil then if store.angle == nil then
store.angle = 220 store.angle = 220
end end
@@ -155,6 +177,7 @@ function MTH_MinimapButton:Initialize()
button:SetScript("OnClick", function() button:SetScript("OnClick", function()
local mouseButton = arg1 local mouseButton = arg1
MTH_MB_AnimateClick()
if mouseButton == "RightButton" then if mouseButton == "RightButton" then
if type(MTH_OpenOptions) == "function" then if type(MTH_OpenOptions) == "function" then
MTH_OpenOptions("General") MTH_OpenOptions("General")
+135
View File
@@ -0,0 +1,135 @@
-- MetaHunt: "Animations" options panel.
-- Global enable/disable plus a per-animation checkbox for each effect, grouped
-- into sections (Pet, Book, ...). The animation registry lives in fx-celebrate.lua
-- (MTH_FX_ANIM_SECTIONS / MTH_FX_ANIM_DEFS) so both the effects and this panel
-- stay in sync.
local MTH_ANIM_READY = MTH_OptionsRequire and MTH_OptionsRequire("options-animations", {
"MTH_GetFrame",
"MTH_ClearContainer",
"MTH_CreateCheckbox",
})
local MTH_ANIM_LINK = "https://codeberg.org/Pizzahawaii/PizzaSauce"
local MTH_ANIM_CTRL = {}
local function MTH_ANIM_L(key, default)
if MTH and MTH.GetLocalization then
return MTH:GetLocalization(key, default)
end
return default or key
end
local function MTH_ANIM_InsertLink(url)
local link = tostring(url or "")
if link == "" then return end
if type(MTH_InsertLinkToChat) == "function" and MTH_InsertLinkToChat(link) then
if MTH and MTH.Print then
MTH:Print(MTH_ANIM_L("ANIM_LINK_INSERTED", "Link inserted in chat: ") .. link)
end
return
end
if MTH and MTH.Print then
MTH:Print(MTH_ANIM_L("ANIM_LINK_FALLBACK", "Link: ") .. link)
end
end
local function MTH_ANIM_UpdateChildStates()
local master = MTH_FX_AnimationsEnabled()
local i = 1
while i <= table.getn(MTH_ANIM_CTRL) do
local cb = MTH_ANIM_CTRL[i]
if cb then
cb:SetChecked(MTH_FX_GetFlag(cb.mthKey))
if master then
cb:Enable()
else
cb:Disable()
end
end
i = i + 1
end
end
function MTH_SetupAnimationsOptions()
if not MTH_ANIM_READY then
return
end
if type(MTH_FX_AnimationsEnabled) ~= "function" then
return
end
local container = MTH_GetFrame("MetaHuntOptionsAnimations")
if not container then
return
end
local panel = MTH_Layout.Panel(container)
panel:Title(MTH_ANIM_L("ANIM_TITLE", "Animations"))
panel:Note(MTH_ANIM_L("ANIM_CREDIT",
"Animations are powered by PizzaSauce, a WoW 1.12 animation library kindly made and shared by Pizzahawaii. Thank you!"),
{ color = MTH_Layout.Theme.colText, lines = 2 })
panel:Link("MetaHuntOptionsAnimLink", MTH_ANIM_LINK, function() MTH_ANIM_InsertLink(MTH_ANIM_LINK) end)
MTH_ANIM_CTRL = {}
-- Global master toggle (full width, above the columns).
panel:TopCheckbox("MetaHuntOptionsAnimMaster",
MTH_ANIM_L("ANIM_ENABLE_ALL", "Enable animations"), {
checked = MTH_FX_AnimationsEnabled,
onClick = function()
local cb = getglobal("MetaHuntOptionsAnimMaster")
MTH_FX_SetAnimationsEnabled(cb:GetChecked())
MTH_ANIM_UpdateChildStates()
end,
})
-- Keep the two columns balanced at standard Options-window sizes. The
-- Interface section is intentionally paired with Pet instead of stacked
-- below Leveling and Combat, where it would run below the window.
local s = 1
while MTH_FX_ANIM_SECTIONS and s <= table.getn(MTH_FX_ANIM_SECTIONS) do
local sec = MTH_FX_ANIM_SECTIONS[s]
-- Collect this section's defs in order.
local secDefs = {}
local d = 1
while MTH_FX_ANIM_DEFS and d <= table.getn(MTH_FX_ANIM_DEFS) do
local def = MTH_FX_ANIM_DEFS[d]
if def.section == sec.id then
table.insert(secDefs, def)
end
d = d + 1
end
local count = table.getn(secDefs)
if count > 0 then
local side = (sec.id == "pet" or sec.id == "ui") and "left" or "right"
local box = panel:Box(side,
"MetaHuntOptionsAnimBox_" .. tostring(sec.id),
MTH_ANIM_L(sec.titleKey, sec.title))
local di = 1
while di <= count do
local def = secDefs[di]
local cbName = "MetaHuntOptionsAnim_" .. string.gsub(def.key, "%.", "_")
local cb = box:Checkbox(cbName, MTH_ANIM_L(def.labelKey, def.label), {
checked = function() return MTH_FX_GetFlag(def.key) end,
tooltip = (def.tooltip and def.tooltip ~= "") and MTH_ANIM_L(def.tooltipKey, def.tooltip) or nil,
})
if cb then
cb.mthKey = def.key
cb:SetScript("OnClick", function()
MTH_FX_SetFlag(cb.mthKey, cb:GetChecked())
end)
table.insert(MTH_ANIM_CTRL, cb)
end
di = di + 1
end
end
s = s + 1
end
panel:Finish()
MTH_ANIM_UpdateChildStates()
end
+70 -95
View File
@@ -589,7 +589,9 @@ local function MTH_AB_RefreshOptionsUI()
end end
end end
local function MTH_AB_CreateRow(container, subtype, index, xBase, yOffset) local function MTH_AB_CreateRow(box, subtype, index)
local f, y = box:Row(38)
local padX = box:PadX()
local row = { local row = {
subtype = subtype, subtype = subtype,
index = index, index = index,
@@ -597,14 +599,15 @@ local function MTH_AB_CreateRow(container, subtype, index, xBase, yOffset)
} }
local nameBase = "MetaHuntOptionsAutoBuy_" .. subtype .. "_" .. tostring(index) local nameBase = "MetaHuntOptionsAutoBuy_" .. subtype .. "_" .. tostring(index)
row.check = MTH_CreateCheckbox(container, nameBase .. "Check", "", yOffset, xBase + MTH_AB_LAYOUT.ROW_CHECK_X) row.check = MTH_CreateCheckbox(f, nameBase .. "Check", "", y, padX)
row.label = container:CreateFontString(nameBase .. "Label", "ARTWORK", "GameFontNormalSmall") row.label = getglobal(nameBase .. "Label") or f:CreateFontString(nameBase .. "Label", "ARTWORK", "GameFontNormalSmall")
row.label:SetPoint("TOPLEFT", container, "TOPLEFT", xBase + MTH_AB_LAYOUT.ROW_CHECK_LABEL_X, yOffset - 5) row.label:ClearAllPoints()
row.label:SetPoint("TOPLEFT", f, "TOPLEFT", padX + 27, y - 5)
row.label:SetText("On?") row.label:SetText("On?")
row.dropdown = CreateFrame("Frame", nameBase .. "Drop", container, "UIDropDownMenuTemplate") row.dropdown = getglobal(nameBase .. "Drop") or CreateFrame("Frame", nameBase .. "Drop", f, "UIDropDownMenuTemplate")
row.dropdown:ClearAllPoints() row.dropdown:ClearAllPoints()
row.dropdown:SetPoint("TOPLEFT", container, "TOPLEFT", xBase + MTH_AB_LAYOUT.ROW_DROPDOWN_X, yOffset + 3) row.dropdown:SetPoint("TOPLEFT", f, "TOPLEFT", padX + 33, y + 3)
if UIDropDownMenu_SetWidth then if UIDropDownMenu_SetWidth then
UIDropDownMenu_SetWidth(MTH_AB_LAYOUT.ROW_DROPDOWN_WIDTH, row.dropdown) UIDropDownMenu_SetWidth(MTH_AB_LAYOUT.ROW_DROPDOWN_WIDTH, row.dropdown)
end end
@@ -640,7 +643,8 @@ local function MTH_AB_CreateRow(container, subtype, index, xBase, yOffset)
end) end)
end end
row.qty = CreateFrame("EditBox", nameBase .. "Qty", container, "InputBoxTemplate") row.qty = getglobal(nameBase .. "Qty") or CreateFrame("EditBox", nameBase .. "Qty", f, "InputBoxTemplate")
row.qty:ClearAllPoints()
local dropButton = getglobal(nameBase .. "DropButton") local dropButton = getglobal(nameBase .. "DropButton")
if dropButton then if dropButton then
row.qty:SetPoint("LEFT", dropButton, "RIGHT", MTH_AB_LAYOUT.ROW_QTY_GAP, -1) row.qty:SetPoint("LEFT", dropButton, "RIGHT", MTH_AB_LAYOUT.ROW_QTY_GAP, -1)
@@ -652,7 +656,8 @@ local function MTH_AB_CreateRow(container, subtype, index, xBase, yOffset)
row.qty:SetNumeric(true) row.qty:SetNumeric(true)
row.qty:SetAutoFocus(false) row.qty:SetAutoFocus(false)
row.suffix = container:CreateFontString(nameBase .. "Suffix", "ARTWORK", "GameFontNormalSmall") row.suffix = getglobal(nameBase .. "Suffix") or f:CreateFontString(nameBase .. "Suffix", "ARTWORK", "GameFontNormalSmall")
row.suffix:ClearAllPoints()
row.suffix:SetPoint("LEFT", row.qty, "RIGHT", MTH_AB_LAYOUT.ROW_SUFFIX_GAP, 0) row.suffix:SetPoint("LEFT", row.qty, "RIGHT", MTH_AB_LAYOUT.ROW_SUFFIX_GAP, 0)
row.suffix:SetText("Stacks") row.suffix:SetText("Stacks")
@@ -687,32 +692,18 @@ local function MTH_AB_BuildUI(container)
return return
end end
MTH_ClearContainer(container) local panel = MTH_Layout.Panel(container)
MTH_AB_STATE.container = container MTH_AB_STATE.container = container
MTH_AB_STATE.built = true MTH_AB_STATE.built = true
local controls = MTH_AB_STATE.controls local controls = MTH_AB_STATE.controls
controls.title = container:CreateFontString("MetaHuntOptionsAutoBuyTitle", "ARTWORK", "GameFontHighlight") panel:Title("Auto Buy")
if controls.title then panel:Note("Configure automatic buying rules that trigger when you open a vendor. Quantity is how many STACKS of the item you want in your bags after purchase.",
if controls.title.SetPoint then controls.title:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -10) end { lines = 2, color = MTH_Layout.Theme.colText })
if controls.title.SetText then controls.title:SetText("Auto Buy") end
end
controls.body = container:CreateFontString("MetaHuntOptionsAutoBuyBody", "ARTWORK", "GameFontNormalSmall") controls.moduleEnabled = panel:TopCheckbox("MetaHuntOptionsAutoBuyModuleEnabled", "Enable Auto Buy module", {
if controls.body then onClick = function()
if controls.body.SetPoint then controls.body:SetPoint("TOPLEFT", container, "TOPLEFT", 176, -30) end
if controls.body.SetWidth then controls.body:SetWidth(400) end
if controls.body.SetJustifyH then controls.body:SetJustifyH("LEFT") end
if controls.body.SetTextColor then controls.body:SetTextColor(1, 1, 1) end
if controls.body.SetText then
controls.body:SetText("Configure automatic buying rules that triggers when you open a vendor.\nThe quantity refers to how many STACKS of the item you want to have in your bags after your purchase")
end
end
controls.moduleEnabled = MTH_CreateCheckbox(container, "MetaHuntOptionsAutoBuyModuleEnabled", "Enable Auto Buy module", -30)
if controls.moduleEnabled then
controls.moduleEnabled:SetScript("OnClick", function()
if not this or MTH_AB_STATE.syncing then return end if not this or MTH_AB_STATE.syncing then return end
if MTH and MTH.SetModuleEnabled then if MTH and MTH.SetModuleEnabled then
local ok, err = MTH:SetModuleEnabled("autobuy", MTH_AB_IsChecked(this)) local ok, err = MTH:SetModuleEnabled("autobuy", MTH_AB_IsChecked(this))
@@ -721,70 +712,44 @@ local function MTH_AB_BuildUI(container)
end end
end end
MTH_AB_RefreshOptionsUI() MTH_AB_RefreshOptionsUI()
end) end,
end })
controls.projectilesEnabled = MTH_CreateCheckbox(container, "MetaHuntOptionsAutoBuyProjectilesEnabled", "Enable for Projectiles", -74) controls.projectilesEnabled = panel:TopCheckbox("MetaHuntOptionsAutoBuyProjectilesEnabled", "Enable for Projectiles (arrows & bullets)", {
if controls.projectilesEnabled then onClick = function()
controls.projectilesEnabled:SetScript("OnClick", function()
if not this or MTH_AB_STATE.syncing then return end if not this or MTH_AB_STATE.syncing then return end
local store = MTH_AB_EnsureConfig() local store = MTH_AB_EnsureConfig()
store.projectiles.enabled = MTH_AB_IsChecked(this) store.projectiles.enabled = MTH_AB_IsChecked(this)
MTH_AB_RefreshOptionsUI() MTH_AB_RefreshOptionsUI()
end) end,
end })
controls.sectionArrowTitle = container:CreateFontString("MetaHuntOptionsAutoBuyArrowsTitle", "ARTWORK", "GameFontHighlight")
if controls.sectionArrowTitle then
if controls.sectionArrowTitle.SetPoint then
controls.sectionArrowTitle:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_AB_LAYOUT.LEFT_SECTION_X, MTH_AB_LAYOUT.SECTION_TITLE_Y)
end
if controls.sectionArrowTitle.SetText then controls.sectionArrowTitle:SetText("Arrows") end
end
controls.sectionBulletTitle = container:CreateFontString("MetaHuntOptionsAutoBuyBulletsTitle", "ARTWORK", "GameFontHighlight")
if controls.sectionBulletTitle then
if controls.sectionBulletTitle.SetPoint then
controls.sectionBulletTitle:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_AB_LAYOUT.RIGHT_SECTION_X, MTH_AB_LAYOUT.SECTION_TITLE_Y)
end
if controls.sectionBulletTitle.SetText then controls.sectionBulletTitle:SetText("Bullets") end
end
-- ===== LEFT: Arrows / RIGHT: Bullets (3 rules each) =====
local arrowBox = panel:Box("left", "MetaHuntOptionsAutoBuyArrowsBox", "Arrows")
for i = 1, 3 do for i = 1, 3 do
local rowY = MTH_AB_LAYOUT.SECTION_ROWS_Y0 - ((i - 1) * MTH_AB_LAYOUT.ROW_STEP) controls.rows.arrow[i] = MTH_AB_CreateRow(arrowBox, "arrow", i)
controls.rows.arrow[i] = MTH_AB_CreateRow(container, "arrow", i, MTH_AB_LAYOUT.LEFT_SECTION_X, rowY)
controls.rows.bullet[i] = MTH_AB_CreateRow(container, "bullet", i, MTH_AB_LAYOUT.RIGHT_SECTION_X, rowY)
end end
controls.petFoodTitle = container:CreateFontString("MetaHuntOptionsAutoBuyPetFoodTitle", "ARTWORK", "GameFontHighlight") local bulletBox = panel:Box("right", "MetaHuntOptionsAutoBuyBulletsBox", "Bullets")
if controls.petFoodTitle then for i = 1, 3 do
controls.petFoodTitle:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_AB_LAYOUT.LEFT_SECTION_X, -300) controls.rows.bullet[i] = MTH_AB_CreateRow(bulletBox, "bullet", i)
controls.petFoodTitle:SetText("Pet Food")
end end
controls.petFoodEnabled = MTH_CreateCheckbox(container, "MetaHuntOptionsAutoBuyPetFoodEnabled", "Enable for Pet Food", -322, MTH_AB_LAYOUT.LEFT_SECTION_X) -- ===== Pet Food =====
if controls.petFoodEnabled then local petBox = panel:Box(panel:ShorterSide(), "MetaHuntOptionsAutoBuyPetFoodBox", "Pet Food")
controls.petFoodEnabled:SetScript("OnClick", function()
controls.petFoodEnabled = petBox:Checkbox("MetaHuntOptionsAutoBuyPetFoodEnabled", "Enable for Pet Food", {
onClick = function()
if not this or MTH_AB_STATE.syncing then return end if not this or MTH_AB_STATE.syncing then return end
local store = MTH_AB_EnsureConfig() local store = MTH_AB_EnsureConfig()
store.petFood.enabled = MTH_AB_IsChecked(this) store.petFood.enabled = MTH_AB_IsChecked(this)
MTH_AB_RefreshOptionsUI() MTH_AB_RefreshOptionsUI()
end) end,
end })
controls.petFoodQtyLabel = container:CreateFontString("MetaHuntOptionsAutoBuyPetFoodQtyLabel", "ARTWORK", "GameFontNormalSmall") controls.petFoodQty = petBox:EditRow("MetaHuntOptionsAutoBuyPetFoodQtyLabel", "Quantity:",
if controls.petFoodQtyLabel then "MetaHuntOptionsAutoBuyPetFoodQty", 40, { numeric = true, height = 20 })
controls.petFoodQtyLabel:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_AB_LAYOUT.LEFT_SECTION_X + 28, -350)
controls.petFoodQtyLabel:SetText("Quantity :")
end
controls.petFoodQty = CreateFrame("EditBox", "MetaHuntOptionsAutoBuyPetFoodQty", container, "InputBoxTemplate")
if controls.petFoodQty then if controls.petFoodQty then
controls.petFoodQty:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_AB_LAYOUT.LEFT_SECTION_X + 95, -347)
controls.petFoodQty:SetWidth(40)
controls.petFoodQty:SetHeight(20)
controls.petFoodQty:SetNumeric(true)
controls.petFoodQty:SetAutoFocus(false)
controls.petFoodQty:SetScript("OnEnterPressed", function() controls.petFoodQty:SetScript("OnEnterPressed", function()
if not this then return end if not this then return end
local store = MTH_AB_EnsureConfig() local store = MTH_AB_EnsureConfig()
@@ -798,43 +763,52 @@ local function MTH_AB_BuildUI(container)
MTH_AB_SetPetFoodStacks(store, this:GetText()) MTH_AB_SetPetFoodStacks(store, this:GetText())
MTH_AB_RefreshOptionsUI() MTH_AB_RefreshOptionsUI()
end) end)
local suffix = getglobal("MetaHuntOptionsAutoBuyPetFoodQtySuffix")
if not suffix then
suffix = petBox:Frame():CreateFontString("MetaHuntOptionsAutoBuyPetFoodQtySuffix", "ARTWORK", "GameFontNormalSmall")
end
suffix:ClearAllPoints()
suffix:SetPoint("LEFT", controls.petFoodQty, "RIGHT", 8, 0)
suffix:SetText("stacks")
controls.petFoodQtySuffix = suffix
end end
controls.petFoodQtySuffix = container:CreateFontString("MetaHuntOptionsAutoBuyPetFoodQtySuffix", "ARTWORK", "GameFontNormalSmall") controls.petFoodScopeCurrent = petBox:Checkbox("MetaHuntOptionsAutoBuyPetFoodScopeCurrent", "Buy only for my current pet", {
if controls.petFoodQtySuffix then onClick = function()
controls.petFoodQtySuffix:SetPoint("LEFT", controls.petFoodQty, "RIGHT", 8, 0)
controls.petFoodQtySuffix:SetText("stacks")
end
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()
if not this or MTH_AB_STATE.syncing then return end if not this or MTH_AB_STATE.syncing then return end
local store = MTH_AB_EnsureConfig() local store = MTH_AB_EnsureConfig()
store.petFood.scope = "current" store.petFood.scope = "current"
MTH_AB_RefreshOptionsUI() MTH_AB_RefreshOptionsUI()
end) end,
end })
controls.petFoodScopeAll = MTH_CreateCheckbox(container, "MetaHuntOptionsAutoBuyPetFoodScopeAll", "Buy for all my pets", -402, MTH_AB_LAYOUT.LEFT_SECTION_X + 28) controls.petFoodScopeAll = petBox:Checkbox("MetaHuntOptionsAutoBuyPetFoodScopeAll", "Buy for all my pets", {
if controls.petFoodScopeAll then onClick = function()
controls.petFoodScopeAll:SetScript("OnClick", function()
if not this or MTH_AB_STATE.syncing then return end if not this or MTH_AB_STATE.syncing then return end
local store = MTH_AB_EnsureConfig() local store = MTH_AB_EnsureConfig()
store.petFood.scope = "all" store.petFood.scope = "all"
MTH_AB_RefreshOptionsUI() MTH_AB_RefreshOptionsUI()
end) end,
end })
controls.petFoodPetLines = {} controls.petFoodPetLines = {}
for i = 1, 5 do for i = 1, 5 do
local rowY = -434 - ((i - 1) * 22) local f, y = petBox:Row(22)
local icon = container:CreateTexture("MetaHuntOptionsAutoBuyPetFoodPetIcon" .. tostring(i), "ARTWORK") local padX = petBox:PadX()
local icon = getglobal("MetaHuntOptionsAutoBuyPetFoodPetIcon" .. tostring(i))
if not icon then
icon = f:CreateTexture("MetaHuntOptionsAutoBuyPetFoodPetIcon" .. tostring(i), "ARTWORK")
end
icon:SetWidth(16) icon:SetWidth(16)
icon:SetHeight(16) icon:SetHeight(16)
icon:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_AB_LAYOUT.LEFT_SECTION_X + 28, rowY) icon:ClearAllPoints()
icon:SetPoint("TOPLEFT", f, "TOPLEFT", padX + 4, y)
icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark") icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
local text = container:CreateFontString("MetaHuntOptionsAutoBuyPetFoodPetText" .. tostring(i), "ARTWORK", "GameFontNormalSmall") local text = getglobal("MetaHuntOptionsAutoBuyPetFoodPetText" .. tostring(i))
if not text then
text = f:CreateFontString("MetaHuntOptionsAutoBuyPetFoodPetText" .. tostring(i), "ARTWORK", "GameFontNormalSmall")
end
text:ClearAllPoints()
text:SetPoint("LEFT", icon, "RIGHT", 6, 0) text:SetPoint("LEFT", icon, "RIGHT", 6, 0)
text:SetJustifyH("LEFT") text:SetJustifyH("LEFT")
text:SetTextColor(1, 1, 1) text:SetTextColor(1, 1, 1)
@@ -842,6 +816,7 @@ local function MTH_AB_BuildUI(container)
controls.petFoodPetLines[i] = { icon = icon, text = text } controls.petFoodPetLines[i] = { icon = icon, text = text }
end end
panel:Finish()
end end
function MTH_SetupAutoBuyOptions() function MTH_SetupAutoBuyOptions()
+86 -166
View File
@@ -16,26 +16,30 @@ local function MTH_AQ_EnsureStore()
if type(MTH_CharSavedVariables) ~= "table" then if type(MTH_CharSavedVariables) ~= "table" then
MTH_CharSavedVariables = {} MTH_CharSavedVariables = {}
end end
if type(MTH_CharSavedVariables.autoquest) ~= "table" then local store = nil
MTH_CharSavedVariables.autoquest = {} if MTH and MTH.GetModuleCharSavedVariables then
store = MTH:GetModuleCharSavedVariables("autoquest")
end end
local store = MTH_CharSavedVariables.autoquest if type(store) ~= "table" then
if type(MTH_CharSavedVariables.questautomation) == "table" then if type(MTH_CharSavedVariables.modules) ~= "table" then
local legacy = MTH_CharSavedVariables.questautomation MTH_CharSavedVariables.modules = {}
if store.scorpokDrazial == nil and legacy.scorpokDrazial ~= nil then
store.scorpokDrazial = legacy.scorpokDrazial and true or false
end end
if store.scorpokTooltip == nil and legacy.scorpokTooltip ~= nil then if type(MTH_CharSavedVariables.modules.autoquest) ~= "table" then
store.scorpokTooltip = legacy.scorpokTooltip and true or false MTH_CharSavedVariables.modules.autoquest = {}
end end
store = MTH_CharSavedVariables.modules.autoquest
end end
if store.scorpokDrazial == nil then if type(store.settings) ~= "table" then
store.scorpokDrazial = false store.settings = {}
end end
if store.arrowsForSissies == nil then local settings = store.settings
store.arrowsForSissies = false if settings.scorpokDrazial == nil then
settings.scorpokDrazial = false
end end
return store if settings.arrowsForSissies == nil then
settings.arrowsForSissies = false
end
return settings
end end
function MTH_SetupAutoQuestOptions() function MTH_SetupAutoQuestOptions()
@@ -44,19 +48,10 @@ function MTH_SetupAutoQuestOptions()
local container = MTH_GetFrame("MetaHuntOptionsAutoQuest") local container = MTH_GetFrame("MetaHuntOptionsAutoQuest")
if not container then return end if not container then return end
MTH_ClearContainer(container) local store = MTH_AQ_EnsureStore()
if store.scorpokTooltip == nil then
local title = container:CreateFontString("MetaHuntOptionsAutoQuestTitle", "ARTWORK", "GameFontHighlight") store.scorpokTooltip = false
title:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -10) end
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 local moduleEnabled = true
if MTH and MTH.GetModule then if MTH and MTH.GetModule then
@@ -69,10 +64,14 @@ function MTH_SetupAutoQuestOptions()
elseif MTH and MTH.IsModuleEnabled then elseif MTH and MTH.IsModuleEnabled then
moduleEnabled = MTH:IsModuleEnabled("autoquest", false) and true or false moduleEnabled = MTH:IsModuleEnabled("autoquest", false) and true or false
end end
local moduleToggle = MTH_CreateCheckbox(container, "MetaHuntOptionsAutoQuestModuleToggle", "Enable Auto Quest module", -70, 20)
if moduleToggle then local panel = MTH_Layout.Panel(container)
moduleToggle:SetChecked(moduleEnabled and true or false) panel:Title("Auto Quest")
moduleToggle:SetScript("OnClick", function() panel:Note("Automation for specific quest interactions.")
panel:TopCheckbox("MetaHuntOptionsAutoQuestModuleToggle", "Enable Auto Quest module", {
checked = moduleEnabled,
onClick = function()
if not this then return end if not this then return end
local enabled = MTH_AQ_IsChecked(this) local enabled = MTH_AQ_IsChecked(this)
if MTH and MTH.SetModuleEnabled then if MTH and MTH.SetModuleEnabled then
@@ -84,145 +83,66 @@ function MTH_SetupAutoQuestOptions()
local module = MTH and MTH.GetModule and MTH:GetModule("autoquest") or nil 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 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) this:SetChecked(actual and true or false)
end) end,
end })
local store = MTH_AQ_EnsureStore() -- Salt of the Scorpok (left column)
if store.scorpokTooltip == nil then local scorpok = panel:Box("left", "MetaHuntAutoQuestScorpokBox", "Salt of the Scorpok")
store.scorpokTooltip = false scorpok:Checkbox("MetaHuntOptionsAutoQuestScorpokToggle",
end "Enable SHIFT-rightclick for Bloodmage Drazial", {
checked = store.scorpokDrazial and true or false,
local sectionWidth = (container:GetWidth() or 600) - 40 onClick = function()
if sectionWidth < 360 then local enabled = MTH_AQ_IsChecked(this)
sectionWidth = 360 store.scorpokDrazial = enabled and true or false
end if MTH and MTH.GetModuleCharSavedVariables then
local moduleStore = MTH:GetModuleCharSavedVariables("autoquest")
local function ensureSection(name, sectionTitle, yOffset, height) if type(moduleStore) == "table" then
local section = getglobal(name) moduleStore.scorpokDrazial = store.scorpokDrazial and true or false
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 end
local module = MTH and MTH.GetModule and MTH:GetModule("autoquest") end
if module and module.SetScorpokDrazialEnabled then local module = MTH and MTH.GetModule and MTH:GetModule("autoquest")
module:SetScorpokDrazialEnabled(enabled) if module and module.SetScorpokDrazialEnabled then
module:SetScorpokDrazialEnabled(enabled)
end
end,
})
scorpok:Checkbox("MetaHuntOptionsAutoQuestScorpokTooltipToggle",
"Enhance Tooltip on Drazial and related beasts (requires Tooltips module enabled)", {
checked = store.scorpokTooltip and true or false,
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 end,
})
local tooltipToggle = ensureCheckbox( -- Arrows Are For Sissies (right column)
scorpokSection, local sissies = panel:Box("right", "MetaHuntAutoQuestSissiesBox", "Arrows Are For Sissies")
"MetaHuntOptionsAutoQuestScorpokTooltipToggle", sissies:Note("MetaHuntOptionsAutoQuestSissiesNote",
"Enhance Tooltip on Drazial and related beasts (requires Tooltips module enabled)", "Note that you should not activate this if you are using LazyPig", { lines = 2 })
-42, sissies:Checkbox("MetaHuntOptionsAutoQuestSissiesToggle",
store.scorpokTooltip and true or false "Enable SHIFT-rightclick for Artilleryman Sheldonore", {
) checked = store.arrowsForSissies and true or false,
if tooltipToggle then onClick = function()
tooltipToggle:SetScript("OnClick", function() local enabled = MTH_AQ_IsChecked(this)
local enabled = MTH_AQ_IsChecked(this) store.arrowsForSissies = enabled and true or false
store.scorpokTooltip = enabled and true or false if MTH and MTH.GetModuleCharSavedVariables then
if MTH and MTH.GetModuleCharSavedVariables then local moduleStore = MTH:GetModuleCharSavedVariables("autoquest")
local moduleStore = MTH:GetModuleCharSavedVariables("autoquest") if type(moduleStore) == "table" then
if type(moduleStore) == "table" then moduleStore.arrowsForSissies = store.arrowsForSissies and true or false
moduleStore.scorpokTooltip = store.scorpokTooltip and true or false
end
end end
end) end
end local module = MTH and MTH.GetModule and MTH:GetModule("autoquest")
end if module and module.SetArrowsForSissiesEnabled then
module:SetArrowsForSissiesEnabled(enabled)
end
end,
})
local sissiesSection = ensureSection("MetaHuntAutoQuestSissiesBox", "Arrows Are For Sissies", -255, 108) panel:Finish()
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 end
+145 -178
View File
@@ -252,8 +252,14 @@ local function MTH_CHRON_ApplyLiveProfile(profile)
end end
engine.profile = profile engine.profile = profile
if engine.SetCandyBarGroupGrowth then if engine.SetCandyBarGroupGrowth and engine.SetCandyBarGroupPoint and engine.anchor then
engine:SetCandyBarGroupGrowth("MTHChronometer", profile.growup and true or false) local growup = profile.growup and true or false
if growup then
engine:SetCandyBarGroupPoint("MTHChronometer", "BOTTOM", engine.anchor, "TOP", 0, 0)
else
engine:SetCandyBarGroupPoint("MTHChronometer", "TOP", engine.anchor, "BOTTOM", 0, 0)
end
engine:SetCandyBarGroupGrowth("MTHChronometer", growup)
end end
if engine.SetCandyBarGroupVerticalSpacing then if engine.SetCandyBarGroupVerticalSpacing then
engine:SetCandyBarGroupVerticalSpacing("MTHChronometer", profile.spacing or 0) engine:SetCandyBarGroupVerticalSpacing("MTHChronometer", profile.spacing or 0)
@@ -352,25 +358,14 @@ local function MTH_CHRON_NormalizeSection(tabKey)
return "general" return "general"
end end
local function MTH_CHRON_RenderTimerSection(container, profile, title, items, bucket, opts) local function MTH_CHRON_RenderTimerBox(box, profile, items, bucket, prefix)
opts = opts or {} prefix = tostring(prefix or "Main")
local timerX = opts.x or 20
local timerY = opts.y or -84
local prefix = tostring(opts.prefix or "Main")
MTH_CHRON_MakeTitle(container, "MetaHuntOptionsChronometerTimersHeader" .. prefix, title, timerX, timerY)
timerY = timerY - 24
if table.getn(items) == 0 then if table.getn(items) == 0 then
MTH_CHRON_MakeSmall(container, "MetaHuntOptionsChronometerSectionEmpty" .. prefix, "(none)", timerX + 8, timerY) box:Text("MetaHuntOptionsChronometerSectionEmpty" .. prefix, "(none)")
return return
end end
for i = 1, table.getn(items) do for i = 1, table.getn(items) do
if timerY < -560 then
MTH_CHRON_MakeSmall(container, "MetaHuntOptionsChronometerOverflow" .. prefix, "...more", timerX + 8, timerY)
break
end
local row = items[i] local row = items[i]
local timerName = row local timerName = row
local label = row local label = row
@@ -381,11 +376,9 @@ local function MTH_CHRON_RenderTimerSection(container, profile, title, items, bu
timerName = tostring(timerName or "") timerName = tostring(timerName or "")
label = tostring(label or timerName) label = tostring(label or timerName)
local controlName = "MetaHuntOptionsChronometerTimer" .. prefix .. tostring(bucket) .. tostring(i) local controlName = "MetaHuntOptionsChronometerTimer" .. prefix .. tostring(bucket) .. tostring(i)
local check = MTH_CreateCheckbox(container, controlName, label, timerY, timerX) box:Checkbox(controlName, label, {
if check then checked = profile.disabledSpells[bucket][timerName] == nil,
check:SetChecked(profile.disabledSpells[bucket][timerName] == nil and true or false) onClick = function()
check:SetScript("OnClick", function()
if not this then return end
local currentlyDisabled = profile.disabledSpells[bucket][timerName] ~= nil local currentlyDisabled = profile.disabledSpells[bucket][timerName] ~= nil
if currentlyDisabled then if currentlyDisabled then
profile.disabledSpells[bucket][timerName] = nil profile.disabledSpells[bucket][timerName] = nil
@@ -396,9 +389,8 @@ local function MTH_CHRON_RenderTimerSection(container, profile, title, items, bu
end end
MTH_CHRON_ApplyLiveProfile(profile) MTH_CHRON_ApplyLiveProfile(profile)
end) end,
end })
timerY = timerY - 20
end end
end end
@@ -455,210 +447,185 @@ function MTH_SetupChronometerOptions(tabKey)
local container = MTH_GetFrame("MetaHuntOptionsChronometer") local container = MTH_GetFrame("MetaHuntOptionsChronometer")
if not container then return end if not container then return end
MTH_ClearContainer(container)
MTH_CHRON_STATE.controls = {} MTH_CHRON_STATE.controls = {}
local profile = MTH_CHRON_GetProfile() local profile = MTH_CHRON_GetProfile()
local _, class = UnitClass("player") local _, class = UnitClass("player")
class = class or "HUNTER" class = class or "HUNTER"
MTH_CHRON_MakeTitle(container, "MetaHuntOptionsChronometerTitle", "Chronometer", 20, -10) local panel = MTH_Layout.Panel(container)
panel:Title("Chronometer")
local moduleCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerEnabled", "Enable Chronometer module", -54) local moduleEnabled = false
if moduleCheck then do
local module = MTH and MTH:GetModule("chronometer") local module = MTH and MTH:GetModule("chronometer")
moduleCheck:SetChecked(module and module.enabled and true or false) moduleEnabled = module and module.enabled and true or false
moduleCheck:SetScript("OnClick", function() end
if not this then return end panel:TopCheckbox("MetaHuntOptionsChronometerEnabled", "Enable Chronometer module", {
local enabled = MTH_CHRON_IsChecked(this) checked = moduleEnabled,
onClick = function()
local enabled = this:GetChecked() == 1
if MTH and MTH.SetModuleEnabled then if MTH and MTH.SetModuleEnabled then
MTH:SetModuleEnabled("chronometer", enabled) MTH:SetModuleEnabled("chronometer", enabled)
end end
end) end,
end })
if section == "general" then if section == "general" then
MTH_CHRON_MakeTitle(container, "MetaHuntOptionsChronometerGeneralHeader", "General", 20, -84) local genBox = panel:Box("left", "MetaHuntOptionsChronometerGeneralBox", "General")
genBox:Slider("MetaHuntOptionsChronometerGhost", {
local ghostSlider = MTH_CreateSlider(container, "MetaHuntOptionsChronometerGhost", "Ghost Duration", 0, 30, 1, -112) label = "Ghost Duration", min = 0, max = 30, step = 1,
if ghostSlider then value = tonumber(profile.ghost) or 0,
ghostSlider:SetValue(tonumber(profile.ghost) or 0) onChange = function(value)
ghostSlider.onChange = function(value)
profile.ghost = tonumber(value) or 0 profile.ghost = tonumber(value) or 0
MTH_CHRON_ApplyLiveProfile(profile) MTH_CHRON_ApplyLiveProfile(profile)
end end,
end })
genBox:Checkbox("MetaHuntOptionsChronometerFadeOnKill", "Fade on kill", {
local killCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerFadeOnKill", "Fade on kill", -154) checked = profile.fadeonkill and true or false,
if killCheck then onClick = function() profile.fadeonkill = this:GetChecked() == 1 end,
killCheck:SetChecked(profile.fadeonkill and true or false) })
killCheck:SetScript("OnClick", function() genBox:Checkbox("MetaHuntOptionsChronometerFadeOnFade", "Fade on aura fade", {
if not this then return end checked = profile.fadeonfade and true or false,
profile.fadeonkill = MTH_CHRON_IsChecked(this) onClick = function() profile.fadeonfade = this:GetChecked() == 1 end,
end) })
end genBox:Checkbox("MetaHuntOptionsChronometerSelfBars", "Show self bars", {
checked = profile.selfbars and true or false,
local fadeCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerFadeOnFade", "Fade on aura fade", -179) onClick = function() profile.selfbars = this:GetChecked() == 1 end,
if fadeCheck then })
fadeCheck:SetChecked(profile.fadeonfade and true or false) genBox:Checkbox("MetaHuntOptionsChronometerOnlySelf", "Only self target", {
fadeCheck:SetScript("OnClick", function() checked = profile.onlyself and true or false,
if not this then return end onClick = function() profile.onlyself = this:GetChecked() == 1 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()
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()
if not this then return end
profile.onlyself = MTH_CHRON_IsChecked(this)
end)
end
elseif section == "bar" then elseif section == "bar" then
MTH_CHRON_MakeTitle(container, "MetaHuntOptionsChronometerBarHeader", "Bars", 20, -84) local ctrlBox = panel:Box("left", "MetaHuntOptionsChronometerControlsBox", "Controls")
ctrlBox:Buttons({
local runTestButton = MTH_CreateActionButton(container, "MetaHuntOptionsChronometerRunTest", "Run Test", 20, -112, 130, 22, function() { name = "MetaHuntOptionsChronometerRunTest", label = "Run Test", width = 120,
local engine = MTH_ChronometerHunter onClick = function()
if engine and engine.RunTest then local engine = MTH_ChronometerHunter
engine:RunTest() if engine and engine.RunTest then engine:RunTest() end
end end },
end) { name = "MetaHuntOptionsChronometerAnchor", label = "Toggle Anchor", width = 120,
local anchorButton = MTH_CreateActionButton(container, "MetaHuntOptionsChronometerAnchor", "Toggle Anchor", 165, -112, 130, 22, function() onClick = function()
local engine = MTH_ChronometerHunter local engine = MTH_ChronometerHunter
if engine and engine.ToggleAnchor then if engine and engine.ToggleAnchor then engine:ToggleAnchor() end
engine:ToggleAnchor() end },
end })
end)
if runTestButton then runTestButton:Show() end
if anchorButton then anchorButton:Show() end
local anchorX = tonumber(profile.barposition and profile.barposition.x) or 0 local anchorX = tonumber(profile.barposition and profile.barposition.x) or 0
local anchorY = tonumber(profile.barposition and profile.barposition.y) or 0 local anchorY = tonumber(profile.barposition and profile.barposition.y) or 0
MTH_CHRON_MakeSmall(container, "MetaHuntOptionsChronometerAnchorPos", "Anchor: X " .. tostring(anchorX) .. " Y " .. tostring(anchorY), 20, -142) ctrlBox:Note("MetaHuntOptionsChronometerAnchorPos",
"Anchor: X " .. tostring(anchorX) .. " Y " .. tostring(anchorY))
local widthSlider = MTH_CreateSlider(container, "MetaHuntOptionsChronometerBarWidth", "Bar Width", 80, 320, 1, -180) local dimBox = panel:Box("left", "MetaHuntOptionsChronometerDimBox", "Dimensions")
if widthSlider then dimBox:Slider("MetaHuntOptionsChronometerBarWidth", {
widthSlider:SetValue(tonumber(profile.barwidth) or 220) label = "Bar Width", min = 80, max = 320, step = 1,
widthSlider.onChange = function(value) value = tonumber(profile.barwidth) or 220,
onChange = function(value)
profile.barwidth = tonumber(value) or 220 profile.barwidth = tonumber(value) or 220
MTH_CHRON_ApplyLiveProfile(profile) MTH_CHRON_ApplyLiveProfile(profile)
end end,
end })
dimBox:Slider("MetaHuntOptionsChronometerBarScale", {
local scaleSlider = MTH_CreateSlider(container, "MetaHuntOptionsChronometerBarScale", "Bar Scale", 0.5, 1.5, 0.1, -222) label = "Bar Scale", min = 0.5, max = 1.5, step = 0.1,
if scaleSlider then value = tonumber(profile.barscale) or 1,
scaleSlider:SetValue(tonumber(profile.barscale) or 1) format = function(v) return string.format("%.1f", v) end,
scaleSlider.onChange = function(value) onChange = function(value)
profile.barscale = tonumber(value) or 1 profile.barscale = tonumber(value) or 1
MTH_CHRON_ApplyLiveProfile(profile) MTH_CHRON_ApplyLiveProfile(profile)
end end,
end })
dimBox:Slider("MetaHuntOptionsChronometerBarHeight", {
local heightSlider = MTH_CreateSlider(container, "MetaHuntOptionsChronometerBarHeight", "Bar Height", 8, 30, 1, -264) label = "Bar Height", min = 8, max = 30, step = 1,
if heightSlider then value = tonumber(profile.barheight) or 16,
heightSlider:SetValue(tonumber(profile.barheight) or 16) onChange = function(value)
heightSlider.onChange = function(value)
profile.barheight = tonumber(value) or 16 profile.barheight = tonumber(value) or 16
MTH_CHRON_ApplyLiveProfile(profile) MTH_CHRON_ApplyLiveProfile(profile)
end end,
end })
dimBox:Slider("MetaHuntOptionsChronometerBarSpacing", {
local spacingSlider = MTH_CreateSlider(container, "MetaHuntOptionsChronometerBarSpacing", "Bar Spacing", 0, 15, 1, -306) label = "Bar Spacing", min = 0, max = 15, step = 1,
if spacingSlider then value = tonumber(profile.spacing) or 0,
spacingSlider:SetValue(tonumber(profile.spacing) or 0) onChange = function(value)
spacingSlider.onChange = function(value)
profile.spacing = tonumber(value) or 0 profile.spacing = tonumber(value) or 0
MTH_CHRON_ApplyLiveProfile(profile) MTH_CHRON_ApplyLiveProfile(profile)
end end,
end })
dimBox:Slider("MetaHuntOptionsChronometerTextSize", {
local textSizeSlider = MTH_CreateSlider(container, "MetaHuntOptionsChronometerTextSize", "Text Size", 8, 20, 1, -348) label = "Text Size", min = 8, max = 20, step = 1,
if textSizeSlider then value = tonumber(profile.textsize) or 10,
textSizeSlider:SetValue(tonumber(profile.textsize) or 10) onChange = function(value)
textSizeSlider.onChange = function(value)
profile.textsize = tonumber(value) or 10 profile.textsize = tonumber(value) or 10
MTH_CHRON_ApplyLiveProfile(profile) MTH_CHRON_ApplyLiveProfile(profile)
end end,
end })
MTH_CHRON_MakeSmall(container, "MetaHuntOptionsChronometerTextPatternLabel", "Bar Text ($s spell, $t target)", 20, -390) local textBox = panel:Box("right", "MetaHuntOptionsChronometerTextBox", "Text & Growth")
MTH_CHRON_MakeInput(container, "MetaHuntOptionsChronometerTextPattern", 20, -408, 220, 20, profile.text or "$t", function(newText) textBox:Note("MetaHuntOptionsChronometerTextPatternHint",
if not newText or newText == "" then "Bar text tokens: $s = spell, $t = target", { lines = 2 })
newText = "$t" local patternEdit = textBox:EditRow("MetaHuntOptionsChronometerTextPatternLabel", "Bar Text",
end "MetaHuntOptionsChronometerTextPattern", 150, { height = 20 })
profile.text = tostring(newText) if patternEdit then
MTH_CHRON_ApplyLiveProfile(profile) patternEdit:SetText(tostring(profile.text or "$t"))
end) patternEdit:SetScript("OnEnterPressed", function()
local newText = this:GetText() or ""
local growCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerGrowUp", "Grow bars upward", -438) if newText == "" then newText = "$t" end
if growCheck then profile.text = tostring(newText)
growCheck:SetChecked(profile.growup and true or false)
growCheck:SetScript("OnClick", function()
if not this then return end
profile.growup = MTH_CHRON_IsChecked(this)
MTH_CHRON_ApplyLiveProfile(profile) MTH_CHRON_ApplyLiveProfile(profile)
this:ClearFocus()
end) end)
end end
textBox:Checkbox("MetaHuntOptionsChronometerGrowUp", "Grow bars upward", {
local reverseCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerReverse", "Reverse bars", -463) checked = profile.growup and true or false,
if reverseCheck then onClick = function()
reverseCheck:SetChecked(profile.reverse and true or false) profile.growup = this:GetChecked() == 1
reverseCheck:SetScript("OnClick", function()
if not this then return end
profile.reverse = MTH_CHRON_IsChecked(this)
MTH_CHRON_ApplyLiveProfile(profile) MTH_CHRON_ApplyLiveProfile(profile)
end) end,
end })
textBox:Checkbox("MetaHuntOptionsChronometerReverse", "Reverse bars", {
checked = profile.reverse and true or false,
onClick = function()
profile.reverse = this:GetChecked() == 1
MTH_CHRON_ApplyLiveProfile(profile)
end,
})
MTH_CHRON_MakeSmall(container, "MetaHuntOptionsChronometerIconPosLabel", "Icon Position", 20, -494) local iconBox = panel:Box("right", "MetaHuntOptionsChronometerIconBox", "Icon Position")
local iconLeft = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerIconLeft", "Left", -514) local iconLeft, iconRight
local iconRight = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerIconRight", "Right", -538) iconLeft = iconBox:Checkbox("MetaHuntOptionsChronometerIconLeft", "Left", {
if iconLeft and iconRight then checked = profile.iconposition ~= "RIGHT",
iconLeft:SetChecked(profile.iconposition ~= "RIGHT") onClick = function()
iconRight:SetChecked(profile.iconposition == "RIGHT")
iconLeft:SetScript("OnClick", function()
profile.iconposition = "LEFT" profile.iconposition = "LEFT"
iconLeft:SetChecked(true) iconLeft:SetChecked(true)
iconRight:SetChecked(nil) iconRight:SetChecked(nil)
MTH_CHRON_ApplyLiveProfile(profile) MTH_CHRON_ApplyLiveProfile(profile)
end) end,
iconRight:SetScript("OnClick", function() })
iconRight = iconBox:Checkbox("MetaHuntOptionsChronometerIconRight", "Right", {
checked = profile.iconposition == "RIGHT",
onClick = function()
profile.iconposition = "RIGHT" profile.iconposition = "RIGHT"
iconRight:SetChecked(true) iconRight:SetChecked(true)
iconLeft:SetChecked(nil) iconLeft:SetChecked(nil)
MTH_CHRON_ApplyLiveProfile(profile) MTH_CHRON_ApplyLiveProfile(profile)
end) end,
end })
else else
local classSpells, classEvents, racial = MTH_CHRON_BuildTimerLists() local classSpells, classEvents, racial = MTH_CHRON_BuildTimerLists()
if section == "classspells" then if section == "classspells" then
MTH_CHRON_RenderTimerSection(container, profile, "Hunter Spells", classSpells, class, { local spellsBox = panel:Box("left", "MetaHuntOptionsChronometerSpellsBox", "Hunter Spells")
x = 20, MTH_CHRON_RenderTimerBox(spellsBox, profile, classSpells, class, "ClassSpells")
y = -84, local trinketBox = panel:Box("right", "MetaHuntOptionsChronometerTrinketsBox", "Hunter Trinkets")
prefix = "ClassSpells", MTH_CHRON_RenderTimerBox(trinketBox, profile, MTH_CHRON_BuildHunterTrinketItems(classEvents), class, "ClassTrinkets")
})
MTH_CHRON_RenderTimerSection(container, profile, "Hunter Trinkets", MTH_CHRON_BuildHunterTrinketItems(classEvents), class, {
x = 300,
y = -84,
prefix = "ClassTrinkets",
})
elseif section == "classevents" then elseif section == "classevents" then
MTH_CHRON_RenderTimerSection(container, profile, "Hunter Events", classEvents, class, { prefix = "ClassEvents" }) local eventsBox = panel:Box("left", "MetaHuntOptionsChronometerEventsBox", "Hunter Events")
MTH_CHRON_RenderTimerBox(eventsBox, profile, classEvents, class, "ClassEvents")
elseif section == "racial" then elseif section == "racial" then
MTH_CHRON_RenderTimerSection(container, profile, "Racial", racial, "RACIAL", { prefix = "Racial" }) local racialBox = panel:Box("left", "MetaHuntOptionsChronometerRacialBox", "Racial")
MTH_CHRON_RenderTimerBox(racialBox, profile, racial, "RACIAL", "Racial")
end end
end end
panel:Finish()
end end
if _G then if _G then
+30 -48
View File
@@ -5,13 +5,6 @@ local MTH_CREDITS_READY = MTH_OptionsRequire and MTH_OptionsRequire("options-cre
local MTH_CREDITS_FONT_DELTA = 2 local MTH_CREDITS_FONT_DELTA = 2
local function MTH_CR_L(key, default)
if MTH and MTH.GetLocalization then
return MTH:GetLocalization(key, default)
end
return default or key
end
local function MTH_CreditsBumpFont(fs) local function MTH_CreditsBumpFont(fs)
if not fs or not fs.GetFont or not fs.SetFont then return end if not fs or not fs.GetFont or not fs.SetFont then return end
local path, size, flags = fs:GetFont() local path, size, flags = fs:GetFont()
@@ -25,12 +18,12 @@ local function MTH_CreditsInsertLink(url)
if link == "" then return end if link == "" then return end
if type(MTH_InsertLinkToChat) == "function" and MTH_InsertLinkToChat(link) then if type(MTH_InsertLinkToChat) == "function" and MTH_InsertLinkToChat(link) then
if MTH and MTH.Print then if MTH and MTH.Print then
MTH:Print(MTH_CR_L("CREDITS_LINK_INSERTED", "Link inserted in chat: ") .. link) MTH:Print("Link inserted in chat: " .. link)
end end
return return
end end
if MTH and MTH.Print then if MTH and MTH.Print then
MTH:Print(MTH_CR_L("CREDITS_LINK_FALLBACK", "Link: ") .. link) MTH:Print("Link: " .. link)
end end
end end
@@ -96,12 +89,8 @@ function MTH_SetupCreditsOptions()
local container = MTH_GetFrame("MetaHuntOptionsCredits") local container = MTH_GetFrame("MetaHuntOptionsCredits")
if not container then return end if not container then return end
MTH_ClearContainer(container) local panel = MTH_Layout.Panel(container)
local title = panel:Title("Credits")
local title = container:CreateFontString("MetaHuntOptionsCreditsTitle", "ARTWORK", "GameFontNormal")
title:SetPoint("TOPLEFT", container, "TOPLEFT", 10, -10)
title:SetText(MTH_CR_L("CREDITS_TITLE", "Credits"))
title:SetTextColor(1.00, 0.82, 0.00)
local scrollFrame = MTH_GetFrame("MetaHuntOptionsCreditsScroll") local scrollFrame = MTH_GetFrame("MetaHuntOptionsCreditsScroll")
if not scrollFrame then if not scrollFrame then
@@ -120,20 +109,27 @@ function MTH_SetupCreditsOptions()
MTH_CreditsClearFrame(content) MTH_CreditsClearFrame(content)
scrollFrame:SetScrollChild(content) scrollFrame:SetScrollChild(content)
local cursor = MTH_CreditsCreateText(content, content, MTH_CR_L("CREDITS_ABOUT_CODE_TITLE", "About the code"), "GameFontNormal", -2, 1.00, 0.82, 0.00) local cursor = MTH_CreditsCreateText(content, content, "About the code", "GameFontNormal", -2, 1.00, 0.82, 0.00)
cursor = MTH_CreditsCreateText(content, cursor, cursor = MTH_CreditsCreateText(content, cursor,
MTH_CR_L("CREDITS_ABOUT_CODE_INTRO", "This addon is, among others, a compilation of old Vanilla addons that I loved and played with for years. But most of them had become very buggy on Turtle WoW as new content was added to the server.\nI fixed, reworked, enhanced, and melted them within a modern modular framework that I created.\n"
"This addon is, in others, a compilation of old vanilla addons that I loved and played with during years. But most of them had become very buggy with TwoW as new contain was added to the server. \n I fixed, reworked, enhanced, and melted them within a modern modular framework that I created.\n" .. "I want to make clear what work is mine, what is not, and respectfully credit the original authors for the great stuff I've taken from them:",
.. "I want to make it crystal clear about what work is mine, and what is not and respectfully credit the original authors for the great stuff I've taken from them:"),
"GameFontNormalSmall", -10) "GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateText(content, cursor, cursor = MTH_CreditsCreateText(content, cursor,
MTH_CR_L("CREDITS_ABOUT_ZHUNTER", "- MetaHunt animations are powered by the great PizzaSauce library of Pizzahawaii! Thank you man.",
"- zBars, Antidaze and Autostrip were core functionalities of Vanilla zHunterMod addon. And I kept the \"z\" naming of bars/buttons to always remember it. On top of this I have created myself zAmmo, zCompanions, zMount, zToys.\nAlso the SmartAmmo feature idea is coming from Zhuntermod: there was a file in it with that functionality, but was like a work-in-progress, totally unfunctional and even \"dangerous\" in its current state, and was not active in the addon. Since it was a fucking great idea, I recoded it mostly from scratch and made it work reliably and safely."), "GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateLink(content,
"https://codeberg.org/Pizzahawaii/",
"https://codeberg.org/Pizzahawaii/",
cursor,
-4)
cursor = MTH_CreditsCreateText(content, cursor,
"- zBars, AntiDaze, and AutoStrip were core functionalities of the Vanilla zHunterMod addon. I kept the \"z\" naming of bars/buttons to always remember it. I improved it and added zBar, zAmmo, zCompanions, zMount, zToys, and zCraft.\nAlso, the SmartAmmo feature idea comes from zHunterMod: there was a file with that functionality, but it was a work in progress, totally non-functional and even \"dangerous\" in its current state, and was not active in the addon. Since it was a fucking great idea, I recoded it mostly from scratch and made it work reliably and safely.",
"GameFontNormalSmall", -10) "GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateText(content, cursor, cursor = MTH_CreditsCreateText(content, cursor,
MTH_CR_L("CREDITS_ABOUT_FEEDOMATIC", "- Feed-o-Matic was entirely the work of Fizzwidget. I made it work for Twow, by melting it as a module calling Metahunt core to get pet's state and up-to-date data for new pet families and their diet."), "- Feed-o-Matic was entirely the work of Fizzwidget. I made it work for Turtle WoW by melting it into a module that calls the MetaHunt core to get the pet's state and up-to-date data for new pet families and their diet.",
"GameFontNormalSmall", -10) "GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateLink(content, cursor = MTH_CreditsCreateLink(content,
"https://www.wowinterface.com/downloads/info4160-FizzwidgetFeed-O-Matic.html", "https://www.wowinterface.com/downloads/info4160-FizzwidgetFeed-O-Matic.html",
@@ -142,7 +138,7 @@ function MTH_SetupCreditsOptions()
-4) -4)
cursor = MTH_CreditsCreateText(content, cursor, cursor = MTH_CreditsCreateText(content, cursor,
MTH_CR_L("CREDITS_ABOUT_TOOLTIPS", "- Tooltips module is based on Hunter Helper, another vanilla addon of Fizzwidget. I kept the general idea, but there's not much original code left from it in Metahunt as I refactored everything so it plugs on MetaHunt controlled event handlers, can work with Turtle wow data, and is now usable on other things than Beasts."), "- The Tooltips module is based on Hunter Helper, another Vanilla addon by Fizzwidget. I kept the general idea, but there's not much original code left from it in MetaHunt, as I refactored everything so it plugs into MetaHunt-controlled event handlers, can work with Turtle WoW data, and is now usable for things other than Beasts.",
"GameFontNormalSmall", -10) "GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateLink(content, cursor = MTH_CreditsCreateLink(content,
"https://legacy-wow.com/vanilla-addons/fizzwidget-hunter-helper/", "https://legacy-wow.com/vanilla-addons/fizzwidget-hunter-helper/",
@@ -151,7 +147,7 @@ function MTH_SetupCreditsOptions()
-4) -4)
cursor = MTH_CreditsCreateText(content, cursor, cursor = MTH_CreditsCreateText(content, cursor,
MTH_CR_L("CREDITS_ABOUT_CHRONOMETER", "- Chronometer was an old and very popular vanilla addon working with ACE2 libraries. It has known uncountable iterations by various people over the years, and I can't credit them all of them here. The current version used by Metahunt is based on this Twow conversion made by \"wigan91\". I have reworked it to have it Hunter-focused-only: removed the non-hunter/race stuff, improved the definition for spells/effects config that were not correct, added missing hunter spells (not many), and implemented my own bar color scheme fitting better the hunter spells."), "- Chronometer was an old and very popular Vanilla addon working with ACE2 libraries. It has had countless iterations by various people over the years, and I can't credit them all here. The current version used by MetaHunt is based on this Turtle WoW conversion made by \"wigan91\". I reworked it to be Hunter-focused only: removed the non-Hunter/racial stuff, improved incorrect spell/effect configuration definitions, added missing Hunter spells (not many), and implemented my own bar color scheme that fits Hunter spells better.",
"GameFontNormalSmall", -10) "GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateLink(content, cursor = MTH_CreditsCreateLink(content,
"https://github.com/wigan91/Chronometer-TWoW", "https://github.com/wigan91/Chronometer-TWoW",
@@ -161,7 +157,7 @@ function MTH_SetupCreditsOptions()
cursor = MTH_CreditsCreateText(content, cursor, 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."), "- ICU was an old Vanilla addon. I used the latest revision below and added many more configuration options to it.",
"GameFontNormalSmall", -10) "GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateLink(content, cursor = MTH_CreditsCreateLink(content,
"https://github.com/wigan91/ICU-TWoW", "https://github.com/wigan91/ICU-TWoW",
@@ -170,43 +166,29 @@ function MTH_SetupCreditsOptions()
-4) -4)
cursor = MTH_CreditsCreateText(content, cursor, 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."), "- The Map/Marker system is from pfQuest by Master Shagu. I mostly left it untouched and only made slight modifications so it can integrate better within MetaHunt and does not conflict with pfQuest.",
"GameFontNormalSmall", -10) "GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateText(content, cursor, cursor = MTH_CreditsCreateText(content, cursor,
MTH_CR_L("CREDITS_ABOUT_ATLAS_LINKS", "- The system allowing to fetch itemlinks in game (In the Weapon tab of the Hunter Book notably) is the one of Atlas Twow."), "- The system allowing item links to be fetched in-game, notably in the Hunter Book Weapon tab, is from Atlas Turtle WoW.",
"GameFontNormalSmall", -10) "GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateText(content, cursor, MTH_CR_L("CREDITS_ABOUT_DATA_TITLE", "About data sources"), "GameFontNormal", -14, 1.00, 0.82, 0.00) cursor = MTH_CreditsCreateText(content, cursor, "About data sources", "GameFontNormal", -14, 1.00, 0.82, 0.00)
cursor = MTH_CreditsCreateText(content, cursor, cursor = MTH_CreditsCreateText(content, cursor,
MTH_CR_L("CREDITS_ABOUT_DATA_INTRO", "Metahunt ships with its own Turtle-WoW datastores, limited to hunter stuff only, and most of this data is up-to-date (Feb 2026).\n\n" "MetaHunt ships with its own Turtle WoW datastores, limited to Hunter-related information, and most of this data is up to date for Turtle WoW 1.18.1. The beast information comes directly from an extract of the live Turtle WoW database, made for MetaHunt by the Turtle WoW dev lead in March 2026.\n\n"
.. "Sources used to build the data include:"), .. "Other sources used to build the data include:",
"GameFontNormalSmall", -10) "GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateText(content, cursor, cursor = MTH_CreditsCreateText(content, cursor,
MTH_CR_L("CREDITS_DATA_PFQUEST", "- pfQuest for Beasts and Ammo vendors NPCs and their spawn points (and beasts respawn times if any). Thank you again Shagu."), "- Atlas Turtle WoW for ranged weapon sources\n\n"
.. "- Turtle WoW Wiki for pet diets (which was incomplete, missing Serpents and Foxes)",
"GameFontNormalSmall", -10) "GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateText(content, cursor, cursor = MTH_CreditsCreateText(content, cursor,
MTH_CR_L("CREDITS_DATA_SHEET", "- This invaluable and very accurate spreadsheet for beasts having pet abilities. A big thanks to all twow players that crafted this! Now you dont need that sheet anymore, you have Metahunt! And the Great Book of Huntards is awesome and accurate also because of YOU."), "",
"GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateLink(content,
"https://docs.google.com/spreadsheets/d/1u33knqlYXvY-jR6pvTd58gyDg8OYEuwYsSoEnIjeA8k/edit?gid=4136205#gid=4136205",
"https://docs.google.com/spreadsheets/d/1u33knqlYXvY-jR6pvTd58gyDg8OYEuwYsSoEnIjeA8k/edit?gid=4136205#gid=4136205",
cursor,
-4)
cursor = MTH_CreditsCreateText(content, cursor,
MTH_CR_L("CREDITS_DATA_OTHER", "- Atlas twow for ranged weapon sources\n\n"
.. "- Twow wiki for pet diet (which was incomplete, missing Serpents and Foxes)"),
"GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateText(content, cursor,
MTH_CR_L("CREDITS_CLOSING", "With that being said, go back hunting fellas!"),
"GameFontNormalSmall", -10, 1.00, 0.82, 0.00) "GameFontNormalSmall", -10, 1.00, 0.82, 0.00)
cursor = MTH_CreditsCreateText(content, cursor, cursor = MTH_CreditsCreateText(content, cursor,
MTH_CR_L("CREDITS_SIGNATURE", "\n" "\n" .. "Metasploit <Atlantis>, of Nordaanar, now N'Zoth",
.. "Metasploit <Atlantis>, of Nordaanar."),
"GameFontNormalSmall", -10) "GameFontNormalSmall", -10)
end end
+285
View File
@@ -0,0 +1,285 @@
------------------------------------------------------
-- 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_STATE = { bindStatus = nil, bindButton = nil, captureFrame = nil }
local function MTH_EA_OPT_GetBindText()
if not GetBindingKey then return "Unbound" end
local k1, k2 = GetBindingKey("EXPAMMO ACTION")
if not k1 and not k2 then return "Unbound" end
if k1 and k2 then return k1 .. " / " .. k2 end
return k1 or k2 or "Unbound"
end
local function MTH_EA_OPT_UpdateBindStatus()
local bs = MTH_EA_OPT_STATE.bindStatus
if not bs then return end
bs:SetText("Key: " .. MTH_EA_OPT_GetBindText())
end
-- key must already be the full chord string (e.g. "ALT-F", "BUTTON3", "MOUSEWHEELUP")
local function MTH_EA_OPT_SaveBinding(key)
if not SetBinding or not SaveBindings or not GetBindingKey then return end
local old1, old2 = GetBindingKey("EXPAMMO ACTION")
if old1 then SetBinding(old1) end
if old2 then SetBinding(old2) end
if key and key ~= "" then
SetBinding(key, "EXPAMMO ACTION")
end
local bs = 1
if GetCurrentBindingSet then bs = GetCurrentBindingSet() or 1 end
SaveBindings(bs)
end
local function MTH_EA_OPT_GetPrefix()
return string.format("%s%s%s",
(IsAltKeyDown and IsAltKeyDown() and "ALT-" or ""),
(IsControlKeyDown and IsControlKeyDown() and "CTRL-" or ""),
(IsShiftKeyDown and IsShiftKeyDown() and "SHIFT-" or ""))
end
local function MTH_EA_OPT_StopCapture()
if MTH_EA_OPT_STATE.captureFrame then
MTH_EA_OPT_STATE.captureFrame:Hide()
end
local bb = MTH_EA_OPT_STATE.bindButton
if bb then bb:SetText("Set Key") end
MTH_EA_OPT_UpdateBindStatus()
end
local function MTH_EA_OPT_MakeCaptureFrame()
local f = CreateFrame("Frame", "MTH_ExpAmmoBindCapture", UIParent)
f:SetFrameStrata("FULLSCREEN_DIALOG")
f:SetAllPoints(UIParent)
f:EnableKeyboard(true)
f:EnableMouse(true)
f:EnableMouseWheel(true)
f:Hide()
local bg = f:CreateTexture(nil, "BACKGROUND")
bg:SetAllPoints(f)
bg:SetTexture(0, 0, 0, 0.5)
local lbl = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
lbl:SetPoint("CENTER", f, "CENTER", 0, 0)
lbl:SetText("|cffffff00Press a key, mouse button, or scroll wheel|r\n|cffaaaaaa(ESC to cancel)|r")
f:SetScript("OnKeyUp", function()
local key = arg1
if not key or key == "" then return end
if key == "ESCAPE" then MTH_EA_OPT_StopCapture() return end
if key == "UNKNOWN" or key == "PRINTSCREEN" then return end
if key == "ALT" or key == "CTRL" or key == "SHIFT" then return end
MTH_EA_OPT_SaveBinding(MTH_EA_OPT_GetPrefix() .. key)
MTH_EA_OPT_StopCapture()
end)
f:SetScript("OnMouseUp", function()
local btmap = { LeftButton="BUTTON1", RightButton="BUTTON2", MiddleButton="BUTTON3", Button4="BUTTON4", Button5="BUTTON5" }
local mapped = btmap[arg1]
if not mapped then return end
local prefix = MTH_EA_OPT_GetPrefix()
if prefix == "" and (mapped == "BUTTON1" or mapped == "BUTTON2") then return end
MTH_EA_OPT_SaveBinding(prefix .. mapped)
MTH_EA_OPT_StopCapture()
end)
f:SetScript("OnMouseWheel", function()
local wheelkey = (arg1 == 1 and "MOUSEWHEELUP") or (arg1 == -1 and "MOUSEWHEELDOWN") or nil
if not wheelkey then return end
MTH_EA_OPT_SaveBinding(MTH_EA_OPT_GetPrefix() .. wheelkey)
MTH_EA_OPT_StopCapture()
end)
return f
end
local function MTH_EA_OPT_StartCapture()
if not MTH_EA_OPT_STATE.captureFrame then
MTH_EA_OPT_STATE.captureFrame = MTH_EA_OPT_MakeCaptureFrame()
end
MTH_EA_OPT_STATE.captureFrame:Show()
local bb = MTH_EA_OPT_STATE.bindButton
if bb then bb:SetText("Press key...") end
end
local function MTH_EA_OPT_BuildUI(container)
local cfg = MTH_EA_OPT_GetCfg()
local showHint = cfg.showHint ~= false
local panel = MTH_Layout.Panel(container)
panel:Title("MM Widget — Experimental Ammunition Tracker")
-- Right-column gating: dim the box frames and disable their interactive
-- controls when the "Show action hint cell" checkbox is off.
local rightBoxes = {}
local rightControls = {}
local function setRightEnabled(enabled)
local i
for i = 1, table.getn(rightBoxes) do
local f = rightBoxes[i]
if f and f.SetAlpha then f:SetAlpha(enabled and 1.0 or 0.35) end
end
for i = 1, table.getn(rightControls) do
local c = rightControls[i]
if c and c.Enable and c.Disable then
if enabled then c:Enable() else c:Disable() end
end
end
end
-- ===== LEFT: Module =====
local moduleBox = panel:Box("left", "MTH_ExpAmmoModuleBox", "Module")
local moduleEnabled = true
if MTH and MTH.IsModuleEnabled then
moduleEnabled = MTH:IsModuleEnabled("expammo", true) and true or false
end
moduleBox:Checkbox("MTH_ExpAmmoModuleCB", "Enable MM Widget module", {
checked = moduleEnabled,
onClick = function()
local v = this:GetChecked() == 1
if MTH and MTH.SetModuleEnabled then MTH:SetModuleEnabled("expammo", v) end
if MTH_ExpAmmo then MTH_ExpAmmo.SetEnabled(v) end
end,
})
-- ===== LEFT: Tracker Visibility =====
local visBox = panel:Box("left", "MTH_ExpAmmoVisBox", "Tracker Visibility")
visBox:Checkbox("MTH_ExpAmmoShowLnLCB", "Show Lock and Load cell (top)", {
checked = cfg.showLnL ~= false,
onClick = function() if MTH_ExpAmmo then MTH_ExpAmmo.SetShowLnL(this:GetChecked() == 1) end end,
})
visBox:Checkbox("MTH_ExpAmmoShowHintCB", "Show action hint cell (bottom)", {
checked = showHint,
onClick = function()
local v = this:GetChecked() == 1
if MTH_ExpAmmo then MTH_ExpAmmo.SetShowHint(v) end
setRightEnabled(v)
end,
})
visBox:Checkbox("MTH_ExpAmmoHideOOCCB", "Hide widget when out of combat", {
checked = cfg.hideOOC == true,
onClick = function() if MTH_ExpAmmo then MTH_ExpAmmo.SetHideOOC(this:GetChecked() == 1) end end,
})
-- ===== LEFT: Appearance =====
local appBox = panel:Box("left", "MTH_ExpAmmoAppBox", "Appearance")
appBox:Slider("MTH_ExpAmmoCellSizeSlider", {
label = "Cell Size (px)", min = 20, max = 80, step = 4,
value = math.max(20, math.min(80, cfg.cellSize or 40)),
onChange = function(val) if MTH_ExpAmmo then MTH_ExpAmmo.SetCellSize(val) end end,
})
appBox:Slider("MTH_ExpAmmoCellGapSlider", {
label = "Cell Spacing (px)", min = 0, max = 20, step = 1,
value = math.max(0, math.min(20, cfg.cellGap or 2)),
onChange = function(val) if MTH_ExpAmmo then MTH_ExpAmmo.SetCellGap(val) end end,
})
appBox:Checkbox("MTH_ExpAmmoBigCDCB", "Large cooldown numbers (centered in cell)", {
checked = cfg.bigCD == true,
onClick = function() if MTH_ExpAmmo then MTH_ExpAmmo.SetBigCD(this:GetChecked() == 1) end end,
})
-- ===== LEFT: Position =====
local posBox = panel:Box("left", "MTH_ExpAmmoPosBox", "Position")
posBox:Button("MTH_ExpAmmoToggleAnchor", "Toggle Anchor", {
width = 130,
onClick = function() if MTH_ExpAmmo then MTH_ExpAmmo.ToggleAnchor() end end,
})
posBox:Note("MTH_ExpAmmoPosNote",
"Shows 4 drag handles around the widget (top / bottom / left / right). Drag any one to reposition it.",
{ color = MTH_Layout.Theme.colText, lines = 3 })
-- ===== RIGHT: Smart Action Cell (helper + keybind + casting) =====
-- One feature = one box. The intro notes explain the bottom cell, and the
-- Keybind / Casting controls that drive it live underneath as sub-sections.
local cellBox = panel:Box("right", "MTH_ExpAmmoCellBox", "Smart Action Cell")
table.insert(rightBoxes, cellBox:Frame())
cellBox:Text("MTH_ExpAmmoHintSub",
"Options below apply to the bottom cell only. Disable \"Show action hint cell\" to hide the cell and these controls.",
{ color = { 1, 1, 1 }, lines = 3 })
cellBox:Note("MTH_ExpAmmoHintNam",
"Notes:\n - You need NAMPOWER for this to work reliably.\n - Aimed/Steady/Multi/Arcane Shots SHOULD BE on your action bars.",
{ lines = 3 })
-- Keybind sub-section
cellBox:Gap(4)
cellBox:Text("MTH_ExpAmmoKeyHead", "Keybind",
{ font = MTH_Layout.Theme.fontBoxTitle, color = MTH_Layout.Theme.colPanelTitle })
cellBox:Note("MTH_ExpAmmoBindTip",
"One key to rule them all.\n Aimed Shot when ready, Steady Shot while Aimed is on cooldown, consume-spell (Multi / Serpent / Arcane) when proc is up.",
{ lines = 4 })
MTH_EA_OPT_STATE.bindStatus = cellBox:Text("MTH_ExpAmmoBindStatus",
"Key: " .. MTH_EA_OPT_GetBindText())
local keyButtons = cellBox:Buttons({
{ name = "MTH_ExpAmmoBindButton", label = "Set Key", width = 90,
onClick = function()
if MTH_EA_OPT_STATE.captureFrame and MTH_EA_OPT_STATE.captureFrame:IsShown() then
MTH_EA_OPT_StopCapture()
else
MTH_EA_OPT_StartCapture()
end
end },
{ name = "MTH_ExpAmmoBindClear", label = "Clear", width = 70,
onClick = function() MTH_EA_OPT_SaveBinding(nil) end },
})
MTH_EA_OPT_STATE.bindButton = keyButtons[1]
table.insert(rightControls, keyButtons[1])
table.insert(rightControls, keyButtons[2])
-- Casting sub-section
cellBox:Gap(4)
cellBox:Text("MTH_ExpAmmoCastHead", "Casting",
{ font = MTH_Layout.Theme.fontBoxTitle, color = MTH_Layout.Theme.colPanelTitle })
local quiverCB = cellBox:Checkbox("MTH_ExpAmmoQuiverNoClipCB", "Use Quiver no-clip casting", {
checked = cfg.useQuiverNoClip == true,
onClick = function() if MTH_ExpAmmo then MTH_ExpAmmo.SetUseQuiverNoClip(this:GetChecked() == 1) end end,
})
table.insert(rightControls, quiverCB)
cellBox:Note("MTH_ExpAmmoQuiverTip",
"Requires the Quiver addon. Routes Aimed/Steady/Multi cast through Quiver.CastNoClip to avoid clipping your auto-shot swing timer.",
{ lines = 3 })
-- ===== RIGHT: Rotation =====
local rotBox = panel:Box("right", "MTH_ExpAmmoRotBox", "Rotation")
table.insert(rightBoxes, rotBox:Frame())
rotBox:Note("MTH_ExpAmmoRotTip",
"Uncheck a shot to permanently skip it during its proc window. The keybind will cast Steady/Aimed instead.",
{ lines = 2 })
local multiCB = rotBox:Checkbox("MTH_ExpAmmoBlockMultiCB", "Multi-Shot (Explosive)", {
checked = cfg.blockExplosive ~= true,
onClick = function() if MTH_ExpAmmo then MTH_ExpAmmo.SetBlockExplosive(not (this:GetChecked() == 1)) end end,
})
table.insert(rightControls, multiCB)
local serpentCB = rotBox:Checkbox("MTH_ExpAmmoBlockSerpentCB", "Serpent Sting (Poisonous)", {
checked = cfg.blockPoisonous ~= true,
onClick = function() if MTH_ExpAmmo then MTH_ExpAmmo.SetBlockPoisonous(not (this:GetChecked() == 1)) end end,
})
table.insert(rightControls, serpentCB)
local arcaneCB = rotBox:Checkbox("MTH_ExpAmmoBlockArcaneCB", "Arcane Shot (Enchanted)", {
checked = cfg.blockEnchanted ~= true,
onClick = function() if MTH_ExpAmmo then MTH_ExpAmmo.SetBlockEnchanted(not (this:GetChecked() == 1)) end end,
})
table.insert(rightControls, arcaneCB)
panel:Finish()
-- Apply initial gating state.
setRightEnabled(showHint)
end
function MTH_SetupExpAmmoOptions()
if not MTH_EA_OPT_Ready() then return end
local container = MTH_GetFrame("MetaHuntOptionsExpAmmo")
if not container then return end
local ok, err = pcall(MTH_EA_OPT_BuildUI, container)
if not ok and MTH and MTH.Print then
MTH:Print("[MTH ExpAmmo OPTIONS] " .. tostring(err), "error")
end
end
+173 -169
View File
@@ -10,7 +10,7 @@ local MTH_FOM_STATE = {
bindStatus = nil, bindStatus = nil,
bindButton = nil, bindButton = nil,
bindClearButton = nil, bindClearButton = nil,
bindingCapture = false, captureFrame = nil,
ctrl = {}, ctrl = {},
} }
@@ -93,46 +93,86 @@ local function MTH_FOM_UpdateBindingStatus()
if not bindStatus or not bindStatus.SetText then if not bindStatus or not bindStatus.SetText then
return return
end end
if MTH_FOM_STATE.bindingCapture then bindStatus:SetText(string.format(MTH_FOM_L("FOM_BIND_STATUS_VALUE", "Feed key: %s"), MTH_FOM_GetBindingDisplayText()))
bindStatus:SetText(MTH_FOM_L("FOM_BIND_STATUS_PROMPT", "Feed key: press a key... (ESC to clear)"))
else
bindStatus:SetText(string.format(MTH_FOM_L("FOM_BIND_STATUS_VALUE", "Feed key: %s"), MTH_FOM_GetBindingDisplayText()))
end
end end
local function MTH_FOM_StopBindingCapture() local function MTH_FOM_StopBindingCapture()
MTH_FOM_STATE.bindingCapture = false if MTH_FOM_STATE.captureFrame then
MTH_FOM_STATE.captureFrame:Hide()
end
if MTH_FOM_STATE.bindButton then if MTH_FOM_STATE.bindButton then
if MTH_FOM_STATE.bindButton.SetPropagateKeyboardInput then
MTH_FOM_STATE.bindButton:SetPropagateKeyboardInput(true)
end
MTH_FOM_STATE.bindButton:EnableKeyboard(false)
MTH_FOM_STATE.bindButton:SetText(MTH_FOM_L("FOM_BIND_SET_KEY", "Set Key")) MTH_FOM_STATE.bindButton:SetText(MTH_FOM_L("FOM_BIND_SET_KEY", "Set Key"))
end end
MTH_FOM_UpdateBindingStatus() MTH_FOM_UpdateBindingStatus()
end end
local function MTH_FOM_BuildBindingChord(key)
return string.format("%s%s%s%s",
(IsAltKeyDown and IsAltKeyDown() and "ALT-" or ""),
(IsControlKeyDown and IsControlKeyDown() and "CTRL-" or ""),
(IsShiftKeyDown and IsShiftKeyDown() and "SHIFT-" or ""),
key)
end
-- Forward declaration: defined below but referenced by the capture-frame
-- closures above it, so it must be an in-scope local upvalue (not a global).
local MTH_FOM_SaveBinding
local function MTH_FOM_MakeCaptureFrame()
local f = CreateFrame("Frame", "MTH_FOMBindCapture", UIParent)
f:SetFrameStrata("FULLSCREEN_DIALOG")
f:SetAllPoints(UIParent)
f:EnableKeyboard(true)
f:EnableMouse(true)
f:EnableMouseWheel(true)
f:Hide()
local bg = f:CreateTexture(nil, "BACKGROUND")
bg:SetAllPoints(f)
bg:SetTexture(0, 0, 0, 0.5)
local lbl = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
lbl:SetPoint("CENTER", f, "CENTER", 0, 0)
lbl:SetText("|cffffff00Press a key, mouse button, or scroll wheel|r\n|cffaaaaaa(ESC to cancel)|r")
f:SetScript("OnKeyUp", function()
local key = arg1
if not key or key == "" then return end
if key == "ESCAPE" then MTH_FOM_StopBindingCapture() return end
if key == "UNKNOWN" or key == "PRINTSCREEN" then return end
if key == "ALT" or key == "CTRL" or key == "SHIFT" then return end
MTH_FOM_SaveBinding(MTH_FOM_BuildBindingChord(key))
MTH_FOM_StopBindingCapture()
end)
f:SetScript("OnMouseUp", function()
local btmap = { LeftButton="BUTTON1", RightButton="BUTTON2", MiddleButton="BUTTON3", Button4="BUTTON4", Button5="BUTTON5" }
local mapped = btmap[arg1]
if not mapped then return end
local prefix = string.format("%s%s%s",
(IsAltKeyDown and IsAltKeyDown() and "ALT-" or ""),
(IsControlKeyDown and IsControlKeyDown() and "CTRL-" or ""),
(IsShiftKeyDown and IsShiftKeyDown() and "SHIFT-" or ""))
if prefix == "" and (mapped == "BUTTON1" or mapped == "BUTTON2") then return end
MTH_FOM_SaveBinding(prefix .. mapped)
MTH_FOM_StopBindingCapture()
end)
f:SetScript("OnMouseWheel", function()
local wheelkey = (arg1 == 1 and "MOUSEWHEELUP") or (arg1 == -1 and "MOUSEWHEELDOWN") or nil
if not wheelkey then return end
MTH_FOM_SaveBinding(MTH_FOM_BuildBindingChord(wheelkey))
MTH_FOM_StopBindingCapture()
end)
return f
end
local function MTH_FOM_StartBindingCapture() local function MTH_FOM_StartBindingCapture()
MTH_FOM_STATE.bindingCapture = true if not MTH_FOM_STATE.captureFrame then
MTH_FOM_STATE.captureFrame = MTH_FOM_MakeCaptureFrame()
end
MTH_FOM_STATE.captureFrame:Show()
if MTH_FOM_STATE.bindButton then if MTH_FOM_STATE.bindButton then
MTH_FOM_STATE.bindButton:SetText(MTH_FOM_L("FOM_BIND_PRESS_KEY", "Press key...")) MTH_FOM_STATE.bindButton:SetText(MTH_FOM_L("FOM_BIND_PRESS_KEY", "Press key..."))
MTH_FOM_STATE.bindButton:EnableKeyboard(true)
if MTH_FOM_STATE.bindButton.SetPropagateKeyboardInput then
MTH_FOM_STATE.bindButton:SetPropagateKeyboardInput(false)
end
end end
MTH_FOM_UpdateBindingStatus()
end end
local function MTH_FOM_BuildBindingChord(key) function MTH_FOM_SaveBinding(key)
local chord = ""
if IsControlKeyDown and IsControlKeyDown() and key ~= "LCTRL" and key ~= "RCTRL" then chord = chord .. "CTRL-" end
if IsAltKeyDown and IsAltKeyDown() and key ~= "LALT" and key ~= "RALT" then chord = chord .. "ALT-" end
if IsShiftKeyDown and IsShiftKeyDown() and key ~= "LSHIFT" and key ~= "RSHIFT" then chord = chord .. "SHIFT-" end
return chord .. key
end
local function MTH_FOM_SaveBinding(key)
if not SetBinding or not SaveBindings or not GetBindingKey then if not SetBinding or not SaveBindings or not GetBindingKey then
if MTH and MTH.Print then if MTH and MTH.Print then
MTH:Print(MTH_FOM_L("FOM_ERROR_KEYBIND_API_UNAVAILABLE", "Keybinding APIs unavailable.")) MTH:Print(MTH_FOM_L("FOM_ERROR_KEYBIND_API_UNAVAILABLE", "Keybinding APIs unavailable."))
@@ -202,31 +242,25 @@ function MTH_RefreshFeedOMaticOptions()
if MTH_FOM_STATE.ctrl.SaveCookNone then MTH_FOM_STATE.ctrl.SaveCookNone:SetChecked(FOM_Config.SaveForCookingLevel and FOM_Config.SaveForCookingLevel >= 4 and 1 or nil) end if MTH_FOM_STATE.ctrl.SaveCookNone then MTH_FOM_STATE.ctrl.SaveCookNone:SetChecked(FOM_Config.SaveForCookingLevel and FOM_Config.SaveForCookingLevel >= 4 and 1 or nil) end
end end
-- Small label helper: prefer Feed-O-Matic's own button text, else localized.
local function MTH_FOM_Label(key, lkey, fallback)
return (FOM_OptionsButtonText and FOM_OptionsButtonText[key]) or MTH_FOM_L(lkey, fallback)
end
function MTH_SetupFeedOMaticOptions() function MTH_SetupFeedOMaticOptions()
if not MTH_FOM_READY then return end if not MTH_FOM_READY then return end
local container = MTH_GetFrame("MetaHuntOptionsFeedOMatic") local container = MTH_GetFrame("MetaHuntOptionsFeedOMatic")
if not container then return end if not container then return end
local rightColumnX = 300
local yAdjust = -64
MTH_ClearContainer(container)
MTH_FOM_STATE.ctrl = {} MTH_FOM_STATE.ctrl = {}
local title = container:CreateFontString("MetaHuntOptionsFeedOMaticTitle", "ARTWORK", "GameFontHighlight") local panel = MTH_Layout.Panel(container)
title:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -10) panel:Title(MTH_FOM_L("FOM_TITLE", "Feed-O-Matic"))
title:SetText(MTH_FOM_L("FOM_TITLE", "Feed-O-Matic")) panel:Note(MTH_FOM_L("FOM_STATUS_NOTICE", "Feed-O-Matic, created by the great Fizzwidget, is not yet entirely ready for Twow because I am missing \na reliable list of all foods, mostly impossible to fetch from the DB.\n It still works much better in this version, but all stuff related to Food buff and Cooking isnt fully functional."), { lines = 4 })
local statusNotice = container:CreateFontString("MetaHuntOptionsFeedOMaticStatusNotice", "ARTWORK", "GameFontNormalSmall") MTH_FOM_STATE.enableCheck = panel:TopCheckbox("MetaHuntOptionsFeedOMaticEnabled",
statusNotice:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -34) MTH_FOM_L("FOM_ENABLE_MODULE", "Enable FeedOMatic module"), {
statusNotice:SetWidth(600) onClick = function()
statusNotice:SetJustifyH("LEFT")
statusNotice:SetJustifyV("TOP")
statusNotice:SetTextColor(0.35, 0.65, 1)
statusNotice:SetText(MTH_FOM_L("FOM_STATUS_NOTICE", "Feed-O-Matic, created by the great Fizzwidget, is not yet entirely ready for TurtleWoW because I am missing \na reliable list of all foods, mostly impossible to fetch from the DB.\n It still works much better in this version, but all stuff related to Food buff and Cooking isnt fully functional."))
local moduleCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsFeedOMaticEnabled", MTH_FOM_L("FOM_ENABLE_MODULE", "Enable FeedOMatic module"), -32 + yAdjust)
if moduleCheck then
moduleCheck:SetScript("OnClick", function()
if not this then return end if not this then return end
local enabled = this:GetChecked() == 1 local enabled = this:GetChecked() == 1
if MTH and MTH.SetModuleEnabled then if MTH and MTH.SetModuleEnabled then
@@ -236,143 +270,113 @@ function MTH_SetupFeedOMaticOptions()
end end
end end
MTH_RefreshFeedOMaticOptions() MTH_RefreshFeedOMaticOptions()
end) end,
MTH_FOM_STATE.enableCheck = moduleCheck })
end
local leftHeader = container:CreateFontString("MetaHuntOptionsFeedOMaticGeneralHeader", "ARTWORK", "GameFontHighlight") -- ===== General (left column) =====
leftHeader:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -62 + yAdjust) local general = panel:Box("left", "MetaHuntOptionsFOMGeneralBox", MTH_FOM_L("FOM_HEADER_GENERAL", "General"))
leftHeader:SetText(MTH_FOM_L("FOM_HEADER_GENERAL", "General"))
local bindStatus = container:CreateFontString("MetaHuntOptionsFOMBindStatus", "ARTWORK", "GameFontNormalSmall") MTH_FOM_STATE.bindStatus = general:Text("MetaHuntOptionsFOMBindStatus",
bindStatus:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -80 + yAdjust) string.format(MTH_FOM_L("FOM_BIND_STATUS_VALUE", "Feed key: %s"), MTH_FOM_GetBindingDisplayText()))
bindStatus:SetText(string.format(MTH_FOM_L("FOM_BIND_STATUS_VALUE", "Feed key: %s"), MTH_FOM_GetBindingDisplayText()))
MTH_FOM_STATE.bindStatus = bindStatus
local bindButton = CreateFrame("Button", "MetaHuntOptionsFOMBindButton", container, "UIPanelButtonTemplate") local bindButtons = general:Buttons({
bindButton:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -100 + yAdjust) { name = "MetaHuntOptionsFOMBindButton", label = MTH_FOM_L("FOM_BIND_SET_KEY", "Set Key"), width = 90,
bindButton:SetWidth(90) onClick = function()
bindButton:SetHeight(22) if MTH_FOM_STATE.captureFrame and MTH_FOM_STATE.captureFrame:IsShown() then
bindButton:SetText(MTH_FOM_L("FOM_BIND_SET_KEY", "Set Key")) MTH_FOM_StopBindingCapture()
bindButton:EnableKeyboard(false) else
bindButton:SetScript("OnClick", function() MTH_FOM_StartBindingCapture()
if MTH_FOM_STATE.bindingCapture then end
MTH_FOM_StopBindingCapture() end },
else { name = "MetaHuntOptionsFOMBindClearButton", label = MTH_FOM_L("FOM_BIND_CLEAR", "Clear"), width = 70,
MTH_FOM_StartBindingCapture() onClick = function() MTH_FOM_SaveBinding(nil) end },
end })
end) MTH_FOM_STATE.bindButton = bindButtons[1]
bindButton:SetScript("OnKeyDown", function() MTH_FOM_STATE.bindClearButton = bindButtons[2]
if not MTH_FOM_STATE.bindingCapture then
return
end
local key = arg1
if not key or key == "" then
return
end
if key == "ESCAPE" then
MTH_FOM_SaveBinding(nil)
return
end
if key == "UNKNOWN" or key == "PRINTSCREEN" then
return
end
MTH_FOM_SaveBinding(MTH_FOM_BuildBindingChord(key))
end)
MTH_FOM_STATE.bindButton = bindButton
local bindClearButton = CreateFrame("Button", "MetaHuntOptionsFOMBindClearButton", container, "UIPanelButtonTemplate") general:Note("MetaHuntOptionsFOMBindHint",
bindClearButton:SetPoint("LEFT", bindButton, "RIGHT", 8, 0) MTH_FOM_L("FOM_BIND_HINT", "You should assign a key bind for feed-o-matic,\nin order to feed your pet automatically.\nThe key \"P\" is an excellent candidate !"), { lines = 3 })
bindClearButton:SetWidth(70)
bindClearButton:SetHeight(22)
bindClearButton:SetText(MTH_FOM_L("FOM_BIND_CLEAR", "Clear"))
bindClearButton:SetScript("OnClick", function()
MTH_FOM_SaveBinding(nil)
end)
MTH_FOM_STATE.bindClearButton = bindClearButton
local bindHint = container:CreateFontString("MetaHuntOptionsFOMBindHint", "ARTWORK", "GameFontNormalSmall") MTH_FOM_STATE.ctrl.AvoidQuestFood = general:Checkbox("MetaHuntOptionsFOMAvoidQuest",
bindHint:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -128 + yAdjust) MTH_FOM_Label("AvoidQuestFood", "FOM_LABEL_AVOID_QUEST_FOODS", "Avoid quest foods"),
bindHint:SetWidth(360) { onClick = function() if not this then return end FOM_Config.AvoidQuestFood = this:GetChecked() == 1 end })
bindHint:SetJustifyH("LEFT") MTH_FOM_STATE.ctrl.AvoidBonusFood = general:Checkbox("MetaHuntOptionsFOMAvoidBonus",
bindHint:SetJustifyV("TOP") MTH_FOM_Label("AvoidBonusFood", "FOM_LABEL_AVOID_BONUS_FOODS", "Avoid bonus foods"),
bindHint:SetTextColor(0.35, 0.65, 1) { onClick = function() if not this then return end FOM_Config.AvoidBonusFood = this:GetChecked() == 1 end })
bindHint:SetText(MTH_FOM_L("FOM_BIND_HINT", "You should assign a key bind for feed-o-matic,\nin order to feed your pet automatically.\nThe key \"P\" is an excellent candidate !")) MTH_FOM_STATE.ctrl.PreferHigherQuality = general:Checkbox("MetaHuntOptionsFOMPreferQuality",
MTH_FOM_Label("PreferHigherQuality", "FOM_LABEL_PREFER_HIGHER_QUALITY_FOOD", "Prefer higher quality food"),
{ onClick = function() if not this then return end FOM_Config.PreferHigherQuality = this:GetChecked() == 1 end })
MTH_FOM_STATE.ctrl.Fallback = general:Checkbox("MetaHuntOptionsFOMFallback",
MTH_FOM_Label("Fallback", "FOM_LABEL_FALLBACK_TO_AVOIDED_FOODS", "Fallback to avoided foods"),
{ onClick = function() if not this then return end FOM_Config.Fallback = this:GetChecked() == 1 end })
MTH_FOM_STATE.ctrl.AvoidQuestFood = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMAvoidQuest", FOM_OptionsButtonText and FOM_OptionsButtonText["AvoidQuestFood"] or MTH_FOM_L("FOM_LABEL_AVOID_QUEST_FOODS", "Avoid quest foods"), -170 + yAdjust) MTH_FOM_STATE.keepOpenEdit = general:EditRow("MetaHuntOptionsFOMKeepOpenLabel",
MTH_FOM_STATE.ctrl.AvoidBonusFood = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMAvoidBonus", FOM_OptionsButtonText and FOM_OptionsButtonText["AvoidBonusFood"] or MTH_FOM_L("FOM_LABEL_AVOID_BONUS_FOODS", "Avoid bonus foods"), -195 + yAdjust) MTH_FOM_L("FOM_KEEP_OPEN_BAG_SLOTS", "Keep open bag slots:"),
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) "MetaHuntOptionsFOMKeepOpen", 40, {
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) numeric = true,
onChange = function()
if not this then return end
FOM_Config.KeepOpenSlots = tonumber(this:GetText()) or 0
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 -- ===== Notify when feeding (left column) =====
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 local notify = panel:Box("left", "MetaHuntOptionsFOMNotifyBox", MTH_FOM_L("FOM_HEADER_NOTIFY_WHEN_FEEDING", "Notify when feeding"))
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 MTH_FOM_STATE.ctrl.AlertEmote = notify:Checkbox("MetaHuntOptionsFOMAlertEmote",
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 MTH_FOM_Label("AlertEmote", "FOM_LABEL_NOTIFY_VIA_EMOTE", "Via emote"),
{ onClick = function() MTH_FOM_SetAlert("emote"); MTH_RefreshFeedOMaticOptions() end })
MTH_FOM_STATE.ctrl.AlertChat = notify:Checkbox("MetaHuntOptionsFOMAlertChat",
MTH_FOM_Label("AlertChat", "FOM_LABEL_NOTIFY_IN_CHAT", "In chat"),
{ onClick = function() MTH_FOM_SetAlert("chat"); MTH_RefreshFeedOMaticOptions() end })
MTH_FOM_STATE.ctrl.AlertNone = notify:Checkbox("MetaHuntOptionsFOMAlertNone",
MTH_FOM_Label("AlertNone", "FOM_LABEL_NOTIFY_NONE", "Don't notify"),
{ onClick = function() MTH_FOM_SetAlert("none"); MTH_RefreshFeedOMaticOptions() end })
local keepOpenLabel = container:CreateFontString("MetaHuntOptionsFOMKeepOpenLabel", "ARTWORK", "GameFontNormalSmall") -- ===== Avoid foods used in cooking (right column) =====
keepOpenLabel:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -302 + yAdjust) local avoid = panel:Box("right", "MetaHuntOptionsFOMAvoidBox", MTH_FOM_L("FOM_HEADER_AVOID_FOODS_USED_IN_COOKING", "Avoid foods used in cooking"))
keepOpenLabel:SetText(MTH_FOM_L("FOM_KEEP_OPEN_BAG_SLOTS", "Keep open bag slots:")) MTH_FOM_STATE.ctrl.SaveCookOrange = avoid:Checkbox("MetaHuntOptionsFOMSaveCookOrange",
MTH_FOM_Label("SaveForCook_Orange", "FOM_LABEL_ONLY_DIFFICULT_RECIPES", "Only difficult recipes"),
{ onClick = function() MTH_FOM_SetCookingLevel(3); MTH_RefreshFeedOMaticOptions() end })
MTH_FOM_STATE.ctrl.SaveCookYellow = avoid:Checkbox("MetaHuntOptionsFOMSaveCookYellow",
MTH_FOM_Label("SaveForCook_Yellow", "FOM_LABEL_MEDIUM_OR_BETTER", "Medium or better"),
{ onClick = function() MTH_FOM_SetCookingLevel(2); MTH_RefreshFeedOMaticOptions() end })
MTH_FOM_STATE.ctrl.SaveCookGreen = avoid:Checkbox("MetaHuntOptionsFOMSaveCookGreen",
MTH_FOM_Label("SaveForCook_Green", "FOM_LABEL_EASY_OR_BETTER", "Easy or better"),
{ onClick = function() MTH_FOM_SetCookingLevel(1); MTH_RefreshFeedOMaticOptions() end })
MTH_FOM_STATE.ctrl.SaveCookAll = avoid:Checkbox("MetaHuntOptionsFOMSaveCookAll",
MTH_FOM_Label("SaveForCook_All", "FOM_LABEL_ALL_FOODS", "All foods"),
{ onClick = function() MTH_FOM_SetCookingLevel(0); MTH_RefreshFeedOMaticOptions() end })
MTH_FOM_STATE.ctrl.SaveCookNone = avoid:Checkbox("MetaHuntOptionsFOMSaveCookNone",
MTH_FOM_Label("SaveForCook_None", "FOM_LABEL_DO_NOT_SAVE_COOKING_FOODS", "Do not save cooking foods"),
{ onClick = function() MTH_FOM_SetCookingLevel(4); MTH_RefreshFeedOMaticOptions() end })
local keepOpen = CreateFrame("EditBox", "MetaHuntOptionsFOMKeepOpen", container, "InputBoxTemplate") -- ===== Warn when pet needs feeding (right column) =====
keepOpen:SetPoint("LEFT", keepOpenLabel, "RIGHT", 8, 0) local warn = panel:Box("right", "MetaHuntOptionsFOMWarnBox", MTH_FOM_L("FOM_HEADER_WARN_WHEN_PET_NEEDS_FEEDING", "Warn when pet needs feeding"))
keepOpen:SetWidth(40) MTH_FOM_STATE.ctrl.LevelContent = warn:Checkbox("MetaHuntOptionsFOMLevelContent",
keepOpen:SetHeight(20) MTH_FOM_Label("LevelContent", "FOM_LABEL_WARN_WHEN_CONTENT", "When content"),
keepOpen:SetNumeric(true) { onClick = function() MTH_FOM_SetLevel("content"); MTH_RefreshFeedOMaticOptions() end })
keepOpen:SetAutoFocus(false) MTH_FOM_STATE.ctrl.LevelUnhappy = warn:Checkbox("MetaHuntOptionsFOMLevelUnhappy",
keepOpen:SetScript("OnTextChanged", function() MTH_FOM_Label("LevelUnhappy", "FOM_LABEL_WARN_WHEN_UNHAPPY", "When unhappy"),
if not this then return end { onClick = function() MTH_FOM_SetLevel("unhappy"); MTH_RefreshFeedOMaticOptions() end })
local value = tonumber(this:GetText()) or 0 MTH_FOM_STATE.ctrl.LevelOff = warn:Checkbox("MetaHuntOptionsFOMLevelOff",
FOM_Config.KeepOpenSlots = value MTH_FOM_Label("LevelOff", "FOM_LABEL_DONT_WARN", "Don't warn"),
end) { onClick = function() MTH_FOM_SetLevel("off"); MTH_RefreshFeedOMaticOptions() 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") warn:Gap(6)
notifyHeader:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -336 + yAdjust)
notifyHeader:SetText(MTH_FOM_L("FOM_HEADER_NOTIFY_WHEN_FEEDING", "Notify when feeding"))
MTH_FOM_STATE.ctrl.AlertEmote = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMAlertEmote", FOM_OptionsButtonText and FOM_OptionsButtonText["AlertEmote"] or MTH_FOM_L("FOM_LABEL_NOTIFY_VIA_EMOTE", "Via emote"), -360 + yAdjust) MTH_FOM_STATE.ctrl.AudioWarning = warn:Checkbox("MetaHuntOptionsFOMAudioWarning",
MTH_FOM_STATE.ctrl.AlertChat = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMAlertChat", FOM_OptionsButtonText and FOM_OptionsButtonText["AlertChat"] or MTH_FOM_L("FOM_LABEL_NOTIFY_IN_CHAT", "In chat"), -385 + yAdjust) MTH_FOM_Label("AudioWarning", "FOM_LABEL_PLAY_SOUND", "Play sound"),
MTH_FOM_STATE.ctrl.AlertNone = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMAlertNone", FOM_OptionsButtonText and FOM_OptionsButtonText["AlertNone"] or MTH_FOM_L("FOM_LABEL_NOTIFY_NONE", "Don't notify"), -410 + yAdjust) { 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 })
if MTH_FOM_STATE.ctrl.AlertEmote then MTH_FOM_STATE.ctrl.AlertEmote:SetScript("OnClick", function() MTH_FOM_SetAlert("emote"); MTH_RefreshFeedOMaticOptions() end) end MTH_FOM_STATE.ctrl.AudioWarningBell = warn:Checkbox("MetaHuntOptionsFOMAudioWarningBell",
if MTH_FOM_STATE.ctrl.AlertChat then MTH_FOM_STATE.ctrl.AlertChat:SetScript("OnClick", function() MTH_FOM_SetAlert("chat"); MTH_RefreshFeedOMaticOptions() end) end MTH_FOM_Label("AudioWarningBell", "FOM_LABEL_USE_BELL_SOUND", "Use bell sound"),
if MTH_FOM_STATE.ctrl.AlertNone then MTH_FOM_STATE.ctrl.AlertNone:SetScript("OnClick", function() MTH_FOM_SetAlert("none"); MTH_RefreshFeedOMaticOptions() end) end { 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 })
MTH_FOM_STATE.ctrl.TextWarning = warn:Checkbox("MetaHuntOptionsFOMTextWarning",
local warnHeader = container:CreateFontString("MetaHuntOptionsFOMWarnHeader", "ARTWORK", "GameFontHighlight") MTH_FOM_Label("TextWarning", "FOM_LABEL_SHOW_TEXT", "Show text"),
warnHeader:SetPoint("TOPLEFT", container, "TOPLEFT", rightColumnX, -276 + yAdjust) { onClick = function() if not this then return end FOM_Config.TextWarning = this:GetChecked() == 1 end })
warnHeader:SetText(MTH_FOM_L("FOM_HEADER_WARN_WHEN_PET_NEEDS_FEEDING", "Warn when pet needs feeding")) MTH_FOM_STATE.ctrl.IconWarning = warn:Checkbox("MetaHuntOptionsFOMIconWarning",
MTH_FOM_Label("IconWarning", "FOM_LABEL_FLASH_ICON", "Flash icon"),
MTH_FOM_STATE.ctrl.LevelContent = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMLevelContent", FOM_OptionsButtonText and FOM_OptionsButtonText["LevelContent"] or MTH_FOM_L("FOM_LABEL_WARN_WHEN_CONTENT", "When content"), -300 + yAdjust, rightColumnX) { onClick = function() if not this then return end FOM_Config.IconWarning = this:GetChecked() == 1 end })
MTH_FOM_STATE.ctrl.LevelUnhappy = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMLevelUnhappy", FOM_OptionsButtonText and FOM_OptionsButtonText["LevelUnhappy"] or MTH_FOM_L("FOM_LABEL_WARN_WHEN_UNHAPPY", "When unhappy"), -325 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.LevelOff = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMLevelOff", FOM_OptionsButtonText and FOM_OptionsButtonText["LevelOff"] or MTH_FOM_L("FOM_LABEL_DONT_WARN", "Don't warn"), -350 + yAdjust, rightColumnX)
if MTH_FOM_STATE.ctrl.LevelContent then MTH_FOM_STATE.ctrl.LevelContent:SetScript("OnClick", function() MTH_FOM_SetLevel("content"); MTH_RefreshFeedOMaticOptions() end) end
if MTH_FOM_STATE.ctrl.LevelUnhappy then MTH_FOM_STATE.ctrl.LevelUnhappy:SetScript("OnClick", function() MTH_FOM_SetLevel("unhappy"); MTH_RefreshFeedOMaticOptions() end) end
if MTH_FOM_STATE.ctrl.LevelOff then MTH_FOM_STATE.ctrl.LevelOff:SetScript("OnClick", function() MTH_FOM_SetLevel("off"); MTH_RefreshFeedOMaticOptions() end) end
MTH_FOM_STATE.ctrl.AudioWarning = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMAudioWarning", FOM_OptionsButtonText and FOM_OptionsButtonText["AudioWarning"] or MTH_FOM_L("FOM_LABEL_PLAY_SOUND", "Play sound"), -378 + yAdjust, rightColumnX)
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() 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)
cookHeader:SetText(MTH_FOM_L("FOM_HEADER_AVOID_FOODS_USED_IN_COOKING", "Avoid foods used in cooking"))
MTH_FOM_STATE.ctrl.SaveCookOrange = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMSaveCookOrange", FOM_OptionsButtonText and FOM_OptionsButtonText["SaveForCook_Orange"] or MTH_FOM_L("FOM_LABEL_ONLY_DIFFICULT_RECIPES", "Only difficult recipes"), -86 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.SaveCookYellow = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMSaveCookYellow", FOM_OptionsButtonText and FOM_OptionsButtonText["SaveForCook_Yellow"] or MTH_FOM_L("FOM_LABEL_MEDIUM_OR_BETTER", "Medium or better"), -111 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.SaveCookGreen = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMSaveCookGreen", FOM_OptionsButtonText and FOM_OptionsButtonText["SaveForCook_Green"] or MTH_FOM_L("FOM_LABEL_EASY_OR_BETTER", "Easy or better"), -136 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.SaveCookAll = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMSaveCookAll", FOM_OptionsButtonText and FOM_OptionsButtonText["SaveForCook_All"] or MTH_FOM_L("FOM_LABEL_ALL_FOODS", "All foods"), -161 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.SaveCookNone = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMSaveCookNone", FOM_OptionsButtonText and FOM_OptionsButtonText["SaveForCook_None"] or MTH_FOM_L("FOM_LABEL_DO_NOT_SAVE_COOKING_FOODS", "Do not save cooking foods"), -186 + yAdjust, rightColumnX)
if MTH_FOM_STATE.ctrl.SaveCookOrange then MTH_FOM_STATE.ctrl.SaveCookOrange:SetScript("OnClick", function() MTH_FOM_SetCookingLevel(3); MTH_RefreshFeedOMaticOptions() end) end
if MTH_FOM_STATE.ctrl.SaveCookYellow then MTH_FOM_STATE.ctrl.SaveCookYellow:SetScript("OnClick", function() MTH_FOM_SetCookingLevel(2); MTH_RefreshFeedOMaticOptions() end) end
if MTH_FOM_STATE.ctrl.SaveCookGreen then MTH_FOM_STATE.ctrl.SaveCookGreen:SetScript("OnClick", function() MTH_FOM_SetCookingLevel(1); MTH_RefreshFeedOMaticOptions() end) end
if MTH_FOM_STATE.ctrl.SaveCookAll then MTH_FOM_STATE.ctrl.SaveCookAll:SetScript("OnClick", function() MTH_FOM_SetCookingLevel(0); MTH_RefreshFeedOMaticOptions() end) end
if MTH_FOM_STATE.ctrl.SaveCookNone then MTH_FOM_STATE.ctrl.SaveCookNone:SetScript("OnClick", function() MTH_FOM_SetCookingLevel(4); MTH_RefreshFeedOMaticOptions() end) end
panel:Finish()
MTH_RefreshFeedOMaticOptions() MTH_RefreshFeedOMaticOptions()
end end
+103 -195
View File
@@ -197,31 +197,25 @@ local function MTH_ICU_OpenColorPicker(colorKey)
ColorPickerFrame:Show() ColorPickerFrame:Show()
end end
local function MTH_ICU_CreateOptionRow(container, rowName, label, key, y, labelX, dropdownX) local function MTH_ICU_BuildUI(container)
labelX = labelX or MTH_ICU_LAYOUT.LEFT_X MTH_ICU_OPT_STATE.controls = {}
dropdownX = dropdownX or MTH_ICU_LAYOUT.LEFT_DROPDOWN_X MTH_ICU_OPT_STATE.container = container
MTH_ICU_OPT_STATE.built = true
local labelFs = container:CreateFontString(rowName .. "Label", "ARTWORK", "GameFontNormalSmall") local controls = MTH_ICU_OPT_STATE.controls
labelFs:SetPoint("TOPLEFT", container, "TOPLEFT", labelX, y)
labelFs:SetText(label)
local dropdown = CreateFrame("Frame", rowName .. "Drop", container, "UIDropDownMenuTemplate") local panel = MTH_Layout.Panel(container)
dropdown:ClearAllPoints() panel:Title("MTH ICU")
dropdown:SetPoint("TOPLEFT", container, "TOPLEFT", dropdownX, y + 12) panel:Note("ICU is a minimap targeting utility module for your Trackings. It provides enhanced target information and click actions for players and NPCs on the minimap.",
if UIDropDownMenu_SetWidth then { lines = 2 })
UIDropDownMenu_SetWidth(MTH_ICU_LAYOUT.DROPDOWN_WIDTH, dropdown)
end
if UIDropDownMenu_JustifyText then
UIDropDownMenu_JustifyText("LEFT", dropdown)
end
if UIDropDownMenu_Initialize then -- Standard UIDropDownMenu initialize factory for a stored option key.
UIDropDownMenu_Initialize(dropdown, function() local function makeInit(key)
return function()
local values = MTH_ICU_GetOptionValues(key) local values = MTH_ICU_GetOptionValues(key)
local store = MTH_ICU_GetStore() local store = MTH_ICU_GetStore()
local current = tostring(store[key] or "") local current = tostring(store[key] or "")
local infoFactory = UIDropDownMenu_CreateInfo local infoFactory = UIDropDownMenu_CreateInfo
for i = 1, table.getn(values) do for i = 1, table.getn(values) do
local optionValue = tostring(values[i]) local optionValue = tostring(values[i])
local info = type(infoFactory) == "function" and infoFactory() or {} local info = type(infoFactory) == "function" and infoFactory() or {}
@@ -229,76 +223,23 @@ local function MTH_ICU_CreateOptionRow(container, rowName, label, key, y, labelX
info.checked = optionValue == current and 1 or nil info.checked = optionValue == current and 1 or nil
info.func = function() info.func = function()
MTH_ICU_SetValue(key, optionValue) MTH_ICU_SetValue(key, optionValue)
MTH_RefreshICUOptions() -- ANCHOR toggles whether the Toggle Anchor / Expand Up
-- controls exist, so rebuild instead of a plain refresh.
if key == "ANCHOR" then
MTH_SetupICUOptions()
else
MTH_RefreshICUOptions()
end
end end
if UIDropDownMenu_AddButton then UIDropDownMenu_AddButton(info) end if UIDropDownMenu_AddButton then UIDropDownMenu_AddButton(info) end
end end
end) end
end end
MTH_ICU_OPT_STATE.controls[key] = { -- ===== LEFT: Module =====
label = labelFs, local moduleBox = panel:Box("left", "MTH_ICUModuleBox", "Module")
dropdown = dropdown, controls.moduleEnabled = moduleBox:Checkbox("MetaHuntOptionsICUModuleEnabled", "Enable ICU module", {
} onClick = function()
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 if MTH and MTH.SetModuleEnabled then
local ok, err = MTH:SetModuleEnabled("icu", this:GetChecked() == 1) local ok, err = MTH:SetModuleEnabled("icu", this:GetChecked() == 1)
if not ok and MTH and MTH.Print then if not ok and MTH and MTH.Print then
@@ -306,130 +247,97 @@ local function MTH_ICU_BuildUI(container)
end end
end end
MTH_RefreshICUOptions() MTH_RefreshICUOptions()
end) end,
MTH_ICU_OPT_STATE.controls.moduleEnabled = moduleEnabled })
end controls.mouseOver = moduleBox:Checkbox("MetaHuntOptionsICUMouseOver", "Enable CTRL mouseover scan", {
onClick = function()
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() local store = MTH_ICU_GetStore()
store.mouseOver = this:GetChecked() == 1 and true or false store.mouseOver = this:GetChecked() == 1 and true or false
end) end,
MTH_ICU_OPT_STATE.controls.mouseOver = mouseOver })
-- ===== LEFT: General Settings =====
local genBox = panel:Box("left", "MTH_ICUGeneralBox", "General Settings")
controls.ALERT = { dropdown = genBox:Dropdown("MetaHuntOptionsICUAlertDrop",
{ label = "ALERT PvP", width = 130, initialize = makeInit("ALERT") }) }
controls.ANNOUNCE = { dropdown = genBox:Dropdown("MetaHuntOptionsICUAnnounceDrop",
{ label = "ANNOUNCE on click", width = 130, initialize = makeInit("ANNOUNCE") }) }
controls.HEALTH_TEXT_MODE = { dropdown = genBox:Dropdown("MetaHuntOptionsICUHealthModeDrop",
{ label = "Health text", width = 130, initialize = makeInit("HEALTH_TEXT_MODE") }) }
controls.POPUP_HIDE_DELAY = { dropdown = genBox:Dropdown("MetaHuntOptionsICUHideDelayDrop",
{ label = "List hide delay", width = 130, initialize = makeInit("POPUP_HIDE_DELAY") }) }
controls.ANCHOR = { dropdown = genBox:Dropdown("MetaHuntOptionsICUAnchorDrop",
{ label = "ANCHOR", width = 130, initialize = makeInit("ANCHOR") }) }
-- The manual-anchor controls only apply to a CUSTOM anchor. Build them
-- only in that case so the box never reserves empty space for them.
if tostring(MTH_ICU_GetStore().ANCHOR or "") == "CUSTOM" then
controls.customAnchorButton = genBox:Button("MetaHuntOptionsICUToggleAnchorButton", "Toggle Anchor", {
width = 116,
onClick = function() if type(ICU_ToggleAnchor) == "function" then ICU_ToggleAnchor() end end,
})
controls.expandUp = genBox:Checkbox("MetaHuntOptionsICUExpandUp", "Expand Up", {
onClick = function()
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,
})
end end
local sectionHeader = container:CreateFontString("MetaHuntOptionsICUSectionHeader", "ARTWORK", "GameFontHighlight") -- ===== RIGHT: Background Colors =====
sectionHeader:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_ICU_LAYOUT.LEFT_X, -146) local bgBox = panel:Box("right", "MTH_ICUBgBox", "Background Colors")
sectionHeader:SetText("General Settings") controls.PLAYER_COLOR_MODE = { dropdown = bgBox:Dropdown("MetaHuntOptionsICUPlayerColorModeDrop",
{ label = "Player colors", width = 130, initialize = makeInit("PLAYER_COLOR_MODE") }) }
MTH_ICU_CreateOptionRow(container, "MetaHuntOptionsICUAlert", "ALERT PVP", "ALERT", -172, MTH_ICU_LAYOUT.LEFT_X, MTH_ICU_LAYOUT.LEFT_DROPDOWN_X) controls.reactionOnlyPvp = bgBox:Checkbox("MetaHuntOptionsICUReactionOnlyPvp", "Reaction only when PvP-flagged", {
MTH_ICU_CreateOptionRow(container, "MetaHuntOptionsICUAnnounce", "ANNOUNCE on click", "ANNOUNCE", -198, MTH_ICU_LAYOUT.LEFT_X, MTH_ICU_LAYOUT.LEFT_DROPDOWN_X) onClick = function()
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() local store = MTH_ICU_GetStore()
store.REACTION_ONLY_PVP_PLAYERS = this:GetChecked() == 1 and true or false store.REACTION_ONLY_PVP_PLAYERS = this:GetChecked() == 1 and true or false
end) end,
MTH_ICU_OPT_STATE.controls.reactionOnlyPvp = reactionOnlyPvp })
end
local colorHeader = container:CreateFontString("MetaHuntOptionsICUColorsHeader", "ARTWORK", "GameFontHighlight") -- ===== RIGHT: Color Pickers =====
colorHeader:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_ICU_LAYOUT.RIGHT_X, -224) local colorBox = panel:Box("right", "MTH_ICUColorBox", "Color Pickers")
colorBox:Note("MetaHuntOptionsICUColorsScope",
local colorScope = container:CreateFontString("MetaHuntOptionsICUColorsScope", "ARTWORK", "GameFontNormalSmall") "Player pickers: REACTION / FACTION / CUSTOM\nNPC pickers: always", { lines = 2 })
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 for i = 1, table.getn(MTH_ICU_COLOR_ROWS) do
local row = MTH_ICU_COLOR_ROWS[i] 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) local btn, swatch = colorBox:ColorRow("MetaHuntOptionsICUColor" .. tostring(i), {
colorY = colorY - MTH_ICU_LAYOUT.COLOR_ROW_STEP label = row.label,
buttonText = "Pick",
buttonWidth = 78,
onClick = function() MTH_ICU_OpenColorPicker(row.key) end,
})
controls[row.key] = { button = btn, swatch = swatch, isColor = true }
end end
-- ===== Player Info (placed on whichever column has the most room) =====
local infoBox = panel:Box(panel:ShorterSide(), "MTH_ICUInfoBox", "Player Info")
controls.showGuildName = infoBox:Checkbox("MetaHuntOptionsICUShowGuild", "Show player guild names", {
onClick = function()
local store = MTH_ICU_GetStore()
store.SHOW_GUILD_NAME = this:GetChecked() == 1 and true or false
end,
})
controls.showPlayerClass = infoBox:Checkbox("MetaHuntOptionsICUShowClass", "Show player class", {
onClick = function()
local store = MTH_ICU_GetStore()
store.SHOW_PLAYER_CLASS = this:GetChecked() == 1 and true or false
end,
})
controls.showPlayerRace = infoBox:Checkbox("MetaHuntOptionsICUShowRace", "Show player race", {
onClick = function()
local store = MTH_ICU_GetStore()
store.SHOW_PLAYER_RACE = this:GetChecked() == 1 and true or false
end,
})
controls.showCustomTitles = infoBox:Checkbox("MetaHuntOptionsICUShowTitles", "Show custom titles in alerts", {
onClick = function()
local store = MTH_ICU_GetStore()
store.SHOW_CUSTOM_TITLES = this:GetChecked() == 1 and true or false
end,
})
panel:Finish()
end end
function MTH_RefreshICUOptions() function MTH_RefreshICUOptions()
+608
View File
@@ -0,0 +1,608 @@
-- =====================================================================
-- MetaHunt Option Panel Layout Engine
-- ---------------------------------------------------------------------
-- One reusable builder that every option panel uses. It removes ALL
-- hand-typed pixel offsets and box heights from the panels: you declare
-- rows, the engine stacks them and auto-sizes each box.
--
-- Think of it as CSS + flexbox for WoW 1.12:
-- * MTH_Layout.Theme -> the "CSS variables" (spacing / fonts / colors).
-- Change a value here to restyle every panel.
-- * panel / box methods -> the "flexbox": append rows, boxes grow to fit.
--
-- Design rules baked in (so the classic bugs are impossible):
-- * Each box TITLE lives INSIDE its box -> titles can never collide.
-- * Box HEIGHT is computed from its content -> never cramped/too short.
-- * Box CONTENT is parented to the box frame -> correct z-order.
-- * Everything is reuse-by-name (getglobal) -> no duplicates on /rl.
--
-- Typical usage:
-- local panel = MTH_Layout.Panel(container)
-- panel:Title("Feed-O-Matic")
-- panel:Note("blue helper paragraph", { lines = 3 })
-- panel:TopCheckbox("MyEnable", "Enable module", { onClick = fn, checked = fn })
-- local box = panel:Box("left", "MyBoxName", "General")
-- box:Text("MyStatus", "Feed key: P")
-- box:Buttons({ { name="A", label="Set Key", width=90, onClick=fn },
-- { name="B", label="Clear", width=70, onClick=fn } })
-- box:Note("MyHint", "blue helper text", { lines = 3 })
-- local cb = box:Checkbox("MyOpt", "Some option", { onClick = fn, checked = true })
-- box:EditRow("MyLbl", "Keep open bag slots:", "MyEdit", 40, { numeric = true, onChange = fn })
-- panel:Finish()
-- =====================================================================
MTH_Layout = MTH_Layout or {}
local L = MTH_Layout
-- ---------------------------------------------------------------------
-- THEME ("CSS variables": tweak these to restyle the whole UI)
-- ---------------------------------------------------------------------
L.Theme = {
-- Spacing (pixels)
panelPad = 12, -- outer gutter from the panel edges
colGap = 12, -- horizontal gap between the two columns
boxGap = 16, -- vertical gap between stacked boxes
colTopGap = 8, -- gap between the header block and the first boxes
boxPadX = 10, -- content inset from a box's left edge
boxPadTop = 28, -- first row offset from a box's top (clears inside title)
boxPadBottom = 12, -- padding beneath the last row inside a box
cbInset = 8, -- checkbox inset from a box's left edge
rowCheckbox = 24, -- vertical step consumed by a checkbox / edit row
rowButton = 30, -- vertical step consumed by a button row
rowSlider = 46, -- vertical step consumed by a slider (label+track+value)
rowDropdown = 50, -- vertical step consumed by a dropdown (label+menu)
textLine = 14, -- height of one wrapped text line
textGap = 6, -- extra gap after a text/note block
titleInsetX = 10, -- box title inset X (inside the box)
titleInsetY = -12, -- box title inset Y (inside the box)
headTop = -12, -- Y where the panel header starts
headTitleStep = 24, -- vertical step after the panel title
headNoteGap = 10, -- extra gap after a header note
headRowGap = 8, -- extra gap after a header checkbox
headLinkStep = 18, -- vertical step after a header link
-- Fonts
fontPanelTitle = "GameFontNormal", -- panel heading
fontBoxTitle = "GameFontNormal", -- box title (recolored)
fontLabel = "GameFontNormalSmall", -- checkbox / edit labels
fontText = "GameFontNormalSmall", -- plain text
fontNote = "GameFontNormalSmall", -- helper paragraphs
fontLink = "GameFontHighlightSmall",
-- Colors { r, g, b }
colPanelTitle = { 1.00, 0.82, 0.00 }, -- gold
colBoxTitle = { 1.00, 1.00, 1.00 }, -- white
colNote = { 0.35, 0.65, 1.00 }, -- blue helper text
colText = { 0.93, 0.93, 0.93 }, -- normal text
colLink = { 0.40, 0.75, 1.00 }, -- link idle
colLinkHover = { 0.60, 0.90, 1.00 }, -- link hover
-- Templates
boxTemplate = "OptionFrameBoxTemplate",
checkTemplate = "UICheckButtonTemplate",
buttonTemplate = "UIPanelButtonTemplate",
editTemplate = "InputBoxTemplate",
}
-- ---------------------------------------------------------------------
-- Small shared helpers
-- ---------------------------------------------------------------------
local function applyColor(region, c)
if region and c then region:SetTextColor(c[1], c[2], c[3]) end
end
local function ensureFontString(parent, name, font)
local fs = getglobal(name)
if not fs then
fs = parent:CreateFontString(name, "ARTWORK", font)
end
fs:Show()
return fs
end
local function ensureFrame(kind, name, parent, template)
local f = getglobal(name)
if not f then
f = CreateFrame(kind, name, parent, template)
end
f:SetParent(parent)
return f
end
-- Attach (or refresh) a checkbox's label, tooltip, checked state and click.
-- labelWidth (optional) enables word-wrap; the wrapped text height is returned
-- so the caller can grow the row to fit multi-line labels.
local function configureCheckbox(cb, label, opts, T, labelWidth)
local txt = getglobal(cb:GetName() .. "Text")
if not txt then
txt = cb:CreateFontString(cb:GetName() .. "Text", "ARTWORK", T.fontLabel)
end
-- Anchor the label to the checkbox's top-right and let it grow downward so
-- wrapped lines never collide with the row above. Centre a single line
-- vertically against the checkbox.
local cbH = (cb.GetHeight and cb:GetHeight()) or 24
local topOff = -((cbH - T.textLine) / 2)
if topOff > 0 then topOff = 0 end
txt:ClearAllPoints()
txt:SetPoint("TOPLEFT", cb, "TOPRIGHT", 4, topOff)
if labelWidth and labelWidth > 0 then
txt:SetWidth(labelWidth)
else
txt:SetWidth(0)
end
txt:SetJustifyH("LEFT")
txt:SetJustifyV("TOP")
txt:SetText(label or "")
txt:Show()
if opts.checked ~= nil then
if type(opts.checked) == "function" then
cb:SetChecked(opts.checked())
else
cb:SetChecked(opts.checked)
end
end
if opts.onClick then
cb:SetScript("OnClick", opts.onClick)
end
if opts.tooltip and opts.tooltip ~= "" then
cb.mthTooltip = opts.tooltip
cb:SetScript("OnEnter", function()
GameTooltip:SetOwner(cb, "ANCHOR_RIGHT")
GameTooltip:SetText(cb.mthTooltip, 1, 1, 1, 1, true)
GameTooltip:Show()
end)
cb:SetScript("OnLeave", function() GameTooltip:Hide() end)
end
local h = 0
if txt.GetStringHeight then h = txt:GetStringHeight() end
if (not h or h <= 0) and txt.GetHeight then h = txt:GetHeight() end
return h or 0
end
-- ---------------------------------------------------------------------
-- Box object (returned by panel:Box). Appends rows top-to-bottom and
-- keeps the box height in sync with its content.
-- ---------------------------------------------------------------------
local function makeBox(container, box, boxWidth, state, T)
local self = {}
local function apply()
state.height = -state.cursor + T.boxPadBottom
box:SetHeight(state.height)
end
function self:Checkbox(name, label, opts)
opts = opts or {}
local cb = ensureFrame("CheckButton", name, box, T.checkTemplate)
cb:ClearAllPoints()
cb:SetPoint("TOPLEFT", box, "TOPLEFT", T.cbInset, state.cursor)
cb:Show()
-- Wrap width = box content width minus the checkbox frame, the 4px label
-- gap, and a 2px right cushion so wrapped text never touches the border.
local cbW = (cb.GetWidth and cb:GetWidth()) or 26
local labelWidth = boxWidth - T.boxPadX - (T.cbInset + cbW + 4) - 2
local txtH = configureCheckbox(cb, label, opts, T, labelWidth)
-- A single line keeps the compact row height; a wrapped label grows the
-- row to clear its top-centre offset plus a gap before the next row.
local rowH = T.rowCheckbox
local needed = txtH + 8
if txtH > (T.textLine + 4) then needed = txtH + 14 end
if needed > rowH then rowH = needed end
state.cursor = state.cursor - rowH
apply()
return cb
end
function self:Text(name, text, opts)
opts = opts or {}
local fs = ensureFontString(box, name, opts.font or T.fontText)
fs:ClearAllPoints()
fs:SetPoint("TOPLEFT", box, "TOPLEFT", T.boxPadX, state.cursor)
fs:SetWidth(opts.width or (boxWidth - T.boxPadX * 2))
fs:SetJustifyH("LEFT")
fs:SetJustifyV("TOP")
fs:SetText(text or "")
applyColor(fs, opts.color or T.colText)
-- Reserve vertical space: honour an explicit line count, otherwise
-- measure the wrapped height so callers don't have to guess.
local reserve
if opts.lines then
reserve = opts.lines * T.textLine
else
local h = (fs.GetStringHeight and fs:GetStringHeight()) or 0
if (not h or h <= 0) and fs.GetHeight then h = fs:GetHeight() end
if not h or h <= 0 then h = T.textLine end
reserve = h
end
state.cursor = state.cursor - reserve - T.textGap
apply()
return fs
end
-- Blue helper paragraph (same as MM Widget style), usable inside a box.
function self:Note(name, text, opts)
opts = opts or {}
opts.color = opts.color or T.colNote
opts.font = opts.font or T.fontNote
return self:Text(name, text, opts)
end
-- One row of buttons placed left-to-right.
-- list = { { name, label, width, height, onClick, gap, template }, ... }
function self:Buttons(list)
local out = {}
local x = T.boxPadX
local i = 1
while list and i <= table.getn(list) do
local spec = list[i]
local b = ensureFrame("Button", spec.name, box, spec.template or T.buttonTemplate)
b:ClearAllPoints()
b:SetPoint("TOPLEFT", box, "TOPLEFT", x, state.cursor)
b:SetWidth(spec.width or 90)
b:SetHeight(spec.height or 22)
b:SetText(spec.label or "")
b:Show()
if spec.onClick then b:SetScript("OnClick", spec.onClick) end
table.insert(out, b)
x = x + (spec.width or 90) + (spec.gap or 8)
i = i + 1
end
state.cursor = state.cursor - T.rowButton
apply()
return out
end
-- Label + edit box on one row (e.g. "Keep open bag slots: [__]").
function self:EditRow(labelName, labelText, editName, editWidth, opts)
opts = opts or {}
local lbl = ensureFontString(box, labelName, T.fontLabel)
lbl:ClearAllPoints()
lbl:SetPoint("TOPLEFT", box, "TOPLEFT", T.boxPadX, state.cursor)
lbl:SetText(labelText or "")
applyColor(lbl, T.colText)
local edit = ensureFrame("EditBox", editName, box, T.editTemplate)
edit:ClearAllPoints()
edit:SetPoint("LEFT", lbl, "RIGHT", 8, 0)
edit:SetWidth(editWidth or 40)
edit:SetHeight(opts.height or 20)
if opts.numeric then edit:SetNumeric(true) end
edit:SetAutoFocus(false)
edit:Show()
if opts.onChange then edit:SetScript("OnTextChanged", opts.onChange) end
edit:SetScript("OnEnterPressed", function() if this then this:ClearFocus() end end)
edit:SetScript("OnEscapePressed", function() if this then this:ClearFocus() end end)
state.cursor = state.cursor - T.rowCheckbox
apply()
return edit, lbl
end
-- Slider with a left label and a right-aligned live value readout.
-- opts = { label, min, max, step, value, width, onChange, format }
-- format(value) -> string (defaults to a rounded integer).
function self:Slider(name, opts)
opts = opts or {}
local lbl = ensureFontString(box, name .. "Caption", T.fontLabel)
lbl:ClearAllPoints()
lbl:SetPoint("TOPLEFT", box, "TOPLEFT", T.boxPadX, state.cursor)
lbl:SetText(opts.label or "")
applyColor(lbl, T.colText)
local valueFs = ensureFontString(box, name .. "Value", T.fontLabel)
valueFs:ClearAllPoints()
valueFs:SetPoint("TOPRIGHT", box, "TOPRIGHT", -T.boxPadX, state.cursor)
valueFs:SetJustifyH("RIGHT")
applyColor(valueFs, T.colPanelTitle)
local slider = ensureFrame("Slider", name, box, "OptionsSliderTemplate")
slider:ClearAllPoints()
slider:SetPoint("TOPLEFT", box, "TOPLEFT", T.boxPadX + 4, state.cursor - 18)
slider:SetWidth(opts.width or (boxWidth - T.boxPadX * 2 - 8))
slider:SetMinMaxValues(opts.min or 0, opts.max or 1)
slider:SetValueStep(opts.step or 1)
slider:Show()
-- OptionsSliderTemplate ships Low/High/Text children; silence them.
local lowFs = getglobal(name .. "Low"); if lowFs then lowFs:SetText(""); lowFs:Hide() end
local highFs = getglobal(name .. "High"); if highFs then highFs:SetText(""); highFs:Hide() end
local midFs = getglobal(name .. "Text"); if midFs then midFs:SetText("") end
slider.mthValueFs = valueFs
slider.mthFormat = opts.format
slider.mthOnChange = opts.onChange
slider:SetScript("OnValueChanged", function()
local v = tonumber(arg1)
if not v and this and this.GetValue then v = this:GetValue() end
if not v then v = 0 end
if this.mthValueFs then
if this.mthFormat then
this.mthValueFs:SetText(this.mthFormat(v))
else
this.mthValueFs:SetText(tostring(math.floor(v + 0.5)))
end
end
if this.mthOnChange then this.mthOnChange(v) end
end)
slider:SetValue(opts.value or opts.min or 0)
state.cursor = state.cursor - T.rowSlider
apply()
return slider
end
-- Dropdown menu with a left label. The caller supplies the standard
-- UIDropDownMenu initialize function and (optionally) the current text.
-- opts = { label, width, initialize, text }
function self:Dropdown(name, opts)
opts = opts or {}
local lbl = ensureFontString(box, name .. "Caption", T.fontLabel)
lbl:ClearAllPoints()
lbl:SetPoint("TOPLEFT", box, "TOPLEFT", T.boxPadX, state.cursor)
lbl:SetText(opts.label or "")
applyColor(lbl, T.colText)
local dd = ensureFrame("Frame", name, box, "UIDropDownMenuTemplate")
dd:ClearAllPoints()
-- UIDropDownMenu textures carry ~15px of built-in left inset.
dd:SetPoint("TOPLEFT", box, "TOPLEFT", T.boxPadX - 15, state.cursor - 16)
dd:Show()
if UIDropDownMenu_SetWidth then
UIDropDownMenu_SetWidth(opts.width or (boxWidth - T.boxPadX * 2 - 20), dd)
end
if UIDropDownMenu_JustifyText then UIDropDownMenu_JustifyText("LEFT", dd) end
if opts.initialize and UIDropDownMenu_Initialize then
UIDropDownMenu_Initialize(dd, opts.initialize)
end
if opts.text and UIDropDownMenu_SetText then
UIDropDownMenu_SetText(opts.text, dd)
end
state.cursor = state.cursor - T.rowDropdown
apply()
return dd
end
-- Single button occupying one button row (thin wrapper over :Buttons).
function self:Button(name, label, opts)
opts = opts or {}
local out = self:Buttons({ {
name = name, label = label,
width = opts.width, height = opts.height,
onClick = opts.onClick, template = opts.template,
} })
return out[1]
end
-- Label on the left, a colour swatch + "Pick" button on the right.
-- opts = { label, color = {r,g,b}, buttonText, buttonWidth, onClick }
function self:ColorRow(name, opts)
opts = opts or {}
local lbl = ensureFontString(box, name .. "Caption", T.fontLabel)
lbl:ClearAllPoints()
lbl:SetPoint("TOPLEFT", box, "TOPLEFT", T.boxPadX, state.cursor)
lbl:SetText(opts.label or "")
applyColor(lbl, T.colText)
local btn = ensureFrame("Button", name .. "Button", box, T.buttonTemplate)
btn:ClearAllPoints()
btn:SetPoint("TOPRIGHT", box, "TOPRIGHT", -T.boxPadX, state.cursor + 2)
btn:SetWidth(opts.buttonWidth or 60)
btn:SetHeight(20)
btn:SetText(opts.buttonText or "Pick")
btn:Show()
if opts.onClick then btn:SetScript("OnClick", opts.onClick) end
local swatch = getglobal(name .. "Swatch")
if not swatch then
swatch = btn:CreateTexture(name .. "Swatch", "OVERLAY")
swatch:SetTexture("Interface\\Buttons\\WHITE8X8")
swatch:SetWidth(12)
swatch:SetHeight(12)
swatch:SetPoint("LEFT", btn, "LEFT", 5, 0)
end
if opts.color then
swatch:SetVertexColor(opts.color[1], opts.color[2], opts.color[3])
end
state.cursor = state.cursor - T.rowCheckbox
apply()
return btn, swatch
end
-- Manual vertical spacer.
function self:Gap(px)
state.cursor = state.cursor - (px or 12)
apply()
end
-- Reserve a custom row of the given pixel height. Returns the box frame and
-- the TOPLEFT y-offset (within the box) where custom widgets can anchor.
-- Use for composite rows the primitives don't cover (e.g. checkbox +
-- dropdown + edit box on one line). x anchoring is left to the caller.
function self:Row(height)
local y = state.cursor
state.cursor = state.cursor - (height or T.rowCheckbox)
apply()
return box, y
end
-- Left content inset (px) for callers positioning custom row widgets.
function self:PadX() return T.boxPadX end
function self:Frame() return box end
return self
end
-- ---------------------------------------------------------------------
-- Panel object (returned by MTH_Layout.Panel).
-- ---------------------------------------------------------------------
function L.Panel(container)
if not container then return nil end
local T = L.Theme
MTH_ClearContainer(container)
local cname = (container.GetName and container:GetName()) or "MTHPanel"
local width = (container.GetWidth and container:GetWidth()) or 0
if not width or width < 460 then width = 580 end
local colWidth = math.floor((width - T.panelPad * 2 - T.colGap) / 2)
if colWidth < 220 then colWidth = 220 end
local leftX = T.panelPad
local rightX = T.panelPad + colWidth + T.colGap
local fullWidth = width - T.panelPad * 2
local headY = T.headTop
local started = false
local col = {
left = { x = leftX, y = 0, open = nil },
right = { x = rightX, y = 0, open = nil },
}
local noteId = 0
local panel = {}
local function startColumns()
if started then return end
started = true
local top = headY - T.colTopGap
col.left.y = top
col.right.y = top
end
local function closeSide(side)
local c = col[side]
if c and c.open then
c.y = c.y - c.open.height - T.boxGap
c.open = nil
end
end
-- ---- Header elements (full width, above the columns) ----
function panel:Title(text)
local fs = ensureFontString(container, cname .. "_Title", T.fontPanelTitle)
fs:ClearAllPoints()
fs:SetPoint("TOPLEFT", container, "TOPLEFT", T.panelPad, headY)
fs:SetText(text or "")
applyColor(fs, T.colPanelTitle)
headY = headY - T.headTitleStep
return fs
end
function panel:Note(text, opts)
opts = opts or {}
noteId = noteId + 1
local fs = ensureFontString(container, cname .. "_Note" .. noteId, T.fontNote)
fs:ClearAllPoints()
fs:SetPoint("TOPLEFT", container, "TOPLEFT", T.panelPad, headY)
fs:SetWidth(opts.width or fullWidth)
fs:SetJustifyH("LEFT")
fs:SetJustifyV("TOP")
fs:SetText(text or "")
applyColor(fs, opts.color or T.colNote)
local lines = opts.lines or 1
headY = headY - (lines * T.textLine) - T.headNoteGap
return fs
end
function panel:Link(name, text, onClick)
local btn = ensureFrame("Button", name, container, nil)
btn:ClearAllPoints()
btn:SetPoint("TOPLEFT", container, "TOPLEFT", T.panelPad, headY)
btn:SetHeight(16)
btn:SetWidth(fullWidth)
local fs = getglobal(name .. "Text")
if not fs then
fs = btn:CreateFontString(name .. "Text", "ARTWORK", T.fontLink)
fs:SetPoint("LEFT", btn, "LEFT", 0, 0)
fs:SetJustifyH("LEFT")
end
fs:SetText(text or "")
applyColor(fs, T.colLink)
btn:Show()
btn:SetScript("OnEnter", function() applyColor(fs, T.colLinkHover) end)
btn:SetScript("OnLeave", function() applyColor(fs, T.colLink) end)
if onClick then btn:SetScript("OnClick", onClick) end
headY = headY - T.headLinkStep
return btn
end
function panel:TopCheckbox(name, label, opts)
opts = opts or {}
local cb = ensureFrame("CheckButton", name, container, T.checkTemplate)
cb:ClearAllPoints()
cb:SetPoint("TOPLEFT", container, "TOPLEFT", T.panelPad, headY)
cb:Show()
local txtH = configureCheckbox(cb, label, opts, T, fullWidth - 26)
local rowH = T.rowCheckbox
if (txtH + 8) > rowH then rowH = txtH + 8 end
headY = headY - rowH - T.headRowGap
return cb
end
-- ---- Boxes (columns) ----
function panel:Box(side, name, title)
startColumns()
side = (side == "right") and "right" or "left"
closeSide(side)
local c = col[side]
local box = ensureFrame("Frame", name, container, T.boxTemplate)
box:ClearAllPoints()
box:SetPoint("TOPLEFT", container, "TOPLEFT", c.x, c.y)
box:SetWidth(colWidth)
box:Show()
local titleFS = getglobal(name .. "Title")
if titleFS then
titleFS:SetText(title or "")
applyColor(titleFS, T.colBoxTitle)
titleFS:ClearAllPoints()
titleFS:SetPoint("TOPLEFT", box, "TOPLEFT", T.titleInsetX, T.titleInsetY)
end
local state = {
cursor = -T.boxPadTop,
height = T.boxPadTop + T.boxPadBottom,
}
box:SetHeight(state.height)
c.open = state
return makeBox(container, box, colWidth, state, T)
end
-- Returns "left" or "right": whichever column currently has more room
-- left (i.e. its content bottom is higher). Lets callers auto-balance
-- a variable number of boxes without hand-picking columns.
function panel:ShorterSide()
startColumns()
local function bottom(side)
local c = col[side]
local y = c.y
if c.open then y = y - c.open.height - T.boxGap end
return y
end
if bottom("right") > bottom("left") then return "right" end
return "left"
end
function panel:Finish()
closeSide("left")
closeSide("right")
end
-- Expose geometry for callers that need it (rare).
panel.colWidth = colWidth
panel.fullWidth = fullWidth
return panel
end
+4 -5
View File
@@ -300,11 +300,10 @@ function MTH_SetupProfilesOptions()
if not container then return end if not container then return end
if not gSetupDone then if not gSetupDone then
-- Title local panel = MTH_Layout.Panel(container)
local title = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
title:SetPoint("TOPLEFT", container, "TOPLEFT", 6, -6) -- Title (engine header, consistent gold styling)
title:SetText("Profiles") local title = panel:Title("Profiles")
title:SetTextColor(1.00, 0.82, 0.00)
-- Active profile indicator -- Active profile indicator
gActiveLabel = container:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall") gActiveLabel = container:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
+132 -3
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 = MTH_OPTIONS_CONST or {}
MTH_OPTIONS_CONST.NAV_DEFAULT_WIDTH = MTH_OPTIONS_CONST.NAV_DEFAULT_WIDTH or 120 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.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_TOP_OFFSET = MTH_OPTIONS_CONST.CONTENT_TOP_OFFSET or -30
MTH_OPTIONS_CONST.CONTENT_HEIGHT = MTH_OPTIONS_CONST.CONTENT_HEIGHT or 540 MTH_OPTIONS_CONST.CONTENT_HEIGHT = MTH_OPTIONS_CONST.CONTENT_HEIGHT or 600
MTH_OPTIONS_CONST.CLOSE_BTN_SIZE = MTH_OPTIONS_CONST.CLOSE_BTN_SIZE or 22 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_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 MTH_OPTIONS_CONST.CLOSE_BTN_OFFSET_Y = MTH_OPTIONS_CONST.CLOSE_BTN_OFFSET_Y or -8
@@ -20,6 +20,62 @@ local function MTH_OPT_L(key, default)
return default or key return default or key
end end
-- Capture our PizzaSauce instance for the options-window open animation (guarded).
local MTH_OPT_Sauce = PizzaSauce
local function MTH_OPT_AnimateOpen(frame)
if not MTH_OPT_Sauce or not frame then return end
if type(MTH_FX_IsEnabled) == "function" and not MTH_FX_IsEnabled("ui.options") then return end
-- Hide alpha this frame so there is no one-frame opaque flash before onStart.
if type(frame.SetAlpha) == "function" then frame:SetAlpha(0) end
-- Bigger travel + a springy "outBack" overshoot so the window clearly
-- pops up into place instead of a subtle nudge.
MTH_OPT_Sauce:SlideIn(frame, "UP", 110, 0.5, "outBack")
end
-- Fade the active settings panel when switching options tabs, unless disabled.
local function MTH_OPT_AnimateTabContent(tabKey)
if not MTH_OPT_Sauce then return end
if type(MTH_FX_IsEnabled) == "function" and not MTH_FX_IsEnabled("ui.optiontabs") then return end
if type(MTH_OPTIONS_TABS) ~= "table" then return end
for i = 1, table.getn(MTH_OPTIONS_TABS) do
if MTH_OPTIONS_TABS[i].key == tabKey then
local frame = MTH_GetFrame(MTH_OPTIONS_TABS[i].frame)
if frame then
if type(frame.SetAlpha) == "function" then frame:SetAlpha(0) end
-- Slide the panel in from the right while fading so the content
-- visibly moves, not just brightens.
MTH_OPT_Sauce:SlideIn(frame, "RIGHT", 55, 0.3, "outCubic")
end
return
end
end
end
-- Slide the options window down and out before hiding it, unless disabled.
local function MTH_OPT_AnimateClose()
local frame = MetaHuntOptions
if not frame then return end
if not MTH_OPT_Sauce
or (type(MTH_FX_IsEnabled) == "function" and not MTH_FX_IsEnabled("ui.optionsclose")) then
frame:SetAlpha(1)
frame:Hide()
return
end
-- Capture the resting anchor so SlideOut's downward displacement can be
-- restored once the window is hidden (otherwise the next open is offset).
local p, rel, rp, x, y = frame:GetPoint(1)
MTH_OPT_Sauce:SlideOut(frame, "DOWN", 90, 0.26, "inCubic", {
onFinish = function()
frame:Hide()
frame:SetAlpha(1)
if p then
frame:ClearAllPoints()
frame:SetPoint(p, rel, rp, x, y)
end
end,
})
end
local function MTH_RegisterEscClose(frameName) local function MTH_RegisterEscClose(frameName)
if not (frameName and UISpecialFrames) then if not (frameName and UISpecialFrames) then
return return
@@ -76,7 +132,7 @@ local function MTH_EnsureOptionsFrame()
closeBtn:SetWidth(MTH_OPTIONS_CONST.CLOSE_BTN_SIZE) closeBtn:SetWidth(MTH_OPTIONS_CONST.CLOSE_BTN_SIZE)
closeBtn:SetHeight(MTH_OPTIONS_CONST.CLOSE_BTN_SIZE) closeBtn:SetHeight(MTH_OPTIONS_CONST.CLOSE_BTN_SIZE)
closeBtn:SetText(MTH_OPT_L("COMMON_CLOSE_SHORT", "X")) closeBtn:SetText(MTH_OPT_L("COMMON_CLOSE_SHORT", "X"))
closeBtn:SetScript("OnClick", function() MetaHuntOptions:Hide() end) closeBtn:SetScript("OnClick", function() MTH_OPT_AnimateClose() end)
end end
MTH_BuildOptionsTree() MTH_BuildOptionsTree()
@@ -133,6 +189,11 @@ function MTH_SelectOptionsTab(tabKey)
MTH_SetupMessagesOptions() MTH_SetupMessagesOptions()
end end
end end
elseif tabKey == "ZBar" then
if type(MTH_SetupZBarOptions) == "function" then
MTH_SetupZBarOptions()
MTH_OPTIONS_SETUP["ZBar"] = true
end
elseif tabKey == "Pet" then elseif tabKey == "Pet" then
if not MTH_OPTIONS_SETUP["Pet"] then if not MTH_OPTIONS_SETUP["Pet"] then
MTH_SetupPetOptions() MTH_SetupPetOptions()
@@ -182,6 +243,11 @@ function MTH_SelectOptionsTab(tabKey)
MTH_SetupToysOptions() MTH_SetupToysOptions()
MTH_OPTIONS_SETUP["Toys"] = true MTH_OPTIONS_SETUP["Toys"] = true
end end
elseif tabKey == "Craft" then
if not MTH_OPTIONS_SETUP["Craft"] then
MTH_SetupCraftOptions()
MTH_OPTIONS_SETUP["Craft"] = true
end
elseif tabKey == "SmartAmmo" then elseif tabKey == "SmartAmmo" then
if not MTH_OPTIONS_SETUP["SmartAmmo"] then if not MTH_OPTIONS_SETUP["SmartAmmo"] then
MTH_SetupSmartAmmoOptions() MTH_SetupSmartAmmoOptions()
@@ -205,6 +271,13 @@ function MTH_SelectOptionsTab(tabKey)
MTH_SetupAutoQuestOptions() MTH_SetupAutoQuestOptions()
MTH_OPTIONS_SETUP["AutoQuest"] = true MTH_OPTIONS_SETUP["AutoQuest"] = true
end 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 elseif tabKey == "ICU" then
if type(MTH_SetupICUOptions) == "function" then if type(MTH_SetupICUOptions) == "function" then
MTH_SetupICUOptions() MTH_SetupICUOptions()
@@ -215,6 +288,13 @@ function MTH_SelectOptionsTab(tabKey)
elseif MTH and MTH.Print then elseif MTH and MTH.Print then
MTH:Print("ICU options setup function is not loaded", "error") MTH:Print("ICU options setup function is not loaded", "error")
end end
elseif tabKey == "Animations" then
if not MTH_OPTIONS_SETUP["Animations"] then
if type(MTH_SetupAnimationsOptions) == "function" then
MTH_SetupAnimationsOptions()
MTH_OPTIONS_SETUP["Animations"] = true
end
end
elseif tabKey == "Credits" then elseif tabKey == "Credits" then
local setupCredits = _G and _G["MTH_SetupCreditsOptions"] local setupCredits = _G and _G["MTH_SetupCreditsOptions"]
if type(setupCredits) == "function" then if type(setupCredits) == "function" then
@@ -243,6 +323,7 @@ function MTH_SelectOptionsTab(tabKey)
end end
end end
end end
MTH_OPT_AnimateTabContent(tabKey)
end end
function MTH_ResetAndSelectOptionsTab(tabKey) function MTH_ResetAndSelectOptionsTab(tabKey)
@@ -254,6 +335,52 @@ function MTH_ResetAndSelectOptionsTab(tabKey)
MTH_SelectOptionsTab(tabKey) MTH_SelectOptionsTab(tabKey)
end end
-- On a blocked realm the options window must be inert too. Instead of drawing any tabs we
-- drop an opaque "DISABLED." panel over the whole window (with its own close button so it can
-- still be dismissed). Returns true when the realm gate is active.
local function MTH_OPTIONS_UpdateRealmGateOverlay(frame)
if not frame then return false end
local blocked = (MTH and MTH.IsRealmGateBlocked and MTH:IsRealmGateBlocked()) and true or false
local overlay = frame.realmGateOverlay
if not overlay then
if not blocked then return false end
overlay = CreateFrame("Frame", "MetaHuntOptionsRealmGateOverlay", frame)
overlay:SetAllPoints(frame)
overlay:EnableMouse(true)
overlay:EnableMouseWheel(true)
overlay:SetFrameStrata("FULLSCREEN_DIALOG")
if overlay.SetFrameLevel and frame.GetFrameLevel then
overlay:SetFrameLevel(frame:GetFrameLevel() + 50)
end
local bg = overlay:CreateTexture(nil, "BACKGROUND")
bg:SetAllPoints(overlay)
bg:SetTexture("Interface\\Buttons\\WHITE8X8")
if bg.SetVertexColor then bg:SetVertexColor(0, 0, 0, 1) end
local label = overlay:CreateFontString(nil, "OVERLAY")
label:SetPoint("CENTER", overlay, "CENTER", 0, 0)
if label.SetFont then label:SetFont("Fonts\\FRIZQT__.TTF", 48, "OUTLINE") end
label:SetText("DISABLED.")
label:SetTextColor(1.0, 0.1, 0.1)
local close = CreateFrame("Button", nil, overlay, "UIPanelCloseButton")
close:SetPoint("TOPRIGHT", overlay, "TOPRIGHT", -4, -4)
close:SetScript("OnClick", function() frame:Hide() end)
frame.realmGateOverlay = overlay
end
if blocked then
overlay:Show()
else
overlay:Hide()
end
return blocked
end
function MTH_OpenOptions(tabKey) function MTH_OpenOptions(tabKey)
if not MTH_OPTIONS_SHELL_READY then return end if not MTH_OPTIONS_SHELL_READY then return end
local optionsFrame = MTH_EnsureOptionsFrame() local optionsFrame = MTH_EnsureOptionsFrame()
@@ -261,5 +388,7 @@ function MTH_OpenOptions(tabKey)
return return
end end
optionsFrame:Show() optionsFrame:Show()
MTH_OPT_AnimateOpen(optionsFrame)
if MTH_OPTIONS_UpdateRealmGateOverlay(optionsFrame) then return end
MTH_SelectOptionsTab(tabKey or "General") MTH_SelectOptionsTab(tabKey or "General")
end end
+11 -3
View File
@@ -1,4 +1,4 @@
MTH_OPTIONS_TREE_STATE = MTH_OPTIONS_TREE_STATE or { zButtons = true, chronometer = true } MTH_OPTIONS_TREE_STATE = MTH_OPTIONS_TREE_STATE or { zButtons = false, chronometer = false }
MTH_OPTIONS_TREE_BUTTONS = MTH_OPTIONS_TREE_BUTTONS or {} MTH_OPTIONS_TREE_BUTTONS = MTH_OPTIONS_TREE_BUTTONS or {}
MTH_OPTIONS_STATE = MTH_OPTIONS_STATE or {} MTH_OPTIONS_STATE = MTH_OPTIONS_STATE or {}
if MTH_OPTIONS_STATE.activeTab == nil and MTH_OPTIONS_ACTIVE_TAB ~= nil then if MTH_OPTIONS_STATE.activeTab == nil and MTH_OPTIONS_ACTIVE_TAB ~= nil then
@@ -30,7 +30,8 @@ MTH_OPTIONS_TABS = MTH_OPTIONS_TABS or {
{ key = "General", label = "General", frame = "MetaHuntOptionsGeneral" }, { key = "General", label = "General", frame = "MetaHuntOptionsGeneral" },
{ key = "Profiles", label = "Profiles", frame = "MetaHuntOptionsProfiles" }, { key = "Profiles", label = "Profiles", frame = "MetaHuntOptionsProfiles" },
{ key = "Messages", label = "Messages", frame = "MetaHuntOptionsMessages" }, { key = "Messages", label = "Messages", frame = "MetaHuntOptionsMessages" },
{ key = "Pet", label = "ZPet", frame = "MetaHuntOptionsPet" }, { key = "ZBar", label = "zBar", frame = "MetaHuntOptionsZBar" },
{ key = "Pet", label = "ZPet", frame = "MetaHuntOptionsPet" },
{ key = "Track", label = "ZTrack", frame = "MetaHuntOptionsTrack" }, { key = "Track", label = "ZTrack", frame = "MetaHuntOptionsTrack" },
{ key = "Aspect", label = "ZAspect", frame = "MetaHuntOptionsAspect" }, { key = "Aspect", label = "ZAspect", frame = "MetaHuntOptionsAspect" },
{ key = "Trap", label = "ZTrap", frame = "MetaHuntOptionsTrap" }, { key = "Trap", label = "ZTrap", frame = "MetaHuntOptionsTrap" },
@@ -39,16 +40,19 @@ MTH_OPTIONS_TABS = MTH_OPTIONS_TABS or {
{ key = "Mounts", label = "ZMounts", frame = "MetaHuntOptionsMounts" }, { key = "Mounts", label = "ZMounts", frame = "MetaHuntOptionsMounts" },
{ key = "Companions", label = "ZCompanions", frame = "MetaHuntOptionsCompanions" }, { key = "Companions", label = "ZCompanions", frame = "MetaHuntOptionsCompanions" },
{ key = "Toys", label = "ZToys", frame = "MetaHuntOptionsToys" }, { key = "Toys", label = "ZToys", frame = "MetaHuntOptionsToys" },
{ key = "Craft", label = "ZCraft", frame = "MetaHuntOptionsCraft" },
{ key = "SmartAmmo", label = "Smart Ammo", frame = "MetaHuntOptionsSmartAmmo" }, { key = "SmartAmmo", label = "Smart Ammo", frame = "MetaHuntOptionsSmartAmmo" },
{ key = "FeedOMatic", label = "FeedOMatic", frame = "MetaHuntOptionsFeedOMatic" }, { key = "FeedOMatic", label = "FeedOMatic", frame = "MetaHuntOptionsFeedOMatic" },
{ key = "AutoBuy", label = "Auto Buy", frame = "MetaHuntOptionsAutoBuy" }, { key = "AutoBuy", label = "Auto Buy", frame = "MetaHuntOptionsAutoBuy" },
{ key = "AutoQuest", label = "Auto Quest", frame = "MetaHuntOptionsAutoQuest" }, { key = "AutoQuest", label = "Auto Quest", frame = "MetaHuntOptionsAutoQuest" },
{ key = "ICU", label = "ICU", frame = "MetaHuntOptionsICU" }, { key = "ICU", label = "ICU", frame = "MetaHuntOptionsICU" },
{ key = "ExpAmmo", label = "MM Widget", frame = "MetaHuntOptionsExpAmmo" },
{ key = "ChronometerGeneral", label = "General", frame = "MetaHuntOptionsChronometer" }, { key = "ChronometerGeneral", label = "General", frame = "MetaHuntOptionsChronometer" },
{ key = "ChronometerBar", label = "Bar", frame = "MetaHuntOptionsChronometer" }, { key = "ChronometerBar", label = "Bar", frame = "MetaHuntOptionsChronometer" },
{ key = "ChronometerClassSpells", label = "Hunter Spells", frame = "MetaHuntOptionsChronometer" }, { key = "ChronometerClassSpells", label = "Hunter Spells", frame = "MetaHuntOptionsChronometer" },
{ key = "ChronometerClassEvents", label = "Hunter Events", frame = "MetaHuntOptionsChronometer" }, { key = "ChronometerClassEvents", label = "Hunter Events", frame = "MetaHuntOptionsChronometer" },
{ key = "ChronometerRacial", label = "Racial", frame = "MetaHuntOptionsChronometer" }, { key = "ChronometerRacial", label = "Racial", frame = "MetaHuntOptionsChronometer" },
{ key = "Animations", label = "Animations", frame = "MetaHuntOptionsAnimations" },
{ key = "Credits", label = "Credits", frame = "MetaHuntOptionsCredits" }, { key = "Credits", label = "Credits", frame = "MetaHuntOptionsCredits" },
} }
@@ -60,11 +64,13 @@ MTH_OPTIONS_TREE = MTH_OPTIONS_TREE or {
{ label = "Auto Quest", key = "AutoQuest" }, { label = "Auto Quest", key = "AutoQuest" },
{ label = "Feed-O-Matic", key = "FeedOMatic" }, { label = "Feed-O-Matic", key = "FeedOMatic" },
{ label = "ICU", key = "ICU" }, { label = "ICU", key = "ICU" },
{ label = "MM Widget", key = "ExpAmmo" },
{ {
label = "zButtons", label = "zButtons",
node = "zButtons", node = "zButtons",
children = { children = {
{ label = "zAmmo", key = "Ammo" }, { label = "zBar", key = "ZBar" },
{ label = "zAmmo", key = "Ammo" },
{ label = "zPet", key = "Pet" }, { label = "zPet", key = "Pet" },
{ label = "zAspect", key = "Aspect" }, { label = "zAspect", key = "Aspect" },
{ label = "zTrack", key = "Track" }, { label = "zTrack", key = "Track" },
@@ -73,6 +79,7 @@ MTH_OPTIONS_TREE = MTH_OPTIONS_TREE or {
{ label = "zMounts", key = "Mounts" }, { label = "zMounts", key = "Mounts" },
{ label = "zCompanions", key = "Companions" }, { label = "zCompanions", key = "Companions" },
{ label = "zToys", key = "Toys" }, { label = "zToys", key = "Toys" },
{ label = "zCraft", key = "Craft" },
}, },
}, },
{ {
@@ -86,6 +93,7 @@ MTH_OPTIONS_TREE = MTH_OPTIONS_TREE or {
{ label = "Racial", key = "ChronometerRacial" }, { label = "Racial", key = "ChronometerRacial" },
}, },
}, },
{ label = "Animations", key = "Animations" },
{ label = "Credits", key = "Credits" }, { label = "Credits", key = "Credits" },
} }
+835 -565
View File
File diff suppressed because it is too large Load Diff
+6 -3
View File
@@ -46,12 +46,12 @@
<Frames> <Frames>
<Frame name="$parentNav" enableMouse="true"> <Frame name="$parentNav" enableMouse="true">
<Size> <Size>
<AbsDimension x="120" y="540"/> <AbsDimension x="120" y="600"/>
</Size> </Size>
<Anchors> <Anchors>
<Anchor point="TOPLEFT"> <Anchor point="TOPLEFT">
<Offset> <Offset>
<AbsDimension x="12" y="-72"/> <AbsDimension x="12" y="-30"/>
</Offset> </Offset>
</Anchor> </Anchor>
</Anchors> </Anchors>
@@ -64,7 +64,7 @@
</Backdrop> </Backdrop>
</Frame> </Frame>
<!-- Content Areas --> <Frame name="$parentZBar" hidden="true" enableMouse="true"/>
<Frame name="$parentGeneral" hidden="true" enableMouse="true"/> <Frame name="$parentGeneral" hidden="true" enableMouse="true"/>
<Frame name="$parentProfiles" hidden="true" enableMouse="true"/> <Frame name="$parentProfiles" hidden="true" enableMouse="true"/>
<Frame name="$parentMessages" hidden="true" enableMouse="true"/> <Frame name="$parentMessages" hidden="true" enableMouse="true"/>
@@ -77,12 +77,15 @@
<Frame name="$parentMounts" hidden="true" enableMouse="true"/> <Frame name="$parentMounts" hidden="true" enableMouse="true"/>
<Frame name="$parentCompanions" hidden="true" enableMouse="true"/> <Frame name="$parentCompanions" hidden="true" enableMouse="true"/>
<Frame name="$parentToys" hidden="true" enableMouse="true"/> <Frame name="$parentToys" hidden="true" enableMouse="true"/>
<Frame name="$parentCraft" hidden="true" enableMouse="true"/>
<Frame name="$parentSmartAmmo" hidden="true" enableMouse="true"/> <Frame name="$parentSmartAmmo" hidden="true" enableMouse="true"/>
<Frame name="$parentFeedOMatic" hidden="true" enableMouse="true"/> <Frame name="$parentFeedOMatic" hidden="true" enableMouse="true"/>
<Frame name="$parentAutoBuy" hidden="true" enableMouse="true"/> <Frame name="$parentAutoBuy" hidden="true" enableMouse="true"/>
<Frame name="$parentAutoQuest" hidden="true" enableMouse="true"/> <Frame name="$parentAutoQuest" hidden="true" enableMouse="true"/>
<Frame name="$parentICU" 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="$parentChronometer" hidden="true" enableMouse="true"/>
<Frame name="$parentAnimations" hidden="true" enableMouse="true"/>
<Frame name="$parentCredits" hidden="true" enableMouse="true"/> <Frame name="$parentCredits" hidden="true" enableMouse="true"/>
<!-- Top-right close button --> <!-- Top-right close button -->
+330
View File
@@ -0,0 +1,330 @@
-- MetaHunt: auto-train Growl to a freshly tamed pet.
--
local MTH_GROWL_MAX_RANK = 7
-- ---------------------------------------------------------------------------
-- Saved-variable backed toggle (per character), default ON.
-- ---------------------------------------------------------------------------
local function MTH_Growl_GetStore()
if type(MTH_CharSavedVariables) ~= "table" then
MTH_CharSavedVariables = {}
end
if type(MTH_CharSavedVariables.growlTeach) ~= "table" then
MTH_CharSavedVariables.growlTeach = {}
end
local store = MTH_CharSavedVariables.growlTeach
if store.enabled == nil then
store.enabled = true
end
return store
end
function MTH_Growl_IsEnabled()
return MTH_Growl_GetStore().enabled and true or false
end
function MTH_Growl_SetEnabled(enabled)
MTH_Growl_GetStore().enabled = enabled and true or false
end
-- ---------------------------------------------------------------------------
-- Helpers
-- ---------------------------------------------------------------------------
local function MTH_Growl_DebugLog(line)
if type(MTH_DebugFrame) == "table" and type(MTH_DebugFrame.AddInfo) == "function" then
MTH_DebugFrame:AddInfo("[Growl] " .. tostring(line))
end
end
local function MTH_Growl_GetGlobal(name)
if type(getglobal) == "function" then
return getglobal(name)
end
if _G then
return _G[name]
end
return nil
end
-- Rank appropriate for a pet of the given level.
local function MTH_Growl_RankForLevel(level)
local lvl = tonumber(level) or 0
if lvl < 1 then lvl = 1 end
local rank = math.floor(lvl / 10) + 1
if rank < 1 then rank = 1 end
if rank > MTH_GROWL_MAX_RANK then rank = MTH_GROWL_MAX_RANK end
return rank
end
-- Parse a rank number out of a craft row's name/subtext (e.g. "Growl", "Rank 5").
local function MTH_Growl_ParseRank(name, subText)
local sub = tostring(subText or "")
local _, _, subRank = string.find(sub, "(%d+)")
if subRank then return tonumber(subRank) end
local nm = tostring(name or "")
local _, _, nameRank = string.find(nm, "(%d+)")
if nameRank then return tonumber(nameRank) end
return nil
end
-- Find the hunter spellbook slot for a spell by name (e.g. "Beast Training").
local function MTH_Growl_FindSpellSlot(spellName)
if not spellName or spellName == "" then return nil end
local getSpellName = MTH_Growl_GetGlobal("GetSpellName")
if type(getSpellName) ~= "function" then return nil end
local getNumSpellTabs = MTH_Growl_GetGlobal("GetNumSpellTabs")
local getSpellTabInfo = MTH_Growl_GetGlobal("GetSpellTabInfo")
if type(getNumSpellTabs) == "function" and type(getSpellTabInfo) == "function" then
local numTabs = getNumSpellTabs() or 0
for tabIndex = 1, numTabs do
local _, _, offset, numSpells = getSpellTabInfo(tabIndex)
offset = tonumber(offset) or 0
numSpells = tonumber(numSpells) or 0
for spellIndex = (offset + 1), (offset + numSpells) do
if getSpellName(spellIndex, "spell") == spellName then
return spellIndex
end
end
end
end
-- Fallback: linear scan of the spellbook.
local nilStreak = 0
for spellIndex = 1, 1024 do
local nameAtIndex = getSpellName(spellIndex, "spell")
if nameAtIndex then
nilStreak = 0
if nameAtIndex == spellName then
return spellIndex
end
else
nilStreak = nilStreak + 1
if nilStreak >= 30 then break end
end
end
return nil
end
-- Open the Beast Training window by casting the hunter "Beast Training" spell.
local function MTH_Growl_OpenTrainingWindow()
local spellName = MTH_Growl_GetGlobal("ZHUNTER_PET_TRAINING") or "Beast Training"
local slot = MTH_Growl_FindSpellSlot(spellName)
local castSpell = MTH_Growl_GetGlobal("CastSpell")
if slot and type(castSpell) == "function" then
local getSpellName = MTH_Growl_GetGlobal("GetSpellName")
local atSlot = (type(getSpellName) == "function") and getSpellName(slot, "spell") or "?"
castSpell(slot, "spell")
MTH_Growl_DebugLog("Opened Beast Training via CastSpell(slot=" .. tostring(slot)
.. " name='" .. tostring(atSlot) .. "')")
return true
end
local castSpellByName = MTH_Growl_GetGlobal("CastSpellByName")
if type(castSpellByName) == "function" then
castSpellByName(spellName)
MTH_Growl_DebugLog("Opened Beast Training via CastSpellByName('" .. tostring(spellName) .. "') (slot not found)")
return true
end
MTH_Growl_DebugLog("Could not open Beast Training (no spell slot, no CastSpellByName)")
return false
end
-- Is the Beast Training / craft window currently visible?
local function MTH_Growl_IsTrainingWindowVisible()
local frameNames = { "BeastTrainingFrame", "PetTrainingFrame", "CraftFrame" }
for i = 1, table.getn(frameNames) do
local frame = MTH_Growl_GetGlobal(frameNames[i])
if frame then
if frame.IsVisible and frame:IsVisible() then return true end
if frame.IsShown and frame:IsShown() then return true end
end
end
return false
end
-- ---------------------------------------------------------------------------
-- Pending auto-train request. Set when a fresh tame is confirmed; cleared once
-- we train (or time out). { rank, name, elapsed, closeAfter, done }
-- ---------------------------------------------------------------------------
local MTH_Growl_Pending = nil
-- The offer currently awaiting the player's Yes/No answer. { rank, name, level }
local MTH_Growl_Offer = nil
-- While we auto-open the trainer to teach Growl, silence the Beast Training
-- scanner's chat feedback (set on confirm; expires after the teach window).
local MTH_Growl_SuppressScanUntil = 0
function MTH_Growl_IsTeaching()
local getTime = MTH_Growl_GetGlobal("GetTime")
local now = (type(getTime) == "function" and getTime()) or 0
return now < (MTH_Growl_SuppressScanUntil or 0)
end
local function MTH_Growl_TryTrainNow()
if not MTH_Growl_Pending or MTH_Growl_Pending.done then
return false
end
local getNumCrafts = MTH_Growl_GetGlobal("GetNumCrafts")
local getCraftInfo = MTH_Growl_GetGlobal("GetCraftInfo")
local doCraft = MTH_Growl_GetGlobal("DoCraft")
if type(getNumCrafts) ~= "function" or type(getCraftInfo) ~= "function" or type(doCraft) ~= "function" then
return false
end
local total = tonumber(getNumCrafts()) or 0
if total <= 0 then
return false -- window not populated yet
end
local target = MTH_Growl_Pending.rank
local bestIndex, bestRank = nil, 0
for i = 1, total do
local craftName, craftSub, craftType = getCraftInfo(i)
local lname = string.lower(tostring(craftName or ""))
if string.find(lname, "growl", 1, true) and tostring(craftType) ~= "header" then
local rank = MTH_Growl_ParseRank(craftName, craftSub)
if rank then
if rank == target then
bestIndex, bestRank = i, rank
break
elseif rank < target and rank > bestRank then
-- Highest rank we could give if the exact target isn't offered.
bestIndex, bestRank = i, rank
end
end
end
end
if not bestIndex then
return false -- no Growl row found yet
end
doCraft(bestIndex)
MTH_Growl_DebugLog("Trained Growl Rank " .. tostring(bestRank)
.. " (target " .. tostring(target) .. ") for '" .. tostring(MTH_Growl_Pending.name)
.. "' via DoCraft(" .. tostring(bestIndex) .. ")")
if MTH and type(MTH.Print) == "function" then
MTH:Print("Auto-taught Growl (Rank " .. tostring(bestRank) .. ") to " .. tostring(MTH_Growl_Pending.name) .. ".")
end
MTH_Growl_Pending.done = true
if MTH_Growl_Pending.closeAfter then
local closeCraft = MTH_Growl_GetGlobal("CloseCraft")
if type(closeCraft) == "function" then
closeCraft()
end
end
MTH_Growl_Pending = nil
return true
end
-- Driver frame: react to the craft window opening/updating, with an OnUpdate
-- poll as backup and a timeout so we never leave a stale pending request.
local MTH_Growl_Frame = CreateFrame("Frame", "MTH_GrowlTeachFrame")
MTH_Growl_Frame:RegisterEvent("CRAFT_SHOW")
MTH_Growl_Frame:RegisterEvent("CRAFT_UPDATE")
MTH_Growl_Frame:RegisterEvent("PET_TRAINING_SHOW")
MTH_Growl_Frame:SetScript("OnEvent", function()
MTH_Growl_TryTrainNow()
end)
MTH_Growl_Frame:SetScript("OnUpdate", function()
if not MTH_Growl_Pending then return end
local p = MTH_Growl_Pending
p.elapsed = (p.elapsed or 0) + (arg1 or 0)
-- Train as soon as the window is populated.
if MTH_Growl_TryTrainNow() then return end
if p.elapsed > 8 then
local getNumCrafts = MTH_Growl_GetGlobal("GetNumCrafts")
local crafts = (type(getNumCrafts) == "function" and tonumber(getNumCrafts())) or -1
MTH_Growl_DebugLog("Gave up training Growl Rank " .. tostring(p.rank)
.. " for '" .. tostring(p.name) .. "' (timeout: windowVisible="
.. tostring(MTH_Growl_IsTrainingWindowVisible()) .. " numCrafts=" .. tostring(crafts)
.. " openAttempts=" .. tostring(p.openAttempts) .. ")")
MTH_Growl_Pending = nil
return
end
-- Window not up yet: re-attempt opening every second (the pet may still be
-- settling right after the tame, so the first cast can be ignored).
if not MTH_Growl_IsTrainingWindowVisible() then
p.sinceOpen = (p.sinceOpen or 0) + (arg1 or 0)
if p.sinceOpen >= 1.0 then
p.sinceOpen = 0
p.openAttempts = (p.openAttempts or 0) + 1
if p.openAttempts <= 6 then
MTH_Growl_OpenTrainingWindow()
end
end
end
end)
-- ---------------------------------------------------------------------------
-- Start training the confirmed offer. Called from the popup's Yes button (a
-- hardware event), which makes opening the Beast Training window reliable.
-- ---------------------------------------------------------------------------
local function MTH_Growl_StartTraining(rank, petName)
local alreadyOpen = MTH_Growl_IsTrainingWindowVisible()
local getTime = MTH_Growl_GetGlobal("GetTime")
MTH_Growl_SuppressScanUntil = ((type(getTime) == "function" and getTime()) or 0) + 10
MTH_Growl_Pending = {
rank = rank,
name = petName,
elapsed = 0,
sinceOpen = 0,
openAttempts = 0,
closeAfter = not alreadyOpen,
done = false,
}
MTH_Growl_DebugLog("Confirmed: train Growl Rank " .. tostring(rank) .. " for '"
.. tostring(petName) .. "' (windowOpen=" .. tostring(alreadyOpen) .. ")")
if alreadyOpen then
MTH_Growl_TryTrainNow()
else
MTH_Growl_Pending.openAttempts = 1
MTH_Growl_OpenTrainingWindow()
end
end
-- ---------------------------------------------------------------------------
-- Confirmation popup. Yes = hardware event -> open Beast Training + DoCraft.
-- ---------------------------------------------------------------------------
StaticPopupDialogs["MTH_GROWL_TEACH"] = {
text = "Teach %s to your newly tamed %s?",
button1 = YES or "Yes",
button2 = NO or "No",
OnAccept = function()
local offer = MTH_Growl_Offer
MTH_Growl_Offer = nil
if not offer then return end
MTH_Growl_StartTraining(offer.rank, offer.name)
end,
OnCancel = function()
MTH_Growl_Offer = nil
end,
timeout = 0,
whileDead = 1,
hideOnEscape = 1,
}
-- ---------------------------------------------------------------------------
-- Public entry point: called when a fresh tame is confirmed.
-- ---------------------------------------------------------------------------
function MTH_Growl_OfferForTamedPet(petName, petLevel)
if not MTH_Growl_IsEnabled() then
return
end
if type(StaticPopup_Show) ~= "function" then
return
end
local rank = MTH_Growl_RankForLevel(petLevel)
MTH_Growl_Offer = { rank = rank, name = petName, level = petLevel }
local spellLabel = "Growl (Rank " .. tostring(rank) .. ")"
local name = tostring(petName or "your pet")
MTH_Growl_DebugLog("Offer pet='" .. name .. "' level=" .. tostring(petLevel) .. " -> " .. spellLabel)
StaticPopup_Show("MTH_GROWL_TEACH", spellLabel, name)
end
+3
View File
@@ -6,6 +6,9 @@ function MTH_ST_LifecycleService:HandleEvent(evt, eventArg1, eventArg2)
if type(MTH_ST_HandlePlayerEnteringWorld) == "function" and MTH_ST_HandlePlayerEnteringWorld(evt) then if type(MTH_ST_HandlePlayerEnteringWorld) == "function" and MTH_ST_HandlePlayerEnteringWorld(evt) then
return true, "lifecycle-enter-world" return true, "lifecycle-enter-world"
end 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 type(MTH_ST_HandleUnitPetEvent) == "function" and MTH_ST_HandleUnitPetEvent(evt, eventArg1) then
if evt == "UNIT_PET" then if evt == "UNIT_PET" then
if eventArg1 == "player" then if eventArg1 == "player" then
+26 -2
View File
@@ -19,6 +19,30 @@ local function MTH_Profile_DeepCopy(orig)
return copy return copy
end 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() local function MTH_Profile_EnsureStore()
if type(MTH_SavedVariables) ~= "table" then if type(MTH_SavedVariables) ~= "table" then
MTH_SavedVariables = {} MTH_SavedVariables = {}
@@ -47,13 +71,13 @@ local function MTH_Profile_BuildSnapshot()
-- Account-wide module option values (e.g. feedomatic) -- Account-wide module option values (e.g. feedomatic)
snap.config = (type(MTH_SavedVariables) == "table" snap.config = (type(MTH_SavedVariables) == "table"
and type(MTH_SavedVariables.modules) == "table") and type(MTH_SavedVariables.modules) == "table")
and MTH_Profile_DeepCopy(MTH_SavedVariables.modules) and MTH_Profile_CopyModules(MTH_SavedVariables.modules)
or {} or {}
-- Per-character module option values (ICU, Chronometer, AutoBuy, SmartAmmo, etc.) -- Per-character module option values (ICU, Chronometer, AutoBuy, SmartAmmo, etc.)
snap.charConfig = (type(MTH_CharSavedVariables) == "table" snap.charConfig = (type(MTH_CharSavedVariables) == "table"
and type(MTH_CharSavedVariables.modules) == "table") and type(MTH_CharSavedVariables.modules) == "table")
and MTH_Profile_DeepCopy(MTH_CharSavedVariables.modules) and MTH_Profile_CopyModules(MTH_CharSavedVariables.modules)
or {} or {}
-- Per-character module enabled/disabled states -- Per-character module enabled/disabled states
+434
View File
@@ -0,0 +1,434 @@
------------------------------------------------------
-- MetaHunt — Unified SavedVariables schema + migration engine
------------------------------------------------------
-- PURPOSE
-- Collapse every legacy / scattered SavedVariable into just TWO persisted
-- globals and one consistent shape, so old-addon names (FOM_*, ZHunterMod_Saved,
-- MTHSmartAmmo) and ad-hoc key styles disappear.
--
-- TARGET SHAPE (canonical)
-- MTH_SavedVariables (account-wide) = {
-- schemaVersion = <MTH_SV_SCHEMA_VERSION>,
-- modules = {
-- <id> = { schemaVersion = N, settings = { ...camelCase... }, <dataTables> },
-- feedomatic = { settings{...}, foodQuality, cooking, questFood,
-- addedFoods, removedFoods, localeInfo },
-- },
-- messages = {...}, moduleStates = {...}, profiles = {...},
-- charSnapshots = {...}, versionCheck = {...},
-- }
-- MTH_CharSavedVariables (per-character) = {
-- schemaVersion = <MTH_SV_SCHEMA_VERSION>,
-- modules = {
-- <id> = { schemaVersion = N, settings{...}, <dataTables> },
-- zhunter = { buttons{...}, bar{...}, widgetSpawnLayout },
-- antidaze = { settings{ enabled } },
-- autostrip = { settings{ enabled, display }, frame{ point, relativePoint, x, y } },
-- minimapbutton = { settings{ angle } },
-- },
-- moduleStates = {...},
-- -- pet-system core stays at root (out of scope, heavily referenced):
-- MTH_Pets, feedTracking, petTraining, trainScan, stableScan, petSpellScan,
-- }
--
-- RULES
-- * One-shot, gated by schemaVersion on each root. Idempotent. Non-destructive
-- (data is COPIED into the new location before the old location is cleared).
-- * Lua 5.0-safe (no '#', no string.match/gmatch; pairs()/ipairs() only).
-- * Testable offline: MTH_SV_EnsureSchema(account, char) accepts explicit roots
-- (defaults to the live globals) and returns a report table.
------------------------------------------------------
MTH_SV_SCHEMA_VERSION = 1
------------------------------------------------------
-- Helpers
------------------------------------------------------
local function MTH_SV_IsTable(v)
return type(v) == "table"
end
local function MTH_SV_DeepCopy(orig)
if type(orig) ~= "table" then
return orig
end
local copy = {}
for k, v in pairs(orig) do
copy[MTH_SV_DeepCopy(k)] = MTH_SV_DeepCopy(v)
end
return copy
end
-- Ensure parent[key] is a table and return it.
local function MTH_SV_Ensure(parent, key)
if type(parent[key]) ~= "table" then
parent[key] = {}
end
return parent[key]
end
-- Pick the first non-nil value from a list of candidates; else default.
local function MTH_SV_Coalesce(candidates, default)
for _, v in ipairs(candidates) do
if v ~= nil then
return v
end
end
return default
end
-- Copy every key from src into dst, applying a rename map (oldKey -> newKey).
-- Keys not present in the map keep their original name. Existing dst values win
-- (so re-running never clobbers already-migrated data). Returns count copied.
local function MTH_SV_CopyRenamed(src, dst, renameMap)
local moved = 0
if not MTH_SV_IsTable(src) then
return moved
end
for oldKey, value in pairs(src) do
local newKey = (renameMap and renameMap[oldKey]) or oldKey
if dst[newKey] == nil then
dst[newKey] = value
moved = moved + 1
end
end
return moved
end
-- Count entries in a table (Lua 5.0-safe).
local function MTH_SV_Count(t)
local n = 0
if MTH_SV_IsTable(t) then
for _ in pairs(t) do
n = n + 1
end
end
return n
end
------------------------------------------------------
-- Per-module migrators
-- Each takes the relevant module store table (already ensured) plus context,
-- transforms in place, and records notes into the shared report.
------------------------------------------------------
-- SMARTAMMO (per-character)
-- OLD: modules.smartammo = { enabled, smartEnabled, reloadEnabled,
-- weaponSwapEnabled, legacy = { MTHSmartAmmo = {
-- enabled, reload, weaponSwap } } }
-- + optional global MTHSmartAmmo = { enabled, reload, weaponSwap }
-- NEW: modules.smartammo = { schemaVersion, settings = {
-- smartEnabled, reloadEnabled, weaponSwapEnabled } }
local function MTH_SV_Migrate_SmartAmmo(store, globalMTHSmartAmmo, report)
if not MTH_SV_IsTable(store) then
return
end
local legacy = MTH_SV_IsTable(store.legacy) and store.legacy.MTHSmartAmmo or nil
local g = MTH_SV_IsTable(globalMTHSmartAmmo) and globalMTHSmartAmmo or nil
local settings = MTH_SV_Ensure(store, "settings")
-- smartEnabled: modern flat -> settings -> legacy.enabled -> global.enabled -> old `enabled` -> true
if settings.smartEnabled == nil then
settings.smartEnabled = MTH_SV_Coalesce({
store.smartEnabled,
legacy and legacy.enabled,
g and g.enabled,
store.enabled,
}, true) and true or false
end
if settings.reloadEnabled == nil then
settings.reloadEnabled = MTH_SV_Coalesce({
store.reloadEnabled,
legacy and legacy.reload,
g and g.reload,
}, true) and true or false
end
if settings.weaponSwapEnabled == nil then
settings.weaponSwapEnabled = MTH_SV_Coalesce({
store.weaponSwapEnabled,
legacy and legacy.weaponSwap,
g and g.weaponSwap,
}, true) and true or false
end
-- Clear the old flat/legacy shape now that settings holds the truth.
store.legacy = nil
store.enabled = nil
store.smartEnabled = nil
store.reloadEnabled = nil
store.weaponSwapEnabled = nil
store.schemaVersion = 1
report.smartammo = "settings{smartEnabled=" .. tostring(settings.smartEnabled)
.. ",reloadEnabled=" .. tostring(settings.reloadEnabled)
.. ",weaponSwapEnabled=" .. tostring(settings.weaponSwapEnabled) .. "}"
end
-- AUTOQUEST (per-character)
-- OLD (canonical): MTH_CharSavedVariables.autoquest = { scorpokDrazial,
-- arrowsForSissies, scorpokTooltip, _migrated } mirrored into
-- modules.autoquest = { scorpokDrazial, arrowsForSissies, scorpokTooltip, enabled }
-- plus oldest legacy MTH_CharSavedVariables.questautomation.
-- NEW: modules.autoquest = { schemaVersion, settings = {
-- scorpokDrazial, arrowsForSissies, scorpokTooltip } }
local function MTH_SV_Migrate_AutoQuest(char, report)
local modules = MTH_SV_Ensure(char, "modules")
local store = MTH_SV_Ensure(modules, "autoquest")
local settings = MTH_SV_Ensure(store, "settings")
local root = MTH_SV_IsTable(char.autoquest) and char.autoquest or {}
local qa = MTH_SV_IsTable(char.questautomation) and char.questautomation or {}
local keys = { "scorpokDrazial", "arrowsForSissies", "scorpokTooltip" }
for _, k in ipairs(keys) do
if settings[k] == nil then
settings[k] = MTH_SV_Coalesce({
store[k], -- flat mirror on modules.autoquest
root[k], -- former canonical root .autoquest
qa[k], -- oldest legacy .questautomation
}, false) and true or false
end
end
-- Clear old / duplicate locations.
store.scorpokDrazial = nil
store.arrowsForSissies = nil
store.scorpokTooltip = nil
store._migrated = nil
char.autoquest = nil
char.questautomation = nil
store.schemaVersion = 1
report.autoquest = "settings{scorpokDrazial=" .. tostring(settings.scorpokDrazial)
.. ",arrowsForSissies=" .. tostring(settings.arrowsForSissies)
.. ",scorpokTooltip=" .. tostring(settings.scorpokTooltip) .. "}"
end
-- ANTIDAZE (per-character): char-root .antiDaze{enabled} -> modules.antidaze.settings{enabled}
local function MTH_SV_Migrate_AntiDaze(char, report)
local modules = MTH_SV_Ensure(char, "modules")
local store = MTH_SV_Ensure(modules, "antidaze")
local settings = MTH_SV_Ensure(store, "settings")
local old = MTH_SV_IsTable(char.antiDaze) and char.antiDaze or {}
if settings.enabled == nil and old.enabled ~= nil then
settings.enabled = old.enabled and true or false
end
char.antiDaze = nil
store.schemaVersion = 1
report.antidaze = "settings{enabled=" .. tostring(settings.enabled) .. "}"
end
-- AUTOSTRIP (per-character): char-root .autoStrip{autostrip,display,point,relativePoint,x,y}
-- -> modules.autostrip{autostrip,display,point,relativePoint,x,y}
-- (keys preserved verbatim; only the storage location moves under .modules)
local function MTH_SV_Migrate_AutoStrip(char, report)
local modules = MTH_SV_Ensure(char, "modules")
local store = MTH_SV_Ensure(modules, "autostrip")
local old = MTH_SV_IsTable(char.autoStrip) and char.autoStrip or {}
local carry = { "autostrip", "display", "point", "relativePoint", "x", "y" }
for _, k in ipairs(carry) do
if store[k] == nil and old[k] ~= nil then
store[k] = old[k]
end
end
char.autoStrip = nil
store.schemaVersion = 1
report.autostrip = "store{autostrip=" .. tostring(store.autostrip)
.. ",display=" .. tostring(store.display) .. "}"
end
-- MINIMAPBUTTON (per-character): char-root .minimapButton{angle} -> modules.minimapbutton{angle}
local function MTH_SV_Migrate_MinimapButton(char, report)
local modules = MTH_SV_Ensure(char, "modules")
local store = MTH_SV_Ensure(modules, "minimapbutton")
local old = MTH_SV_IsTable(char.minimapButton) and char.minimapButton or {}
if store.angle == nil and old.angle ~= nil then
store.angle = old.angle
end
char.minimapButton = nil
store.schemaVersion = 1
report.minimapbutton = "store{angle=" .. tostring(store.angle) .. "}"
end
-- FEEDOMATIC (account-wide): the 7 old FeedOMatic globals were stored under
-- modules.feedomatic.legacy.FOM_* (with _G.FOM_* bound as runtime aliases) and
-- duplicated at the account-root alias MTH_SavedVariables.feedomatic.
-- NEW: modules.feedomatic = { schemaVersion, settings, foodQuality, addedFoods,
-- removedFoods, cooking, questFood, localeInfo } — no .legacy wrapper, no root
-- alias. The _G.FOM_* globals stay as RUNTIME aliases (re-bound to these nested
-- tables by MTH_FeedOMatic_SyncSavedVariables) so FeedOMatic.lua is untouched.
local MTH_SV_FOM_MAP = {
{ global = "FOM_Config", key = "settings" },
{ global = "FOM_FoodQuality", key = "foodQuality" },
{ global = "FOM_AddedFoods", key = "addedFoods" },
{ global = "FOM_RemovedFoods", key = "removedFoods" },
{ global = "FOM_Cooking", key = "cooking" },
{ global = "FOM_QuestFood", key = "questFood" },
{ global = "FOM_LocaleInfo", key = "localeInfo" },
}
local function MTH_SV_Migrate_FeedOMatic(account, report)
local modules = MTH_SV_Ensure(account, "modules")
local store = MTH_SV_Ensure(modules, "feedomatic")
local legacy = MTH_SV_IsTable(store.legacy) and store.legacy or {}
for _, m in ipairs(MTH_SV_FOM_MAP) do
if store[m.key] == nil then
local src = legacy[m.global]
if type(src) ~= "table" and _G then
src = _G[m.global]
end
if type(src) == "table" then
store[m.key] = src
end
end
end
store.legacy = nil
account.feedomatic = nil -- drop the account-root alias duplicate
store.schemaVersion = 1
report.feedomatic = "keys{settings,foodQuality,addedFoods,removedFoods,cooking,questFood,localeInfo}"
end
-- ZHUNTER (per-character): the zButton layout used to live in the TOC-declared
-- global ZHunterMod_Saved AND was mirrored into modules.zhunter (two on-disk
-- copies). NEW: modules.zhunter is the single canonical store (button tables,
-- _zbar, enabled, _mth_widget_spawn_layout_v1). ZHunterMod_Saved becomes a pure
-- RUNTIME alias re-bound by MTH_ZH_GetSavedRoot, and is dropped from the TOC.
-- The vestigial account-root alias MTH_SavedVariables.zhunter is removed too.
local function MTH_SV_Migrate_ZHunter(account, char, report)
local modules = MTH_SV_Ensure(char, "modules")
local store = MTH_SV_Ensure(modules, "zhunter")
-- If the nested store is still empty but a per-char global carries data
-- (legacy TOC copy present during the transition), adopt it once.
if _G and MTH_SV_IsTable(_G.ZHunterMod_Saved) and _G.ZHunterMod_Saved ~= store then
if MTH_SV_Count(store) == 0 then
for k, v in pairs(_G.ZHunterMod_Saved) do
store[k] = v
end
end
end
account.zhunter = nil -- drop the vestigial account-root alias duplicate
report.zhunter = "modules.zhunter canonical; ZHunterMod_Saved -> runtime alias"
end
------------------------------------------------------
-- Orchestrator
------------------------------------------------------
-- MTH_SV_EnsureSchema(account, char)
-- account / char default to the live globals. Returns a report table.
-- Gated by schemaVersion on each root so it only runs once; idempotent anyway.
function MTH_SV_EnsureSchema(account, char)
if account == nil then account = MTH_SavedVariables end
if char == nil then char = MTH_CharSavedVariables end
local report = { ran = false }
if not MTH_SV_IsTable(account) or not MTH_SV_IsTable(char) then
report.error = "roots unavailable"
return report
end
local accountDone = (tonumber(account.schemaVersion) or 0) >= MTH_SV_SCHEMA_VERSION
local charDone = (tonumber(char.schemaVersion) or 0) >= MTH_SV_SCHEMA_VERSION
if accountDone and charDone then
report.skipped = true
return report
end
report.ran = true
local accountModules = MTH_SV_Ensure(account, "modules")
local charModules = MTH_SV_Ensure(char, "modules")
-- Detect whether there is genuine legacy data to relocate BEFORE the migrators
-- consume/delete it. On a brand-new install there is nothing to move, so we still
-- stamp the schema (to avoid re-checking) but skip the player-facing announcement.
local smartLegacy = MTH_SV_IsTable(charModules.smartammo) and charModules.smartammo.legacy or nil
local feedLegacy = MTH_SV_IsTable(accountModules.feedomatic) and accountModules.feedomatic.legacy or nil
report.changed = (account.feedomatic ~= nil)
or (account.zhunter ~= nil)
or (char.antiDaze ~= nil)
or (char.autoStrip ~= nil)
or (char.minimapButton ~= nil)
or (char.autoquest ~= nil)
or (char.questautomation ~= nil)
or (feedLegacy ~= nil)
or (smartLegacy ~= nil)
or (_G ~= nil and (_G.FOM_Config ~= nil or _G.ZHunterMod_Saved ~= nil or _G.MTHSmartAmmo ~= nil))
or false
-- ---- account-wide modules ----
MTH_SV_Migrate_FeedOMatic(account, report)
-- ---- per-character modules ----
MTH_SV_Migrate_SmartAmmo(
MTH_SV_Ensure(charModules, "smartammo"),
_G and _G.MTHSmartAmmo or nil,
report
)
if _G then _G.MTHSmartAmmo = nil end
MTH_SV_Migrate_AutoQuest(char, report)
MTH_SV_Migrate_AntiDaze(char, report)
MTH_SV_Migrate_AutoStrip(char, report)
MTH_SV_Migrate_MinimapButton(char, report)
MTH_SV_Migrate_ZHunter(account, char, report)
-- ---- stamp ----
account.schemaVersion = MTH_SV_SCHEMA_VERSION
char.schemaVersion = MTH_SV_SCHEMA_VERSION
-- ---- player-facing announcement (only when real legacy data was relocated) ----
-- Guarded on MTH_Log so the offline test harness (no MetaHunt runtime) stays silent.
if report.changed and type(MTH_Log) == "function" then
local order = {
"feedomatic", "smartammo", "autoquest",
"antidaze", "autostrip", "minimapbutton", "zhunter",
}
local done = {}
for _, id in ipairs(order) do
if report[id] ~= nil then
table.insert(done, id)
end
end
MTH_Log("Tidying your saved settings into the new 2.0 layout (one-time)...")
if table.getn(done) > 0 then
MTH_Log(" consolidated: " .. table.concat(done, ", "))
end
MTH_Log("Saved-settings cleanup complete. Your options were carried over unchanged.")
end
return report
end
------------------------------------------------------
-- Load trigger
------------------------------------------------------
-- Run the migration exactly once, AFTER WoW has loaded MetaHunt's SavedVariables
-- from disk. ADDON_LOADED with arg1 == "MetaHunt" is the earliest point at which the
-- real account + per-character saved data is actually available. Running it any earlier
-- (e.g. from a top-level MTH:InitSavedVariables() at file-load) operates on the empty
-- default tables WoW then overwrites when it loads the SV file — which is exactly why
-- the migration used to re-announce every login and /reload. CreateFrame is guarded so
-- the offline test harness (no WoW API) simply skips this.
local MTH_SV_LoadFrame = CreateFrame and CreateFrame("Frame")
if MTH_SV_LoadFrame then
MTH_SV_LoadFrame:RegisterEvent("ADDON_LOADED")
MTH_SV_LoadFrame:SetScript("OnEvent", function()
if event ~= "ADDON_LOADED" or arg1 ~= "MetaHunt" then
return
end
MTH_SV_LoadFrame:UnregisterEvent("ADDON_LOADED")
if MTH and MTH.InitSavedVariables then
MTH:InitSavedVariables()
end
if type(MTH_SV_EnsureSchema) == "function" then
MTH_SV_EnsureSchema(MTH_SavedVariables, MTH_CharSavedVariables)
end
end)
end
+159
View File
@@ -0,0 +1,159 @@
-- MetaHunt shared trainer spell status and cost planner.
-- UI, chat notifications, and future level-up messages consume these functions.
local function MTH_TP_Lower(value)
return string.lower(tostring(value or ""))
end
local function MTH_TP_RankNumber(rank)
local _, _, value = string.find(tostring(rank or ""), "(%d+)")
return tonumber(value) or 0
end
function MTH_TrainerPlan_GetPrice(row, honored)
local base = tonumber(row and row.trainerCost) or 0
if honored then return math.floor(base * 0.90) end
return base
end
local function MTH_TP_IsKnownHunter(row)
if type(MTH_HT_GetSpellInfo) ~= "function" then return false end
local known = MTH_HT_GetSpellInfo(row and row.name)
if type(known) ~= "table" then return false end
local requiredRank = MTH_TP_RankNumber(row and row.rank)
return requiredRank == 0 or (tonumber(known.rankNum) or 0) >= requiredRank
end
local function MTH_TP_IsKnownPet(row)
if type(MTH_PETS_GetRootStore) ~= "function" then return false end
local pets = MTH_PETS_GetRootStore()
local store = pets and pets.petStore
local petId = store and store.activeCurrentId
local pet = store and store.activeById and petId and store.activeById[tostring(petId)]
local spells = pet and pet.petSpellbook and pet.petSpellbook.spells
if type(spells) ~= "table" then return false end
local requiredName = MTH_TP_Lower(row and row.name)
local requiredRank = MTH_TP_RankNumber(row and row.rank)
for _, spell in ipairs(spells) do
if MTH_TP_Lower(spell and spell.name) == requiredName then
local knownRank = tonumber(spell.rank) or 0
if requiredRank == 0 or knownRank >= requiredRank then return true end
end
end
return false
end
function MTH_TrainerPlan_IsKnown(kind, row)
if kind == "hunter" then return MTH_TP_IsKnownHunter(row) end
return MTH_TP_IsKnownPet(row)
end
local function MTH_TP_HasMissingPrerequisite(kind, row)
if type(row and row.prerequisites) ~= "table" then return false end
for _, prerequisite in ipairs(row.prerequisites) do
local _, _, name, rank = string.find(tostring(prerequisite), "^(.+)%s%([Rr]ank%s*(%d+)%)$")
if name and rank and not MTH_TrainerPlan_IsKnown(kind, { name = name, rank = "Rank " .. rank }) then
return true
end
end
return false
end
function MTH_TrainerPlan_GetStatus(kind, row, playerLevel)
if MTH_TrainerPlan_IsKnown(kind, row) then return "known" end
local observed = type(MTH_GetTrainerLiveStatus) == "function"
and MTH_GetTrainerLiveStatus(kind, row and row.name, row and row.rank) or nil
if observed and observed.status == "used" then return "known" end
if observed and observed.status == "available" then return "notlearned" end
if observed and observed.status == "unavailable" then return "notyet" end
local requiredLevel = tonumber(row and row.requiredLevel) or 0
if not playerLevel or playerLevel < requiredLevel or MTH_TP_HasMissingPrerequisite(kind, row) then
return "notyet"
end
return "notlearned"
end
function MTH_TrainerPlan_Build(kind, options)
options = options or {}
local catalog = MTH_DS_TrainerCatalog
local rows = catalog and catalog[kind] or nil
local playerLevel = tonumber(options.playerLevel)
if not playerLevel and type(UnitLevel) == "function" then playerLevel = tonumber(UnitLevel("player")) end
local honored = options.honored and true or false
local plan = {
kind = kind,
playerLevel = playerLevel,
honored = honored,
rows = {},
now = { count = 0, cost = 0 },
nextLevel = { level = nil, count = 0, cost = 0 },
remaining = { count = 0, cost = 0 },
}
if type(rows) ~= "table" then return plan end
for _, row in ipairs(rows) do
local status = MTH_TrainerPlan_GetStatus(kind, row, playerLevel)
local price = MTH_TrainerPlan_GetPrice(row, honored)
local entry = { row = row, status = status, price = price }
table.insert(plan.rows, entry)
if status ~= "known" then
plan.remaining.count = plan.remaining.count + 1
plan.remaining.cost = plan.remaining.cost + price
if status == "notlearned" then
plan.now.count = plan.now.count + 1
plan.now.cost = plan.now.cost + price
end
local requiredLevel = tonumber(row.requiredLevel) or 0
if playerLevel and requiredLevel > playerLevel then
if not plan.nextLevel.level or requiredLevel < plan.nextLevel.level then
plan.nextLevel.level = requiredLevel
plan.nextLevel.count = 0
plan.nextLevel.cost = 0
end
if requiredLevel == plan.nextLevel.level then
plan.nextLevel.count = plan.nextLevel.count + 1
plan.nextLevel.cost = plan.nextLevel.cost + price
end
end
end
end
return plan
end
function MTH_TrainerPlan_BuildLevel(kind, targetLevel, options)
options = options or {}
local level = tonumber(targetLevel)
local catalog = MTH_DS_TrainerCatalog
local rows = catalog and catalog[kind] or nil
local honored = options.honored and true or false
local plan = {
kind = kind,
level = level,
honored = honored,
rows = {},
available = { count = 0, cost = 0 },
unlearned = { count = 0, cost = 0 },
}
if not level or type(rows) ~= "table" then return plan end
for _, row in ipairs(rows) do
if tonumber(row.requiredLevel) == level and (options.includeKnown or not MTH_TrainerPlan_IsKnown(kind, row)) then
local status = MTH_TrainerPlan_GetStatus(kind, row, level)
local price = MTH_TrainerPlan_GetPrice(row, honored)
local entry = { row = row, status = status, price = price }
table.insert(plan.rows, entry)
plan.unlearned.count = plan.unlearned.count + 1
plan.unlearned.cost = plan.unlearned.cost + price
if status == "notlearned" then
plan.available.count = plan.available.count + 1
plan.available.cost = plan.available.cost + price
end
end
end
return plan
end
function MTH_TrainerPlan_MatchesQuick(status, quick)
if quick == "known" then return status == "known" end
if quick == "notlearned" then return status == "notlearned" end
if quick == "notyet" then return status == "notyet" end
return true
end
+5 -5
View File
@@ -4,13 +4,13 @@ MTH.VersionCheck = MTH.VersionCheck or {}
local VC = MTH.VersionCheck local VC = MTH.VersionCheck
VC.abbrev = "MTH" VC.abbrev = "MTH"
VC.channelName = "LFT" VC.channelName = "LFT"
VC.updateUrl = "https://github.com/DuvelCorp/MetaHunt" VC.updateUrl = "https://codeberg.org/Duvelcorp/MetaHunt"
VC.nextPublishAt = nil VC.nextPublishAt = nil
VC.lastPublishedAt = nil VC.lastPublishedAt = nil
VC.joinAt = nil VC.joinAt = nil
VC.notified = false VC.notified = false
VC._invalidVersionWarned = false VC._invalidVersionWarned = false
VC.maxPersistedPeers = 200 VC.maxPersistedPeers = 10000
VC.seenPeers = VC.seenPeers or {} VC.seenPeers = VC.seenPeers or {}
local function VC_GetTimeNow() local function VC_GetTimeNow()
@@ -141,7 +141,7 @@ end
function VC:PrunePersistedPeers(limit) function VC:PrunePersistedPeers(limit)
local maxCount = tonumber(limit) or tonumber(self.maxPersistedPeers) or 200 local maxCount = tonumber(limit) or tonumber(self.maxPersistedPeers) or 200
if maxCount <= 0 then if maxCount <= 0 then
maxCount = 200 maxCount = 10000
end end
if type(self.seenPeers) ~= "table" then if type(self.seenPeers) ~= "table" then
return return
@@ -221,8 +221,8 @@ end
function VC:ResetPublishDelay() function VC:ResetPublishDelay()
local now = VC_GetTimeNow() local now = VC_GetTimeNow()
if self.lastPublishedAt and (now - self.lastPublishedAt) < 600 then if self.lastPublishedAt and (now - self.lastPublishedAt) < 3600 then
return -- 10-minute cooldown between publishes return -- 1-hour cooldown between publishes
end end
self.nextPublishAt = now + math.random(10, 20) self.nextPublishAt = now + math.random(10, 20)
end end
+23 -23
View File
@@ -1,5 +1,5 @@
-- MetaHunt Bags Database (Quivers + Ammo Pouches) -- MetaHunt Bags Database (Quivers + Ammo Pouches)
-- Source: https://database.turtlecraft.gg/?items=11 -- Source: https://octowow.st/db/?items=11
-- Auto-generated extraction with source recursion. -- Auto-generated extraction with source recursion.
if not MTH_DS then MTH_DS = {} end if not MTH_DS then MTH_DS = {} end
@@ -14,7 +14,7 @@ MTH_DS_BagItems = {
["subtype"] = "quiver", ["subtype"] = "quiver",
["description"] = "", ["description"] = "",
["tooltip"] = "Light Quiver\n6 Slot Bag\nEquip: Increases ranged attack speed by 10%.", ["tooltip"] = "Light Quiver\n6 Slot Bag\nEquip: Increases ranged attack speed by 10%.",
["sourceUrl"] = "https://database.turtlecraft.gg/?item=2101", ["sourceUrl"] = "https://octowow.st/db/?item=2101",
["sources"] = { ["sources"] = {
["sold-by"] = { ["sold-by"] = {
["template"] = "npc", ["template"] = "npc",
@@ -89,7 +89,7 @@ MTH_DS_BagItems = {
["subtype"] = "ammo pouch", ["subtype"] = "ammo pouch",
["description"] = "", ["description"] = "",
["tooltip"] = "Small Ammo Pouch\n6 Slot Bag\nEquip: Increases ranged attack speed by 10%.", ["tooltip"] = "Small Ammo Pouch\n6 Slot Bag\nEquip: Increases ranged attack speed by 10%.",
["sourceUrl"] = "https://database.turtlecraft.gg/?item=2102", ["sourceUrl"] = "https://octowow.st/db/?item=2102",
["sources"] = { ["sources"] = {
["sold-by"] = { ["sold-by"] = {
["template"] = "npc", ["template"] = "npc",
@@ -144,7 +144,7 @@ MTH_DS_BagItems = {
["subtype"] = "quiver", ["subtype"] = "quiver",
["description"] = "", ["description"] = "",
["tooltip"] = "Ribbly's Quiver\nBinds when picked up\nUnique\n16 Slot Bag\nRequires Level 50\nEquip: Increases ranged attack speed by 14%.", ["tooltip"] = "Ribbly's Quiver\nBinds when picked up\nUnique\n16 Slot Bag\nRequires Level 50\nEquip: Increases ranged attack speed by 14%.",
["sourceUrl"] = "https://database.turtlecraft.gg/?item=2662", ["sourceUrl"] = "https://octowow.st/db/?item=2662",
["sources"] = { ["sources"] = {
["dropped-by"] = { ["dropped-by"] = {
["template"] = "npc", ["template"] = "npc",
@@ -171,7 +171,7 @@ MTH_DS_BagItems = {
["subtype"] = "ammo pouch", ["subtype"] = "ammo pouch",
["description"] = "", ["description"] = "",
["tooltip"] = "Ribbly's Bandolier\nBinds when picked up\nUnique\n16 Slot Bag\nRequires Level 50\nEquip: Increases ranged attack speed by 14%.", ["tooltip"] = "Ribbly's Bandolier\nBinds when picked up\nUnique\n16 Slot Bag\nRequires Level 50\nEquip: Increases ranged attack speed by 14%.",
["sourceUrl"] = "https://database.turtlecraft.gg/?item=2663", ["sourceUrl"] = "https://octowow.st/db/?item=2663",
["sources"] = { ["sources"] = {
["dropped-by"] = { ["dropped-by"] = {
["template"] = "npc", ["template"] = "npc",
@@ -197,7 +197,7 @@ MTH_DS_BagItems = {
["subtype"] = "quiver", ["subtype"] = "quiver",
["description"] = "", ["description"] = "",
["tooltip"] = "Hunting Quiver\nBinds when picked up\nUnique\n10 Slot Bag\nEquip: Increases ranged attack speed by 10%.", ["tooltip"] = "Hunting Quiver\nBinds when picked up\nUnique\n10 Slot Bag\nEquip: Increases ranged attack speed by 10%.",
["sourceUrl"] = "https://database.turtlecraft.gg/?item=3573", ["sourceUrl"] = "https://octowow.st/db/?item=3573",
["sources"] = { ["sources"] = {
["reward-of"] = { ["reward-of"] = {
["template"] = "quest", ["template"] = "quest",
@@ -216,7 +216,7 @@ MTH_DS_BagItems = {
["subtype"] = "ammo pouch", ["subtype"] = "ammo pouch",
["description"] = "", ["description"] = "",
["tooltip"] = "Hunting Ammo Sack\nBinds when picked up\n10 Slot Bag\nEquip: Increases ranged attack speed by 10%.", ["tooltip"] = "Hunting Ammo Sack\nBinds when picked up\n10 Slot Bag\nEquip: Increases ranged attack speed by 10%.",
["sourceUrl"] = "https://database.turtlecraft.gg/?item=3574", ["sourceUrl"] = "https://octowow.st/db/?item=3574",
["sources"] = { ["sources"] = {
["reward-of"] = { ["reward-of"] = {
["template"] = "quest", ["template"] = "quest",
@@ -235,7 +235,7 @@ MTH_DS_BagItems = {
["subtype"] = "ammo pouch", ["subtype"] = "ammo pouch",
["description"] = "", ["description"] = "",
["tooltip"] = "Bandolier of the Night Watch\nBinds when picked up\nUnique\n12 Slot Bag\nEquip: Increases ranged attack speed by 11%.", ["tooltip"] = "Bandolier of the Night Watch\nBinds when picked up\nUnique\n12 Slot Bag\nEquip: Increases ranged attack speed by 11%.",
["sourceUrl"] = "https://database.turtlecraft.gg/?item=3604", ["sourceUrl"] = "https://octowow.st/db/?item=3604",
["sources"] = { ["sources"] = {
["reward-of"] = { ["reward-of"] = {
["template"] = "quest", ["template"] = "quest",
@@ -254,7 +254,7 @@ MTH_DS_BagItems = {
["subtype"] = "quiver", ["subtype"] = "quiver",
["description"] = "", ["description"] = "",
["tooltip"] = "Quiver of the Night Watch\nBinds when picked up\nUnique\n12 Slot Bag\nEquip: Increases ranged attack speed by 11%.", ["tooltip"] = "Quiver of the Night Watch\nBinds when picked up\nUnique\n12 Slot Bag\nEquip: Increases ranged attack speed by 11%.",
["sourceUrl"] = "https://database.turtlecraft.gg/?item=3605", ["sourceUrl"] = "https://octowow.st/db/?item=3605",
["sources"] = { ["sources"] = {
["reward-of"] = { ["reward-of"] = {
["template"] = "quest", ["template"] = "quest",
@@ -273,7 +273,7 @@ MTH_DS_BagItems = {
["subtype"] = "quiver", ["subtype"] = "quiver",
["description"] = "", ["description"] = "",
["tooltip"] = "Small Quiver\n8 Slot Bag\nEquip: Increases ranged attack speed by 10%.", ["tooltip"] = "Small Quiver\n8 Slot Bag\nEquip: Increases ranged attack speed by 10%.",
["sourceUrl"] = "https://database.turtlecraft.gg/?item=5439", ["sourceUrl"] = "https://octowow.st/db/?item=5439",
["sources"] = { ["sources"] = {
["sold-by"] = { ["sold-by"] = {
["template"] = "npc", ["template"] = "npc",
@@ -621,7 +621,7 @@ MTH_DS_BagItems = {
["subtype"] = "ammo pouch", ["subtype"] = "ammo pouch",
["description"] = "", ["description"] = "",
["tooltip"] = "Small Shot Pouch\n8 Slot Bag\nEquip: Increases ranged attack speed by 10%.", ["tooltip"] = "Small Shot Pouch\n8 Slot Bag\nEquip: Increases ranged attack speed by 10%.",
["sourceUrl"] = "https://database.turtlecraft.gg/?item=5441", ["sourceUrl"] = "https://octowow.st/db/?item=5441",
["sources"] = { ["sources"] = {
["sold-by"] = { ["sold-by"] = {
["template"] = "npc", ["template"] = "npc",
@@ -773,7 +773,7 @@ MTH_DS_BagItems = {
["subtype"] = "quiver", ["subtype"] = "quiver",
["description"] = "", ["description"] = "",
["tooltip"] = "Light Leather Quiver\n8 Slot Bag\nEquip: Increases ranged attack speed by 10%.", ["tooltip"] = "Light Leather Quiver\n8 Slot Bag\nEquip: Increases ranged attack speed by 10%.",
["sourceUrl"] = "https://database.turtlecraft.gg/?item=7278", ["sourceUrl"] = "https://octowow.st/db/?item=7278",
["sources"] = { ["sources"] = {
["created-by"] = { ["created-by"] = {
["template"] = "spell", ["template"] = "spell",
@@ -796,7 +796,7 @@ MTH_DS_BagItems = {
["subtype"] = "ammo pouch", ["subtype"] = "ammo pouch",
["description"] = "", ["description"] = "",
["tooltip"] = "Small Leather Ammo Pouch\n8 Slot Bag\nEquip: Increases ranged attack speed by 10%.", ["tooltip"] = "Small Leather Ammo Pouch\n8 Slot Bag\nEquip: Increases ranged attack speed by 10%.",
["sourceUrl"] = "https://database.turtlecraft.gg/?item=7279", ["sourceUrl"] = "https://octowow.st/db/?item=7279",
["sources"] = { ["sources"] = {
["created-by"] = { ["created-by"] = {
["template"] = "spell", ["template"] = "spell",
@@ -820,7 +820,7 @@ MTH_DS_BagItems = {
["subtype"] = "quiver", ["subtype"] = "quiver",
["description"] = "", ["description"] = "",
["tooltip"] = "Heavy Quiver\n14 Slot Bag\nRequires Level 30\nEquip: Increases ranged attack speed by 12%.", ["tooltip"] = "Heavy Quiver\n14 Slot Bag\nRequires Level 30\nEquip: Increases ranged attack speed by 12%.",
["sourceUrl"] = "https://database.turtlecraft.gg/?item=7371", ["sourceUrl"] = "https://octowow.st/db/?item=7371",
["sources"] = { ["sources"] = {
["sold-by"] = { ["sold-by"] = {
["template"] = "npc", ["template"] = "npc",
@@ -871,7 +871,7 @@ MTH_DS_BagItems = {
["subtype"] = "ammo pouch", ["subtype"] = "ammo pouch",
["description"] = "", ["description"] = "",
["tooltip"] = "Heavy Leather Ammo Pouch\n14 Slot Bag\nRequires Level 30\nEquip: Increases ranged attack speed by 12%.", ["tooltip"] = "Heavy Leather Ammo Pouch\n14 Slot Bag\nRequires Level 30\nEquip: Increases ranged attack speed by 12%.",
["sourceUrl"] = "https://database.turtlecraft.gg/?item=7372", ["sourceUrl"] = "https://octowow.st/db/?item=7372",
["sources"] = { ["sources"] = {
["created-by"] = { ["created-by"] = {
["template"] = "spell", ["template"] = "spell",
@@ -895,7 +895,7 @@ MTH_DS_BagItems = {
["subtype"] = "quiver", ["subtype"] = "quiver",
["description"] = "", ["description"] = "",
["tooltip"] = "Quickdraw Quiver\n16 Slot Bag\nRequires Level 40\nEquip: Increases ranged attack speed by 13%.", ["tooltip"] = "Quickdraw Quiver\n16 Slot Bag\nRequires Level 40\nEquip: Increases ranged attack speed by 13%.",
["sourceUrl"] = "https://database.turtlecraft.gg/?item=8217", ["sourceUrl"] = "https://octowow.st/db/?item=8217",
["sources"] = { ["sources"] = {
["created-by"] = { ["created-by"] = {
["template"] = "spell", ["template"] = "spell",
@@ -919,7 +919,7 @@ MTH_DS_BagItems = {
["subtype"] = "ammo pouch", ["subtype"] = "ammo pouch",
["description"] = "", ["description"] = "",
["tooltip"] = "Thick Leather Ammo Pouch\n16 Slot Bag\nRequires Level 40\nEquip: Increases ranged attack speed by 13%.", ["tooltip"] = "Thick Leather Ammo Pouch\n16 Slot Bag\nRequires Level 40\nEquip: Increases ranged attack speed by 13%.",
["sourceUrl"] = "https://database.turtlecraft.gg/?item=8218", ["sourceUrl"] = "https://octowow.st/db/?item=8218",
["sources"] = { ["sources"] = {
["created-by"] = { ["created-by"] = {
["template"] = "spell", ["template"] = "spell",
@@ -943,7 +943,7 @@ MTH_DS_BagItems = {
["subtype"] = "quiver", ["subtype"] = "quiver",
["description"] = "", ["description"] = "",
["tooltip"] = "Medium Quiver\n10 Slot Bag\nRequires Level 10\nEquip: Increases ranged attack speed by 10%.", ["tooltip"] = "Medium Quiver\n10 Slot Bag\nRequires Level 10\nEquip: Increases ranged attack speed by 10%.",
["sourceUrl"] = "https://database.turtlecraft.gg/?item=11362", ["sourceUrl"] = "https://octowow.st/db/?item=11362",
["sources"] = { ["sources"] = {
["sold-by"] = { ["sold-by"] = {
["template"] = "npc", ["template"] = "npc",
@@ -1327,7 +1327,7 @@ MTH_DS_BagItems = {
["subtype"] = "ammo pouch", ["subtype"] = "ammo pouch",
["description"] = "", ["description"] = "",
["tooltip"] = "Medium Shot Pouch\n10 Slot Bag\nRequires Level 10\nEquip: Increases ranged attack speed by 10%.", ["tooltip"] = "Medium Shot Pouch\n10 Slot Bag\nRequires Level 10\nEquip: Increases ranged attack speed by 10%.",
["sourceUrl"] = "https://database.turtlecraft.gg/?item=11363", ["sourceUrl"] = "https://octowow.st/db/?item=11363",
["sources"] = { ["sources"] = {
["sold-by"] = { ["sold-by"] = {
["template"] = "npc", ["template"] = "npc",
@@ -1368,7 +1368,7 @@ MTH_DS_BagItems = {
["subtype"] = "quiver", ["subtype"] = "quiver",
["description"] = "", ["description"] = "",
["tooltip"] = "Ancient Sinew Wrapped Lamina\nBinds when picked up\nUnique\n18 Slot Bag\nClasses: Hunter\nRequires Level 60\nEquip: Increases ranged attack speed by 15%.", ["tooltip"] = "Ancient Sinew Wrapped Lamina\nBinds when picked up\nUnique\n18 Slot Bag\nClasses: Hunter\nRequires Level 60\nEquip: Increases ranged attack speed by 15%.",
["sourceUrl"] = "https://database.turtlecraft.gg/?item=18714", ["sourceUrl"] = "https://octowow.st/db/?item=18714",
["sources"] = { ["sources"] = {
["reward-of"] = { ["reward-of"] = {
["template"] = "quest", ["template"] = "quest",
@@ -1388,7 +1388,7 @@ MTH_DS_BagItems = {
["subtype"] = "quiver", ["subtype"] = "quiver",
["description"] = "", ["description"] = "",
["tooltip"] = "Harpy Hide Quiver\nBinds when picked up\nUnique\n16 Slot Bag\nRequires Level 55\nEquip: Increases ranged attack speed by 15%.", ["tooltip"] = "Harpy Hide Quiver\nBinds when picked up\nUnique\n16 Slot Bag\nRequires Level 55\nEquip: Increases ranged attack speed by 15%.",
["sourceUrl"] = "https://database.turtlecraft.gg/?item=19319", ["sourceUrl"] = "https://octowow.st/db/?item=19319",
["sources"] = { ["sources"] = {
["sold-by"] = { ["sold-by"] = {
["template"] = "npc", ["template"] = "npc",
@@ -1440,7 +1440,7 @@ MTH_DS_BagItems = {
["subtype"] = "ammo pouch", ["subtype"] = "ammo pouch",
["description"] = "", ["description"] = "",
["tooltip"] = "Gnoll Skin Bandolier\nBinds when picked up\nUnique\n16 Slot Bag\nRequires Level 55\nEquip: Increases ranged attack speed by 15%.", ["tooltip"] = "Gnoll Skin Bandolier\nBinds when picked up\nUnique\n16 Slot Bag\nRequires Level 55\nEquip: Increases ranged attack speed by 15%.",
["sourceUrl"] = "https://database.turtlecraft.gg/?item=19320", ["sourceUrl"] = "https://octowow.st/db/?item=19320",
["sources"] = { ["sources"] = {
["sold-by"] = { ["sold-by"] = {
["template"] = "npc", ["template"] = "npc",
@@ -1491,7 +1491,7 @@ MTH_DS_BagItems = {
["subtype"] = "quiver", ["subtype"] = "quiver",
["description"] = "", ["description"] = "",
["tooltip"] = "Swiftfeather Quiver\nBinds when equipped\nUnique\n16 Slot Bag\nRequires Level 57\nEquip: Increases ranged attack speed by 14%.", ["tooltip"] = "Swiftfeather Quiver\nBinds when equipped\nUnique\n16 Slot Bag\nRequires Level 57\nEquip: Increases ranged attack speed by 14%.",
["sourceUrl"] = "https://database.turtlecraft.gg/?item=61549", ["sourceUrl"] = "https://octowow.st/db/?item=61549",
["sources"] = { ["sources"] = {
["dropped-by"] = { ["dropped-by"] = {
["template"] = "npc", ["template"] = "npc",
File diff suppressed because it is too large Load Diff
+9 -9
View File
@@ -1,7 +1,7 @@
-- MetaHunt: Beast family DBC enrichment (scales, food validation) -- MetaHunt: Beast family DBC enrichment (scales, food validation)
-- AUTO-GENERATED by .tools — do not edit by hand. -- AUTO-GENERATED by .tools — do not edit by hand.
-- Source DBC(s): CreatureFamily.dbc, ItemPetFood.dbc -- Source DBC(s): CreatureFamily.dbc, ItemPetFood.dbc
-- Generated: 2026-03-17 08:39 UTC -- Generated: 2026-03-20 13:57 UTC
if not MTH_DS_Families then return end if not MTH_DS_Families then return end
@@ -295,14 +295,14 @@ if MTH_DS_Families["Wind Serpents"] then
end end
-- DBC ID=35 PetFoodMask=1009 scale=[0.60-0.80] -- DBC ID=35 PetFoodMask=1009 scale=[0.60-0.80]
if MTH_DS_Families["Serpents (Cobra)"] then if MTH_DS_Families["Serpents"] then
MTH_DS_Families["Serpents (Cobra)"]["dbcId"] = 35 MTH_DS_Families["Serpents"]["dbcId"] = 35
MTH_DS_Families["Serpents (Cobra)"]["minScale"] = 0.6 MTH_DS_Families["Serpents"]["minScale"] = 0.6
MTH_DS_Families["Serpents (Cobra)"]["maxScale"] = 0.8 MTH_DS_Families["Serpents"]["maxScale"] = 0.8
MTH_DS_Families["Serpents (Cobra)"]["minScaleLevel"] = 1 MTH_DS_Families["Serpents"]["minScaleLevel"] = 1
MTH_DS_Families["Serpents (Cobra)"]["maxScaleLevel"] = 60 MTH_DS_Families["Serpents"]["maxScaleLevel"] = 60
MTH_DS_Families["Serpents (Cobra)"]["foodMask"] = 1009 MTH_DS_Families["Serpents"]["foodMask"] = 1009
MTH_DS_Families["Serpents (Cobra)"]["foodDbc"] = { MTH_DS_Families["Serpents"]["foodDbc"] = {
"meat", "meat",
"fungus", "fungus",
"fruit", "fruit",
+37 -1
View File
@@ -9,6 +9,7 @@ MTH_DS_Families = {
["icon"] = "Ability_Hunter_Pet_Bat", ["icon"] = "Ability_Hunter_Pet_Bat",
["named"] = 20, ["named"] = 20,
["coords"] = 18, ["coords"] = 18,
["stats"] = { ["health"] = 1, ["armor"] = 1, ["damage"] = 1.07 },
["food"] = { ["food"] = {
"fruit", "fruit",
"fungus", "fungus",
@@ -25,6 +26,7 @@ MTH_DS_Families = {
["icon"] = "Ability_Hunter_Pet_Bear", ["icon"] = "Ability_Hunter_Pet_Bear",
["named"] = 45, ["named"] = 45,
["coords"] = 45, ["coords"] = 45,
["stats"] = { ["health"] = 1.08, ["armor"] = 1.05, ["damage"] = 0.91 },
["food"] = { ["food"] = {
"meat", "meat",
"fish", "fish",
@@ -47,6 +49,7 @@ MTH_DS_Families = {
["icon"] = "Ability_Hunter_Pet_Boar", ["icon"] = "Ability_Hunter_Pet_Boar",
["named"] = 45, ["named"] = 45,
["coords"] = 87, ["coords"] = 87,
["stats"] = { ["health"] = 1.04, ["armor"] = 1.09, ["damage"] = 0.9 },
["food"] = { ["food"] = {
"meat", "meat",
"fish", "fish",
@@ -69,6 +72,7 @@ MTH_DS_Families = {
["icon"] = "Ability_Hunter_Pet_Vulture", ["icon"] = "Ability_Hunter_Pet_Vulture",
["named"] = 36, ["named"] = 36,
["coords"] = 101, ["coords"] = 101,
["stats"] = { ["health"] = 1, ["armor"] = 1.05, ["damage"] = 1 },
["food"] = { ["food"] = {
"meat", "meat",
"raw meat", "raw meat",
@@ -86,6 +90,7 @@ MTH_DS_Families = {
["icon"] = "Ability_Hunter_Pet_Cat", ["icon"] = "Ability_Hunter_Pet_Cat",
["named"] = 85, ["named"] = 85,
["coords"] = 88, ["coords"] = 88,
["stats"] = { ["health"] = 0.98, ["armor"] = 1, ["damage"] = 1.1 },
["food"] = { ["food"] = {
"meat", "meat",
"fish", "fish",
@@ -105,6 +110,7 @@ MTH_DS_Families = {
["icon"] = "Ability_Hunter_Pet_Crab", ["icon"] = "Ability_Hunter_Pet_Crab",
["named"] = 32, ["named"] = 32,
["coords"] = 74, ["coords"] = 74,
["stats"] = { ["health"] = 0.96, ["armor"] = 1.13, ["damage"] = 0.95 },
["food"] = { ["food"] = {
"fruit", "fruit",
"fish", "fish",
@@ -123,6 +129,7 @@ MTH_DS_Families = {
["icon"] = "Ability_Hunter_Pet_Crocolisk", ["icon"] = "Ability_Hunter_Pet_Crocolisk",
["named"] = 27, ["named"] = 27,
["coords"] = 71, ["coords"] = 71,
["stats"] = { ["health"] = 0.95, ["armor"] = 1.1, ["damage"] = 1 },
["food"] = { ["food"] = {
"meat", "meat",
"fish", "fish",
@@ -140,6 +147,7 @@ MTH_DS_Families = {
["icon"] = "ability_hunter_aspectofthefox", ["icon"] = "ability_hunter_aspectofthefox",
["named"] = 9, ["named"] = 9,
["coords"] = 51, ["coords"] = 51,
["stats"] = { ["health"] = 1, ["armor"] = 1, ["damage"] = 1.07 },
["food"] = { ["food"] = {
"meat", "meat",
"fruit", "fruit",
@@ -157,6 +165,7 @@ MTH_DS_Families = {
["icon"] = "Ability_Hunter_Pet_Gorilla", ["icon"] = "Ability_Hunter_Pet_Gorilla",
["named"] = 24, ["named"] = 24,
["coords"] = 117, ["coords"] = 117,
["stats"] = { ["health"] = 1.04, ["armor"] = 1, ["damage"] = 1.02 },
["food"] = { ["food"] = {
"fruit", "fruit",
"fungus", "fungus",
@@ -172,6 +181,7 @@ MTH_DS_Families = {
["icon"] = "Ability_Hunter_Pet_Hyena", ["icon"] = "Ability_Hunter_Pet_Hyena",
["named"] = 26, ["named"] = 26,
["coords"] = 36, ["coords"] = 36,
["stats"] = { ["health"] = 1, ["armor"] = 1.05, ["damage"] = 1 },
["food"] = { ["food"] = {
"meat", "meat",
"fruit", "fruit",
@@ -189,6 +199,7 @@ MTH_DS_Families = {
["icon"] = "Ability_Hunter_Pet_Owl", ["icon"] = "Ability_Hunter_Pet_Owl",
["named"] = 16, ["named"] = 16,
["coords"] = 59, ["coords"] = 59,
["stats"] = { ["health"] = 1, ["armor"] = 1, ["damage"] = 1.07 },
["food"] = { ["food"] = {
"meat", "meat",
"raw meat", "raw meat",
@@ -205,6 +216,7 @@ MTH_DS_Families = {
["icon"] = "Ability_Hunter_Pet_Raptor", ["icon"] = "Ability_Hunter_Pet_Raptor",
["named"] = 51, ["named"] = 51,
["coords"] = 115, ["coords"] = 115,
["stats"] = { ["health"] = 0.95, ["armor"] = 1.03, ["damage"] = 1.1 },
["food"] = { ["food"] = {
"meat", "meat",
"raw meat", "raw meat",
@@ -222,6 +234,7 @@ MTH_DS_Families = {
["icon"] = "Ability_Hunter_Pet_Scorpid", ["icon"] = "Ability_Hunter_Pet_Scorpid",
["named"] = 38, ["named"] = 38,
["coords"] = 36, ["coords"] = 36,
["stats"] = { ["health"] = 1, ["armor"] = 1.1, ["damage"] = 0.94 },
["food"] = { ["food"] = {
"meat", "meat",
"raw meat", "raw meat",
@@ -233,10 +246,11 @@ MTH_DS_Families = {
"Scorpid Poison", "Scorpid Poison",
}, },
}, },
["Serpents (Cobra)"] = { ["Serpents"] = {
["icon"] = "Ability_Hunter_Pet_Serpent1", ["icon"] = "Ability_Hunter_Pet_Serpent1",
["named"] = 10, ["named"] = 10,
["coords"] = 13, ["coords"] = 13,
["stats"] = { ["health"] = 1.03, ["armor"] = 1.03, ["damage"] = 1.135 },
["food"] = { ["food"] = {
"meat", "meat",
"fish", "fish",
@@ -254,6 +268,7 @@ MTH_DS_Families = {
["icon"] = "Ability_Hunter_Pet_Spider", ["icon"] = "Ability_Hunter_Pet_Spider",
["named"] = 91, ["named"] = 91,
["coords"] = 213, ["coords"] = 213,
["stats"] = { ["health"] = 1, ["armor"] = 1, ["damage"] = 1.07 },
["food"] = { ["food"] = {
"meat", "meat",
"raw meat", "raw meat",
@@ -269,6 +284,7 @@ MTH_DS_Families = {
["icon"] = "Ability_Hunter_Pet_TallStrider", ["icon"] = "Ability_Hunter_Pet_TallStrider",
["named"] = 15, ["named"] = 15,
["coords"] = 112, ["coords"] = 112,
["stats"] = { ["health"] = 1.05, ["armor"] = 1, ["damage"] = 1 },
["food"] = { ["food"] = {
"fruit", "fruit",
"fungus", "fungus",
@@ -286,6 +302,7 @@ MTH_DS_Families = {
["icon"] = "Ability_Hunter_Pet_Turtle", ["icon"] = "Ability_Hunter_Pet_Turtle",
["named"] = 25, ["named"] = 25,
["coords"] = 42, ["coords"] = 42,
["stats"] = { ["health"] = 1, ["armor"] = 1.13, ["damage"] = 0.9 },
["food"] = { ["food"] = {
"fruit", "fruit",
"fish", "fish",
@@ -303,6 +320,7 @@ MTH_DS_Families = {
["icon"] = "Ability_Hunter_Pet_WindSerpent", ["icon"] = "Ability_Hunter_Pet_WindSerpent",
["named"] = 27, ["named"] = 27,
["coords"] = 42, ["coords"] = 42,
["stats"] = { ["health"] = 1, ["armor"] = 1, ["damage"] = 1.07 },
["food"] = { ["food"] = {
"fish", "fish",
"cheese", "cheese",
@@ -321,6 +339,7 @@ MTH_DS_Families = {
["icon"] = "Ability_Hunter_Pet_Wolf", ["icon"] = "Ability_Hunter_Pet_Wolf",
["named"] = 81, ["named"] = 81,
["coords"] = 99, ["coords"] = 99,
["stats"] = { ["health"] = 1, ["armor"] = 1.05, ["damage"] = 1 },
["food"] = { ["food"] = {
"meat", "meat",
"raw meat", "raw meat",
@@ -333,4 +352,21 @@ MTH_DS_Families = {
"Growl", "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
+19 -3
View File
@@ -1,8 +1,8 @@
-- MetaHunt: Beast appearance skin variants per family -- MetaHunt: Beast appearance skin variants per family
-- AUTO-GENERATED by .tools — do not edit by hand. -- AUTO-GENERATED by .tools — do not edit by hand.
-- Source DBC(s): CreatureDisplayInfo.dbc, CreatureModelData.dbc -- Source DBC(s): CreatureDisplayInfo.dbc, CreatureModelData.dbc
-- Generated: 2026-03-17 14:49 UTC -- Generated: 2026-03-23 15:09 UTC
-- Total skins: 244 across 19 families -- Total skins: 258 across 20 families
if not MTH_DS then MTH_DS = {} end if not MTH_DS then MTH_DS = {} end
@@ -28,6 +28,8 @@ MTH_DS_BeastSkins = {
{id=20635, tex="BearSkinBrownDiseased", 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=20636, tex="BearSkinBlueDiseased", unique=true, modelPath="Creature\\bear\\Bear.mdx"},
{id=20637, tex="BearSkinWhiteDiseased", 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"] = { ["Boars"] = {
{id=193, tex="BoarSkinIvory", unique=false, modelPath="Creature\\Boar\\Boar.mdx"}, {id=193, tex="BoarSkinIvory", unique=false, modelPath="Creature\\Boar\\Boar.mdx"},
@@ -151,6 +153,18 @@ MTH_DS_BeastSkins = {
{id=2714, tex="HyenaSkinWhite", 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"}, {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"] = { ["Owls"] = {
{id=4566, tex="OwlBrown", unique=false, modelPath="Creature\\Owl\\Owl.mdx"}, {id=4566, tex="OwlBrown", unique=false, modelPath="Creature\\Owl\\Owl.mdx"},
{id=4877, tex="OwlGrey", unique=false, modelPath="Creature\\Owl\\Owl.mdx"}, {id=4877, tex="OwlGrey", unique=false, modelPath="Creature\\Owl\\Owl.mdx"},
@@ -194,7 +208,7 @@ MTH_DS_BeastSkins = {
{id=18373, tex="SilithidScarabSkinGreen", unique=true, 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"}, {id=21268, tex="ScorpionskinDarkIron", unique=true, modelPath="Creature\\Scorpion\\Scorpion.mdx"},
}, },
["Serpents (Cobra)"] = { ["Serpents"] = {
{id=1206, tex="SnakeSkinBlack", unique=false, modelPath="Creature\\snake\\snake.mdx"}, {id=1206, tex="SnakeSkinBlack", unique=false, modelPath="Creature\\snake\\snake.mdx"},
{id=1986, tex="SnakeSkinDarkGreenYellow", 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=1987, tex="SnakeSkinYellowBlack", unique=false, modelPath="Creature\\snake\\snake.mdx"},
@@ -242,6 +256,8 @@ MTH_DS_BeastSkins = {
{id=20511, tex="HawkstriderGreen", 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=20512, tex="HawkstriderRed", unique=true, modelPath="Creature\\Hawkstrider\\Hawkstrider.mdx"},
{id=20513, tex="HawkstriderWhite", 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"] = { ["Turtles"] = {
{id=1244, tex="SeaTurtleSkin", unique=false, modelPath="Creature\\SeaTurtle\\SeaTurtle.mdx"}, {id=1244, tex="SeaTurtleSkin", unique=false, modelPath="Creature\\SeaTurtle\\SeaTurtle.mdx"},
File diff suppressed because it is too large Load Diff
+26481 -1541
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, }, [9426] = {["boss"] = true, },
[9456] = {["boss"] = true, }, [9456] = {["boss"] = true, },
[9487] = {["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 for itemId, sourceMap in pairs(MTH_DS_AtlasSources) do
+350 -324
View File
File diff suppressed because it is too large Load Diff
+72
View File
@@ -10,6 +10,7 @@ MTH_DS_AmmoItems = {
["level"] = 5, ["level"] = 5,
["reqlevel"] = 1, ["reqlevel"] = 1,
["icon"] = "INV_Ammo_Arrow_02", ["icon"] = "INV_Ammo_Arrow_02",
["dps"] = 1.5,
["subtype"] = "arrow", ["subtype"] = "arrow",
["vendors"] = { ["vendors"] = {
[150] = 0, [150] = 0,
@@ -118,6 +119,7 @@ MTH_DS_AmmoItems = {
["level"] = 15, ["level"] = 15,
["reqlevel"] = 10, ["reqlevel"] = 10,
["icon"] = "INV_Ammo_Arrow_02", ["icon"] = "INV_Ammo_Arrow_02",
["dps"] = 3.5,
["subtype"] = "arrow", ["subtype"] = "arrow",
["vendors"] = { ["vendors"] = {
[150] = 0, [150] = 0,
@@ -275,6 +277,7 @@ MTH_DS_AmmoItems = {
["level"] = 5, ["level"] = 5,
["reqlevel"] = 1, ["reqlevel"] = 1,
["icon"] = "INV_Ammo_Bullet_02", ["icon"] = "INV_Ammo_Bullet_02",
["dps"] = 2,
["subtype"] = "bullet", ["subtype"] = "bullet",
["vendors"] = { ["vendors"] = {
[151] = 0, [151] = 0,
@@ -359,6 +362,7 @@ MTH_DS_AmmoItems = {
["level"] = 15, ["level"] = 15,
["reqlevel"] = 10, ["reqlevel"] = 10,
["icon"] = "INV_Ammo_Bullet_02", ["icon"] = "INV_Ammo_Bullet_02",
["dps"] = 5.5,
["subtype"] = "bullet", ["subtype"] = "bullet",
["vendors"] = { ["vendors"] = {
[227] = 0, [227] = 0,
@@ -494,6 +498,7 @@ MTH_DS_AmmoItems = {
["level"] = 30, ["level"] = 30,
["reqlevel"] = 25, ["reqlevel"] = 25,
["icon"] = "INV_Ammo_Arrow_02", ["icon"] = "INV_Ammo_Arrow_02",
["dps"] = 5.5,
["subtype"] = "arrow", ["subtype"] = "arrow",
["vendors"] = { ["vendors"] = {
[227] = 0, [227] = 0,
@@ -621,6 +626,7 @@ MTH_DS_AmmoItems = {
["level"] = 30, ["level"] = 30,
["reqlevel"] = 25, ["reqlevel"] = 25,
["icon"] = "INV_Ammo_Bullet_02", ["icon"] = "INV_Ammo_Bullet_02",
["dps"] = 7.5,
["subtype"] = "bullet", ["subtype"] = "bullet",
["vendors"] = { ["vendors"] = {
[227] = 0, [227] = 0,
@@ -736,18 +742,21 @@ MTH_DS_AmmoItems = {
["name"] = "Feathered Arrow", ["name"] = "Feathered Arrow",
["level"] = 35, ["level"] = 35,
["icon"] = "INV_Ammo_Arrow_02", ["icon"] = "INV_Ammo_Arrow_02",
["dps"] = 7.5,
["subtype"] = "arrow", ["subtype"] = "arrow",
}, },
[3465] = { [3465] = {
["name"] = "Exploding Shot", ["name"] = "Exploding Shot",
["level"] = 36, ["level"] = 36,
["icon"] = "INV_Ammo_Bullet_02", ["icon"] = "INV_Ammo_Bullet_02",
["dps"] = 7.5,
["subtype"] = "bullet", ["subtype"] = "bullet",
}, },
[4960] = { [4960] = {
["name"] = "Flash Pellet", ["name"] = "Flash Pellet",
["level"] = 7, ["level"] = 7,
["icon"] = "INV_Ammo_Bullet_02", ["icon"] = "INV_Ammo_Bullet_02",
["dps"] = 3.5,
["subtype"] = "bullet", ["subtype"] = "bullet",
}, },
[5568] = { [5568] = {
@@ -755,6 +764,7 @@ MTH_DS_AmmoItems = {
["level"] = 18, ["level"] = 18,
["reqlevel"] = 13, ["reqlevel"] = 13,
["icon"] = "INV_Ammo_Bullet_02", ["icon"] = "INV_Ammo_Bullet_02",
["dps"] = 3.5,
["subtype"] = "bullet", ["subtype"] = "bullet",
["drops"] = { ["drops"] = {
[2156] = 30.97, [2156] = 30.97,
@@ -781,6 +791,7 @@ MTH_DS_AmmoItems = {
["level"] = 10, ["level"] = 10,
["reqlevel"] = 5, ["reqlevel"] = 5,
["icon"] = "INV_Ammo_Bullet_02", ["icon"] = "INV_Ammo_Bullet_02",
["dps"] = 2,
["subtype"] = "bullet", ["subtype"] = "bullet",
}, },
[8068] = { [8068] = {
@@ -788,6 +799,7 @@ MTH_DS_AmmoItems = {
["level"] = 20, ["level"] = 20,
["reqlevel"] = 15, ["reqlevel"] = 15,
["icon"] = "INV_Ammo_Bullet_02", ["icon"] = "INV_Ammo_Bullet_02",
["dps"] = 5.5,
["subtype"] = "bullet", ["subtype"] = "bullet",
}, },
[8069] = { [8069] = {
@@ -795,6 +807,7 @@ MTH_DS_AmmoItems = {
["level"] = 35, ["level"] = 35,
["reqlevel"] = 30, ["reqlevel"] = 30,
["icon"] = "INV_Ammo_Bullet_02", ["icon"] = "INV_Ammo_Bullet_02",
["dps"] = 7.5,
["subtype"] = "bullet", ["subtype"] = "bullet",
}, },
[9399] = { [9399] = {
@@ -802,6 +815,7 @@ MTH_DS_AmmoItems = {
["level"] = 40, ["level"] = 40,
["reqlevel"] = 35, ["reqlevel"] = 35,
["icon"] = "INV_Ammo_Arrow_01", ["icon"] = "INV_Ammo_Arrow_01",
["dps"] = 7.5,
["subtype"] = "arrow", ["subtype"] = "arrow",
["drops"] = { ["drops"] = {
[6906] = 100, [6906] = 100,
@@ -812,6 +826,7 @@ MTH_DS_AmmoItems = {
["level"] = 42, ["level"] = 42,
["reqlevel"] = 37, ["reqlevel"] = 37,
["icon"] = "INV_Ammo_Bullet_01", ["icon"] = "INV_Ammo_Bullet_01",
["dps"] = 12.5,
["subtype"] = "bullet", ["subtype"] = "bullet",
}, },
[10513] = { [10513] = {
@@ -819,6 +834,7 @@ MTH_DS_AmmoItems = {
["level"] = 49, ["level"] = 49,
["reqlevel"] = 44, ["reqlevel"] = 44,
["icon"] = "INV_Ammo_Bullet_01", ["icon"] = "INV_Ammo_Bullet_01",
["dps"] = 12.5,
["subtype"] = "bullet", ["subtype"] = "bullet",
}, },
[10579] = { [10579] = {
@@ -826,6 +842,7 @@ MTH_DS_AmmoItems = {
["level"] = 42, ["level"] = 42,
["reqlevel"] = 37, ["reqlevel"] = 37,
["icon"] = "INV_Ammo_Arrow_02", ["icon"] = "INV_Ammo_Arrow_02",
["dps"] = 12.5,
["subtype"] = "arrow", ["subtype"] = "arrow",
}, },
[11284] = { [11284] = {
@@ -833,6 +850,7 @@ MTH_DS_AmmoItems = {
["level"] = 45, ["level"] = 45,
["reqlevel"] = 40, ["reqlevel"] = 40,
["icon"] = "INV_Ammo_Bullet_01", ["icon"] = "INV_Ammo_Bullet_01",
["dps"] = 10.5,
["subtype"] = "bullet", ["subtype"] = "bullet",
["vendors"] = { ["vendors"] = {
[734] = 0, [734] = 0,
@@ -934,6 +952,7 @@ MTH_DS_AmmoItems = {
["level"] = 45, ["level"] = 45,
["reqlevel"] = 40, ["reqlevel"] = 40,
["icon"] = "INV_Weapon_ShortBlade_25", ["icon"] = "INV_Weapon_ShortBlade_25",
["dps"] = 10.5,
["subtype"] = "arrow", ["subtype"] = "arrow",
["vendors"] = { ["vendors"] = {
[228] = 0, [228] = 0,
@@ -1043,6 +1062,7 @@ MTH_DS_AmmoItems = {
["level"] = 52, ["level"] = 52,
["reqlevel"] = 47, ["reqlevel"] = 47,
["icon"] = "INV_Ammo_Bullet_02", ["icon"] = "INV_Ammo_Bullet_02",
["dps"] = 10.5,
["subtype"] = "bullet", ["subtype"] = "bullet",
["drops"] = { ["drops"] = {
[9025] = 16, [9025] = 16,
@@ -1053,6 +1073,7 @@ MTH_DS_AmmoItems = {
["level"] = 59, ["level"] = 59,
["reqlevel"] = 54, ["reqlevel"] = 54,
["icon"] = "Ability_Hunter_CriticalShot", ["icon"] = "Ability_Hunter_CriticalShot",
["dps"] = 20,
["subtype"] = "arrow", ["subtype"] = "arrow",
["drops"] = { ["drops"] = {
[9236] = 100, [9236] = 100,
@@ -1063,6 +1084,7 @@ MTH_DS_AmmoItems = {
["level"] = 61, ["level"] = 61,
["reqlevel"] = 56, ["reqlevel"] = 56,
["icon"] = "INV_Ammo_Bullet_02", ["icon"] = "INV_Ammo_Bullet_02",
["dps"] = 20.5,
["subtype"] = "bullet", ["subtype"] = "bullet",
["drops"] = { ["drops"] = {
[10997] = 100, [10997] = 100,
@@ -1073,6 +1095,7 @@ MTH_DS_AmmoItems = {
["level"] = 57, ["level"] = 57,
["reqlevel"] = 52, ["reqlevel"] = 52,
["icon"] = "INV_Ammo_Bullet_03", ["icon"] = "INV_Ammo_Bullet_03",
["dps"] = 17.5,
["subtype"] = "bullet", ["subtype"] = "bullet",
["vendors"] = { ["vendors"] = {
[17078] = 0, [17078] = 0,
@@ -1083,6 +1106,7 @@ MTH_DS_AmmoItems = {
["level"] = 57, ["level"] = 57,
["reqlevel"] = 52, ["reqlevel"] = 52,
["icon"] = "INV_Ammo_Arrow_02", ["icon"] = "INV_Ammo_Arrow_02",
["dps"] = 17.5,
["subtype"] = "arrow", ["subtype"] = "arrow",
["vendors"] = { ["vendors"] = {
[17078] = 0, [17078] = 0,
@@ -1093,6 +1117,7 @@ MTH_DS_AmmoItems = {
["level"] = 1, ["level"] = 1,
["reqlevel"] = 1, ["reqlevel"] = 1,
["icon"] = "INV_Ammo_Arrow_02", ["icon"] = "INV_Ammo_Arrow_02",
["dps"] = 1.5,
["subtype"] = "arrow", ["subtype"] = "arrow",
}, },
[19316] = { [19316] = {
@@ -1100,6 +1125,7 @@ MTH_DS_AmmoItems = {
["level"] = 54, ["level"] = 54,
["reqlevel"] = 51, ["reqlevel"] = 51,
["icon"] = "Spell_Frost_IceShard", ["icon"] = "Spell_Frost_IceShard",
["dps"] = 14.5,
["subtype"] = "arrow", ["subtype"] = "arrow",
["vendors"] = { ["vendors"] = {
[13216] = 0, [13216] = 0,
@@ -1113,6 +1139,7 @@ MTH_DS_AmmoItems = {
["level"] = 54, ["level"] = 54,
["reqlevel"] = 51, ["reqlevel"] = 51,
["icon"] = "Spell_Frost_FrostBlast", ["icon"] = "Spell_Frost_FrostBlast",
["dps"] = 14.5,
["subtype"] = "bullet", ["subtype"] = "bullet",
["vendors"] = { ["vendors"] = {
[13216] = 0, [13216] = 0,
@@ -1121,6 +1148,51 @@ MTH_DS_AmmoItems = {
[13219] = 0, [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 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 = { MTH_DS_MinimapSizes = {
[0] = { 0, 0 }, [0] = { 0, 0 },
[1] = { 4925, 3283.34 }, [9] = { 5137.50, 3425.00 }, -- Mulgore
[3] = { 2487.5, 1658.34 }, [11] = { 2487.50, 1658.34 }, -- Barrens
[4] = { 3350, 2233.3 }, [15] = { 2170.84, 1447.90 }, -- Alterac
[8] = { 2293.75, 1529.17 }, [19] = { 5287.50, 3525.00 }, -- BlastedLands
[10] = { 2700, 1800.03 }, [20] = { 4518.75, 3012.50 }, -- Tirisfal
[11] = { 4135.42, 2756.25 }, [21] = { 4200.00, 2800.00 }, -- Silverpine
[12] = { 3470.84, 2314.62 }, [22] = { 2231.25, 1487.50 }, -- WesternPlaguelands
[14] = { 5287.5, 3525 }, [23] = { 3870.83, 2581.25 }, -- EasternPlaguelands
[15] = { 5250, 3500 }, [24] = { 3200.00, 2133.33 }, -- Hilsbrad
[16] = { 5070.84, 3381.25 }, [26] = { 3850.00, 2566.67 }, -- Hinterlands
[17] = { 10133.34, 6756.25 }, [27] = { 4925.00, 3283.34 }, -- DunMorogh
[25] = { 711.56, 468.68 }, [29] = { 2929.16, 1952.08 }, -- BurningSteppes
[28] = { 4300, 2866.67 }, [30] = { 3470.84, 2314.62 }, -- Elwynn
[33] = { 6381.25, 4254.1 }, [32] = { 5091.66, 3393.70 }, -- DeadwindPass
[36] = { 2800, 1866.67 }, [34] = { 2700.00, 1800.03 }, -- Duskwood
[38] = { 2758.33, 1839.58 }, [35] = { 2293.75, 1529.17 }, -- LochModan
[40] = { 3500, 2333.3 }, [37] = { 6381.25, 4254.10 }, -- Stranglethorn
[41] = { 2500, 1666.63 }, [39] = { 4135.42, 2756.25 }, -- Westfall
[44] = { 2170.84, 1447.9 }, [40] = { 10133.34, 6756.25 }, -- Wetlands
[45] = { 3600, 2400 }, [41] = { 5250.00, 3500.00 }, -- Teldrassil
[46] = { 2929.16, 1952.08 }, [42] = { 6550.00, 4366.66 }, -- Darkshore
[47] = { 3850, 2566.67 }, [43] = { 5766.67, 3843.75 }, -- Ashenvale
[51] = { 2231.25, 1487.5 }, [61] = { 4400.00, 2933.33 }, -- ThousandNeedles
[85] = { 4518.75, 3012.5 }, [81] = { 6018.00, 4010.00 }, -- StonetalonMountains
[130] = { 4200, 2800 }, [101] = { 4495.83, 2997.91 }, -- Desolace
[139] = { 3870.83, 2581.25 }, [121] = { 6950.00, 4633.33 }, -- Feralas
[141] = { 5091.66, 3393.7 }, [141] = { 2800.00, 1866.67 }, -- Dustwallow
[148] = { 6550, 4366.66 }, [161] = { 6900.00, 4600.00 }, -- Tanaris
[209] = { 381, 254 }, [181] = { 3600.00, 2400.00 }, -- Azshara
[215] = { 5137.5, 3425 }, [182] = { 5750.00, 3833.33 }, -- Felwood
[267] = { 3200, 2133.33 }, [201] = { 3700.00, 2466.66 }, -- UngoroCrater
[331] = { 5766.67, 3843.75 }, [241] = { 2308.33, 1539.59 }, -- Moonglade
[357] = { 6950, 4633.33 }, [261] = { 3483.33, 2322.92 }, -- Silithus
[361] = { 5750, 3833.33 }, [281] = { 7100.00, 4733.33 }, -- Winterspring
[400] = { 4400, 2933.33 }, [301] = { 1737.50, 1158.34 }, -- Stormwind
[405] = { 4495.83, 2997.91 }, [321] = { 1402.61, 935.42 }, -- Orgrimmar
[406] = { 6018, 4010 }, [341] = { 790.63, 527.61 }, -- Ironforge
[408] = { 3092.1, 2047 }, [362] = { 1043.75, 695.83 }, -- ThunderBluff
[409] = { 2901.45, 1915.9 }, [381] = { 1058.33, 705.71 }, -- Darnassus
[440] = { 6900, 4600 }, [382] = { 959.37, 640.11 }, -- Undercity
[490] = { 3700, 2466.66 }, [401] = { 4237.50, 2825.00 }, -- AlteracValley
[491] = { 736.45, 490.96 }, [443] = { 1145.84, 764.58 }, -- WarsongGulch
[493] = { 2308.33, 1539.59 }, [461] = { 1756.25, 1170.83 }, -- ArathiBasin
[616] = { 3206, 2142 }, [500] = { 828.00, 541.00 }, -- pfQ876
[618] = { 7100, 4733.33 }, [501] = { 3206.00, 2142.00 }, -- pfQ616
[717] = { 378.15, 252.1 }, [502] = { 3159.00, 2108.00 }, -- pfQ4012
[718] = { 572.78, 381.85 }, [503] = { 988.63, 1393.17 }, -- pfQ5023
[719] = { 1221.87, 806.42 }, [504] = { 2901.45, 1915.90 }, -- pfQ409
[721] = { 769.67, 513.11 }, [505] = { 3092.10, 2047.00 }, -- pfQ408
[722] = { 881, 589 }, [507] = { 3227.00, 2187.00 }, -- pfQ5121
[796] = { 203.66, 135.04 }, [509] = { 1468.00, 976.00 }, -- pfQ2040
[876] = { 828, 541 }, [510] = { 3666.00, 2442.00 }, -- pfQ5179
[1176] = { 1383.33, 922.91 }, [511] = { 1608.00, 1075.00 }, -- pfQ5024
[1337] = { 893.67, 595.78 }, [512] = { 2472.00, 1665.00 }, -- pfQ5536
[1377] = { 3483.33, 2322.92 }, [513] = { 3082.00, 2061.00 }, -- pfQ5225
[1477] = { 695.03, 463.35 }, [514] = { 449.89, 300.00 }, -- pfQ1581
[1497] = { 959.37, 640.11 }, [516] = { 572.78, 381.85 }, -- pfQ718
[1519] = { 1737.5, 1158.34 }, [517] = { 2112.09, 1410.89 }, -- pfQ2100
[1537] = { 790.63, 527.61 }, [518] = { 769.67, 513.11 }, -- pfQ721
[1581] = { 449.89, 300 }, [519] = { 711.56, 468.68 }, -- pfQ25
[1583] = { 886.84, 591.23 }, [520] = { 203.66, 135.04 }, -- pfQ796
[1584] = { 1985, 1268 }, [521] = { 893.67, 595.78 }, -- pfQ1337
[1637] = { 1402.61, 935.42 }, [522] = { 4139.02, 2946.00 }, -- pfQ3478
[1638] = { 1043.75, 695.83 }, [523] = { 1348.24, 888.09 }, -- pfQ1941
[1657] = { 1058.33, 705.71 }, [601] = { 1432.00, 977.00 }, -- pfQ5130
[1941] = { 1348.24, 888.09 }, [603] = { 738.86, 492.57 }, -- pfQ2437
[1977] = { 2120.83, 1414.6 }, [605] = { 1383.33, 922.91 }, -- pfQ1176
[2017] = { 1185.35, 789.86 }, [607] = { 695.03, 463.35 }, -- pfQ1477
[2040] = { 1468, 976 }, [609] = { 1221.87, 806.42 }, -- pfQ719
[2057] = { 320.05, 213.37 }, [611] = { 378.15, 252.10 }, -- pfQ717
[2100] = { 2112.09, 1410.89 }, [617] = { 1495.00, 983.00 }, -- pfQ2717
[2159] = { 483.11, 322.08 }, [619] = { 2120.83, 1414.60 }, -- pfQ1977
[2257] = { 312, 208 }, [623] = { 1985.00, 1268.00 }, -- pfQ1584
[2366] = { 1085.86, 726.61 }, [625] = { 2512.50, 1675.00 }, -- pfQ3429
[2437] = { 738.86, 492.57 }, [627] = { 483.11, 322.08 }, -- pfQ2159
[2557] = { 1324, 869 }, [629] = { 886.84, 591.23 }, -- pfQ1583
[2597] = { 4237.5, 2825 }, [635] = { 499.43, 332.95 }, -- pfQ2677
[2677] = { 499.43, 332.95 }, [637] = { 656.59, 434.97 }, -- pfQ5138
[2717] = { 1495, 983 }, [639] = { 881.00, 589.00 }, -- pfQ722
[3277] = { 1145.84, 764.58 }, [641] = { 736.45, 490.96 }, -- pfQ491
[3358] = { 1756.25, 1170.83 }, [643] = { 619.98, 413.32 }, -- pfQ5136
[3428] = { 977.56, 651.7 }, [644] = { 320.19, 213.46 }, -- pfQ5135
[3429] = { 2512.5, 1675 }, [645] = { 612.69, 408.46 }, -- pfQ5153
[3456] = { 1991.69, 1318.42 }, [646] = { 703.30, 468.87 }, -- pfQ5163
[3457] = { 823, 549 }, [648] = { 320.05, 213.37 }, -- pfQ2057
[3478] = { 4139.02, 2946 }, [650] = { 381.00, 254.00 }, -- pfQ209
[4012] = { 3159, 2108 }, [652] = { 1185.35, 789.86 }, -- pfQ2017
[5023] = { 988.63, 1393.17 }, [654] = { 977.56, 651.70 }, -- pfQ3428
[5024] = { 1608, 1075 }, [655] = { 2777.54, 1851.69 }, -- pfQ5147
[5053] = { 1007.7, 671.8 }, [657] = { 619.00, 410.00 }, -- pfQ5723
[5077] = { 2643.21, 1751.16 }, [659] = { 312.00, 208.00 }, -- pfQ2257
[5086] = { 546.75, 391.9 }, [662] = { 1271.99, 845.48 }, -- pfQ5204
[5087] = { 354.5, 234.74 }, [663] = { 1085.86, 726.61 }, -- pfQ2366
[5097] = { 1273.1, 853.72 }, [665] = { 1250.18, 837.44 }, -- pfQ5208
[5103] = { 752.12, 510.33 }, [667] = { 1991.69, 1318.42 }, -- pfQ3456
[5121] = { 3227, 2187 }, [668] = { 652.10, 439.67 }, -- pfQ5148
[5130] = { 1432, 977 }, [670] = { 2643.21, 1751.16 }, -- pfQ5077
[5135] = { 320.19, 213.46 }, [672] = { 752.12, 510.33 }, -- pfQ5103
[5136] = { 619.98, 413.32 }, [674] = { 546.75, 391.90 }, -- pfQ5086
[5138] = { 656.59, 434.97 }, [676] = { 354.50, 234.74 }, -- pfQ5087
[5144] = { 309, 208 }, [678] = { 1273.10, 853.72 }, -- pfQ5097
[5147] = { 2777.54, 1851.69 }, [680] = { 1007.70, 671.80 }, -- pfQ5053
[5148] = { 652.1, 439.67 }, [682] = { 823.00, 549.00 }, -- pfQ3457
[5153] = { 612.69, 408.46 }, [683] = { 2800.00, 1868.00 }, -- pfQ5557
[5163] = { 703.3, 468.87 }, [684] = { 5387.00, 3584.00 }, -- pfQ5602
[5179] = { 3666, 2442 }, [685] = { 3098.00, 2068.00 }, -- pfQ5561
[5204] = { 1271.99, 845.48 }, [686] = { 3241.00, 2157.00 }, -- pfQ5581
[5208] = { 1250.18, 837.44 }, [689] = { 1324.00, 869.00 }, -- pfQ2557
[5225] = { 3082, 2061 }, [691] = { 1634.00, 1073.00 }, -- pfQ5601
[5536] = { 2472, 1665 }, [693] = { 1980.00, 1350.00 }, -- pfQ5628
[5557] = { 2800, 1868 }, [698] = { 1234.00, 829.00 }, -- pfQ5641
[5561] = { 3098, 2068 }, [699] = { 7856.00, 5241.00 }, -- pfQ5642
[5581] = { 3241, 2157 }, [700] = { 861.00, 607.00 }, -- pfQ1769
[5601] = { 1634, 1073 }, [701] = { 1347.00, 971.00 }, -- pfQ5640
[5602] = { 5387, 3584 }, [702] = { 1047.00, 694.00 }, -- pfQ5722
[5628] = { 1980, 1350 }, [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 },
}
-163
View File
@@ -1,163 +0,0 @@
-- 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-17 07:31 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 },
[36554] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[36555] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[36556] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[36557] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[36558] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[36559] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[36560] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[36561] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[36563] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[36564] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[36565] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[36566] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[36567] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[36568] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[36569] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[36570] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[36571] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[36572] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[36573] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[36574] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[46023] = { learnMethod="trainer", minRank=0, nextSpellId=0, skillLineId=261 },
[46276] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[46303] = { learnMethod="trainer", minRank=0, nextSpellId=0, skillLineId=261 },
[46304] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
[46307] = { learnMethod="trainer", minRank=0, nextSpellId=0, skillLineId=261 },
[46310] = { learnMethod="trainer", minRank=1, 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
+8 -8
View File
@@ -51,7 +51,7 @@ local growlTrainerRows = {
["rank"] = "Rank 1", ["rank"] = "Rank 1",
["rankNumber"] = 1, ["rankNumber"] = 1,
["school"] = "Physical", ["school"] = "Physical",
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=2649", ["sourceUrl"] = "https://octowow.st/db/?spell=2649",
["trainLevel"] = 1, ["trainLevel"] = 1,
}, },
{ {
@@ -72,7 +72,7 @@ local growlTrainerRows = {
["rank"] = "Rank 2", ["rank"] = "Rank 2",
["rankNumber"] = 2, ["rankNumber"] = 2,
["school"] = "Physical", ["school"] = "Physical",
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=14916", ["sourceUrl"] = "https://octowow.st/db/?spell=14916",
["trainLevel"] = 10, ["trainLevel"] = 10,
}, },
{ {
@@ -93,7 +93,7 @@ local growlTrainerRows = {
["rank"] = "Rank 3", ["rank"] = "Rank 3",
["rankNumber"] = 3, ["rankNumber"] = 3,
["school"] = "Physical", ["school"] = "Physical",
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=14917", ["sourceUrl"] = "https://octowow.st/db/?spell=14917",
["trainLevel"] = 20, ["trainLevel"] = 20,
}, },
{ {
@@ -114,7 +114,7 @@ local growlTrainerRows = {
["rank"] = "Rank 4", ["rank"] = "Rank 4",
["rankNumber"] = 4, ["rankNumber"] = 4,
["school"] = "Physical", ["school"] = "Physical",
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=14918", ["sourceUrl"] = "https://octowow.st/db/?spell=14918",
["trainLevel"] = 30, ["trainLevel"] = 30,
}, },
{ {
@@ -135,7 +135,7 @@ local growlTrainerRows = {
["rank"] = "Rank 5", ["rank"] = "Rank 5",
["rankNumber"] = 5, ["rankNumber"] = 5,
["school"] = "Physical", ["school"] = "Physical",
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=14919", ["sourceUrl"] = "https://octowow.st/db/?spell=14919",
["trainLevel"] = 40, ["trainLevel"] = 40,
}, },
{ {
@@ -156,7 +156,7 @@ local growlTrainerRows = {
["rank"] = "Rank 6", ["rank"] = "Rank 6",
["rankNumber"] = 6, ["rankNumber"] = 6,
["school"] = "Physical", ["school"] = "Physical",
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=14920", ["sourceUrl"] = "https://octowow.st/db/?spell=14920",
["trainLevel"] = 50, ["trainLevel"] = 50,
}, },
{ {
@@ -177,7 +177,7 @@ local growlTrainerRows = {
["rank"] = "Rank 7", ["rank"] = "Rank 7",
["rankNumber"] = 7, ["rankNumber"] = 7,
["school"] = "Physical", ["school"] = "Physical",
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=14921", ["sourceUrl"] = "https://octowow.st/db/?spell=14921",
["trainLevel"] = 60, ["trainLevel"] = 60,
}, },
} }
@@ -228,7 +228,7 @@ end)
MTH_DS_PetSpells.byAbility["Growl"] = { MTH_DS_PetSpells.byAbility["Growl"] = {
["ability"] = "Growl", ["ability"] = "Growl",
["searchUrl"] = "https://database.turtlecraft.gg/?search=Growl", ["searchUrl"] = "https://octowow.st/db/?search=Growl",
["spellCount"] = table.getn(growlTrainerRows), ["spellCount"] = table.getn(growlTrainerRows),
["spells"] = growlTrainerRows, ["spells"] = growlTrainerRows,
} }
+244 -206
View File
File diff suppressed because it is too large Load Diff
+42 -42
View File
@@ -6,7 +6,7 @@ MTH_DS_Racial = {
version = "1.0", version = "1.0",
generatedAtUTC = "2026-02-20T00:00:00Z", generatedAtUTC = "2026-02-20T00:00:00Z",
source = { source = {
site = "https://database.turtlecraft.gg", site = "https://octowow.st/db",
runRoot = ".TurtleDBparsing/racial-audit/runs/20260220-164554Z", runRoot = ".TurtleDBparsing/racial-audit/runs/20260220-164554Z",
selection = "user-curated-approved", selection = "user-curated-approved",
}, },
@@ -25,46 +25,46 @@ MTH_DS_Racial = {
["Human"] = { 20599, 20600, 20597, 20864, 20598 }, ["Human"] = { 20599, 20600, 20597, 20864, 20598 },
}, },
spellsById = { spellsById = {
[2481] = { id = 2481, name = "Find Treasure", rank = "Racial", level = 1, school = 0, race = "Dwarf", sourceUrl = "https://database.turtlecraft.gg/?spell=2481" }, [2481] = { id = 2481, name = "Find Treasure", rank = "Racial", level = 1, school = 0, race = "Dwarf", sourceUrl = "https://octowow.st/db/?spell=2481" },
[5227] = { id = 5227, name = "Underwater Breathing", rank = "Racial Passive", level = 0, school = 0, race = "Undead", sourceUrl = "https://database.turtlecraft.gg/?spell=5227" }, [5227] = { id = 5227, name = "Underwater Breathing", rank = "Racial Passive", level = 0, school = 0, race = "Undead", sourceUrl = "https://octowow.st/db/?spell=5227" },
[7340] = { id = 7340, name = "Language Gnomish", rank = "", level = 1, school = 0, race = "Gnome", sourceUrl = "https://database.turtlecraft.gg/?spell=7340" }, [7340] = { id = 7340, name = "Language Gnomish", rank = "", level = 1, school = 0, race = "Gnome", sourceUrl = "https://octowow.st/db/?spell=7340" },
[7744] = { id = 7744, name = "Will of the Forsaken", rank = "Racial", level = 0, school = 0, race = "Undead", sourceUrl = "https://database.turtlecraft.gg/?spell=7744" }, [7744] = { id = 7744, name = "Will of the Forsaken", rank = "Racial", level = 0, school = 0, race = "Undead", sourceUrl = "https://octowow.st/db/?spell=7744" },
[20549] = { id = 20549, name = "War Stomp", rank = "Racial", level = 0, school = 0, race = "Tauren", sourceUrl = "https://database.turtlecraft.gg/?spell=20549" }, [20549] = { id = 20549, name = "War Stomp", rank = "Racial", level = 0, school = 0, race = "Tauren", sourceUrl = "https://octowow.st/db/?spell=20549" },
[20550] = { id = 20550, name = "Endurance", rank = "Racial Passive", level = 0, school = 0, race = "Tauren", sourceUrl = "https://database.turtlecraft.gg/?spell=20550" }, [20550] = { id = 20550, name = "Endurance", rank = "Racial Passive", level = 0, school = 0, race = "Tauren", sourceUrl = "https://octowow.st/db/?spell=20550" },
[20551] = { id = 20551, name = "Nature Resistance", rank = "Racial Passive", level = 0, school = 0, race = "Tauren", raceGuess = true, sourceUrl = "https://database.turtlecraft.gg/?spell=20551" }, [20551] = { id = 20551, name = "Nature Resistance", rank = "Racial Passive", level = 0, school = 0, race = "Tauren", raceGuess = true, sourceUrl = "https://octowow.st/db/?spell=20551" },
[20552] = { id = 20552, name = "Cultivation", rank = "Racial Passive", level = 0, school = 0, race = "Tauren", sourceUrl = "https://database.turtlecraft.gg/?spell=20552" }, [20552] = { id = 20552, name = "Cultivation", rank = "Racial Passive", level = 0, school = 0, race = "Tauren", sourceUrl = "https://octowow.st/db/?spell=20552" },
[20554] = { id = 20554, name = "Berserking", rank = "Racial", level = 0, school = 0, race = "Troll", sourceUrl = "https://database.turtlecraft.gg/?spell=20554" }, [20554] = { id = 20554, name = "Berserking", rank = "Racial", level = 0, school = 0, race = "Troll", sourceUrl = "https://octowow.st/db/?spell=20554" },
[20555] = { id = 20555, name = "Regeneration", rank = "Racial Passive", level = 0, school = 0, race = "Troll", sourceUrl = "https://database.turtlecraft.gg/?spell=20555" }, [20555] = { id = 20555, name = "Regeneration", rank = "Racial Passive", level = 0, school = 0, race = "Troll", sourceUrl = "https://octowow.st/db/?spell=20555" },
[20557] = { id = 20557, name = "Beast Slaying", rank = "Racial Passive", level = 0, school = 0, race = "Troll", sourceUrl = "https://database.turtlecraft.gg/?spell=20557" }, [20557] = { id = 20557, name = "Beast Slaying", rank = "Racial Passive", level = 0, school = 0, race = "Troll", sourceUrl = "https://octowow.st/db/?spell=20557" },
[20558] = { id = 20558, name = "Throwing Specialization", rank = "Racial Passive", level = 0, school = 0, race = "Troll", sourceUrl = "https://database.turtlecraft.gg/?spell=20558" }, [20558] = { id = 20558, name = "Throwing Specialization", rank = "Racial Passive", level = 0, school = 0, race = "Troll", sourceUrl = "https://octowow.st/db/?spell=20558" },
[20572] = { id = 20572, name = "Blood Fury", rank = "Racial", level = 0, school = 0, race = "Orc", sourceUrl = "https://database.turtlecraft.gg/?spell=20572" }, [20572] = { id = 20572, name = "Blood Fury", rank = "Racial", level = 0, school = 0, race = "Orc", sourceUrl = "https://octowow.st/db/?spell=20572" },
[20573] = { id = 20573, name = "Hardiness", rank = "Racial Passive", level = 0, school = 0, race = "Orc", sourceUrl = "https://database.turtlecraft.gg/?spell=20573" }, [20573] = { id = 20573, name = "Hardiness", rank = "Racial Passive", level = 0, school = 0, race = "Orc", sourceUrl = "https://octowow.st/db/?spell=20573" },
[20574] = { id = 20574, name = "Axe Specialization", rank = "Racial Passive", level = 0, school = 0, race = "Orc", sourceUrl = "https://database.turtlecraft.gg/?spell=20574" }, [20574] = { id = 20574, name = "Axe Specialization", rank = "Racial Passive", level = 0, school = 0, race = "Orc", sourceUrl = "https://octowow.st/db/?spell=20574" },
[20575] = { id = 20575, name = "Command", rank = "Racial Passive", level = 0, school = 0, race = "Orc", sourceUrl = "https://database.turtlecraft.gg/?spell=20575" }, [20575] = { id = 20575, name = "Command", rank = "Racial Passive", level = 0, school = 0, race = "Orc", sourceUrl = "https://octowow.st/db/?spell=20575" },
[20576] = { id = 20576, name = "Command", rank = "Racial Passive", level = 0, school = 0, race = "Orc", sourceUrl = "https://database.turtlecraft.gg/?spell=20576" }, [20576] = { id = 20576, name = "Command", rank = "Racial Passive", level = 0, school = 0, race = "Orc", sourceUrl = "https://octowow.st/db/?spell=20576" },
[20577] = { id = 20577, name = "Cannibalize", rank = "Racial", level = 0, school = 0, race = "Undead", sourceUrl = "https://database.turtlecraft.gg/?spell=20577" }, [20577] = { id = 20577, name = "Cannibalize", rank = "Racial", level = 0, school = 0, race = "Undead", sourceUrl = "https://octowow.st/db/?spell=20577" },
[20579] = { id = 20579, name = "Shadow Resistance", rank = "Racial Passive", level = 0, school = 0, race = "Undead", sourceUrl = "https://database.turtlecraft.gg/?spell=20579" }, [20579] = { id = 20579, name = "Shadow Resistance", rank = "Racial Passive", level = 0, school = 0, race = "Undead", sourceUrl = "https://octowow.st/db/?spell=20579" },
[20580] = { id = 20580, name = "Shadowmeld", rank = "Racial", level = 1, school = 0, race = "Night Elf", sourceUrl = "https://database.turtlecraft.gg/?spell=20580" }, [20580] = { id = 20580, name = "Shadowmeld", rank = "Racial", level = 1, school = 0, race = "Night Elf", sourceUrl = "https://octowow.st/db/?spell=20580" },
[20582] = { id = 20582, name = "Quickness", rank = "Racial Passive", level = 0, school = 0, race = "Night Elf", sourceUrl = "https://database.turtlecraft.gg/?spell=20582" }, [20582] = { id = 20582, name = "Quickness", rank = "Racial Passive", level = 0, school = 0, race = "Night Elf", sourceUrl = "https://octowow.st/db/?spell=20582" },
[20583] = { id = 20583, name = "Nature Resistance", rank = "Racial Passive", level = 0, school = 0, race = "Night Elf", raceGuess = true, sourceUrl = "https://database.turtlecraft.gg/?spell=20583" }, [20583] = { id = 20583, name = "Nature Resistance", rank = "Racial Passive", level = 0, school = 0, race = "Night Elf", raceGuess = true, sourceUrl = "https://octowow.st/db/?spell=20583" },
[20585] = { id = 20585, name = "Wisp Spirit", rank = "Racial Passive", level = 0, school = 0, race = "Night Elf", sourceUrl = "https://database.turtlecraft.gg/?spell=20585" }, [20585] = { id = 20585, name = "Wisp Spirit", rank = "Racial Passive", level = 0, school = 0, race = "Night Elf", sourceUrl = "https://octowow.st/db/?spell=20585" },
[20589] = { id = 20589, name = "Escape Artist", rank = "Racial", level = 0, school = 0, race = "Gnome", sourceUrl = "https://database.turtlecraft.gg/?spell=20589" }, [20589] = { id = 20589, name = "Escape Artist", rank = "Racial", level = 0, school = 0, race = "Gnome", sourceUrl = "https://octowow.st/db/?spell=20589" },
[20591] = { id = 20591, name = "Expansive Mind", rank = "Racial Passive", level = 0, school = 0, race = "Gnome", sourceUrl = "https://database.turtlecraft.gg/?spell=20591" }, [20591] = { id = 20591, name = "Expansive Mind", rank = "Racial Passive", level = 0, school = 0, race = "Gnome", sourceUrl = "https://octowow.st/db/?spell=20591" },
[20592] = { id = 20592, name = "Arcane Resistance", rank = "Racial Passive", level = 0, school = 0, race = "Gnome", sourceUrl = "https://database.turtlecraft.gg/?spell=20592" }, [20592] = { id = 20592, name = "Arcane Resistance", rank = "Racial Passive", level = 0, school = 0, race = "Gnome", sourceUrl = "https://octowow.st/db/?spell=20592" },
[20593] = { id = 20593, name = "Engineering Specialization", rank = "Racial Passive", level = 0, school = 0, race = "Gnome", sourceUrl = "https://database.turtlecraft.gg/?spell=20593" }, [20593] = { id = 20593, name = "Engineering Specialization", rank = "Racial Passive", level = 0, school = 0, race = "Gnome", sourceUrl = "https://octowow.st/db/?spell=20593" },
[20594] = { id = 20594, name = "Stoneform", rank = "Racial", level = 0, school = 0, race = "Dwarf", sourceUrl = "https://database.turtlecraft.gg/?spell=20594" }, [20594] = { id = 20594, name = "Stoneform", rank = "Racial", level = 0, school = 0, race = "Dwarf", sourceUrl = "https://octowow.st/db/?spell=20594" },
[20595] = { id = 20595, name = "Gun Specialization", rank = "Racial Passive", level = 0, school = 0, race = "Dwarf", sourceUrl = "https://database.turtlecraft.gg/?spell=20595" }, [20595] = { id = 20595, name = "Gun Specialization", rank = "Racial Passive", level = 0, school = 0, race = "Dwarf", sourceUrl = "https://octowow.st/db/?spell=20595" },
[20596] = { id = 20596, name = "Frost Resistance", rank = "Racial Passive", level = 0, school = 0, race = "Dwarf", sourceUrl = "https://database.turtlecraft.gg/?spell=20596" }, [20596] = { id = 20596, name = "Frost Resistance", rank = "Racial Passive", level = 0, school = 0, race = "Dwarf", sourceUrl = "https://octowow.st/db/?spell=20596" },
[20597] = { id = 20597, name = "Sword Specialization", rank = "Racial Passive", level = 0, school = 0, race = "Human", sourceUrl = "https://database.turtlecraft.gg/?spell=20597" }, [20597] = { id = 20597, name = "Sword Specialization", rank = "Racial Passive", level = 0, school = 0, race = "Human", sourceUrl = "https://octowow.st/db/?spell=20597" },
[20598] = { id = 20598, name = "The Human Spirit", rank = "Racial Passive", level = 0, school = 0, race = "Human", sourceUrl = "https://database.turtlecraft.gg/?spell=20598" }, [20598] = { id = 20598, name = "The Human Spirit", rank = "Racial Passive", level = 0, school = 0, race = "Human", sourceUrl = "https://octowow.st/db/?spell=20598" },
[20599] = { id = 20599, name = "Diplomacy", rank = "Racial Passive", level = 0, school = 0, race = "Human", sourceUrl = "https://database.turtlecraft.gg/?spell=20599" }, [20599] = { id = 20599, name = "Diplomacy", rank = "Racial Passive", level = 0, school = 0, race = "Human", sourceUrl = "https://octowow.st/db/?spell=20599" },
[20600] = { id = 20600, name = "Perception", rank = "Racial", level = 0, school = 0, race = "Human", sourceUrl = "https://database.turtlecraft.gg/?spell=20600" }, [20600] = { id = 20600, name = "Perception", rank = "Racial", level = 0, school = 0, race = "Human", sourceUrl = "https://octowow.st/db/?spell=20600" },
[20864] = { id = 20864, name = "Mace Specialization", rank = "Racial Passive", level = 0, school = 0, race = "Human", sourceUrl = "https://database.turtlecraft.gg/?spell=20864" }, [20864] = { id = 20864, name = "Mace Specialization", rank = "Racial Passive", level = 0, school = 0, race = "Human", sourceUrl = "https://octowow.st/db/?spell=20864" },
[21009] = { id = 21009, name = "Shadowmeld Passive", rank = "Racial Passive", level = 0, school = 0, race = "Night Elf", sourceUrl = "https://database.turtlecraft.gg/?spell=21009" }, [21009] = { id = 21009, name = "Shadowmeld Passive", rank = "Racial Passive", level = 0, school = 0, race = "Night Elf", sourceUrl = "https://octowow.st/db/?spell=21009" },
[21563] = { id = 21563, name = "Command", rank = "Racial Passive", level = 0, school = 0, race = "Orc", sourceUrl = "https://database.turtlecraft.gg/?spell=21563" }, [21563] = { id = 21563, name = "Command", rank = "Racial Passive", level = 0, school = 0, race = "Orc", sourceUrl = "https://octowow.st/db/?spell=21563" },
[26290] = { id = 26290, name = "Bow Specialization", rank = "Racial Passive", level = 0, school = 0, race = "Troll", sourceUrl = "https://database.turtlecraft.gg/?spell=26290" }, [26290] = { id = 26290, name = "Bow Specialization", rank = "Racial Passive", level = 0, school = 0, race = "Troll", sourceUrl = "https://octowow.st/db/?spell=26290" },
[26296] = { id = 26296, name = "Berserking", rank = "Racial", level = 0, school = 0, race = "Troll", sourceUrl = "https://database.turtlecraft.gg/?spell=26296" }, [26296] = { id = 26296, name = "Berserking", rank = "Racial", level = 0, school = 0, race = "Troll", sourceUrl = "https://octowow.st/db/?spell=26296" },
[26297] = { id = 26297, name = "Berserking", rank = "Racial", level = 0, school = 0, race = "Troll", sourceUrl = "https://database.turtlecraft.gg/?spell=26297" }, [26297] = { id = 26297, name = "Berserking", rank = "Racial", level = 0, school = 0, race = "Troll", sourceUrl = "https://octowow.st/db/?spell=26297" },
[52522] = { id = 52522, name = "Vengeance", rank = "Racial Passive", level = 0, school = 0, race = "Undead", sourceUrl = "https://database.turtlecraft.gg/?spell=52522" }, [52522] = { id = 52522, name = "Vengeance", rank = "Racial Passive", level = 0, school = 0, race = "Undead", sourceUrl = "https://octowow.st/db/?spell=52522" },
}, },
} }
-37
View File
@@ -1,37 +0,0 @@
-- MetaHunt: Stable slot prices
-- AUTO-GENERATED by .tools — do not edit by hand.
-- Source DBC(s): StableSlotPrices.dbc
-- Generated: 2026-03-17 07:31 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",
},
[2] = {
["cost_copper"] = 50000,
["cost_gold"] = 5,
["cost_silver"] = 0,
["cost_copper_remainder"] = 0,
["display"] = "5g 0s 0c",
},
[3] = {
["cost_copper"] = 2500000,
["cost_gold"] = 250,
["cost_silver"] = 0,
["cost_copper_remainder"] = 0,
["display"] = "250g 0s 0c",
},
[4] = {
["cost_copper"] = 1,
["cost_gold"] = 0,
["cost_silver"] = 0,
["cost_copper_remainder"] = 1,
["display"] = "0g 0s 1c",
},
}
File diff suppressed because it is too large Load Diff
+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
-84
View File
@@ -1,84 +0,0 @@
-- 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-17 07:31 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=509 map=0 'AlahThalas'
if MTH_DS_Zones[2040] then
MTH_DS_Zones[2040]["dbcAreaId"] = 2040
MTH_DS_Zones[2040]["dbcMapId"] = 0
MTH_DS_Zones[2040]["dbcName"] = "AlahThalas"
MTH_DS_Zones[2040]["worldBounds"] = {
["left"] = -2169.0,
["right"] = -3637.0,
["top"] = 4907.0,
["bottom"] = 3931.0,
}
end
+9
View File
@@ -54,22 +54,31 @@ MTH_DS_ZoneNamesFallback = {
[1519] = "Stormwind City", [1519] = "Stormwind City",
[1537] = "Ironforge", [1537] = "Ironforge",
[1583] = "Blackrock Spire", [1583] = "Blackrock Spire",
[1584] = "Blackrock Depths",
[1941] = "Caverns of Time", [1941] = "Caverns of Time",
[1977] = "Zul'Gurub", [1977] = "Zul'Gurub",
[2040] = "Alah'Thalas", [2040] = "Alah'Thalas",
[2100] = "Maraudon", [2100] = "Maraudon",
[2717] = "Molten Core",
[2597] = "Alterac Valley", [2597] = "Alterac Valley",
[3456] = "Naxxramas", [3456] = "Naxxramas",
[3457] = "Tower of Karazhan", [3457] = "Tower of Karazhan",
[4012] = "Scarlet Enclave", [4012] = "Scarlet Enclave",
[5023] = "Sunnyglade",
[5024] = "Icepoint Rock",
[5077] = "Crescent Grove", [5077] = "Crescent Grove",
[5121] = "Tel'Abim", [5121] = "Tel'Abim",
[5130] = "Winter Veil Vale",
[5135] = "Scarlet Monastery Library", [5135] = "Scarlet Monastery Library",
[5179] = "Gilneas", [5179] = "Gilneas",
[5208] = "Gilneas City",
[5225] = "Thalassian Highlands", [5225] = "Thalassian Highlands",
[5536] = "Blackstone Island", [5536] = "Blackstone Island",
[5723] = "Karazhan",
[5561] = "Balor", [5561] = "Balor",
[5581] = "Northwind", [5581] = "Northwind",
[5601] = "Dragonmaw Retreat", [5601] = "Dragonmaw Retreat",
[5602] = "Grim Reaches", [5602] = "Grim Reaches",
[5641] = "Windhorn Canyon",
[5642] = "Moonwhisper Coast",
} }
File diff suppressed because it is too large Load Diff
+405
View File
@@ -0,0 +1,405 @@
-- MetaHunt: WMA (WorldMapArea.dbc) zone reference data
-- AUTO-GENERATED by .tools/migrate_wma.py — do not edit by hand.
-- Source: Haxxor WMA CSV (patch-9.mpq)
--
-- Provides MTH_MAP_PFQ_TO_WMA: pfQ AreaTable ID → WMA ID lookup
-- Used by map-markers.lua to translate GetCurrentMapAreaID() results
-- and by GetCurrentMapID() to return WMA zone IDs for coord matching.
--
-- Provides MTH_DS_WMAZones: WMA ID → worldBounds + pfqAreaId
-- Used for map overlay features without modifying the pfQ-keyed MTH_DS_Zones.
if not MTH_DS then MTH_DS = {} end
-- pfQ AreaTable ID → WMA ID runtime bridge
MTH_MAP_PFQ_TO_WMA = {
[1] = 27, -- DunMorogh
[3] = 17, -- Badlands
[4] = 19, -- BlastedLands
[8] = 38, -- SwampOfSorrows
[10] = 34, -- Duskwood
[11] = 40, -- Wetlands
[12] = 30, -- Elwynn
[14] = 4, -- Durotar
[15] = 141, -- Dustwallow
[16] = 181, -- Aszhara
[17] = 11, -- Barrens
[25] = 519, -- BlackrockMountain
[28] = 22, -- WesternPlaguelands
[33] = 37, -- Stranglethorn
[36] = 15, -- Alterac
[38] = 35, -- LochModan
[40] = 39, -- Westfall
[41] = 32, -- DeadwindPass
[44] = 36, -- Redridge
[45] = 16, -- Arathi
[46] = 29, -- BurningSteppes
[47] = 26, -- Hinterlands
[51] = 28, -- SearingGorge
[85] = 20, -- Tirisfal
[130] = 21, -- Silverpine
[139] = 23, -- EasternPlaguelands
[141] = 41, -- Teldrassil
[148] = 42, -- Darkshore
[209] = 650, -- ShadowfangKeep
[215] = 9, -- Mulgore
[267] = 24, -- Hilsbrad
[331] = 43, -- Ashenvale
[357] = 121, -- Feralas
[361] = 182, -- Felwood
[400] = 61, -- ThousandNeedles
[405] = 101, -- Desolace
[406] = 81, -- StonetalonMountains
[408] = 505, -- Gillijim
[409] = 504, -- Lapidis
[440] = 161, -- Tanaris
[490] = 201, -- UngoroCrater
[491] = 641, -- RazorfenKraul
[493] = 241, -- Moonglade
[616] = 501, -- Hyjal
[618] = 281, -- Winterspring
[717] = 611, -- TheStockade
[718] = 516, -- WailingCavernsEntrance
[719] = 609, -- BlackFathomDeeps
[721] = 518, -- GnomereganEntrance
[722] = 639, -- RazorfenDowns
[796] = 520, -- ScarletMonasteryEntrance
[876] = 500, -- GMIsland
[1176] = 605, -- ZulFarrak
[1337] = 521, -- UldamanEntrance
[1377] = 261, -- Silithus
[1477] = 607, -- TheTempleOfAtalHakkar
[1497] = 382, -- Undercity
[1519] = 301, -- Stormwind
[1537] = 341, -- Ironforge
[1581] = 514, -- DeadminesEntrance
[1583] = 629, -- BlackrockSpire
[1584] = 623, -- BlackrockDepths
[1637] = 321, -- Orgrimmar
[1638] = 362, -- ThunderBluff
[1657] = 381, -- Darnassus
[1769] = 700, -- TimbermawTunnels
[1941] = 523, -- CavernsOfTime
[1977] = 619, -- ZulGurub
[2017] = 652, -- Stratholme
[2040] = 509, -- AlahThalas
[2057] = 648, -- Scholomance
[2100] = 517, -- MaraudonEntrance
[2159] = 627, -- OnyxiasLair
[2257] = 659, -- DeeprunTram
[2366] = 663, -- BlackMorass2f
[2437] = 603, -- Ragefire
[2557] = 689, -- DireMaulEntrance
[2597] = 401, -- AlteracValley
[2677] = 635, -- BlackwingLair
[2717] = 617, -- MoltenCore
[3277] = 443, -- WarsongGulch
[3358] = 461, -- ArathiBasin
[3428] = 654, -- AhnQiraj
[3429] = 625, -- RuinsofAhnQiraj
[3456] = 667, -- Naxxramas
[3457] = 682, -- UpperKarazhan
[3478] = 522, -- AhnQirajEntrance
[4012] = 502, -- ScarletEnclave
[5023] = 503, -- Sunnyglade
[5024] = 511, -- Icepoint
[5053] = 680, -- Moomoo
[5077] = 670, -- CrescentGrove
[5086] = 674, -- KarazhanCrypt
[5087] = 676, -- StormwindVault
[5097] = 678, -- EmeraldSanctum
[5103] = 672, -- HateforgeQuarry
[5121] = 507, -- TelAbim
[5130] = 601, -- WinterVeilVale
[5135] = 644, -- ScarletMonastery2f
[5136] = 643, -- ScarletMonastery
[5138] = 637, -- TheDeadmines
[5147] = 655, -- AhnQiraj2f
[5148] = 668, -- Naxxramas2f
[5153] = 645, -- ScarletMonastery3f
[5163] = 646, -- ScarletMonastery4f
[5179] = 510, -- Gilneas
[5204] = 662, -- BlackMorass
[5208] = 665, -- GilneasCity
[5225] = 513, -- ThalassianHighlands
[5536] = 512, -- BlackstoneIsland
[5557] = 683, -- UpperKarazhan2f
[5561] = 685, -- Balor
[5581] = 686, -- Northwind
[5601] = 691, -- DragonmawRetreat
[5602] = 684, -- GrimReaches
[5628] = 693, -- StormwroughtRuins
[5640] = 701, -- TimbermawEntrance
[5641] = 698, -- WindhornCanyon
[5642] = 699, -- Moonwhisper
[5722] = 702, -- ThornGorge
[5723] = 657, -- Karazhan
[5731] = 705, -- WindhornCanyonEntrance
[5734] = 704, -- FrostmaneHollow
}
-- WMA ID → pfQ AreaTable ID (for fallback lookups)
MTH_MAP_WMA_TO_PFQ = {
[4] = 14, -- Durotar
[9] = 215, -- Mulgore
[11] = 17, -- Barrens
[15] = 36, -- Alterac
[16] = 45, -- Arathi
[17] = 3, -- Badlands
[19] = 4, -- BlastedLands
[20] = 85, -- Tirisfal
[21] = 130, -- Silverpine
[22] = 28, -- WesternPlaguelands
[23] = 139, -- EasternPlaguelands
[24] = 267, -- Hilsbrad
[26] = 47, -- Hinterlands
[27] = 1, -- DunMorogh
[28] = 51, -- SearingGorge
[29] = 46, -- BurningSteppes
[30] = 12, -- Elwynn
[32] = 41, -- DeadwindPass
[34] = 10, -- Duskwood
[35] = 38, -- LochModan
[36] = 44, -- Redridge
[37] = 33, -- Stranglethorn
[38] = 8, -- SwampOfSorrows
[39] = 40, -- Westfall
[40] = 11, -- Wetlands
[41] = 141, -- Teldrassil
[42] = 148, -- Darkshore
[43] = 331, -- Ashenvale
[61] = 400, -- ThousandNeedles
[81] = 406, -- StonetalonMountains
[101] = 405, -- Desolace
[121] = 357, -- Feralas
[141] = 15, -- Dustwallow
[161] = 440, -- Tanaris
[181] = 16, -- Aszhara
[182] = 361, -- Felwood
[201] = 490, -- UngoroCrater
[241] = 493, -- Moonglade
[261] = 1377, -- Silithus
[281] = 618, -- Winterspring
[301] = 1519, -- Stormwind
[321] = 1637, -- Orgrimmar
[341] = 1537, -- Ironforge
[362] = 1638, -- ThunderBluff
[381] = 1657, -- Darnassus
[382] = 1497, -- Undercity
[401] = 2597, -- AlteracValley
[443] = 3277, -- WarsongGulch
[461] = 3358, -- ArathiBasin
[500] = 876, -- GMIsland
[501] = 616, -- Hyjal
[502] = 4012, -- ScarletEnclave
[503] = 5023, -- Sunnyglade
[504] = 409, -- Lapidis
[505] = 408, -- Gillijim
[507] = 5121, -- TelAbim
[509] = 2040, -- AlahThalas
[510] = 5179, -- Gilneas
[511] = 5024, -- Icepoint
[512] = 5536, -- BlackstoneIsland
[513] = 5225, -- ThalassianHighlands
[514] = 1581, -- DeadminesEntrance
[516] = 718, -- WailingCavernsEntrance
[517] = 2100, -- MaraudonEntrance
[518] = 721, -- GnomereganEntrance
[519] = 25, -- BlackrockMountain
[520] = 796, -- ScarletMonasteryEntrance
[521] = 1337, -- UldamanEntrance
[522] = 3478, -- AhnQirajEntrance
[523] = 1941, -- CavernsOfTime
[601] = 5130, -- WinterVeilVale
[603] = 2437, -- Ragefire
[605] = 1176, -- ZulFarrak
[607] = 1477, -- TheTempleOfAtalHakkar
[609] = 719, -- BlackFathomDeeps
[611] = 717, -- TheStockade
[615] = 1584, -- BlackrockDepths (interior)
[617] = 2717, -- MoltenCore
[619] = 1977, -- ZulGurub
[623] = 1584, -- BlackrockDepths
[625] = 3429, -- RuinsofAhnQiraj
[627] = 2159, -- OnyxiasLair
[629] = 1583, -- BlackrockSpire
[631] = 718, -- WailingCaverns (interior)
[633] = 2100, -- Maraudon (interior)
[635] = 2677, -- BlackwingLair
[637] = 5138, -- TheDeadmines
[639] = 722, -- RazorfenDowns
[641] = 491, -- RazorfenKraul
[643] = 5136, -- ScarletMonastery
[644] = 5135, -- ScarletMonastery2f
[645] = 5153, -- ScarletMonastery3f
[646] = 5163, -- ScarletMonastery4f
[648] = 2057, -- Scholomance
[650] = 209, -- ShadowfangKeep
[652] = 2017, -- Stratholme
[654] = 3428, -- AhnQiraj
[655] = 5147, -- AhnQiraj2f
[657] = 5723, -- Karazhan
[659] = 2257, -- DeeprunTram
[662] = 5204, -- BlackMorass
[663] = 2366, -- BlackMorass2f
[665] = 5208, -- GilneasCity
[667] = 3456, -- Naxxramas
[668] = 5148, -- Naxxramas2f
[670] = 5077, -- CrescentGrove
[672] = 5103, -- HateforgeQuarry
[674] = 5086, -- KarazhanCrypt
[676] = 5087, -- StormwindVault
[678] = 5097, -- EmeraldSanctum
[680] = 5053, -- Moomoo
[682] = 3457, -- UpperKarazhan
[683] = 5557, -- UpperKarazhan2f
[684] = 5602, -- GrimReaches
[685] = 5561, -- Balor
[686] = 5581, -- Northwind
[689] = 2557, -- DireMaulEntrance
[691] = 5601, -- DragonmawRetreat
[693] = 5628, -- StormwroughtRuins
[698] = 5641, -- WindhornCanyon
[699] = 5642, -- Moonwhisper
[700] = 1769, -- TimbermawTunnels
[701] = 5640, -- TimbermawEntrance
[702] = 5722, -- ThornGorge
[704] = 5734, -- FrostmaneHollow
[705] = 5731, -- WindhornCanyonEntrance
}
-- WMA ID → worldBounds + pfqAreaId (for map overlay and minimap calibration)
MTH_DS_WMAZones = {
[4] = { pfqAreaId=14, mapId=1, left=-1962.50, right=-7250.00, top=1808.33, bottom=-1716.67, width=5287.50, height=3525.00 }, -- Durotar
[9] = { pfqAreaId=215, mapId=1, left=2047.92, right=-3089.58, top=-272.92, bottom=-3697.92, width=5137.50, height=3425.00 }, -- Mulgore
[11] = { pfqAreaId=17, mapId=1, left=2622.92, right=-7510.42, top=1612.50, bottom=-5143.75, width=10133.34, height=6756.25 }, -- Barrens
[13] = { pfqAreaId=0, mapId=1, left=17066.60, right=-19733.21, top=12799.90, bottom=-11733.30, width=36799.81, height=24533.20 }, -- Kalimdor
[14] = { pfqAreaId=0, mapId=0, left=16000.00, right=-19199.90, top=7466.60, bottom=-16000.00, width=35199.90, height=23466.60 }, -- Azeroth
[15] = { pfqAreaId=36, mapId=0, left=783.33, right=-2016.67, top=1500.00, bottom=-366.67, width=2800.00, height=1866.67 }, -- Alterac
[16] = { pfqAreaId=45, mapId=0, left=-866.67, right=-4466.67, top=-133.33, bottom=-2533.33, width=3600.00, height=2400.00 }, -- Arathi
[17] = { pfqAreaId=3, mapId=0, left=-2079.17, right=-4566.67, top=-5889.58, bottom=-7547.92, width=2487.50, height=1658.34 }, -- Badlands
[19] = { pfqAreaId=4, mapId=0, left=-1241.67, right=-4591.67, top=-10566.67, bottom=-12800.00, width=3350.00, height=2233.33 }, -- BlastedLands
[20] = { pfqAreaId=85, mapId=0, left=3033.33, right=-1485.42, top=3837.50, bottom=825.00, width=4518.75, height=3012.50 }, -- Tirisfal
[21] = { pfqAreaId=130, mapId=0, left=3450.00, right=-750.00, top=1666.67, bottom=-1133.33, width=4200.00, height=2800.00 }, -- Silverpine
[22] = { pfqAreaId=28, mapId=0, left=416.67, right=-3883.33, top=3366.67, bottom=500.00, width=4300.00, height=2866.67 }, -- WesternPlaguelands
[23] = { pfqAreaId=139, mapId=0, left=-2185.42, right=-6056.25, top=3800.00, bottom=1218.75, width=3870.83, height=2581.25 }, -- EasternPlaguelands
[24] = { pfqAreaId=267, mapId=0, left=1066.67, right=-2133.33, top=400.00, bottom=-1733.33, width=3200.00, height=2133.33 }, -- Hilsbrad
[26] = { pfqAreaId=47, mapId=0, left=-1575.00, right=-5425.00, top=1466.67, bottom=-1100.00, width=3850.00, height=2566.67 }, -- Hinterlands
[27] = { pfqAreaId=1, mapId=0, left=1802.08, right=-3122.92, top=-3877.08, bottom=-7160.42, width=4925.00, height=3283.34 }, -- DunMorogh
[28] = { pfqAreaId=51, mapId=0, left=-322.92, right=-2554.17, top=-6100.00, bottom=-7587.50, width=2231.25, height=1487.50 }, -- SearingGorge
[29] = { pfqAreaId=46, mapId=0, left=-266.67, right=-3195.83, top=-7031.25, bottom=-8983.33, width=2929.16, height=1952.08 }, -- BurningSteppes
[30] = { pfqAreaId=12, mapId=0, left=1535.42, right=-1935.42, top=-7939.58, bottom=-10254.17, width=3470.84, height=2314.59 }, -- Elwynn
[32] = { pfqAreaId=41, mapId=0, left=-833.33, right=-3333.33, top=-9866.67, bottom=-11533.33, width=2500.00, height=1666.66 }, -- DeadwindPass
[34] = { pfqAreaId=10, mapId=0, left=833.33, right=-1866.67, top=-9716.67, bottom=-11516.67, width=2700.00, height=1800.00 }, -- Duskwood
[35] = { pfqAreaId=38, mapId=0, left=-1993.75, right=-4752.08, top=-4487.50, bottom=-6327.08, width=2758.33, height=1839.58 }, -- LochModan
[36] = { pfqAreaId=44, mapId=0, left=-1570.83, right=-3741.67, top=-8575.00, bottom=-10022.92, width=2170.84, height=1447.92 }, -- Redridge
[37] = { pfqAreaId=33, mapId=0, left=2220.83, right=-4160.42, top=-11168.75, bottom=-15422.92, width=6381.25, height=4254.17 }, -- Stranglethorn
[38] = { pfqAreaId=8, mapId=0, left=-2222.92, right=-4516.67, top=-9620.83, bottom=-11150.00, width=2293.75, height=1529.17 }, -- SwampOfSorrows
[39] = { pfqAreaId=40, mapId=0, left=3016.67, right=-483.33, top=-9400.00, bottom=-11733.33, width=3500.00, height=2333.33 }, -- Westfall
[40] = { pfqAreaId=11, mapId=0, left=-389.58, right=-4525.00, top=-2147.92, bottom=-4904.17, width=4135.42, height=2756.25 }, -- Wetlands
[41] = { pfqAreaId=141, mapId=1, left=3814.58, right=-1277.08, top=11831.25, bottom=8437.50, width=5091.66, height=3393.75 }, -- Teldrassil
[42] = { pfqAreaId=148, mapId=1, left=2941.67, right=-3608.33, top=8333.33, bottom=3966.67, width=6550.00, height=4366.66 }, -- Darkshore
[43] = { pfqAreaId=331, mapId=1, left=1700.00, right=-4066.67, top=4672.92, bottom=829.17, width=5766.67, height=3843.75 }, -- Ashenvale
[61] = { pfqAreaId=400, mapId=1, left=-433.33, right=-4833.33, top=-3966.67, bottom=-6900.00, width=4400.00, height=2933.33 }, -- ThousandNeedles
[81] = { pfqAreaId=406, mapId=1, left=3245.83, right=-1637.50, top=2916.67, bottom=-339.58, width=4883.33, height=3256.25 }, -- StonetalonMountains
[101] = { pfqAreaId=405, mapId=1, left=4233.33, right=-262.50, top=452.08, bottom=-2545.83, width=4495.83, height=2997.91 }, -- Desolace
[121] = { pfqAreaId=357, mapId=1, left=5441.67, right=-1508.33, top=-2366.67, bottom=-7000.00, width=6950.00, height=4633.33 }, -- Feralas
[141] = { pfqAreaId=15, mapId=1, left=-975.00, right=-6225.00, top=-2033.33, bottom=-5533.33, width=5250.00, height=3500.00 }, -- Dustwallow
[161] = { pfqAreaId=440, mapId=1, left=-218.75, right=-7118.75, top=-5875.00, bottom=-10475.00, width=6900.00, height=4600.00 }, -- Tanaris
[181] = { pfqAreaId=16, mapId=1, left=-3277.08, right=-8347.92, top=5341.67, bottom=1960.42, width=5070.84, height=3381.25 }, -- Aszhara
[182] = { pfqAreaId=361, mapId=1, left=1641.67, right=-4108.33, top=7133.33, bottom=3300.00, width=5750.00, height=3833.33 }, -- Felwood
[201] = { pfqAreaId=490, mapId=1, left=533.33, right=-3166.67, top=-5966.67, bottom=-8433.33, width=3700.00, height=2466.66 }, -- UngoroCrater
[241] = { pfqAreaId=493, mapId=1, left=-1381.25, right=-3689.58, top=8491.67, bottom=6952.08, width=2308.33, height=1539.59 }, -- Moonglade
[261] = { pfqAreaId=1377, mapId=1, left=2537.50, right=-945.83, top=-5958.33, bottom=-8281.25, width=3483.33, height=2322.92 }, -- Silithus
[281] = { pfqAreaId=618, mapId=1, left=-316.67, right=-7416.67, top=8533.33, bottom=3800.00, width=7100.00, height=4733.33 }, -- Winterspring
[301] = { pfqAreaId=1519, mapId=0, left=1722.92, right=-14.58, top=-7995.83, bottom=-9154.17, width=1737.50, height=1158.34 }, -- Stormwind
[321] = { pfqAreaId=1637, mapId=1, left=-3680.60, right=-5083.21, top=2273.88, bottom=1338.46, width=1402.61, height=935.42 }, -- Orgrimmar
[341] = { pfqAreaId=1537, mapId=0, left=-713.59, right=-1504.22, top=-4569.24, bottom=-5096.85, width=790.63, height=527.61 }, -- Ironforge
[362] = { pfqAreaId=1638, mapId=1, left=516.67, right=-527.08, top=-850.00, bottom=-1545.83, width=1043.75, height=695.83 }, -- ThunderBluff
[381] = { pfqAreaId=1657, mapId=1, left=2938.36, right=1880.03, top=10238.32, bottom=9532.59, width=1058.33, height=705.73 }, -- Darnassus
[382] = { pfqAreaId=1497, mapId=0, left=873.19, right=-86.18, top=1877.95, bottom=1237.84, width=959.37, height=640.11 }, -- Undercity
[401] = { pfqAreaId=2597, mapId=30, left=1781.25, right=-2456.25, top=1085.42, bottom=-1739.58, width=4237.50, height=2825.00 }, -- AlteracValley
[443] = { pfqAreaId=3277, mapId=489, left=2041.67, right=895.83, top=1627.08, bottom=862.50, width=1145.84, height=764.58 }, -- WarsongGulch
[461] = { pfqAreaId=3358, mapId=529, left=1858.33, right=102.08, top=1508.33, bottom=337.50, width=1756.25, height=1170.83 }, -- ArathiBasin
[500] = { pfqAreaId=876, mapId=1, left=16748.00, right=15920.00, top=16525.00, bottom=15984.00, width=828.00, height=541.00 }, -- GMIsland
[501] = { pfqAreaId=616, mapId=1, left=-1080.00, right=-4286.00, top=6125.00, bottom=3983.00, width=3206.00, height=2142.00 }, -- Hyjal
[502] = { pfqAreaId=4012, mapId=0, left=-4050.00, right=-7209.00, top=3087.00, bottom=979.00, width=3159.00, height=2108.00 }, -- ScarletEnclave
[503] = { pfqAreaId=5023, mapId=27, left=1013.00, right=-435.00, top=1823.00, bottom=858.00, width=1448.00, height=965.00 }, -- Sunnyglade
[504] = { pfqAreaId=409, mapId=0, left=4933.33, right=2031.88, top=-11043.48, bottom=-12959.42, width=2901.45, height=1915.94 }, -- Lapidis
[505] = { pfqAreaId=408, mapId=0, left=4436.22, right=1344.12, top=-12528.71, bottom=-14575.72, width=3092.10, height=2047.01 }, -- Gillijim
[507] = { pfqAreaId=5121, mapId=1, left=-5179.00, right=-8406.00, top=-7184.00, bottom=-9371.00, width=3227.00, height=2187.00 }, -- TelAbim
[509] = { pfqAreaId=2040, mapId=0, left=-2169.00, right=-3637.00, top=4907.00, bottom=3931.00, width=1468.00, height=976.00 }, -- AlahThalas
[510] = { pfqAreaId=5179, mapId=0, left=3253.00, right=-413.00, top=-359.00, bottom=-2801.00, width=3666.00, height=2442.00 }, -- Gilneas
[511] = { pfqAreaId=5024, mapId=1, left=-5595.00, right=-7203.00, top=14405.00, bottom=13330.00, width=1608.00, height=1075.00 }, -- Icepoint
[512] = { pfqAreaId=5536, mapId=1, left=-6274.00, right=-8746.00, top=799.00, bottom=-866.00, width=2472.00, height=1665.00 }, -- BlackstoneIsland
[513] = { pfqAreaId=5225, mapId=0, left=-1005.00, right=-4087.00, top=4952.00, bottom=2891.00, width=3082.00, height=2061.00 }, -- ThalassianHighlands
[514] = { pfqAreaId=1581, mapId=0, left=1793.18, right=1343.29, top=-11054.94, bottom=-11354.86, width=449.89, height=299.92 }, -- DeadminesEntrance
[516] = { pfqAreaId=718, mapId=1, left=-1900.87, right=-2473.65, top=-488.64, bottom=-870.49, width=572.78, height=381.85 }, -- WailingCavernsEntrance
[517] = { pfqAreaId=2100, mapId=1, left=3208.00, right=2384.00, top=-1096.00, bottom=-1646.00, width=824.00, height=550.00 }, -- MaraudonEntrance
[518] = { pfqAreaId=721, mapId=0, left=1027.64, right=456.45, top=-4806.32, bottom=-5185.46, width=571.19, height=379.14 }, -- GnomereganEntrance
[519] = { pfqAreaId=25, mapId=0, left=-760.94, right=-1472.50, top=-7327.82, bottom=-7796.50, width=711.56, height=468.68 }, -- BlackrockMountain
[520] = { pfqAreaId=796, mapId=0, left=-659.96, right=-863.62, top=2947.37, bottom=2812.33, width=203.66, height=135.04 }, -- ScarletMonasteryEntrance
[521] = { pfqAreaId=1337, mapId=0, left=-2747.15, right=-3310.46, top=-5953.41, bottom=-6329.51, width=563.31, height=376.10 }, -- UldamanEntrance
[522] = { pfqAreaId=3478, mapId=1, left=3932.67, right=-206.35, top=-8005.20, bottom=-10951.25, width=4139.02, height=2946.05 }, -- AhnQirajEntrance
[523] = { pfqAreaId=1941, mapId=1, left=-3695.85, right=-5044.09, top=-8023.09, bottom=-8911.18, width=1348.24, height=888.09 }, -- CavernsOfTime
[601] = { pfqAreaId=5130, mapId=813, left=1916.00, right=484.00, top=-2332.00, bottom=-3309.00, width=1432.00, height=977.00 }, -- WinterVeilVale
[603] = { pfqAreaId=2437, mapId=389, left=452.87, right=-285.99, top=39.62, bottom=-452.95, width=738.86, height=492.57 }, -- Ragefire
[605] = { pfqAreaId=1176, mapId=209, left=1625.00, right=241.67, top=2052.08, bottom=1129.17, width=1383.33, height=922.91 }, -- ZulFarrak
[607] = { pfqAreaId=1477, mapId=109, left=442.76, right=-252.27, top=-255.17, bottom=-718.52, width=695.03, height=463.35 }, -- TheTempleOfAtalHakkar
[609] = { pfqAreaId=719, mapId=48, left=485.41, right=-736.46, top=-107.91, bottom=-914.33, width=1221.87, height=806.42 }, -- BlackFathomDeeps
[611] = { pfqAreaId=717, mapId=34, left=189.82, right=-188.33, top=220.63, bottom=-31.47, width=378.15, height=252.10 }, -- TheStockade
[613] = { pfqAreaId=721, mapId=90, left=863.00, right=-262.00, top=-245.00, bottom=-985.00, width=1125.00, height=740.00 }, -- Gnomeregan
[615] = { pfqAreaId=1337, mapId=70, left=645.12, right=-248.55, top=204.31, bottom=-391.47, width=893.67, height=595.78 }, -- Uldaman
[617] = { pfqAreaId=2717, mapId=409, left=-11.00, right=-1506.00, top=1439.00, bottom=456.00, width=1495.00, height=983.00 }, -- MoltenCore
[619] = { pfqAreaId=1977, mapId=309, left=-612.50, right=-2733.33, top=-11225.00, bottom=-12639.58, width=2120.83, height=1414.58 }, -- ZulGurub
[621] = { pfqAreaId=2557, mapId=429, left=1007.00, right=-912.00, top=955.00, bottom=-295.00, width=1919.00, height=1250.00 }, -- DireMaul
[623] = { pfqAreaId=1584, mapId=230, left=875.00, right=-1110.00, top=1513.00, bottom=245.00, width=1985.00, height=1268.00 }, -- BlackrockDepths
[625] = { pfqAreaId=3429, mapId=509, left=3035.42, right=522.92, top=-8233.33, bottom=-9908.33, width=2512.50, height=1675.00 }, -- RuinsofAhnQiraj
[627] = { pfqAreaId=2159, mapId=249, left=155.00, right=-366.00, top=86.00, bottom=-262.00, width=521.00, height=348.00 }, -- OnyxiasLair
[629] = { pfqAreaId=1583, mapId=229, left=10.59, right=-876.25, top=304.40, bottom=-286.83, width=886.84, height=591.23 }, -- BlackrockSpire
[631] = { pfqAreaId=718, mapId=43, left=790.00, right=-380.00, top=215.00, bottom=-570.00, width=1170.00, height=785.00 }, -- WailingCaverns
[633] = { pfqAreaId=2100, mapId=349, left=485.41, right=-1626.68, top=1199.76, bottom=-211.13, width=2112.09, height=1410.89 }, -- Maraudon
[635] = { pfqAreaId=2677, mapId=469, left=-645.00, right=-1422.00, top=-7255.00, bottom=-7760.00, width=777.00, height=505.00 }, -- BlackwingLair
[637] = { pfqAreaId=5138, mapId=36, left=-322.94, right=-979.53, top=36.93, bottom=-398.04, width=656.59, height=434.97 }, -- TheDeadmines
[639] = { pfqAreaId=722, mapId=129, left=1271.00, right=390.00, top=2683.00, bottom=2094.00, width=881.00, height=589.00 }, -- RazorfenDowns
[641] = { pfqAreaId=491, mapId=47, left=2058.92, right=1322.47, top=2349.64, bottom=1858.68, width=736.45, height=490.96 }, -- RazorfenKraul
[643] = { pfqAreaId=5136, mapId=189, left=1567.97, right=947.99, top=2030.18, bottom=1616.86, width=619.98, height=413.32 }, -- ScarletMonastery
[644] = { pfqAreaId=5135, mapId=189, left=-162.27, right=-482.46, top=307.37, bottom=93.91, width=320.19, height=213.46 }, -- ScarletMonastery2f
[645] = { pfqAreaId=5153, mapId=189, left=50.27, right=-562.42, top=2009.10, bottom=1600.64, width=612.69, height=408.46 }, -- ScarletMonastery3f
[646] = { pfqAreaId=5163, mapId=189, left=1743.99, right=1040.69, top=1281.29, bottom=812.42, width=703.30, height=468.87 }, -- ScarletMonastery4f
[648] = { pfqAreaId=2057, mapId=289, left=251.41, right=-68.64, top=317.70, bottom=104.33, width=320.05, height=213.37 }, -- Scholomance
[650] = { pfqAreaId=209, mapId=33, left=2436.00, right=2055.00, top=-61.00, bottom=-315.00, width=381.00, height=254.00 }, -- ShadowfangKeep
[652] = { pfqAreaId=2017, mapId=329, left=-2639.83, right=-3825.18, top=4163.90, bottom=3374.04, width=1185.35, height=789.86 }, -- Stratholme
[654] = { pfqAreaId=3428, mapId=531, left=2515.63, right=1538.07, top=-8051.80, bottom=-8703.50, width=977.56, height=651.70 }, -- AhnQiraj
[655] = { pfqAreaId=5147, mapId=531, left=2915.62, right=138.08, top=-7659.29, bottom=-9510.98, width=2777.54, height=1851.69 }, -- AhnQiraj2f
[657] = { pfqAreaId=5723, mapId=532, left=-1546.00, right=-2165.00, top=-10789.00, bottom=-11199.00, width=619.00, height=410.00 }, -- Karazhan
[659] = { pfqAreaId=2257, mapId=369, left=2623.50, right=2311.50, top=95.50, bottom=-112.50, width=312.00, height=208.00 }, -- DeeprunTram
[662] = { pfqAreaId=5204, mapId=269, left=7338.47, right=6066.48, top=-1247.55, bottom=-2093.03, width=1271.99, height=845.48 }, -- BlackMorass
[663] = { pfqAreaId=2366, mapId=269, left=7651.85, right=6565.99, top=-1500.47, bottom=-2227.08, width=1085.86, height=726.61 }, -- BlackMorass2f
[665] = { pfqAreaId=5208, mapId=815, left=3290.32, right=2040.14, top=-760.72, bottom=-1598.16, width=1250.18, height=837.44 }, -- GilneasCity
[667] = { pfqAreaId=3456, mapId=533, left=-2375.27, right=-4366.96, top=3656.70, bottom=2338.28, width=1991.69, height=1318.42 }, -- Naxxramas
[668] = { pfqAreaId=5148, mapId=533, left=-4869.36, right=-5521.46, top=3816.56, bottom=3376.89, width=652.10, height=439.67 }, -- Naxxramas2f
[670] = { pfqAreaId=5077, mapId=802, left=1207.72, right=-1435.49, top=882.98, bottom=-868.18, width=2643.21, height=1751.16 }, -- CrescentGrove
[672] = { pfqAreaId=5103, mapId=808, left=-3024.33, right=-3776.45, top=-7888.28, bottom=-8398.61, width=752.12, height=510.33 }, -- HateforgeQuarry
[674] = { pfqAreaId=5086, mapId=800, left=-1302.51, right=-1849.26, top=-10916.17, bottom=-11308.14, width=546.75, height=391.97 }, -- KarazhanCrypt
[676] = { pfqAreaId=5087, mapId=35, left=314.34, right=-40.16, top=89.30, bottom=-145.44, width=354.50, height=234.74 }, -- StormwindVault
[678] = { pfqAreaId=5097, mapId=807, left=3939.10, right=2666.00, top=3519.72, bottom=2666.00, width=1273.10, height=853.72 }, -- EmeraldSanctum
[680] = { pfqAreaId=5053, mapId=801, left=17296.30, right=16288.62, top=17158.52, bottom=16486.73, width=1007.68, height=671.79 }, -- Moomoo
[682] = { pfqAreaId=3457, mapId=814, left=-1378.00, right=-2201.00, top=-10887.00, bottom=-11436.00, width=823.00, height=549.00 }, -- UpperKarazhan
[683] = { pfqAreaId=5557, mapId=814, left=-1483.00, right=-4283.00, top=-5800.00, bottom=-7668.00, width=2800.00, height=1868.00 }, -- UpperKarazhan2f
[684] = { pfqAreaId=5602, mapId=0, left=-2072.00, right=-7459.00, top=-2904.00, bottom=-6488.00, width=5387.00, height=3584.00 }, -- GrimReaches
[685] = { pfqAreaId=5561, mapId=0, left=4728.00, right=1630.00, top=-7577.00, bottom=-9645.00, width=3098.00, height=2068.00 }, -- Balor
[686] = { pfqAreaId=5581, mapId=0, left=2258.00, right=-983.00, top=-6327.00, bottom=-8484.00, width=3241.00, height=2157.00 }, -- Northwind
[689] = { pfqAreaId=2557, mapId=1, left=2014.00, right=690.00, top=-3401.00, bottom=-4270.00, width=1324.00, height=869.00 }, -- DireMaulEntrance
[691] = { pfqAreaId=5601, mapId=816, left=-3428.00, right=-5062.00, top=-5599.00, bottom=-6672.00, width=1634.00, height=1073.00 }, -- DragonmawRetreat
[693] = { pfqAreaId=5628, mapId=818, left=-3365.00, right=-5345.00, top=-5424.00, bottom=-6774.00, width=1980.00, height=1350.00 }, -- StormwroughtRuins
[694] = { pfqAreaId=0, mapId=0, left=0.00, right=0.00, top=0.00, bottom=0.00, width=0.00, height=0.00 }, -- World
[696] = { pfqAreaId=5640, mapId=819, left=-2991.00, right=-4416.00, top=-7195.00, bottom=-8157.00, width=1425.00, height=962.00 }, -- Timbermaw
[698] = { pfqAreaId=5641, mapId=820, left=-3184.00, right=-4418.00, top=-7163.00, bottom=-7992.00, width=1234.00, height=829.00 }, -- WindhornCanyon
[699] = { pfqAreaId=5642, mapId=1, left=-1354.00, right=-9210.00, top=10204.00, bottom=4963.00, width=7856.00, height=5241.00 }, -- Moonwhisper
[700] = { pfqAreaId=1769, mapId=1, left=-1862.00, right=-2723.00, top=7395.00, bottom=6788.00, width=861.00, height=607.00 }, -- TimbermawTunnels
[701] = { pfqAreaId=5640, mapId=1, left=-4646.00, right=-5993.00, top=5175.00, bottom=4204.00, width=1347.00, height=971.00 }, -- TimbermawEntrance
[702] = { pfqAreaId=5722, mapId=821, left=2105.00, right=1058.00, top=2522.00, bottom=1828.00, width=1047.00, height=694.00 }, -- ThornGorge
[704] = { pfqAreaId=5734, mapId=822, left=-3157.00, right=-4168.00, top=-7453.00, bottom=-8106.00, width=1011.00, height=653.00 }, -- FrostmaneHollow
[705] = { pfqAreaId=5731, mapId=1, left=-3231.00, right=-3671.00, top=-5053.00, bottom=-5332.00, width=440.00, height=279.00 }, -- WindhornCanyonEntrance
}
+82 -16
View File
@@ -56,6 +56,11 @@ local function MTH_DS_ApplyCapturedProjectileQualities()
[18042] = 2, [18042] = 2,
[19316] = 2, [19316] = 2,
[19317] = 2, [19317] = 2,
[42198] = 2,
[42199] = 2,
[42200] = 2,
[42201] = 3,
[42202] = 3,
} }
for itemId, quality in pairs(capturedQualityByItemId) do for itemId, quality in pairs(capturedQualityByItemId) do
@@ -207,7 +212,7 @@ local function MTH_DS_EnsurePetSpellsTrainerGrowl()
rank = src.rank, rank = src.rank,
rankNumber = src.rankNumber, rankNumber = src.rankNumber,
school = "Physical", school = "Physical",
sourceUrl = "https://database.turtlecraft.gg/?spell=" .. tostring(src.id), sourceUrl = "https://octowow.st/db/?spell=" .. tostring(src.id),
trainLevel = src.trainLevel, trainLevel = src.trainLevel,
} }
table.insert(growlSpells, row) table.insert(growlSpells, row)
@@ -257,7 +262,7 @@ local function MTH_DS_EnsurePetSpellsTrainerGrowl()
rank = src.rank, rank = src.rank,
rankNumber = src.rankNumber, rankNumber = src.rankNumber,
school = "Physical", school = "Physical",
sourceUrl = "https://database.turtlecraft.gg/?spell=" .. tostring(src.id), sourceUrl = "https://octowow.st/db/?spell=" .. tostring(src.id),
trainLevel = src.trainLevel, trainLevel = src.trainLevel,
} }
table.insert(staminaSpells, row) table.insert(staminaSpells, row)
@@ -307,7 +312,7 @@ local function MTH_DS_EnsurePetSpellsTrainerGrowl()
rank = src.rank, rank = src.rank,
rankNumber = src.rankNumber, rankNumber = src.rankNumber,
school = "Physical", school = "Physical",
sourceUrl = "https://database.turtlecraft.gg/?spell=" .. tostring(src.id), sourceUrl = "https://octowow.st/db/?spell=" .. tostring(src.id),
trainLevel = src.trainLevel, trainLevel = src.trainLevel,
} }
table.insert(armorSpells, row) table.insert(armorSpells, row)
@@ -351,7 +356,7 @@ local function MTH_DS_EnsurePetSpellsTrainerGrowl()
rank = src.rank, rank = src.rank,
rankNumber = src.rankNumber, rankNumber = src.rankNumber,
school = "Physical", school = "Physical",
sourceUrl = "https://database.turtlecraft.gg/?spell=" .. tostring(src.id), sourceUrl = "https://octowow.st/db/?spell=" .. tostring(src.id),
trainLevel = src.trainLevel, trainLevel = src.trainLevel,
} }
table.insert(fireResSpells, row) table.insert(fireResSpells, row)
@@ -395,7 +400,7 @@ local function MTH_DS_EnsurePetSpellsTrainerGrowl()
rank = src.rank, rank = src.rank,
rankNumber = src.rankNumber, rankNumber = src.rankNumber,
school = "Physical", school = "Physical",
sourceUrl = "https://database.turtlecraft.gg/?spell=" .. tostring(src.id), sourceUrl = "https://octowow.st/db/?spell=" .. tostring(src.id),
trainLevel = src.trainLevel, trainLevel = src.trainLevel,
} }
table.insert(frostResSpells, row) table.insert(frostResSpells, row)
@@ -439,7 +444,7 @@ local function MTH_DS_EnsurePetSpellsTrainerGrowl()
rank = src.rank, rank = src.rank,
rankNumber = src.rankNumber, rankNumber = src.rankNumber,
school = "Physical", school = "Physical",
sourceUrl = "https://database.turtlecraft.gg/?spell=" .. tostring(src.id), sourceUrl = "https://octowow.st/db/?spell=" .. tostring(src.id),
trainLevel = src.trainLevel, trainLevel = src.trainLevel,
} }
table.insert(arcaneResSpells, row) table.insert(arcaneResSpells, row)
@@ -483,7 +488,7 @@ local function MTH_DS_EnsurePetSpellsTrainerGrowl()
rank = src.rank, rank = src.rank,
rankNumber = src.rankNumber, rankNumber = src.rankNumber,
school = "Physical", school = "Physical",
sourceUrl = "https://database.turtlecraft.gg/?spell=" .. tostring(src.id), sourceUrl = "https://octowow.st/db/?spell=" .. tostring(src.id),
trainLevel = src.trainLevel, trainLevel = src.trainLevel,
} }
table.insert(natureResSpells, row) table.insert(natureResSpells, row)
@@ -527,7 +532,7 @@ local function MTH_DS_EnsurePetSpellsTrainerGrowl()
rank = src.rank, rank = src.rank,
rankNumber = src.rankNumber, rankNumber = src.rankNumber,
school = "Physical", school = "Physical",
sourceUrl = "https://database.turtlecraft.gg/?spell=" .. tostring(src.id), sourceUrl = "https://octowow.st/db/?spell=" .. tostring(src.id),
trainLevel = src.trainLevel, trainLevel = src.trainLevel,
} }
table.insert(shadowResSpells, row) table.insert(shadowResSpells, row)
@@ -551,56 +556,56 @@ local function MTH_DS_EnsurePetSpellsTrainerGrowl()
MTH_DS_PetSpells.byAbility["Growl"] = { MTH_DS_PetSpells.byAbility["Growl"] = {
ability = "Growl", ability = "Growl",
searchUrl = "https://database.turtlecraft.gg/?search=Growl", searchUrl = "https://octowow.st/db/?search=Growl",
spellCount = table.getn(growlSpells), spellCount = table.getn(growlSpells),
spells = growlSpells, spells = growlSpells,
} }
MTH_DS_PetSpells.byAbility["Great Stamina"] = { MTH_DS_PetSpells.byAbility["Great Stamina"] = {
ability = "Great Stamina", ability = "Great Stamina",
searchUrl = "https://database.turtlecraft.gg/?search=Great+Stamina", searchUrl = "https://octowow.st/db/?search=Great+Stamina",
spellCount = table.getn(staminaSpells), spellCount = table.getn(staminaSpells),
spells = staminaSpells, spells = staminaSpells,
} }
MTH_DS_PetSpells.byAbility["Natural Armor"] = { MTH_DS_PetSpells.byAbility["Natural Armor"] = {
ability = "Natural Armor", ability = "Natural Armor",
searchUrl = "https://database.turtlecraft.gg/?search=Natural+Armor", searchUrl = "https://octowow.st/db/?search=Natural+Armor",
spellCount = table.getn(armorSpells), spellCount = table.getn(armorSpells),
spells = armorSpells, spells = armorSpells,
} }
MTH_DS_PetSpells.byAbility["Fire Resistance"] = { MTH_DS_PetSpells.byAbility["Fire Resistance"] = {
ability = "Fire Resistance", ability = "Fire Resistance",
searchUrl = "https://database.turtlecraft.gg/?search=Fire+Resistance", searchUrl = "https://octowow.st/db/?search=Fire+Resistance",
spellCount = table.getn(fireResSpells), spellCount = table.getn(fireResSpells),
spells = fireResSpells, spells = fireResSpells,
} }
MTH_DS_PetSpells.byAbility["Frost Resistance"] = { MTH_DS_PetSpells.byAbility["Frost Resistance"] = {
ability = "Frost Resistance", ability = "Frost Resistance",
searchUrl = "https://database.turtlecraft.gg/?search=Frost+Resistance", searchUrl = "https://octowow.st/db/?search=Frost+Resistance",
spellCount = table.getn(frostResSpells), spellCount = table.getn(frostResSpells),
spells = frostResSpells, spells = frostResSpells,
} }
MTH_DS_PetSpells.byAbility["Arcane Resistance"] = { MTH_DS_PetSpells.byAbility["Arcane Resistance"] = {
ability = "Arcane Resistance", ability = "Arcane Resistance",
searchUrl = "https://database.turtlecraft.gg/?search=Arcane+Resistance", searchUrl = "https://octowow.st/db/?search=Arcane+Resistance",
spellCount = table.getn(arcaneResSpells), spellCount = table.getn(arcaneResSpells),
spells = arcaneResSpells, spells = arcaneResSpells,
} }
MTH_DS_PetSpells.byAbility["Nature Resistance"] = { MTH_DS_PetSpells.byAbility["Nature Resistance"] = {
ability = "Nature Resistance", ability = "Nature Resistance",
searchUrl = "https://database.turtlecraft.gg/?search=Nature+Resistance", searchUrl = "https://octowow.st/db/?search=Nature+Resistance",
spellCount = table.getn(natureResSpells), spellCount = table.getn(natureResSpells),
spells = natureResSpells, spells = natureResSpells,
} }
MTH_DS_PetSpells.byAbility["Shadow Resistance"] = { MTH_DS_PetSpells.byAbility["Shadow Resistance"] = {
ability = "Shadow Resistance", ability = "Shadow Resistance",
searchUrl = "https://database.turtlecraft.gg/?search=Shadow+Resistance", searchUrl = "https://octowow.st/db/?search=Shadow+Resistance",
spellCount = table.getn(shadowResSpells), spellCount = table.getn(shadowResSpells),
spells = shadowResSpells, spells = shadowResSpells,
} }
@@ -608,6 +613,66 @@ local function MTH_DS_EnsurePetSpellsTrainerGrowl()
MTH_DS_PetSpells.totalSpellRows = table.getn(MTH_DS_PetSpells.allSpells) MTH_DS_PetSpells.totalSpellRows = table.getn(MTH_DS_PetSpells.allSpells)
end end
-- Restore the "unique appearance" flag on beasts.
-- This is a curated allowlist of beasts whose look is genuinely one-of-a-kind
-- (named individuals / distinctive rares that a collector prizes). We do NOT derive
-- this from the skin datastore's unique flag: that flag only means "this texture
-- appears once in the DBC", which over-marks whole color-variant families (e.g. every
-- Moth and Fox colour) and would wrongly flag common mobs like Frenzied Dustwing.
-- Keyed by NPC id (names such as "Crimson Lynx" repeat across entries). Hand-maintained.
local MTH_DS_UniqueBeastIds = {
[5828] = true, -- Humar the Pridelord (Cats)
[61599] = true, -- Darksaber (Cats)
[61699] = true, -- Shar'lan (Cats)
[7434] = true, -- Frostsaber Pride Watcher (Cats)
[3068] = true, -- Mazzranache (Tallstriders)
[61789] = true, -- Moonfeather (Tallstriders)
[61488] = true, -- Azurebeak (Owls)
[59998] = true, -- Blue Owl (Owls)
[59997] = true, -- Red Owl (Owls)
[63147] = true, -- Withered Companion (Owls)
[62778] = true, -- Ahgk'tos the Pure (Spiders)
[8211] = true, -- Old Cliff Jumper (Wolves)
[4512] = true, -- Rotting Agam'ar (Boars)
[62750] = true, -- Magmalash Scorpid (Scorpids)
[11371] = true, -- Razzashi Serpent (Serpents)
[80260] = true, -- Spirit Fox (Foxes)
[61716] = true, -- Crimson Hawkstrider (Tallstriders)
[61691] = true, -- Forest Hawkstrider (Tallstriders)
[61695] = true, -- Ivory Hawkstrider (Tallstriders)
[62817] = true, -- Moonstrider Matriarch (Tallstriders)
[62818] = true, -- Moonstrider Rooster (Tallstriders)
[61774] = true, -- Brilliant Mana Wyrm (Wind Serpents)
[61775] = true, -- Emerald Mana Wyrm (Wind Serpents)
[61776] = true, -- Lavender Mana Wyrm (Wind Serpents)
[61480] = true, -- Brilliant Wind Serpent (Wind Serpents)
[1815] = true, -- Diseased Black Bear (Bears)
[80466] = true, -- Tirisfal Plagued Bear (Bears)
[62820] = true, -- Nightpelt Grizzly (Bears)
[62821] = true, -- Nightpelt Ursa (Bears)
[80816] = true, -- Hinterlands Eagle (Owls)
[61697] = true, -- Bright Lynx (Cats)
[61698] = true, -- Bright Lynx Matriarch (Cats)
[62338] = true, -- Stormwing Buzzard (Carrion Birds)
[62339] = true, -- Stormwing Vulture (Carrion Birds)
}
local function MTH_DS_DeriveBeastUnique()
if type(MTH_DS_Beasts) ~= "table" then
return
end
for npcId, beast in pairs(MTH_DS_Beasts) do
if type(beast) == "table" then
if MTH_DS_UniqueBeastIds[npcId] then
beast.unique = true
else
beast.unique = nil
end
end
end
end
function MTH_DS_OnLoad() function MTH_DS_OnLoad()
if MTH_DS.loaded then if MTH_DS.loaded then
return return
@@ -618,6 +683,7 @@ function MTH_DS_OnLoad()
if MTH_DS_Beasts and MTH_DS_Vendors and MTH_DS_AmmoItems and MTH_DS_Zones and MTH_DS_Families then if MTH_DS_Beasts and MTH_DS_Vendors and MTH_DS_AmmoItems and MTH_DS_Zones and MTH_DS_Families then
MTH_DS_EnsurePetSpellsTrainerGrowl() MTH_DS_EnsurePetSpellsTrainerGrowl()
MTH_DS_DeriveBeastUnique()
MTH_DS.loaded = true MTH_DS.loaded = true
-- Count entries -- Count entries
+14 -1
View File
@@ -1,7 +1,9 @@
<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/ <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"> ..\FrameXML\UI.xsd">
<Include file="..\api\PizzaSauce.lua"/>
<Include file="..\api\constants.lua"/> <Include file="..\api\constants.lua"/>
<Include file="..\api\debug-frame.lua"/> <Include file="..\api\savedvariables.lua"/>
<Include file="..\api\fx-celebrate.lua"/>
<Include file="..\api\minimap-button.lua"/> <Include file="..\api\minimap-button.lua"/>
<Include file="..\api\AutoStrip.xml"/> <Include file="..\api\AutoStrip.xml"/>
<Include file="..\api\AutoStrip.lua"/> <Include file="..\api\AutoStrip.lua"/>
@@ -12,15 +14,19 @@
<Include file="..\api\options-tree.lua"/> <Include file="..\api\options-tree.lua"/>
<Include file="..\api\options.xml"/> <Include file="..\api\options.xml"/>
<Include file="..\api\options.lua"/> <Include file="..\api\options.lua"/>
<Include file="..\api\options-layout.lua"/>
<Include file="..\api\options-chronometer.lua"/> <Include file="..\api\options-chronometer.lua"/>
<Include file="..\api\options-credits.lua"/> <Include file="..\api\options-credits.lua"/>
<Include file="..\api\options-animations.lua"/>
<Include file="..\api\options-autobuy.lua"/> <Include file="..\api\options-autobuy.lua"/>
<Include file="..\api\options-autoquest.lua"/> <Include file="..\api\options-autoquest.lua"/>
<Include file="..\api\options-icu.lua"/> <Include file="..\api\options-icu.lua"/>
<Include file="..\api\options-expammo.lua"/>
<Include file="..\api\profile.lua"/> <Include file="..\api\profile.lua"/>
<Include file="..\api\options-profiles.lua"/> <Include file="..\api\options-profiles.lua"/>
<Include file="..\api\options-shell.lua"/> <Include file="..\api\options-shell.lua"/>
<Include file="..\api\options-zbuttons.lua"/> <Include file="..\api\options-zbuttons.lua"/>
<Include file="..\api\bag-ammo-labels.lua"/>
<Include file="..\api\options-feedomatic.lua"/> <Include file="..\api\options-feedomatic.lua"/>
<Include file="..\api\hunterbook.xml"/> <Include file="..\api\hunterbook.xml"/>
<Include file="..\api\hunterbook.lua"/> <Include file="..\api\hunterbook.lua"/>
@@ -31,16 +37,23 @@
<Include file="..\api\hunterbook-tab-npcfinder.lua"/> <Include file="..\api\hunterbook-tab-npcfinder.lua"/>
<Include file="..\api\hunterbook-tab-stable.lua"/> <Include file="..\api\hunterbook-tab-stable.lua"/>
<Include file="..\api\hunterbook-tab-pethistory.lua"/> <Include file="..\api\hunterbook-tab-pethistory.lua"/>
<Include file="..\api\hunterbook-tab-trainers.lua"/>
<Include file="..\api\hunterbook-model-viewer.lua"/> <Include file="..\api\hunterbook-model-viewer.lua"/>
<Include file="..\api\map-markers.lua"/> <Include file="..\api\map-markers.lua"/>
<Include file="..\api\debug-frame.lua"/>
<Include file="..\api\core-framework.lua"/> <Include file="..\api\core-framework.lua"/>
<Include file="..\api\core-scan-stablemaster.lua"/> <Include file="..\api\core-scan-stablemaster.lua"/>
<Include file="..\api\pets-service-lifecycle.lua"/> <Include file="..\api\pets-service-lifecycle.lua"/>
<Include file="..\api\pets-service-vitals.lua"/> <Include file="..\api\pets-service-vitals.lua"/>
<Include file="..\api\pets-service-runaway.lua"/> <Include file="..\api\pets-service-runaway.lua"/>
<Include file="..\api\pets-service-stablescan.lua"/> <Include file="..\api\pets-service-stablescan.lua"/>
<Include file="..\api\pet-growl-teach.lua"/>
<Include file="..\api\core-scan-beasttraining.lua"/> <Include file="..\api\core-scan-beasttraining.lua"/>
<Include file="..\api\core-scan-trainer.lua"/>
<Include file="..\api\core-scan-spellbookpet.lua"/> <Include file="..\api\core-scan-spellbookpet.lua"/>
<Include file="..\api\core-scan-huntertalents.lua"/>
<Include file="..\api\trainer-spell-planner.lua"/>
<Include file="..\api\core-level-tracking.lua"/>
<Include file="..\api\core.lua"/> <Include file="..\api\core.lua"/>
<Include file="..\api\version-check.lua"/> <Include file="..\api\version-check.lua"/>
<Include file="..\api\localization.lua"/> <Include file="..\api\localization.lua"/>
-4
View File
@@ -1,4 +0,0 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<!-- MetaHunt Compatibility Layer -->
<!-- Handles version compatibility with Turtle WoW -->
</Ui>
-4
View File
@@ -1,4 +0,0 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<!-- MetaHunt Environment Setup -->
<!-- Global tables, localization, and configuration -->
</Ui>
-5
View File
@@ -1,5 +0,0 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<!-- MetaHunt Library includes -->
<!-- External libraries for enhanced functionality -->
<!-- Note: Libraries can be added here as needed -->
</Ui>
+3
View File
@@ -26,4 +26,7 @@ MetaHunt Module Loader
<!-- ICU Module - ENABLED (runtime default: disabled) --> <!-- ICU Module - ENABLED (runtime default: disabled) -->
<Include file="..\modules\icu\loader.xml"/> <Include file="..\modules\icu\loader.xml"/>
<!-- Experimental Ammunition Module - ENABLED -->
<Include file="..\modules\expammo\loader.xml"/>
</Ui> </Ui>
-4
View File
@@ -1,4 +0,0 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<!-- MetaHunt UI Skins and Styling -->
<!-- Placeholder for custom UI theming -->
</Ui>
+5
View File
@@ -2,6 +2,11 @@ MTH_LocaleData = MTH_LocaleData or {}
MTH_LocaleData.spells = MTH_LocaleData.spells or {} MTH_LocaleData.spells = MTH_LocaleData.spells or {}
MTH_LocaleData.spells.deDE = { 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", ["Banish"] = "Verbannen",
["Bestial Wrath"] = "Zorn des Wildtiers", ["Bestial Wrath"] = "Zorn des Wildtiers",
["Concussive Shot"] = "Erschütternder Schuss", ["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 = MTH_LocaleData.spells or {}
MTH_LocaleData.spells.enUS = { 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", ["Banish"] = "Banish",
["Bestial Wrath"] = "Bestial Wrath", ["Bestial Wrath"] = "Bestial Wrath",
["Concussive Shot"] = "Concussive Shot", ["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 = MTH_LocaleData.spells or {}
MTH_LocaleData.spells.esES = { 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", ["Banish"] = "Banish",
["Bestial Wrath"] = "Bestial Wrath", ["Bestial Wrath"] = "Bestial Wrath",
["Concussive Shot"] = "Concussive Shot", ["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 = MTH_LocaleData.spells or {}
MTH_LocaleData.spells.ptBR = { 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", ["Banish"] = "Banish",
["Bestial Wrath"] = "Bestial Wrath", ["Bestial Wrath"] = "Bestial Wrath",
["Concussive Shot"] = "Concussive Shot", ["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 = MTH_LocaleData.spells or {}
MTH_LocaleData.spells.ruRU = { MTH_LocaleData.spells.ruRU = {
["Aimed Shot"] = "Прицельный выстрел",
["Aspect of the Viper"] = "Аспект гадюки",
["Kill Command"] = "Приказ убивать",
["Lock and Load"] = "Lock and Load",
["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 = MTH_LocaleData.spells or {}
MTH_LocaleData.spells.zhCN = { MTH_LocaleData.spells.zhCN = {
["Aimed Shot"] = "瞄准射击",
["Aspect of the Viper"] = "毒蛇图腾",
["Kill Command"] = "猎杀命令",
["Lock and Load"] = "Lock and Load",
["Scent of Blood"] = "血腥气息",
["Banish"] = "放逐术", ["Banish"] = "放逐术",
["Bestial Wrath"] = "狂野怒火", ["Bestial Wrath"] = "狂野怒火",
["Concussive Shot"] = "震荡射击", ["Concussive Shot"] = "震荡射击",
+5 -3
View File
@@ -8,6 +8,8 @@ MTH_LocaleData.ui.enUS = {
BINDING_HEADER_ZTrapHeader = "[MetaHunt] ZTrap Buttons", BINDING_HEADER_ZTrapHeader = "[MetaHunt] ZTrap Buttons",
BINDING_HEADER_ZPetHeader = "[MetaHunt] ZPet Buttons", BINDING_HEADER_ZPetHeader = "[MetaHunt] ZPet Buttons",
BINDING_HEADER_ZAmmoHeader = "[MetaHunt] ZAmmo Buttons", BINDING_HEADER_ZAmmoHeader = "[MetaHunt] ZAmmo Buttons",
BINDING_HEADER_ExpAmmoHeader = "[MetaHunt] MM Widget",
BINDING_NAME_EXPAMMOACTION = "MM Widget Action",
FEEDOMATIC_NAME = "Feed-O-Matic", FEEDOMATIC_NAME = "Feed-O-Matic",
FEEDOMATIC_DESC = "Helps a Hunter keep his pets well fed", FEEDOMATIC_DESC = "Helps a Hunter keep his pets well fed",
@@ -43,7 +45,7 @@ MTH_LocaleData.ui.enUS = {
FOM_ERROR_TOGGLE_FAILED = "Failed to change FeedOMatic state: %s", FOM_ERROR_TOGGLE_FAILED = "Failed to change FeedOMatic state: %s",
FOM_TITLE = "Feed-O-Matic", FOM_TITLE = "Feed-O-Matic",
FOM_STATUS_NOTICE = "Feed-O-Matic, created by the great Fizzwidget, is not yet entirely ready for TurtleWoW because I am missing \na reliable list of all foods, mostly impossible to fetch from the DB.\n It still works much better in this version, but all stuff related to Food buff and Cooking isnt fully functional.", FOM_STATUS_NOTICE = "Feed-O-Matic, created by the great Fizzwidget, is not yet entirely ready for Twow because I am missing \na reliable list of all foods, mostly impossible to fetch from the DB.\n It still works much better in this version, but all stuff related to Food buff and Cooking isnt fully functional.",
FOM_ENABLE_MODULE = "Enable FeedOMatic module", FOM_ENABLE_MODULE = "Enable FeedOMatic module",
FOM_HEADER_GENERAL = "General", FOM_HEADER_GENERAL = "General",
FOM_BIND_HINT = "You should assign a key bind for feed-o-matic,\nin order to feed your pet automatically.\nThe key \"P\" is an excellent candidate !", FOM_BIND_HINT = "You should assign a key bind for feed-o-matic,\nin order to feed your pet automatically.\nThe key \"P\" is an excellent candidate !",
@@ -117,12 +119,12 @@ MTH_LocaleData.ui.enUS = {
CREDITS_ABOUT_CODE_INTRO = "This addon is, in others, a compilation of old vanilla addons that I loved and played with during years. But most of them had become very buggy with TwoW as new contain was added to the server. \n I fixed, reworked, enhanced, and melted them within a modern modular framework that I created.\nI want to make it crystal clear about what work is mine, and what is not and respectfully credit the original authors for the great stuff I've taken from them:", CREDITS_ABOUT_CODE_INTRO = "This addon is, in others, a compilation of old vanilla addons that I loved and played with during years. But most of them had become very buggy with TwoW as new contain was added to the server. \n I fixed, reworked, enhanced, and melted them within a modern modular framework that I created.\nI want to make it crystal clear about what work is mine, and what is not and respectfully credit the original authors for the great stuff I've taken from them:",
CREDITS_ABOUT_ZHUNTER = "- zBars, Antidaze and Autostrip were core functionalities of Vanilla zHunterMod addon. And I kept the \"z\" naming of bars/buttons to always remember it. On top of this I have created myself zAmmo, zCompanions, zMount, zToys.\nAlso the SmartAmmo feature idea is coming from Zhuntermod: there was a file in it with that functionality, but was like a work-in-progress, totally unfunctional and even \"dangerous\" in its current state, and was not active in the addon. Since it was a fucking great idea, I recoded it mostly from scratch and made it work reliably and safely.", CREDITS_ABOUT_ZHUNTER = "- zBars, Antidaze and Autostrip were core functionalities of Vanilla zHunterMod addon. And I kept the \"z\" naming of bars/buttons to always remember it. On top of this I have created myself zAmmo, zCompanions, zMount, zToys.\nAlso the SmartAmmo feature idea is coming from Zhuntermod: there was a file in it with that functionality, but was like a work-in-progress, totally unfunctional and even \"dangerous\" in its current state, and was not active in the addon. Since it was a fucking great idea, I recoded it mostly from scratch and made it work reliably and safely.",
CREDITS_ABOUT_FEEDOMATIC = "- Feed-o-Matic was entirely the work of Fizzwidget. I made it work for Twow, by melting it as a module calling Metahunt core to get pet's state and up-to-date data for new pet families and their diet.", CREDITS_ABOUT_FEEDOMATIC = "- Feed-o-Matic was entirely the work of Fizzwidget. I made it work for Twow, by melting it as a module calling Metahunt core to get pet's state and up-to-date data for new pet families and their diet.",
CREDITS_ABOUT_TOOLTIPS = "- Tooltips module is based on Hunter Helper, another vanilla addon of Fizzwidget. I kept the general idea, but there's not much original code left from it in Metahunt as I refactored everything so it plugs on MetaHunt controlled event handlers, can work with Turtle wow data, and is now usable on other things than Beasts.", CREDITS_ABOUT_TOOLTIPS = "- Tooltips module is based on Hunter Helper, another vanilla addon of Fizzwidget. I kept the general idea, but there's not much original code left from it in Metahunt as I refactored everything so it plugs on MetaHunt controlled event handlers, can work with Twow data, and is now usable on other things than Beasts.",
CREDITS_ABOUT_CHRONOMETER = "- Chronometer was an old and very popular vanilla addon working with ACE2 libraries. It has known uncountable iterations by various people over the years, and I can't credit them all of them here. The current version used by Metahunt is based on this Twow conversion made by \"wigan91\". I have reworked it to have it Hunter-focused-only: removed the non-hunter/race stuff, improved the definition for spells/effects config that were not correct, added missing hunter spells (not many), and implemented my own bar color scheme fitting better the hunter spells.", CREDITS_ABOUT_CHRONOMETER = "- Chronometer was an old and very popular vanilla addon working with ACE2 libraries. It has known uncountable iterations by various people over the years, and I can't credit them all of them here. The current version used by Metahunt is based on this Twow conversion made by \"wigan91\". I have reworked it to have it Hunter-focused-only: removed the non-hunter/race stuff, improved the definition for spells/effects config that were not correct, added missing hunter spells (not many), and implemented my own bar color scheme fitting better the hunter spells.",
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.", 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.",
CREDITS_ABOUT_ATLAS_LINKS = "- The system allowing to fetch itemlinks in game (In the Weapon tab of the Hunter Book notably) is the one of Atlas Twow.", CREDITS_ABOUT_ATLAS_LINKS = "- The system allowing to fetch itemlinks in game (In the Weapon tab of the Hunter Book notably) is the one of Atlas Twow.",
CREDITS_ABOUT_DATA_TITLE = "About data sources", CREDITS_ABOUT_DATA_TITLE = "About data sources",
CREDITS_ABOUT_DATA_INTRO = "Metahunt ships with its own Turtle-WoW datastores, limited to hunter stuff only, and most of this data is up-to-date (Feb 2026).\n\nSources used to build the data include:", CREDITS_ABOUT_DATA_INTRO = "Metahunt ships with its own Twow datastores, limited to hunter stuff only, and most of this data is up-to-date (Feb 2026).\n\nSources used to build the data include:",
CREDITS_DATA_PFQUEST = "- pfQuest for Beasts and Ammo vendors NPCs and their spawn points (and beasts respawn times if any). Thank you again Shagu.", CREDITS_DATA_PFQUEST = "- pfQuest for Beasts and Ammo vendors NPCs and their spawn points (and beasts respawn times if any). Thank you again Shagu.",
CREDITS_DATA_SHEET = "- This invaluable and very accurate spreadsheet for beasts having pet abilities. A big thanks to all twow players that crafted this! Now you dont need that sheet anymore, you have Metahunt! And the Great Book of Huntards is awesome and accurate also because of YOU.", CREDITS_DATA_SHEET = "- This invaluable and very accurate spreadsheet for beasts having pet abilities. A big thanks to all twow players that crafted this! Now you dont need that sheet anymore, you have Metahunt! And the Great Book of Huntards is awesome and accurate also because of YOU.",
CREDITS_DATA_OTHER = "- Atlas twow for ranged weapon sources\n\n- Twow wiki for pet diet (which was incomplete, missing Serpents and Foxes)", CREDITS_DATA_OTHER = "- Atlas twow for ranged weapon sources\n\n- Twow wiki for pet diet (which was incomplete, missing Serpents and Foxes)",
-25
View File
@@ -9,9 +9,6 @@ local Engine = MTH_AutoBuyEngine
local AB_AMMO_BY_NAME = nil local AB_AMMO_BY_NAME = nil
local AB_FOOD_DIET_BY_ITEM_ID = nil local AB_FOOD_DIET_BY_ITEM_ID = nil
local function AB_Trace(message)
return
end
local function AB_ParseItemIdFromLink(link) local function AB_ParseItemIdFromLink(link)
if type(link) ~= "string" then if type(link) ~= "string" then
@@ -1180,7 +1177,6 @@ end
function Engine:ExecutePlan(plan) function Engine:ExecutePlan(plan)
if type(plan) ~= "table" or type(plan.actions) ~= "table" then if type(plan) ~= "table" or type(plan.actions) ~= "table" then
AB_Trace("ExecutePlan skipped: invalid plan table")
return 0, 0 return 0, 0
end end
local executedActions = 0 local executedActions = 0
@@ -1189,10 +1185,6 @@ function Engine:ExecutePlan(plan)
if type(action) == "table" and action.index then if type(action) == "table" and action.index then
local stacksToBuy = math.floor(tonumber(action.quantityStacks) or 0) local stacksToBuy = math.floor(tonumber(action.quantityStacks) or 0)
if stacksToBuy > 0 then if stacksToBuy > 0 then
AB_Trace("ExecutePlan action index=" .. tostring(action.index)
.. " itemId=" .. tostring(action.itemId)
.. " stacks=" .. tostring(stacksToBuy)
.. " reason=" .. tostring(plan.reason or ""))
for i = 1, stacksToBuy do for i = 1, stacksToBuy do
BuyMerchantItem(action.index) BuyMerchantItem(action.index)
end end
@@ -1201,7 +1193,6 @@ function Engine:ExecutePlan(plan)
end end
end end
end end
AB_Trace("ExecutePlan done actions=" .. tostring(executedActions) .. " stacks=" .. tostring(requestedStacks) .. " reason=" .. tostring(plan.reason or ""))
return executedActions, requestedStacks return executedActions, requestedStacks
end end
@@ -1264,42 +1255,30 @@ local function AB_AnnouncePurchases(actions)
end end
function Engine:OnMerchantEvent(eventName) function Engine:OnMerchantEvent(eventName)
AB_Trace("OnMerchantEvent start event=" .. tostring(eventName)
.. " sessionExecuted=" .. tostring(self._merchantSessionExecuted and true or false)
.. " engineEnabled=" .. tostring(self:IsEnabled() and true or false))
if eventName == "MERCHANT_SHOW" then if eventName == "MERCHANT_SHOW" then
self._merchantSessionExecuted = false self._merchantSessionExecuted = false
AB_Trace("MERCHANT_SHOW resets session execution flag")
elseif eventName == "MERCHANT_UPDATE" and self._merchantSessionExecuted then elseif eventName == "MERCHANT_UPDATE" and self._merchantSessionExecuted then
AB_Trace("MERCHANT_UPDATE ignored: already executed this merchant session")
return return
end end
if not self:IsEnabled() then if not self:IsEnabled() then
AB_Trace("event ignored: autobuy module disabled")
return return
end end
local snapshot = self:ScanMerchant() local snapshot = self:ScanMerchant()
AB_Trace("snapshot rows=" .. tostring(snapshot and snapshot.count or 0))
local foodPlan = self:BuildFoodPlan(snapshot) local foodPlan = self:BuildFoodPlan(snapshot)
local ammoPlan = self:BuildAmmoPlan(snapshot) local ammoPlan = self:BuildAmmoPlan(snapshot)
AB_Trace("foodPlan reason=" .. tostring(foodPlan.reason) .. " actions=" .. tostring(table.getn(foodPlan.actions or {})))
for i = 1, table.getn(foodPlan.debugLines or {}) do for i = 1, table.getn(foodPlan.debugLines or {}) do
AB_Trace("food#" .. tostring(i) .. " " .. tostring(foodPlan.debugLines[i]))
end end
AB_Trace("ammoPlan reason=" .. tostring(ammoPlan.reason) .. " actions=" .. tostring(table.getn(ammoPlan.actions or {})))
for i = 1, table.getn(ammoPlan.debugLines or {}) do for i = 1, table.getn(ammoPlan.debugLines or {}) do
AB_Trace("ammo#" .. tostring(i) .. " " .. tostring(ammoPlan.debugLines[i]))
end end
local totalRequestedStacks = 0 local totalRequestedStacks = 0
if table.getn(foodPlan.actions or {}) > 0 then if table.getn(foodPlan.actions or {}) > 0 then
local _, requestedStacks = self:ExecutePlan(foodPlan) local _, requestedStacks = self:ExecutePlan(foodPlan)
totalRequestedStacks = totalRequestedStacks + requestedStacks totalRequestedStacks = totalRequestedStacks + requestedStacks
AB_Trace("food plan executed requestedStacks=" .. tostring(requestedStacks))
if requestedStacks > 0 then if requestedStacks > 0 then
AB_AnnouncePurchases(foodPlan.actions) AB_AnnouncePurchases(foodPlan.actions)
end end
@@ -1308,19 +1287,15 @@ function Engine:OnMerchantEvent(eventName)
if table.getn(ammoPlan.actions or {}) > 0 then if table.getn(ammoPlan.actions or {}) > 0 then
local _, requestedStacks = self:ExecutePlan(ammoPlan) local _, requestedStacks = self:ExecutePlan(ammoPlan)
totalRequestedStacks = totalRequestedStacks + requestedStacks totalRequestedStacks = totalRequestedStacks + requestedStacks
AB_Trace("ammo plan executed requestedStacks=" .. tostring(requestedStacks))
if requestedStacks > 0 then if requestedStacks > 0 then
local moved = AB_MoveAmmoStacksToSpecialBags(300) local moved = AB_MoveAmmoStacksToSpecialBags(300)
AB_Trace("ammo restack moved=" .. tostring(moved))
AB_AnnouncePurchases(ammoPlan.actions) AB_AnnouncePurchases(ammoPlan.actions)
end end
end end
if totalRequestedStacks > 0 then if totalRequestedStacks > 0 then
self._merchantSessionExecuted = true self._merchantSessionExecuted = true
AB_Trace("session marked executed requestedStacks=" .. tostring(totalRequestedStacks))
else else
AB_Trace("no purchases requested in this event")
end end
end end
+1 -27
View File
@@ -6,7 +6,6 @@
local MTH_AutoBuy = { local MTH_AutoBuy = {
name = "autobuy", name = "autobuy",
enabled = false, enabled = false,
version = "1.3.0",
events = { events = {
"VARIABLES_LOADED", "VARIABLES_LOADED",
"MERCHANT_SHOW", "MERCHANT_SHOW",
@@ -24,9 +23,6 @@ end
local AB_Engine local AB_Engine
local function AB_Trace(message)
return
end
local function AB_EnsureBridgeFrame(moduleRef) local function AB_EnsureBridgeFrame(moduleRef)
if MTH_AutoBuy._bridgeFrame then if MTH_AutoBuy._bridgeFrame then
@@ -46,15 +42,12 @@ local function AB_EnsureBridgeFrame(moduleRef)
local eventName = event local eventName = event
local engine = AB_Engine() local engine = AB_Engine()
if not engine then if not engine then
AB_Trace("bridge event=" .. tostring(eventName) .. " engine missing")
return return
end end
if eventName == "MERCHANT_SHOW" or eventName == "MERCHANT_UPDATE" then if eventName == "MERCHANT_SHOW" or eventName == "MERCHANT_UPDATE" then
AB_Trace("bridge " .. tostring(eventName) .. " -> engine")
engine:OnMerchantEvent(eventName) engine:OnMerchantEvent(eventName)
elseif eventName == "MERCHANT_CLOSED" then elseif eventName == "MERCHANT_CLOSED" then
AB_Trace("bridge MERCHANT_CLOSED -> engine")
if engine.OnMerchantClosed then if engine.OnMerchantClosed then
engine:OnMerchantClosed() engine:OnMerchantClosed()
end end
@@ -77,10 +70,8 @@ local function AB_SetBridgeActive(active)
frame:RegisterEvent("MERCHANT_UPDATE") frame:RegisterEvent("MERCHANT_UPDATE")
frame:RegisterEvent("MERCHANT_CLOSED") frame:RegisterEvent("MERCHANT_CLOSED")
MTH_AutoBuy._bridgeActive = true MTH_AutoBuy._bridgeActive = true
AB_Trace("bridge active=true")
else else
MTH_AutoBuy._bridgeActive = false MTH_AutoBuy._bridgeActive = false
AB_Trace("bridge active=false (frame missing)")
end end
return return
end end
@@ -91,7 +82,6 @@ local function AB_SetBridgeActive(active)
frame:UnregisterEvent("MERCHANT_CLOSED") frame:UnregisterEvent("MERCHANT_CLOSED")
end end
MTH_AutoBuy._bridgeActive = false MTH_AutoBuy._bridgeActive = false
AB_Trace("bridge active=false")
end end
AB_Engine = function() AB_Engine = function()
@@ -102,15 +92,12 @@ function MTH_AutoBuy:init()
local engine = AB_Engine() local engine = AB_Engine()
if not engine then if not engine then
AB_Log("init failed: engine missing", "error") AB_Log("init failed: engine missing", "error")
AB_Trace("init failed: engine missing")
return return
end end
engine:Init() engine:Init()
self.initialized = true self.initialized = true
AB_SetBridgeActive(self.enabled and true or false) AB_SetBridgeActive(self.enabled and true or false)
AB_Trace("init done")
if self.enabled then if self.enabled then
AB_Log("module initialized", "debug")
end end
end end
@@ -120,20 +107,16 @@ function MTH_AutoBuy:setEnabled(enabled)
end end
local engine = AB_Engine() local engine = AB_Engine()
if not engine then if not engine then
AB_Trace("setEnabled ignored: engine missing")
return return
end end
local store = engine:EnsureDefaults() local store = engine:EnsureDefaults()
store.enabled = enabled and true or false store.enabled = enabled and true or false
AB_SetBridgeActive(store.enabled and true or false) AB_SetBridgeActive(store.enabled and true or false)
AB_Trace("setEnabled store.enabled=" .. tostring(store.enabled))
end end
function MTH_AutoBuy:onEvent(event) function MTH_AutoBuy:onEvent(event)
local engine = AB_Engine() local engine = AB_Engine()
if not engine then if not engine then
AB_Log("onEvent ignored: engine missing, event=" .. tostring(event), "debug")
AB_Trace("engine missing, event=" .. tostring(event))
return return
end end
if self._bridgeActive and (event == "MERCHANT_SHOW" or event == "MERCHANT_UPDATE" or event == "MERCHANT_CLOSED") then if self._bridgeActive and (event == "MERCHANT_SHOW" or event == "MERCHANT_UPDATE" or event == "MERCHANT_CLOSED") then
@@ -143,27 +126,19 @@ function MTH_AutoBuy:onEvent(event)
if event == "VARIABLES_LOADED" then if event == "VARIABLES_LOADED" then
engine:Init() engine:Init()
AB_SetBridgeActive(self.enabled and true or false) AB_SetBridgeActive(self.enabled and true or false)
local store = engine:EnsureDefaults() 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))
return return
end end
if not self.enabled then if not self.enabled then
AB_Log("onEvent ignored: module disabled", "debug")
if event == "MERCHANT_SHOW" or event == "MERCHANT_UPDATE" then if event == "MERCHANT_SHOW" or event == "MERCHANT_UPDATE" then
AB_Trace(tostring(event) .. " ignored: module disabled")
end end
return return
end end
if event == "MERCHANT_SHOW" or event == "MERCHANT_UPDATE" then 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) engine:OnMerchantEvent(event)
elseif event == "MERCHANT_CLOSED" then elseif event == "MERCHANT_CLOSED" then
AB_Trace("forwarding MERCHANT_CLOSED to engine")
if engine.OnMerchantClosed then if engine.OnMerchantClosed then
engine:OnMerchantClosed() engine:OnMerchantClosed()
end end
@@ -171,7 +146,6 @@ function MTH_AutoBuy:onEvent(event)
end end
function MTH_AutoBuy:cleanup() function MTH_AutoBuy:cleanup()
AB_Log("module cleanup", "debug")
end end
MTH:RegisterModule("autobuy", MTH_AutoBuy) MTH:RegisterModule("autobuy", MTH_AutoBuy)
+25 -81
View File
@@ -6,7 +6,6 @@
local MTH_AutoQuest = { local MTH_AutoQuest = {
name = "autoquest", name = "autoquest",
enabled = false, enabled = false,
version = "1.3.0",
events = { events = {
"GOSSIP_SHOW", "GOSSIP_SHOW",
"QUEST_GREETING", "QUEST_GREETING",
@@ -57,70 +56,33 @@ local function QA_GetOptionsStore()
if type(MTH_CharSavedVariables) ~= "table" then if type(MTH_CharSavedVariables) ~= "table" then
MTH_CharSavedVariables = {} MTH_CharSavedVariables = {}
end end
if type(MTH_CharSavedVariables.autoquest) ~= "table" then local store = nil
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 if MTH and MTH.GetModuleCharSavedVariables then
local moduleStore = MTH:GetModuleCharSavedVariables("autoquest") store = 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 end
if type(store) ~= "table" then
return store if type(MTH_CharSavedVariables.modules) ~= "table" then
MTH_CharSavedVariables.modules = {}
end
if type(MTH_CharSavedVariables.modules.autoquest) ~= "table" then
MTH_CharSavedVariables.modules.autoquest = {}
end
store = MTH_CharSavedVariables.modules.autoquest
end
if type(store.settings) ~= "table" then
store.settings = {}
end
local settings = store.settings
if settings.scorpokDrazial == nil then
settings.scorpokDrazial = false
end
if settings.arrowsForSissies == nil then
settings.arrowsForSissies = false
end
if settings.scorpokTooltip == nil then
settings.scorpokTooltip = false
end
return settings
end end
function MTH_AutoQuest:GetStore() function MTH_AutoQuest:GetStore()
@@ -130,36 +92,18 @@ end
function MTH_AutoQuest:SetScorpokDrazialEnabled(enabled) function MTH_AutoQuest:SetScorpokDrazialEnabled(enabled)
local store = QA_GetOptionsStore() local store = QA_GetOptionsStore()
store.scorpokDrazial = enabled and true or false 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)) QA_Debug("option write: scorpokDrazial=" .. tostring(store.scorpokDrazial and true or false))
end end
function MTH_AutoQuest:SetArrowsForSissiesEnabled(enabled) function MTH_AutoQuest:SetArrowsForSissiesEnabled(enabled)
local store = QA_GetOptionsStore() local store = QA_GetOptionsStore()
store.arrowsForSissies = enabled and true or false 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)) QA_Debug("option write: arrowsForSissies=" .. tostring(store.arrowsForSissies and true or false))
end end
function MTH_AutoQuest:SetScorpokTooltipEnabled(enabled) function MTH_AutoQuest:SetScorpokTooltipEnabled(enabled)
local store = QA_GetOptionsStore() local store = QA_GetOptionsStore()
store.scorpokTooltip = enabled and true or false 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)) QA_Debug("option write: scorpokTooltip=" .. tostring(store.scorpokTooltip and true or false))
end end
+15 -4
View File
@@ -28,6 +28,7 @@ function MTH_ChronometerHunterInstallData(engine)
engine:AddTimer(engine.SPELL, BS["Flare"], 30, 0, 0, 0, { cr = "YELLOW", rc = true }) engine:AddTimer(engine.SPELL, BS["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["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["Immolation Trap"], 60, 0, 0, 0, { gr = 1, cr = "ORANGE", ea = { [BS["Immolation Trap Effect"]] = 60 } })
engine:AddTimer(engine.SPELL, BS["Kill Command"], 12, 0, 0, 0, { cr = "RED", rc = true })
engine:AddTimer(engine.SPELL, BS["Rapid Fire"], 15, 0, 1, 1, { cr = "RED" }) engine:AddTimer(engine.SPELL, BS["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["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" }) engine:AddTimer(engine.SPELL, BS["Scatter Shot"], 4, 1, 0, 0, { cr = "MAROON" })
@@ -38,16 +39,26 @@ function MTH_ChronometerHunterInstallData(engine)
engine:AddTimer(engine.SPELL, BS["Wyvern Sting"], 12, 1, 0, 0, { gr = 2, cr = "MAROON", rc = true }) engine:AddTimer(engine.SPELL, BS["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.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"] }) -- Trap effects: no "a=1" here — primed by the SPELL timer's "ea" chain (cast hook sets timer.v
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"] }) -- and timer.t). Using "a=1" would trigger on any hunter's affliction message since
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"] }) -- CHAT_MSG_SPELL_PERIODIC_CREATURE_BUFFS never embeds a caster name.
engine:AddTimer(engine.EVENT, BS["Immolation Trap Effect"], 15, 1, 0, 1, { gr = 1, cr = "ORANGE", a = 1, xn = BS["Immolation Trap"] }) 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 Concussive Shot"], 3, 1, 0, 0, { a = 1, cr = "BLUE", xn = BS["Concussive Shot"] })
engine:AddTimer(engine.EVENT, BS["Improved Wing Clip"], 5, 1, 0, 0, { a = 1, cr = "MAROON", xn = BS["Wing Clip"] }) engine:AddTimer(engine.EVENT, BS["Improved Wing Clip"], 5, 1, 0, 0, { a = 1, cr = "MAROON", xn = BS["Wing Clip"] })
engine:AddTimer(engine.EVENT, BS["Lock and Load"], 10, 0, 1, 1, { a = 1, cr = "MAROON", tx = "Interface\\Icons\\ability_hunter_lockandload" }) -- 10s (tooltip confirmed; expires when consumed)
engine:AddTimer(engine.EVENT, BS["Scent of Blood"], 8, 0, 1, 1, { a = 1, cr = "RED" })
engine:AddTimer(engine.EVENT, BS["Piercing Shot"], 8, 1, 0, 1, { a = 1, cr = "PURPLE", tx = "Interface\\Icons\\Ability_Hunter_DisarmingShot" }) engine:AddTimer(engine.EVENT, BS["Piercing Shot"], 8, 1, 0, 1, { a = 1, cr = "PURPLE", tx = "Interface\\Icons\\Ability_Hunter_DisarmingShot" })
engine:AddTimer(engine.EVENT, BS["Entrapment"], 5, 1, 0, 1, { a = 1, cr = "MAROON", tx = "Interface\\Icons\\Spell_Nature_StrangleVines" }) engine:AddTimer(engine.EVENT, BS["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["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" }) 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["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["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" }) 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" })
+156 -49
View File
@@ -58,19 +58,20 @@ Chronometer.dataSetup = {}
local latins = { I = 1, II = 2, III = 3, IV = 4, V = 5, VI = 6, VII = 7, VIII = 8, IX = 9, X = 10, XI = 11, XII = 12, XIII = 13, XIV = 14 } local latins = { I = 1, II = 2, III = 3, IV = 4, V = 5, VI = 6, VII = 7, VIII = 8, IX = 9, X = 10, XI = 11, XII = 12, XIII = 13, XIV = 14 }
local BAR_GROUP = "MTHChronometer" local BAR_GROUP = "MTHChronometer"
local PARSER_OWNER = "MTHChronometerHunter"
local MTH_CHRON_TRACE_ENABLED = false
local function MTH_CHRON_Trace(msg) -- When growing up the group attachment point must flip so bars stack above the
if not MTH_CHRON_TRACE_ENABLED then -- anchor rather than below it. CandyBar uses the stored group point as-is when
return -- positioning each bar, so SetCandyBarGroupGrowth alone is not enough.
end local function applyGrowup(self, growup)
if MTH and MTH.Print then if growup then
MTH:Print("[CHRONTRACE] " .. tostring(msg), "debug") self:SetCandyBarGroupPoint(BAR_GROUP, "BOTTOM", self.anchor, "TOP", 0, 0)
elseif DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.AddMessage then else
DEFAULT_CHAT_FRAME:AddMessage("[CHRONTRACE] " .. tostring(msg)) self:SetCandyBarGroupPoint(BAR_GROUP, "TOP", self.anchor, "BOTTOM", 0, 0)
end end
self:SetCandyBarGroupGrowth(BAR_GROUP, growup)
end end
local PARSER_OWNER = "MTHChronometerHunter"
local function MTH_CHRON_FormatBarPosition(profile) local function MTH_CHRON_FormatBarPosition(profile)
local bp = profile and profile.barposition local bp = profile and profile.barposition
@@ -195,13 +196,11 @@ end
local function ensureProfile() local function ensureProfile()
if not MTH or not MTH.GetModuleCharSavedVariables then if not MTH or not MTH.GetModuleCharSavedVariables then
MTH_CHRON_Trace("ensureProfile fallback defaults (no MTH store)")
return shallowCopy(DEFAULTS) return shallowCopy(DEFAULTS)
end end
local store = MTH:GetModuleCharSavedVariables("chronometer") local store = MTH:GetModuleCharSavedVariables("chronometer")
if type(store) ~= "table" then if type(store) ~= "table" then
MTH_CHRON_Trace("ensureProfile fallback defaults (invalid store)")
return shallowCopy(DEFAULTS) return shallowCopy(DEFAULTS)
end end
if type(store.profile) ~= "table" if type(store.profile) ~= "table"
@@ -211,10 +210,8 @@ local function ensureProfile()
local legacyChar = MTH_CharSavedVariables.chronometer local legacyChar = MTH_CharSavedVariables.chronometer
if type(legacyChar.profile) == "table" then if type(legacyChar.profile) == "table" then
store.profile = deepCopyValue(legacyChar.profile) store.profile = deepCopyValue(legacyChar.profile)
MTH_CHRON_Trace("ensureProfile migrated legacy MTH_CharSavedVariables.chronometer.profile")
elseif type(legacyChar.barposition) == "table" then elseif type(legacyChar.barposition) == "table" then
store.profile = { barposition = deepCopyValue(legacyChar.barposition) } store.profile = { barposition = deepCopyValue(legacyChar.barposition) }
MTH_CHRON_Trace("ensureProfile migrated legacy MTH_CharSavedVariables.chronometer.barposition")
end end
end end
if type(store.profile) ~= "table" if type(store.profile) ~= "table"
@@ -224,17 +221,14 @@ local function ensureProfile()
local legacyAccount = MTH_SavedVariables.chronometer local legacyAccount = MTH_SavedVariables.chronometer
if type(legacyAccount.profile) == "table" then if type(legacyAccount.profile) == "table" then
store.profile = deepCopyValue(legacyAccount.profile) store.profile = deepCopyValue(legacyAccount.profile)
MTH_CHRON_Trace("ensureProfile migrated legacy MTH_SavedVariables.chronometer.profile")
elseif type(legacyAccount.barposition) == "table" then elseif type(legacyAccount.barposition) == "table" then
store.profile = { barposition = deepCopyValue(legacyAccount.barposition) } store.profile = { barposition = deepCopyValue(legacyAccount.barposition) }
MTH_CHRON_Trace("ensureProfile migrated legacy MTH_SavedVariables.chronometer.barposition")
end end
end end
if type(store.profile) ~= "table" and MTH.GetModuleSavedVariables then if type(store.profile) ~= "table" and MTH.GetModuleSavedVariables then
local accountStore = MTH:GetModuleSavedVariables("chronometer") local accountStore = MTH:GetModuleSavedVariables("chronometer")
if type(accountStore) == "table" and type(accountStore.profile) == "table" then if type(accountStore) == "table" and type(accountStore.profile) == "table" then
store.profile = deepCopyValue(accountStore.profile) store.profile = deepCopyValue(accountStore.profile)
MTH_CHRON_Trace("ensureProfile copied account profile into char store")
end end
end end
if type(store.profile) ~= "table" then if type(store.profile) ~= "table" then
@@ -242,17 +236,14 @@ local function ensureProfile()
end end
if type(store.profile.barposition) ~= "table" and type(store.barposition) == "table" then if type(store.profile.barposition) ~= "table" and type(store.barposition) == "table" then
store.profile.barposition = deepCopyValue(store.barposition) store.profile.barposition = deepCopyValue(store.barposition)
MTH_CHRON_Trace("ensureProfile migrated legacy char barposition -> profile.barposition")
end end
if type(store.profile.barposition) ~= "table" and MTH.GetModuleSavedVariables then if type(store.profile.barposition) ~= "table" and MTH.GetModuleSavedVariables then
local accountStore = MTH:GetModuleSavedVariables("chronometer") local accountStore = MTH:GetModuleSavedVariables("chronometer")
if type(accountStore) == "table" then if type(accountStore) == "table" then
if type(accountStore.barposition) == "table" then if type(accountStore.barposition) == "table" then
store.profile.barposition = deepCopyValue(accountStore.barposition) store.profile.barposition = deepCopyValue(accountStore.barposition)
MTH_CHRON_Trace("ensureProfile migrated legacy account barposition -> profile.barposition")
elseif type(accountStore.profile) == "table" and type(accountStore.profile.barposition) == "table" then elseif type(accountStore.profile) == "table" and type(accountStore.profile.barposition) == "table" then
store.profile.barposition = deepCopyValue(accountStore.profile.barposition) store.profile.barposition = deepCopyValue(accountStore.profile.barposition)
MTH_CHRON_Trace("ensureProfile copied account profile.barposition -> char profile.barposition")
end end
end end
end end
@@ -282,7 +273,6 @@ local function ensureProfile()
profile.disabledSpells.HUNTER = {} profile.disabledSpells.HUNTER = {}
end end
MTH_CHRON_Trace("ensureProfile result barposition=" .. MTH_CHRON_FormatBarPosition(profile))
return profile return profile
end end
@@ -309,8 +299,6 @@ local function MTH_CHRON_PersistAnchorPosition(self, reason)
self.profile.barposition.relativePoint = relPoint self.profile.barposition.relativePoint = relPoint
self.profile.barposition.x = x self.profile.barposition.x = x
self.profile.barposition.y = y self.profile.barposition.y = y
MTH_CHRON_Trace("persist-anchor reason=" .. tostring(reason or "")
.. " profile=" .. MTH_CHRON_FormatBarPosition(self.profile))
return true return true
end end
@@ -330,8 +318,6 @@ local function MTH_CHRON_RestoreAnchorFromProfile(self, reason)
local ppoint = tostring(bp.point or "") local ppoint = tostring(bp.point or "")
local prelpoint = tostring(bp.relativePoint or "") local prelpoint = tostring(bp.relativePoint or "")
if not (px and py and ppoint ~= "") then if not (px and py and ppoint ~= "") then
MTH_CHRON_Trace("restore-anchor skipped reason=" .. tostring(reason or "")
.. " profile=" .. MTH_CHRON_FormatBarPosition(self.profile))
return false return false
end end
if prelpoint == "" then if prelpoint == "" then
@@ -339,8 +325,6 @@ local function MTH_CHRON_RestoreAnchorFromProfile(self, reason)
end end
self.anchor:ClearAllPoints() self.anchor:ClearAllPoints()
self.anchor:SetPoint(ppoint, UIParent, prelpoint, px, py) self.anchor:SetPoint(ppoint, UIParent, prelpoint, px, py)
MTH_CHRON_Trace("restore-anchor applied reason=" .. tostring(reason or "")
.. " frame=" .. MTH_CHRON_DescribePoint(self.anchor))
return true return true
end end
@@ -527,7 +511,6 @@ function Chronometer:MTH_Initialize()
end end
self.profile = ensureProfile() self.profile = ensureProfile()
MTH_CHRON_Trace("MTH_Initialize profile=" .. MTH_CHRON_FormatBarPosition(self.profile))
self.parser = ParserLib:GetInstance("1.1") self.parser = ParserLib:GetInstance("1.1")
self.COLOR_MAP = { self.COLOR_MAP = {
@@ -538,7 +521,6 @@ function Chronometer:MTH_Initialize()
} }
self.anchor = self:CreateAnchor("MetaHunt Chronometer", 0, 1, 0) self.anchor = self:CreateAnchor("MetaHunt Chronometer", 0, 1, 0)
MTH_CHRON_Trace("MTH_Initialize anchor-created point=" .. MTH_CHRON_DescribePoint(self.anchor))
self:RegisterCandyBarGroup(BAR_GROUP) self:RegisterCandyBarGroup(BAR_GROUP)
self:SetCandyBarGroupPoint(BAR_GROUP, "TOP", self.anchor, "BOTTOM", 0, 0) self:SetCandyBarGroupPoint(BAR_GROUP, "TOP", self.anchor, "BOTTOM", 0, 0)
@@ -547,19 +529,16 @@ end
function Chronometer:MTH_Enable() function Chronometer:MTH_Enable()
self._mth_explicitEnable = true self._mth_explicitEnable = true
MTH_CHRON_Trace("MTH_Enable called")
self:MTH_Initialize() self:MTH_Initialize()
self:OnEnable() self:OnEnable()
self._mth_explicitEnable = nil self._mth_explicitEnable = nil
end end
function Chronometer:MTH_Disable() function Chronometer:MTH_Disable()
MTH_CHRON_Trace("MTH_Disable called")
self:OnDisable() self:OnDisable()
end end
function Chronometer:OnEnable() function Chronometer:OnEnable()
MTH_CHRON_Trace("OnEnable begin enabled=" .. tostring(self._mth_enabled and true or false))
if self._mth_enabled then if self._mth_enabled then
return return
end end
@@ -589,11 +568,9 @@ function Chronometer:OnEnable()
self._mth_enabled = true self._mth_enabled = true
self.profile = ensureProfile() self.profile = ensureProfile()
MTH_CHRON_Trace("OnEnable profile-after-ensure=" .. MTH_CHRON_FormatBarPosition(self.profile)) applyGrowup(self, self.profile.growup and true or false)
self:SetCandyBarGroupGrowth(BAR_GROUP, self.profile.growup and true or false)
self:SetCandyBarGroupVerticalSpacing(BAR_GROUP, self.profile.spacing or 0) self:SetCandyBarGroupVerticalSpacing(BAR_GROUP, self.profile.spacing or 0)
if self.anchor then if self.anchor then
MTH_CHRON_Trace("OnEnable anchor-current-point=" .. MTH_CHRON_DescribePoint(self.anchor))
end end
MTH_CHRON_RestoreAnchorFromProfile(self, "OnEnable") MTH_CHRON_RestoreAnchorFromProfile(self, "OnEnable")
@@ -615,14 +592,18 @@ function Chronometer:OnEnable()
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_AURA_GONE_SELF", function(event, info) self:SPELL_FADE(event, info) end) self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_AURA_GONE_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) 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_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_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_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_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 local enableRoM = false
for _, timer in pairs(self.timers[self.SPELL] or {}) do for _, timer in pairs(self.timers[self.SPELL] or {}) do
@@ -681,15 +662,34 @@ function Chronometer:OnEnable()
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_SELF_DAMAGE", function(event, info) self:SELF_DAMAGE(event, info) end) self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_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_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) self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_FAILED_LOCALPLAYER", function(event, info) self:SPELL_FAILED(event, info) end)
-- NamPower: register authoritative self-aura events for instant bar
-- kill and duration resync. The ParserLib chat paths remain active as
-- they handle creature/target debuffs that NamPower cannot see.
if MTH and MTH.nampower then
self._nampowerSpellIdCache = {}
self:RegisterEvent("BUFF_ADDED_SELF")
self:RegisterEvent("BUFF_REMOVED_SELF")
self:RegisterEvent("DEBUFF_ADDED_SELF")
self:RegisterEvent("DEBUFF_REMOVED_SELF")
end
end end
function Chronometer:VARIABLES_LOADED() function Chronometer:VARIABLES_LOADED()
self.profile = ensureProfile() self.profile = ensureProfile()
if self._mth_enabled then
applyGrowup(self, self.profile.growup and true or false)
self:SetCandyBarGroupVerticalSpacing(BAR_GROUP, self.profile.spacing or 0)
end
MTH_CHRON_RestoreAnchorFromProfile(self, "VARIABLES_LOADED") MTH_CHRON_RestoreAnchorFromProfile(self, "VARIABLES_LOADED")
end end
function Chronometer:PLAYER_ENTERING_WORLD() function Chronometer:PLAYER_ENTERING_WORLD()
self.profile = ensureProfile() self.profile = ensureProfile()
if self._mth_enabled then
applyGrowup(self, self.profile.growup and true or false)
self:SetCandyBarGroupVerticalSpacing(BAR_GROUP, self.profile.spacing or 0)
end
MTH_CHRON_RestoreAnchorFromProfile(self, "PLAYER_ENTERING_WORLD") MTH_CHRON_RestoreAnchorFromProfile(self, "PLAYER_ENTERING_WORLD")
end end
@@ -701,9 +701,7 @@ function Chronometer:OnDisable()
if not self._mth_enabled then if not self._mth_enabled then
return return
end end
MTH_CHRON_Trace("OnDisable begin profile=" .. MTH_CHRON_FormatBarPosition(self.profile))
if self.anchor then if self.anchor then
MTH_CHRON_Trace("OnDisable anchor-point=" .. MTH_CHRON_DescribePoint(self.anchor))
end end
MTH_CHRON_PersistAnchorPosition(self, "OnDisable") MTH_CHRON_PersistAnchorPosition(self, "OnDisable")
self._mth_enabled = false self._mth_enabled = false
@@ -1209,7 +1207,6 @@ function Chronometer:CreateAnchor(text, r, g, b)
local py = tonumber(self.profile and self.profile.barposition and self.profile.barposition.y) local 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 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 "") local prelpoint = tostring(self.profile and self.profile.barposition and self.profile.barposition.relativePoint or "")
MTH_CHRON_Trace("CreateAnchor saved-in profile=" .. MTH_CHRON_FormatBarPosition(self.profile))
if px ~= nil and py ~= nil then if px ~= nil and py ~= nil then
if ppoint == "" then if ppoint == "" then
ppoint = "TOPLEFT" ppoint = "TOPLEFT"
@@ -1219,17 +1216,14 @@ function Chronometer:CreateAnchor(text, r, g, b)
end end
frame:ClearAllPoints() frame:ClearAllPoints()
frame:SetPoint(ppoint, UIParent, prelpoint, px, py) frame:SetPoint(ppoint, UIParent, prelpoint, px, py)
MTH_CHRON_Trace("CreateAnchor restored point=" .. MTH_CHRON_DescribePoint(frame))
else else
frame:SetPoint("CENTER", UIParent, "CENTER", 0, 50) frame:SetPoint("CENTER", UIParent, "CENTER", 0, 50)
MTH_CHRON_Trace("CreateAnchor default point=" .. MTH_CHRON_DescribePoint(frame))
end end
frame:SetScript("OnDragStart", function() frame:SetScript("OnDragStart", function()
if not this or not this.StartMoving then if not this or not this.StartMoving then
return return
end end
MTH_CHRON_Trace("anchor drag-start point=" .. MTH_CHRON_DescribePoint(this))
this:StartMoving() this:StartMoving()
end) end)
frame:SetScript("OnDragStop", function() frame:SetScript("OnDragStop", function()
@@ -1258,8 +1252,6 @@ function Chronometer:CreateAnchor(text, r, g, b)
this.owner.profile.barposition.relativePoint = point or "TOPLEFT" this.owner.profile.barposition.relativePoint = point or "TOPLEFT"
this.owner.profile.barposition.x = x this.owner.profile.barposition.x = x
this.owner.profile.barposition.y = y this.owner.profile.barposition.y = y
MTH_CHRON_Trace("anchor drag-stop saved profile=" .. MTH_CHRON_FormatBarPosition(this.owner.profile)
.. " frame=" .. MTH_CHRON_DescribePoint(this))
end end
end) end)
@@ -1288,6 +1280,85 @@ function Chronometer:CreateAnchor(text, r, g, b)
return frame return frame
end end
-- ────────────────────────────────────────────────────────────
-- NamPower self-aura handlers
-- ────────────────────────────────────────────────────────────
-- Helper: resolve spellId → spell name, with cache
local function NP_ResolveName(cache, spellId)
if not spellId or spellId == 0 then return nil end
local name = cache[spellId]
if name then return name end
name = GetSpellRecField(spellId, "name")
if name and name ~= "" then
cache[spellId] = name
return name
end
return nil
end
-- BUFF / DEBUFF removed from the player → kill matching self-bar instantly
function Chronometer:BUFF_REMOVED_SELF()
if not self.profile.fadeonfade then return end
local spellId = arg3
local name = NP_ResolveName(self._nampowerSpellIdCache, spellId)
if not name then return end
self:KillBar(name)
end
Chronometer.DEBUFF_REMOVED_SELF = Chronometer.BUFF_REMOVED_SELF
-- BUFF / DEBUFF added to the player → resync running bar duration
function Chronometer:BUFF_ADDED_SELF()
local spellId = arg3
local name = NP_ResolveName(self._nampowerSpellIdCache, spellId)
if not name then return end
-- Find the matching self-bar
local bar = nil
for i = 1, 20 do
if self.bars[i].id and self.bars[i].name == name then
local tgt = self.bars[i].target
if tgt == "none" or tgt == UnitName("player") then
bar = self.bars[i]
break
end
end
end
if not bar then return end
-- Get authoritative remaining duration from NamPower aura array
local remaining = nil
local auras = GetUnitField("player", "aura")
if auras then
for slot = 1, 48 do
if auras[slot] == spellId then
local _, ms = GetPlayerAuraDuration(slot - 1)
if ms and ms > 0 then
remaining = ms / 1000
end
break
end
end
end
if not remaining then
-- Fallback: use base DBC duration
local dms = GetSpellDuration(spellId)
if dms and dms > 0 then
remaining = dms / 1000
end
end
if not remaining then return end
-- Resync: update total first (allows SetTimeLeft to extend), then set remaining
self:SetCandyBarTime(bar.id, remaining)
self:SetCandyBarTimeLeft(bar.id, remaining)
end
Chronometer.DEBUFF_ADDED_SELF = Chronometer.BUFF_ADDED_SELF
-- ────────────────────────────────────────────────────────────
function Chronometer:COMBAT_DEATH(event, info) function Chronometer:COMBAT_DEATH(event, info)
if not self.profile.fadeonkill then return end if not self.profile.fadeonkill then return end
if info.type == "experience" then if info.type == "experience" then
@@ -1305,9 +1376,11 @@ function Chronometer:SPELL_FADE(event, info)
if not self.profile.fadeonfade then return end if not self.profile.fadeonfade then return end
if info.skill == BS["Banish"] then return end if info.skill == BS["Banish"] then return end
if info.type == "fade" then 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 if info.victim == ParserLib_SELF then
return self:KillBar(info.skill) return self:KillBar(info.skill)
end end
if info.source and info.source ~= ParserLib_SELF then return end
return self:KillBar(info.skill, info.victim) return self:KillBar(info.skill, info.victim)
end end
end end
@@ -1315,8 +1388,20 @@ end
function Chronometer:SPELL_PERIODIC(event, info) function Chronometer:SPELL_PERIODIC(event, info)
local aura, rank, unit, isgain 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 if info.type == "buff" then
isgain = 1 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 elseif info.type == "debuff" then
isgain = nil isgain = nil
elseif not info.isDOT then elseif not info.isDOT then
@@ -1344,7 +1429,27 @@ function Chronometer:SPELL_PERIODIC(event, info)
if timer.k.t then if timer.k.t then
if not unit then unit = UnitName("player") end if not unit then unit = UnitName("player") end
if timer.k.s then 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 else
if not UnitExists("target") or unit ~= UnitName("target") then return end if not UnitExists("target") or unit ~= UnitName("target") then return end
end end
@@ -1404,7 +1509,9 @@ function Chronometer:UNIT_AURA(unit)
end end
local hasFeedAura = false local hasFeedAura = false
if type(UnitBuff) == "function" then if type(MTH_FEED_HasPetFeedBuff) == "function" then
hasFeedAura = MTH_FEED_HasPetFeedBuff()
elseif type(UnitBuff) == "function" then
for i = 1, 32 do for i = 1, 32 do
local texture = UnitBuff("pet", i) local texture = UnitBuff("pet", i)
if not texture then if not texture then
-19
View File
@@ -1,28 +1,14 @@
local MTH_ChronometerModule = { local MTH_ChronometerModule = {
name = "chronometer", name = "chronometer",
enabled = true, enabled = true,
version = "1.3.0",
events = {}, events = {},
initialized = false, initialized = false,
} }
local MTH_CHRON_MODULE_TRACE_ENABLED = false
local function MTH_CHRON_ModuleTrace(msg)
if not MTH_CHRON_MODULE_TRACE_ENABLED then
return
end
if MTH and MTH.Print then
MTH:Print("[CHRONTRACE] " .. tostring(msg), "debug")
elseif DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.AddMessage then
DEFAULT_CHAT_FRAME:AddMessage("[CHRONTRACE] " .. tostring(msg))
end
end
local function MTH_CHRON_ApplyRuntimeEnabledState(enabled) local function MTH_CHRON_ApplyRuntimeEnabledState(enabled)
MTH_CHRON_ModuleTrace("module apply runtime enabled=" .. tostring(enabled and true or false))
if not MTH_ChronometerHunter then if not MTH_ChronometerHunter then
MTH_CHRON_ModuleTrace("module apply runtime skipped (engine unavailable)")
return return
end end
if enabled then if enabled then
@@ -37,18 +23,14 @@ local function MTH_CHRON_ApplyRuntimeEnabledState(enabled)
end end
function MTH_ChronometerModule:init() function MTH_ChronometerModule:init()
MTH_CHRON_ModuleTrace("module init begin enabled=" .. tostring(self.enabled and true or false))
if MTH_ChronometerHunter and MTH_ChronometerHunter.MTH_Initialize then if MTH_ChronometerHunter and MTH_ChronometerHunter.MTH_Initialize then
MTH_ChronometerHunter:MTH_Initialize() MTH_ChronometerHunter:MTH_Initialize()
end end
MTH_CHRON_ApplyRuntimeEnabledState(self.enabled and true or false) MTH_CHRON_ApplyRuntimeEnabledState(self.enabled and true or false)
self.initialized = true self.initialized = true
MTH_CHRON_ModuleTrace("module init end initialized=true")
end end
function MTH_ChronometerModule:setEnabled(enabled) function MTH_ChronometerModule:setEnabled(enabled)
MTH_CHRON_ModuleTrace("module setEnabled enabled=" .. tostring(enabled and true or false)
.. " initialized=" .. tostring(self.initialized and true or false))
if not self.initialized then if not self.initialized then
self:init() self:init()
end end
@@ -56,7 +38,6 @@ function MTH_ChronometerModule:setEnabled(enabled)
end end
function MTH_ChronometerModule:cleanup() function MTH_ChronometerModule:cleanup()
MTH_CHRON_ModuleTrace("module cleanup")
if MTH_ChronometerHunter and MTH_ChronometerHunter.MTH_Disable then if MTH_ChronometerHunter and MTH_ChronometerHunter.MTH_Disable then
MTH_ChronometerHunter:MTH_Disable() MTH_ChronometerHunter:MTH_Disable()
end end

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