mirror of
https://codeberg.org/Duvelcorp/MetaHunt.git
synced 2026-09-22 07:36:58 +00:00
Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 05cfce240b | |||
| d49ed5abcc | |||
| ea01c9ef15 | |||
| 315f6bb4f1 | |||
| 99e7eaa5d2 | |||
| 44288cd635 | |||
| 7d974f24b5 | |||
| 3de8088bbb | |||
| 7a0f03ba6f | |||
| 0aaa49f33b | |||
| 4770724f60 | |||
| acac6ace6f | |||
| 772299baf3 | |||
| eed6624611 | |||
| 71c84e1704 | |||
| dec41628ad | |||
| d1d2660c2c | |||
| 3bdc66770b | |||
| 67ed20532b | |||
| f3af63926e | |||
| 22232364b1 | |||
| af31acdb3e | |||
| 402cd8aef0 | |||
| aa99ee01cc | |||
| c862cdb25e | |||
| 0fb52f67ff | |||
| aeaddd9558 | |||
| ccf9d8b0b6 | |||
| adb21c336c | |||
| 64e449481f | |||
| 2863adff1c | |||
| 0609d487a7 | |||
| e7cf99facb | |||
| c5a42d8d2a | |||
| 3233ebff7f | |||
| 66ff1ae711 | |||
| d2fb04f7c5 | |||
| 566f3f4925 | |||
| 060fc555fe | |||
| de6621f41f | |||
| da645fa53c | |||
| 9379668e4b | |||
| c55cd66ffc | |||
| 38e080261c | |||
| 04e04a4853 | |||
| 6c007f2c17 | |||
| c4911ef9fe | |||
| 99268643eb | |||
| 51d4c785fa | |||
| c81ce85950 | |||
| 680ddcba8a | |||
| 5c08656532 | |||
| 8d80d0663c | |||
| 43e23e7616 | |||
| f7b0318179 |
@@ -13,3 +13,8 @@ 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-*
|
||||
|
||||
+189
-3
@@ -2,17 +2,203 @@
|
||||
|
||||
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 10–50+ 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. Additionaly, the beast inspector has been reformated and all meaningful beast's data is displayed nicely.
|
||||
- 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 adynamic 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 **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.
|
||||
|
||||
@@ -46,7 +232,7 @@ All notable changes to MetaHunt will be documented in this file.
|
||||
### 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 wasnt tall enough to display all famillies (wolves were hidden).
|
||||
- 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
-1
@@ -4,7 +4,7 @@
|
||||
-- Modern modular architecture with legacy addon support
|
||||
--
|
||||
-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
-- TARGET RUNTIME: LUA 5.0 (WoW 1.12 / Turtle-WoW)
|
||||
-- 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)
|
||||
|
||||
+5
-8
@@ -1,22 +1,18 @@
|
||||
## Interface: 11200
|
||||
## Version: 1.4.0
|
||||
## Version: 2.0.0
|
||||
## Title: MetaHunt - |cff00ff00Hunter
|
||||
## Author: Metasploit and his Copilot ;)
|
||||
## Notes: Unified addon suite for huntards making old addons compatible with TurtleWoW, and adding an arsenal of never seen Hunter's utilities.
|
||||
## SavedVariables: MTH_SavedVariables, FOM_Config, FOM_FoodQuality, FOM_AddedFoods, FOM_RemovedFoods, FOM_Cooking, FOM_QuestFood, FOM_LocaleInfo
|
||||
## SavedVariablesPerCharacter: ZHunterMod_Saved, MTH_CharSavedVariables
|
||||
## 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
|
||||
## SavedVariablesPerCharacter: MTH_CharSavedVariables
|
||||
|
||||
|
||||
data\init.lua
|
||||
Bindings.xml
|
||||
|
||||
## Load initialization and core framework
|
||||
init\env.xml
|
||||
init\compat.xml
|
||||
init\libs.xml
|
||||
init\api.xml
|
||||
api\core-feed-tracking.lua
|
||||
init\skins.xml
|
||||
|
||||
## Load database files (DB files must come before data\init.lua)
|
||||
data\ds-beasts.lua
|
||||
@@ -40,6 +36,7 @@ data\ds-pet-spells.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\init.lua
|
||||
|
||||
|
||||
@@ -1,20 +1,44 @@
|
||||
# MetaHunt
|
||||
|
||||
MetaHunt is a Turtle WoW Hunter toolkit.
|
||||
It is a modern modular addon that combines never-seen features, and improves/fixes a few old hunter add-ons.
|
||||
It provides a bunch useful tools for all huntards, wheter they are still levelling, lone-wolf, or HL raider.
|
||||
MetaHunt is a Twow Hunter toolkit.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
### Installation (READ THIS!) ⚠️⚠️⚠️
|
||||
|
||||
After you download the zip, you have a manual action to do because of Gitea behaviour.
|
||||
|
||||
When you unzip, you will find a "metahunt" root folder, you SHOULD rename it as exactly "MetaHunt", and then move that folder in your \Interface\AddOns\ directory. If you dont restore the capital letters in the folder name, the addon will be broken.
|
||||
|
||||
This is [a known issue from Gitea/Forgejo](https://codeberg.org/forgejo/forgejo/issues/800) when the system creates zip files, and there's nothing add-on devs can do to overcome it.
|
||||
|
||||
|
||||
## 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:
|
||||
|
||||
- 100% accurate beasts' data, their locations, and the abilities they can learn you
|
||||
- All Pet families, their abilities and their diet
|
||||
- All pet abilities ranks
|
||||
- All Ammo (Arrows and Bullets)
|
||||
- All Stable, Hunter and Pet Masters
|
||||
- All hunter and pet spells
|
||||
- All Ammo and ranged weapons
|
||||
- All Hunter/Pet/Stable Masters
|
||||
- All Ammo vendors
|
||||
|
||||
Online bestiary is also available here https://duvelcorp.codeberg.page/MetaHunt-Web/
|
||||
|
||||
|
||||
## 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 !
|
||||
@@ -22,7 +46,8 @@ MetaHunt ships with large up-to-date datastores for all Turtle WOW data a hunter
|
||||
- Auto equip the right ammo when you swap from a box/xbow to a gun and the other way around.
|
||||
|
||||
## MM Widget
|
||||
Lets Make MM great again. Tracks the new 3-state of Experimental ammo cycle (Fire → Nature → Arcane), the Lock&Load procs, and has a dynamic cell that shows either Aimed Shot availability or the right shot to use following the current Experimental ammo proc if any.
|
||||
|
||||
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
|
||||
|
||||
@@ -31,46 +56,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.
|
||||
- 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
|
||||
|
||||
- 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
|
||||
- zAspect: All your trackings in one collapsible bar
|
||||
- zRanged : All the ranged weampons currently in your bags to swap them fast
|
||||
- But also zMounts, zToys and zCompanions.
|
||||
- zAspect: All your aspects in one collapsible bar
|
||||
- zRanged : All the ranged weapons currently in your bags to swap them fast
|
||||
- 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
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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 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
|
||||
- Track all info about the pet abilities that you know and don't know yet
|
||||
- Track all info about your stabled pets
|
||||
- Keep an history of all your pets after you abandon them or if they run away.
|
||||
- Search Hunter-related NPCs (Beasts, vendors, masters) and open the map to locate them like in pfQuest.
|
||||
- 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.
|
||||
- Instantly find where the beasts having a specific ability are lying and plan ahead your tracking sessions.
|
||||
- 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.
|
||||
|
||||
|
||||
### The world is yours. Happy Hunting Fellas !
|
||||
|
||||
|
||||
+62
-7
@@ -10,10 +10,23 @@ local function AntiDaze_GetSaved()
|
||||
if type(MTH_CharSavedVariables) ~= "table" then
|
||||
MTH_CharSavedVariables = {}
|
||||
end
|
||||
if type(MTH_CharSavedVariables.antiDaze) ~= "table" then
|
||||
MTH_CharSavedVariables.antiDaze = {}
|
||||
local store
|
||||
if MTH and MTH.GetModuleCharSavedVariables then
|
||||
store = MTH:GetModuleCharSavedVariables("antidaze")
|
||||
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
|
||||
|
||||
local function AntiDaze_IsEnabled()
|
||||
@@ -25,16 +38,21 @@ local function AntiDaze_RegisterCombatEvents()
|
||||
if not AntiDazeFrame then
|
||||
return
|
||||
end
|
||||
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE")
|
||||
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE")
|
||||
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
|
||||
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PARTY_DAMAGE")
|
||||
if MTH and MTH.nampower then
|
||||
AntiDazeFrame:RegisterEvent("DEBUFF_ADDED_SELF")
|
||||
else
|
||||
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE")
|
||||
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE")
|
||||
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
|
||||
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PARTY_DAMAGE")
|
||||
end
|
||||
end
|
||||
|
||||
local function AntiDaze_UnregisterCombatEvents()
|
||||
if not AntiDazeFrame then
|
||||
return
|
||||
end
|
||||
AntiDazeFrame:UnregisterEvent("DEBUFF_ADDED_SELF")
|
||||
AntiDazeFrame:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE")
|
||||
AntiDazeFrame:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE")
|
||||
AntiDazeFrame:UnregisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
|
||||
@@ -87,10 +105,31 @@ local function AntiDaze_ResolvePlayerFromMessage(msg)
|
||||
return nil
|
||||
end
|
||||
|
||||
local AntiDaze_DazeSpellIdCache = {} -- [spellId] = true/false
|
||||
|
||||
local function AntiDaze_IsDazeSpellId(spellId)
|
||||
local cached = AntiDaze_DazeSpellIdCache[spellId]
|
||||
if cached ~= nil then return cached end
|
||||
if type(GetSpellRecField) ~= "function" then return false end
|
||||
local name = GetSpellRecField(spellId, "name")
|
||||
if name and (name == "Daze" or name == "Dazed") then
|
||||
AntiDaze_DazeSpellIdCache[spellId] = true
|
||||
return true
|
||||
end
|
||||
AntiDaze_DazeSpellIdCache[spellId] = false
|
||||
return false
|
||||
end
|
||||
|
||||
local function AntiDaze_CancelRelevantAspect(isSelfDazed)
|
||||
local packName = _G["ZHUNTER_ASPECT_PACK"] or "Aspect of the Pack"
|
||||
local cheetahName = _G["ZHUNTER_ASPECT_CHEETAH"] or "Aspect of the Cheetah"
|
||||
|
||||
-- Always use GetPlayerBuff + tooltip scan for cancellation.
|
||||
-- The aura-array slot index from GetUnitField does NOT map 1:1
|
||||
-- to the sequential buff index that CancelPlayerBuff expects
|
||||
-- (empty slots create gaps), so we cannot use the NamPower
|
||||
-- array for the cancel call. NamPower is still used for daze
|
||||
-- DETECTION via DEBUFF_ADDED_SELF in the event handler.
|
||||
for i = 0, 32 do
|
||||
if GetPlayerBuff(i, "HELPFUL") < 0 then
|
||||
break
|
||||
@@ -118,6 +157,12 @@ if AntiDazeFrame then
|
||||
AntiDazeFrame:RegisterEvent("VARIABLES_LOADED")
|
||||
AntiDazeFrame:SetScript("OnEvent", function()
|
||||
if event == "VARIABLES_LOADED" then
|
||||
-- Class gate: only hunters use AntiDaze.
|
||||
if MTH and MTH.IsClassGateBlocked and MTH:IsClassGateBlocked() then
|
||||
AntiDazeFrame:UnregisterAllEvents()
|
||||
AntiDazeFrame:SetScript("OnEvent", nil)
|
||||
return
|
||||
end
|
||||
if AntiDaze_IsEnabled() then
|
||||
AntiDaze_RegisterCombatEvents()
|
||||
else
|
||||
@@ -126,6 +171,16 @@ if AntiDazeFrame then
|
||||
return
|
||||
end
|
||||
|
||||
-- NamPower path: DEBUFF_ADDED_SELF fires with spellId in arg3
|
||||
if event == "DEBUFF_ADDED_SELF" then
|
||||
local spellId = tonumber(arg3)
|
||||
if spellId and AntiDaze_IsDazeSpellId(spellId) then
|
||||
AntiDaze_CancelRelevantAspect(true)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- Legacy path: parse chat messages
|
||||
local player = AntiDaze_ResolvePlayerFromMessage(arg1)
|
||||
if player then
|
||||
local you = _G["ZHUNTER_YOU"] or "You"
|
||||
|
||||
+37
-9
@@ -33,10 +33,20 @@ function AutoStrip_GetSaved()
|
||||
if type(MTH_CharSavedVariables) ~= "table" then
|
||||
MTH_CharSavedVariables = {}
|
||||
end
|
||||
if type(MTH_CharSavedVariables.autoStrip) ~= "table" then
|
||||
MTH_CharSavedVariables.autoStrip = {}
|
||||
local store
|
||||
if MTH and MTH.GetModuleCharSavedVariables then
|
||||
store = MTH:GetModuleCharSavedVariables("autostrip")
|
||||
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
|
||||
|
||||
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
|
||||
saved["autostrip"] = 1
|
||||
AutoStrip_On = 1
|
||||
if AutoStrip_Frame then
|
||||
AutoStrip_Frame:RegisterEvent("PLAYER_REGEN_DISABLED")
|
||||
AutoStrip_Frame:RegisterEvent("PLAYER_REGEN_ENABLED")
|
||||
end
|
||||
if AutoStripDisplay and AutoStripDisplay:IsVisible() and AutoStripDisplayAutoCast then
|
||||
AutoStripDisplayAutoCast:Show()
|
||||
end
|
||||
@@ -159,6 +173,10 @@ function AutoStrip_SetAutoStripToggle(enabled, silent)
|
||||
else
|
||||
saved["autostrip"] = nil
|
||||
AutoStrip_On = nil
|
||||
if AutoStrip_Frame then
|
||||
AutoStrip_Frame:UnregisterEvent("PLAYER_REGEN_DISABLED")
|
||||
AutoStrip_Frame:UnregisterEvent("PLAYER_REGEN_ENABLED")
|
||||
end
|
||||
if AutoStripDisplayAutoCast then
|
||||
AutoStripDisplayAutoCast:Hide()
|
||||
end
|
||||
@@ -252,11 +270,19 @@ function AutoStrip_UnequipAll(weaponsOnly)
|
||||
end
|
||||
|
||||
local function AutoStrip_OnEvent()
|
||||
AutoStrip_Trace("OnEvent evt=" .. tostring(event or "")
|
||||
.. " runtime=" .. tostring(AutoStrip_On and 1 or 0)
|
||||
.. " wasInCombat=" .. tostring(AutoStrip_WasInCombat and 1 or 0)
|
||||
.. " playerInCombat=" .. tostring((type(UnitAffectingCombat) == "function" and UnitAffectingCombat("player")) and 1 or 0))
|
||||
if AutoStrip_TraceEnabled then
|
||||
AutoStrip_Trace("OnEvent evt=" .. tostring(event or "")
|
||||
.. " runtime=" .. tostring(AutoStrip_On and 1 or 0)
|
||||
.. " wasInCombat=" .. tostring(AutoStrip_WasInCombat and 1 or 0)
|
||||
.. " playerInCombat=" .. tostring((type(UnitAffectingCombat) == "function" and UnitAffectingCombat("player")) and 1 or 0))
|
||||
end
|
||||
if event == "VARIABLES_LOADED" then
|
||||
-- Class gate: only hunters use AutoStrip.
|
||||
if MTH and MTH.IsClassGateBlocked and MTH:IsClassGateBlocked() then
|
||||
AutoStrip_Frame:UnregisterAllEvents()
|
||||
AutoStrip_Frame:SetScript("OnEvent", nil)
|
||||
return
|
||||
end
|
||||
local saved = AutoStrip_GetSaved()
|
||||
AutoStrip_Trace("VARIABLES_LOADED saved.autostrip=" .. tostring(saved["autostrip"] and 1 or 0)
|
||||
.. " saved.display=" .. tostring(saved["display"] and 1 or 0))
|
||||
@@ -318,9 +344,11 @@ end
|
||||
|
||||
AutoStrip_Frame = CreateFrame("Frame", "MTH_AutoStripEvent")
|
||||
if AutoStrip_Frame then
|
||||
-- PLAYER_REGEN_DISABLED / PLAYER_REGEN_ENABLED are intentionally NOT registered here.
|
||||
-- They are registered only when the feature is enabled via AutoStrip_SetAutoStripToggle(true),
|
||||
-- and unregistered when it is disabled. This prevents any combat event overhead when the
|
||||
-- feature is off.
|
||||
AutoStrip_Frame:RegisterEvent("VARIABLES_LOADED")
|
||||
AutoStrip_Frame:RegisterEvent("PLAYER_REGEN_DISABLED")
|
||||
AutoStrip_Frame:RegisterEvent("PLAYER_REGEN_ENABLED")
|
||||
AutoStrip_Frame:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
AutoStrip_Frame:RegisterEvent("PLAYER_LOGOUT")
|
||||
AutoStrip_Frame:SetScript("OnEvent", AutoStrip_OnEvent)
|
||||
|
||||
+1347
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -1,543 +0,0 @@
|
||||
------------------------------------------------------
|
||||
-- MetaHunt: Beast Lore Scanner
|
||||
-- TWoW 1.18.1 — "Nightmares of Ursol"
|
||||
------------------------------------------------------
|
||||
-- Fires when the player channels Beast Lore on a target.
|
||||
-- Captures all available data and stores it in
|
||||
-- MTH_SavedVariables.beastLoreScan.entries (account-wide).
|
||||
--
|
||||
-- Dedup rule: skips recording if the same creature name
|
||||
-- already exists in this zone within 30 yards of current
|
||||
-- player position (uses ds-minimap-sizes for yard scale).
|
||||
-- A chat message is always printed either way.
|
||||
------------------------------------------------------
|
||||
|
||||
local SPELL_BEAST_LORE = "Beast Lore"
|
||||
local DEDUP_YARDS = 30
|
||||
local FALLBACK_DEDUP_PCT = 0.015 -- ~1.5% of map width if zone size unknown
|
||||
|
||||
-- ── Tooltip probe ────────────────────────────────────────────
|
||||
|
||||
local MTH_BLS_Probe = nil
|
||||
local function MTH_BLS_EnsureProbe()
|
||||
if not MTH_BLS_Probe then
|
||||
MTH_BLS_Probe = CreateFrame("GameTooltip", "MTH_BLSProbe", UIParent, "GameTooltipTemplate")
|
||||
MTH_BLS_Probe:SetOwner(UIParent, "ANCHOR_NONE")
|
||||
end
|
||||
return MTH_BLS_Probe
|
||||
end
|
||||
|
||||
-- Read left/right text lines from the probe (Lua 5.0 compatible: getglobal)
|
||||
local function MTH_BLS_GetProbeLines()
|
||||
local lines = {}
|
||||
for i = 1, 24 do
|
||||
local L = getglobal("MTH_BLSProbeTextLeft" .. i)
|
||||
local R = getglobal("MTH_BLSProbeTextRight" .. i)
|
||||
local lText = L and L:GetText() or nil
|
||||
local rText = R and R:GetText() or nil
|
||||
if (not lText or lText == "") and (not rText or rText == "") then
|
||||
break
|
||||
end
|
||||
table.insert(lines, { left = lText or "", right = rText or "" })
|
||||
end
|
||||
return lines
|
||||
end
|
||||
|
||||
-- Parse Beast Lore tooltip lines into structured fields.
|
||||
-- Uses string.find with captures (Lua 5.0, no string.match).
|
||||
local function MTH_BLS_ParseTooltip(lines)
|
||||
local data = {
|
||||
tameable = nil, -- true / false / nil (unknown)
|
||||
diet = nil,
|
||||
abilities = nil,
|
||||
armor = nil,
|
||||
health = nil,
|
||||
minDmg = nil,
|
||||
maxDmg = nil,
|
||||
}
|
||||
|
||||
for _, line in ipairs(lines) do
|
||||
local L = line.left or ""
|
||||
local R = line.right or ""
|
||||
|
||||
-- Tameability ──────────────────────────────────────────
|
||||
if string.find(L, "Cannot Be Tamed", 1, true) or string.find(R, "Cannot Be Tamed", 1, true) then
|
||||
data.tameable = false
|
||||
elseif string.find(L, "Can Be Tamed", 1, true) or string.find(R, "Can Be Tamed", 1, true)
|
||||
or string.find(L, "Tameable", 1, true) or string.find(R, "Tameable", 1, true) then
|
||||
data.tameable = true
|
||||
end
|
||||
|
||||
-- Diet ─────────────────────────────────────────────────
|
||||
if not data.diet then
|
||||
local _, _, v = string.find(L, "^Diet:%s*(.*)")
|
||||
if not v then _, _, v = string.find(R, "^Diet:%s*(.*)") end
|
||||
if v and v ~= "" then data.diet = v end
|
||||
end
|
||||
|
||||
-- Tamed Abilities ──────────────────────────────────────
|
||||
if not data.abilities then
|
||||
local _, _, v = string.find(L, "^Tamed Abilities:%s+(.*)")
|
||||
if not v then _, _, v = string.find(R, "^Tamed Abilities:%s+(.*)") end
|
||||
if v and v ~= "" then data.abilities = v end
|
||||
end
|
||||
|
||||
-- Damage range ("107 - 133 Damage") ────────────────────
|
||||
if not data.minDmg then
|
||||
local _, _, lo, hi = string.find(L, "(%d+) %- (%d+) Damage")
|
||||
if not lo then _, _, lo, hi = string.find(R, "(%d+) %- (%d+) Damage") end
|
||||
if lo and hi then
|
||||
data.minDmg = tonumber(lo)
|
||||
data.maxDmg = tonumber(hi)
|
||||
end
|
||||
end
|
||||
|
||||
-- Armor ────────────────────────────────────────────────
|
||||
if not data.armor then
|
||||
local _, _, v = string.find(L, "Armor:%s*(%d+)")
|
||||
if not v then _, _, v = string.find(R, "Armor:%s*(%d+)") end
|
||||
if v then data.armor = tonumber(v) end
|
||||
end
|
||||
|
||||
-- Health ───────────────────────────────────────────────
|
||||
if not data.health then
|
||||
local _, _, v = string.find(L, "Health:%s*(%d+)")
|
||||
if not v then _, _, v = string.find(R, "Health:%s*(%d+)") end
|
||||
if v then data.health = tonumber(v) end
|
||||
end
|
||||
end
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
-- ── Location helpers ─────────────────────────────────────────
|
||||
|
||||
-- pfQuest AreaTable ID → WMA ID bridge (GetCurrentMapAreaID returns pfQ IDs)
|
||||
local MTH_PFQ_TO_WMA = {
|
||||
[1]=27,[3]=17,[4]=19,[8]=38,[10]=34,[11]=40,[12]=30,[14]=4,[15]=141,[16]=181,
|
||||
[17]=11,[25]=519,[28]=22,[33]=37,[36]=15,[38]=35,[40]=39,[41]=32,[44]=36,
|
||||
[45]=16,[46]=29,[47]=26,[51]=28,[85]=20,[130]=21,[139]=23,[141]=41,[148]=42,
|
||||
[209]=650,[215]=9,[267]=24,[331]=43,[357]=121,[361]=182,[400]=61,[405]=101,
|
||||
[406]=81,[408]=505,[409]=504,[440]=161,[490]=201,[491]=641,[493]=241,
|
||||
[616]=501,[618]=281,[717]=611,[718]=516,[719]=609,[721]=518,[722]=639,
|
||||
[796]=520,[876]=500,[1176]=605,[1337]=521,[1377]=261,[1477]=607,[1497]=382,
|
||||
[1519]=301,[1537]=341,[1581]=514,[1583]=629,[1584]=623,[1637]=321,[1638]=362,
|
||||
[1657]=381,[1769]=700,[1941]=523,[1977]=619,[2017]=652,[2040]=509,[2057]=648,
|
||||
[2100]=517,[2159]=627,[2257]=659,[2366]=663,[2437]=603,[2557]=689,[2597]=401,
|
||||
[2677]=635,[2717]=617,[3277]=443,[3358]=461,[3428]=654,[3429]=625,[3456]=667,
|
||||
[3457]=682,[3478]=522,[4012]=502,[5023]=503,[5024]=511,[5053]=680,[5077]=670,
|
||||
[5086]=674,[5087]=676,[5097]=678,[5103]=672,[5121]=507,[5130]=601,[5135]=644,
|
||||
[5136]=643,[5138]=637,[5147]=655,[5148]=668,[5153]=645,[5163]=646,[5179]=510,
|
||||
[5204]=662,[5208]=665,[5225]=513,[5536]=512,[5557]=683,[5561]=685,[5581]=686,
|
||||
[5601]=691,[5602]=684,[5628]=693,[5640]=701,[5641]=698,[5642]=699,[5722]=702,
|
||||
[5723]=657,[5731]=705,[5734]=704,
|
||||
}
|
||||
|
||||
local function MTH_BLS_GetZoneId()
|
||||
if type(SetMapToCurrentZone) == "function" then
|
||||
pcall(SetMapToCurrentZone)
|
||||
end
|
||||
if type(GetCurrentMapAreaID) == "function" then
|
||||
local ok, id = pcall(GetCurrentMapAreaID)
|
||||
if ok and tonumber(id) and tonumber(id) > 0 then
|
||||
local pfq = tonumber(id)
|
||||
return MTH_PFQ_TO_WMA[pfq] or pfq
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
local function MTH_BLS_GetPlayerCoords()
|
||||
if type(SetMapToCurrentZone) == "function" then
|
||||
pcall(SetMapToCurrentZone)
|
||||
end
|
||||
if type(GetPlayerMapPosition) ~= "function" then return nil, nil end
|
||||
local ok, x, y = pcall(GetPlayerMapPosition, "player")
|
||||
if ok and x and y and (x ~= 0 or y ~= 0) then return x, y end
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
-- Returns true if a beast with this name is already in the static MTH_DS_Beasts database.
|
||||
-- We skip SavedVariables recording for beasts already catalogued in the DB.
|
||||
local function MTH_BLS_IsInStaticDB(name)
|
||||
if type(MTH_DS_Beasts) ~= "table" or not name or name == "" then return false end
|
||||
for _, row in pairs(MTH_DS_Beasts) do
|
||||
if type(row) == "table" and row.name == name then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- ── Storage ──────────────────────────────────────────────────
|
||||
|
||||
local function MTH_BLS_EnsureStore()
|
||||
if type(MTH_SavedVariables) ~= "table" then
|
||||
MTH_SavedVariables = {}
|
||||
end
|
||||
if type(MTH_SavedVariables.beastLoreScan) ~= "table" then
|
||||
MTH_SavedVariables.beastLoreScan = {}
|
||||
end
|
||||
if type(MTH_SavedVariables.beastLoreScan.entries) ~= "table" then
|
||||
MTH_SavedVariables.beastLoreScan.entries = {}
|
||||
end
|
||||
return MTH_SavedVariables.beastLoreScan
|
||||
end
|
||||
|
||||
-- ── Dedup ────────────────────────────────────────────────────
|
||||
|
||||
-- Returns the existing entry for a same-named beast in the same zone,
|
||||
-- plus whether any of its recorded coords is within DEDUP_YARDS of (x, y).
|
||||
local function MTH_BLS_FindEntry(name, zoneId, x, y)
|
||||
local store = MTH_BLS_EnsureStore()
|
||||
|
||||
-- Normalize: treat 0 as unknown (entries are stored with nil for unknown zones)
|
||||
if not zoneId or zoneId == 0 then zoneId = nil end
|
||||
|
||||
local mapW, mapH = nil, nil
|
||||
if type(MTH_DS_MinimapSizes) == "table" and zoneId then
|
||||
local sz = MTH_DS_MinimapSizes[zoneId]
|
||||
if type(sz) == "table" then
|
||||
mapW = tonumber(sz[1])
|
||||
mapH = tonumber(sz[2])
|
||||
end
|
||||
end
|
||||
|
||||
local function IsNearby(ex, ey)
|
||||
if not ex or not ey or not x or not y then return false end
|
||||
local dx = ex - x
|
||||
local dy = ey - y
|
||||
if mapW and mapH then
|
||||
return math.sqrt((dx * mapW)^2 + (dy * mapH)^2) < DEDUP_YARDS
|
||||
else
|
||||
-- No map size data: use a generous fallback (~5% of map width)
|
||||
return math.sqrt(dx * dx + dy * dy) < 0.05
|
||||
end
|
||||
end
|
||||
|
||||
local entries = store.entries
|
||||
for i = 1, table.getn(entries) do
|
||||
local e = entries[i]
|
||||
-- Match by name and zone. When either side has no zone, match on name alone
|
||||
-- (coords will still be checked below; cross-zone false positives are
|
||||
-- unlikely given players don't share coordinate spaces across zones).
|
||||
local zoneMatch = (e.zoneId == zoneId) or (not e.zoneId) or (not zoneId)
|
||||
if type(e) == "table"
|
||||
and e.name == name
|
||||
and zoneMatch
|
||||
then
|
||||
-- Check all recorded coords for this beast.
|
||||
if type(e.coords) == "table" then
|
||||
for j = 1, table.getn(e.coords) do
|
||||
local c = e.coords[j]
|
||||
if type(c) == "table" and IsNearby(c.x, c.y) then
|
||||
return e, true -- entry found, nearby coord found
|
||||
end
|
||||
end
|
||||
elseif IsNearby(e.x, e.y) then
|
||||
-- Legacy single-coord entry
|
||||
return e, true
|
||||
end
|
||||
return e, false -- entry found, but no coord nearby
|
||||
end
|
||||
end
|
||||
|
||||
return nil, false -- no entry for this beast in this zone
|
||||
end
|
||||
|
||||
-- ── Main scan ────────────────────────────────────────────────
|
||||
-- Per-session cooldown: prevents re-recording the same beast in the
|
||||
-- same zone within SCAN_COOLDOWN seconds, as a safety net on top of
|
||||
-- the coordinate-based dedup.
|
||||
local MTH_BLS_ScanCooldowns = {}
|
||||
local SCAN_COOLDOWN = 120 -- 2 minutes
|
||||
|
||||
local function MTH_BLS_Scan()
|
||||
-- Basic target guards
|
||||
if type(UnitExists) == "function" and not UnitExists("target") then return end
|
||||
if type(UnitIsPlayer) == "function" and UnitIsPlayer("target") then return end
|
||||
|
||||
local name = (type(UnitName) == "function") and UnitName("target") or nil
|
||||
if not name or name == "" then return end
|
||||
|
||||
-- ── Unit data ───────────────────────────────────────────
|
||||
local level = (type(UnitLevel) == "function") and UnitLevel("target") or nil
|
||||
local family = (type(UnitCreatureFamily) == "function") and UnitCreatureFamily("target") or nil
|
||||
local healthMax = (type(UnitHealthMax) == "function") and UnitHealthMax("target") or nil
|
||||
|
||||
local minDmg, maxDmg, mainSpeed = nil, nil, nil
|
||||
if type(UnitDamage) == "function" then
|
||||
local ok, a, b, _, _, spd = pcall(UnitDamage, "target")
|
||||
if ok then minDmg, maxDmg, mainSpeed = a, b, spd end
|
||||
end
|
||||
if not mainSpeed and type(UnitAttackSpeed) == "function" then
|
||||
local ok, spd = pcall(UnitAttackSpeed, "target")
|
||||
if ok then mainSpeed = spd end
|
||||
end
|
||||
|
||||
local guid = nil
|
||||
if type(UnitGUID) == "function" then
|
||||
local ok, g = pcall(UnitGUID, "target")
|
||||
if ok and g then guid = g end
|
||||
end
|
||||
|
||||
-- ── Location ────────────────────────────────────────────
|
||||
local zoneId = MTH_BLS_GetZoneId()
|
||||
local x, y = MTH_BLS_GetPlayerCoords()
|
||||
local zone = (type(GetZoneText) == "function") and GetZoneText() or ""
|
||||
local subzone = (type(GetSubZoneText) == "function") and GetSubZoneText() or ""
|
||||
|
||||
-- Session cooldown: hard block on re-scanning the same beast in same zone
|
||||
local cooldownKey = tostring(name) .. "|" .. tostring(zone)
|
||||
local lastScan = MTH_BLS_ScanCooldowns[cooldownKey]
|
||||
if lastScan and (GetTime() - lastScan) < SCAN_COOLDOWN then
|
||||
MTH:Log("|cff888888Beast Lore: " .. tostring(name) .. " scanned recently — skipped|r")
|
||||
return
|
||||
end
|
||||
|
||||
-- ── Tooltip probe ───────────────────────────────────────
|
||||
local probe = MTH_BLS_EnsureProbe()
|
||||
probe:ClearLines()
|
||||
local probeOk = pcall(function() probe:SetUnit("target") end)
|
||||
local parsed = { tameable = nil, diet = nil, abilities = nil, armor = nil, health = nil }
|
||||
if probeOk then
|
||||
local lines = MTH_BLS_GetProbeLines()
|
||||
parsed = MTH_BLS_ParseTooltip(lines)
|
||||
end
|
||||
|
||||
-- Tooltip values override unit API (Beast Lore reveals more accurate data)
|
||||
if parsed.minDmg then minDmg = parsed.minDmg end
|
||||
if parsed.maxDmg then maxDmg = parsed.maxDmg end
|
||||
if parsed.health then healthMax = parsed.health end
|
||||
|
||||
-- Fallback tameability: family present = tameable
|
||||
if parsed.tameable == nil then
|
||||
parsed.tameable = (family and family ~= "") and true or false
|
||||
end
|
||||
|
||||
-- ── Dedup / upsert ──────────────────────────────────────
|
||||
local function R3(v)
|
||||
if type(v) ~= "number" then return nil end
|
||||
return math.floor(v * 1000 + 0.5) / 1000
|
||||
end
|
||||
local function R2(v)
|
||||
if type(v) ~= "number" then return nil end
|
||||
return math.floor(v * 100 + 0.5) / 100
|
||||
end
|
||||
|
||||
local rx, ry = R3(x), R3(y)
|
||||
-- Mark scanned now (before storing) so any rapid re-trigger is blocked
|
||||
MTH_BLS_ScanCooldowns[cooldownKey] = GetTime()
|
||||
local existing, nearbyFound = MTH_BLS_FindEntry(name, zoneId, x, y)
|
||||
|
||||
if nearbyFound then
|
||||
-- Already recorded at this location — do nothing.
|
||||
MTH:Log("|cff888888Beast Lore: " .. tostring(name)
|
||||
.. " already recorded nearby — skipped|r")
|
||||
return
|
||||
end
|
||||
|
||||
local store = MTH_BLS_EnsureStore()
|
||||
|
||||
if existing then
|
||||
-- Known beast, new location — add coord to its list.
|
||||
if type(existing.coords) ~= "table" then
|
||||
-- Migrate legacy single coord into coords list.
|
||||
existing.coords = {}
|
||||
if existing.x or existing.y then
|
||||
table.insert(existing.coords, { x = existing.x, y = existing.y, timestamp = existing.timestamp })
|
||||
end
|
||||
existing.x = nil
|
||||
existing.y = nil
|
||||
end
|
||||
table.insert(existing.coords, { x = rx, y = ry, timestamp = (type(time) == "function") and time() or nil })
|
||||
existing.lastUpdated = (type(time) == "function") and time() or nil
|
||||
-- Update abilities/data if newly revealed by this cast.
|
||||
if parsed.abilities and parsed.abilities ~= "" then existing.abilities = parsed.abilities end
|
||||
|
||||
local coordStr = (rx and ry)
|
||||
and string.format(" (%.1f, %.1f)", rx * 100, ry * 100)
|
||||
or ""
|
||||
local abilitiesStr = (existing.abilities and existing.abilities ~= "")
|
||||
and (" |cff88ddff[" .. existing.abilities .. "]|r")
|
||||
or ""
|
||||
MTH:Log("Beast Lore: |cffffaa00" .. tostring(name) .. "|r lv" .. (existing.level and tostring(existing.level) or "?")
|
||||
.. " — new coord added" .. coordStr
|
||||
.. abilitiesStr
|
||||
.. " |cff888888[#" .. tostring(table.getn(store.entries)) .. " +" .. tostring(table.getn(existing.coords)) .. " loc]|r")
|
||||
return
|
||||
end
|
||||
|
||||
-- Skip recording if this beast is already in the static database.
|
||||
if MTH_BLS_IsInStaticDB(name) then
|
||||
MTH:Log("|cff888888Beast Lore: " .. tostring(name) .. " is already in the MetaHunt database — skipped|r")
|
||||
return
|
||||
end
|
||||
|
||||
-- New beast entirely.
|
||||
local entry = {
|
||||
name = name,
|
||||
guid = guid,
|
||||
level = level,
|
||||
family = family,
|
||||
tameable = parsed.tameable,
|
||||
minDmg = minDmg and math.floor(minDmg + 0.5) or nil,
|
||||
maxDmg = maxDmg and math.floor(maxDmg + 0.5) or nil,
|
||||
attackSpeed = mainSpeed and R2(mainSpeed) or nil,
|
||||
armor = parsed.armor or nil,
|
||||
health = healthMax or nil,
|
||||
diet = parsed.diet or nil,
|
||||
abilities = parsed.abilities or nil,
|
||||
coords = { { x = rx, y = ry, timestamp = (type(time) == "function") and time() or nil } },
|
||||
zoneId = (zoneId and zoneId > 0) and zoneId or nil,
|
||||
zone = (zone and zone ~= "") and zone or nil,
|
||||
subzone = (subzone and subzone ~= "") and subzone or nil,
|
||||
timestamp = (type(time) == "function") and time() or nil,
|
||||
}
|
||||
table.insert(store.entries, entry)
|
||||
|
||||
-- ── Chat confirmation ────────────────────────────────────
|
||||
local tameStr = parsed.tameable
|
||||
and "|cff33ff55Tameable|r"
|
||||
or "|cffff4444Cannot be Tamed|r"
|
||||
local famStr = (family and family ~= "")
|
||||
and ("|cffffcc00" .. family .. "|r")
|
||||
or "|cff888888unknown family|r"
|
||||
local lvlStr = level and tostring(level) or "?"
|
||||
local coordStr = (x and y)
|
||||
and string.format(" (%.1f, %.1f)", x * 100, y * 100)
|
||||
or ""
|
||||
local zoneStr = (zone and zone ~= "") and (", " .. zone .. coordStr) or coordStr
|
||||
local idx = table.getn(store.entries)
|
||||
|
||||
local abilitiesStr = ""
|
||||
if parsed.abilities and parsed.abilities ~= "" then
|
||||
abilitiesStr = " |cff88ddff[" .. parsed.abilities .. "]|r"
|
||||
end
|
||||
|
||||
MTH:Log("|cff00ff88YOU FOUND AND RECORDED A NEW BEAST!|r |cffffaa00" .. tostring(name) .. "|r lv" .. lvlStr
|
||||
.. " — " .. tameStr .. " • " .. famStr
|
||||
.. zoneStr
|
||||
.. abilitiesStr
|
||||
.. " |cff888888[#" .. tostring(idx) .. "]|r")
|
||||
end
|
||||
|
||||
-- ── Detection: event-gated tooltip poll ─────────────────────
|
||||
-- SPELLCAST_CHANNEL_* events do not reliably fire in TurtleWoW 1.18.1.
|
||||
-- Strategy: spellcast events open a short window (MTH_BLS_WindowEnd).
|
||||
-- OnUpdate only runs during that window, checking the target tooltip
|
||||
-- for Beast Lore-specific lines ("Diet:", "Tamed Abilities:").
|
||||
-- If no event fires at all, the window stays closed and nothing runs.
|
||||
|
||||
local MTH_BLS_Frame = CreateFrame("Frame", "MTH_BeastLoreScanFrame")
|
||||
local MTH_BLS_PollAccum = 0
|
||||
local MTH_BLS_WindowEnd = 0 -- GetTime() deadline; 0 = window closed
|
||||
local MTH_BLS_PollLast = nil -- target name already scanned this window
|
||||
|
||||
local WINDOW_DURATION = 6 -- seconds to probe after Beast Lore cast
|
||||
|
||||
local function MTH_BLS_OpenWindow()
|
||||
MTH_BLS_WindowEnd = GetTime() + WINDOW_DURATION
|
||||
MTH_BLS_PollLast = nil
|
||||
end
|
||||
|
||||
-- [DISABLED] Beast Lore scan activation — not needed while full beast DB is shipped.
|
||||
-- Re-enable by removing the `if false then` / `end` wrapper below.
|
||||
if false then
|
||||
|
||||
-- Register all known spellcast event variants.
|
||||
MTH_BLS_Frame:RegisterEvent("SPELLCAST_CHANNEL_START")
|
||||
MTH_BLS_Frame:RegisterEvent("SPELLCAST_CHANNEL_STOP")
|
||||
MTH_BLS_Frame:RegisterEvent("SPELLCAST_START")
|
||||
MTH_BLS_Frame:RegisterEvent("SPELLCAST_STOP")
|
||||
MTH_BLS_Frame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
|
||||
MTH_BLS_Frame:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
|
||||
MTH_BLS_Frame:RegisterEvent("UNIT_SPELLCAST_START")
|
||||
MTH_BLS_Frame:RegisterEvent("UNIT_SPELLCAST_STOP")
|
||||
MTH_BLS_Frame:RegisterEvent("PLAYER_TARGET_CHANGED")
|
||||
MTH_BLS_Frame:SetScript("OnEvent", function()
|
||||
local evt = event or ""
|
||||
|
||||
if evt == "PLAYER_TARGET_CHANGED" then
|
||||
-- New target: reset so re-targeting after a cast works.
|
||||
MTH_BLS_PollLast = nil
|
||||
return
|
||||
end
|
||||
|
||||
-- Determine spell name from event args.
|
||||
local isUnitEvt = string.sub(evt, 1, 15) == "UNIT_SPELLCAST_"
|
||||
local spellName
|
||||
if isUnitEvt then
|
||||
if (arg1 or "") ~= "player" then return end
|
||||
spellName = arg2 or ""
|
||||
else
|
||||
spellName = arg1 or ""
|
||||
end
|
||||
|
||||
if string.find(spellName, SPELL_BEAST_LORE, 1, true) then
|
||||
MTH_BLS_OpenWindow()
|
||||
end
|
||||
end)
|
||||
|
||||
MTH_BLS_Frame:SetScript("OnUpdate", function()
|
||||
MTH_BLS_PollAccum = MTH_BLS_PollAccum + (arg1 or 0)
|
||||
if MTH_BLS_PollAccum < 2.0 then return end
|
||||
MTH_BLS_PollAccum = 0
|
||||
|
||||
if not (type(UnitExists) == "function" and UnitExists("target")) then return end
|
||||
if type(UnitIsPlayer) == "function" and UnitIsPlayer("target") then return end
|
||||
|
||||
local name = (type(UnitName) == "function") and UnitName("target") or nil
|
||||
if not name or name == "" then return end
|
||||
if name == MTH_BLS_PollLast then return end
|
||||
|
||||
-- Probe the target tooltip for Beast Lore-specific lines.
|
||||
local probe = MTH_BLS_EnsureProbe()
|
||||
probe:ClearLines()
|
||||
local probeOk = pcall(function() probe:SetUnit("target") end)
|
||||
if not probeOk then return end
|
||||
|
||||
local hasData = false
|
||||
for i = 1, 20 do
|
||||
local L = getglobal("MTH_BLSProbeTextLeft" .. i)
|
||||
if not L then break end
|
||||
local txt = (L.GetText and L:GetText()) or ""
|
||||
if string.find(txt, "Diet:", 1, true)
|
||||
or string.find(txt, "Tamed Abilities:", 1, true)
|
||||
or string.find(txt, "Cannot Be Tamed", 1, true)
|
||||
then
|
||||
hasData = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not hasData then return end
|
||||
|
||||
-- Beast Lore data confirmed — record and close window.
|
||||
MTH_BLS_PollLast = name
|
||||
MTH_BLS_WindowEnd = 0
|
||||
local ok, err = pcall(MTH_BLS_Scan)
|
||||
if not ok and MTH and MTH.Print then
|
||||
MTH:Print("[BeastLore Scan] error: " .. tostring(err), "error")
|
||||
end
|
||||
end)
|
||||
|
||||
end -- [DISABLED] if false
|
||||
|
||||
-- ── Public accessors ─────────────────────────────────────────
|
||||
|
||||
-- Returns the first saved entry whose name matches (case-insensitive),
|
||||
-- or nil if none found.
|
||||
function MTH_BLS_FindSavedBeastByName(name)
|
||||
local store = MTH_BLS_EnsureStore()
|
||||
if not store or type(store.entries) ~= "table" then return nil end
|
||||
local wantLower = string.lower(name or "")
|
||||
if wantLower == "" then return nil end
|
||||
for i = 1, table.getn(store.entries) do
|
||||
local e = store.entries[i]
|
||||
if type(e) == "table" and string.lower(e.name or "") == wantLower then
|
||||
return e
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
+4
-1
@@ -38,7 +38,10 @@ local function MTH_ConfigEnsureStore(module_name)
|
||||
root.modules[module_name] = legacy
|
||||
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
|
||||
return root.modules[module_name]
|
||||
end
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
MTH_CONST = MTH_CONST or {}
|
||||
MTH_CONST.version = MTH_CONST.version or "1.4.0"
|
||||
MTH_CONST.version = MTH_CONST.version or "2.0.0"
|
||||
|
||||
MTH_CONST.WEAPON_TYPES = {
|
||||
BOWS = "Bows",
|
||||
|
||||
@@ -167,7 +167,7 @@ local function MTH_FEED_IndexBagFoods()
|
||||
return true
|
||||
end
|
||||
|
||||
local function MTH_FEED_HasPetFeedBuff()
|
||||
function MTH_FEED_HasPetFeedBuff()
|
||||
if type(UnitBuff) ~= "function" then
|
||||
return false
|
||||
end
|
||||
@@ -1385,6 +1385,13 @@ function MTH_FEED_FinalizeAttempt(attemptId, payload)
|
||||
if petKey ~= "" then
|
||||
MTH_FEED_Runtime.sessionFeedsByPetId[petKey] = (tonumber(MTH_FEED_Runtime.sessionFeedsByPetId[petKey]) or 0) + 1
|
||||
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
|
||||
petRow.totals.rejected = petRow.totals.rejected + 1
|
||||
if reason == "no-buff" then
|
||||
|
||||
+108
-14
@@ -1,5 +1,5 @@
|
||||
MTH = MTH or {
|
||||
version = "1.4.0",
|
||||
version = "2.0.0",
|
||||
name = "MetaHunt",
|
||||
modules = {},
|
||||
config = {},
|
||||
@@ -7,13 +7,29 @@ MTH = MTH or {
|
||||
}
|
||||
|
||||
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 = {
|
||||
initModulesLoaded = false,
|
||||
initSarcasticWelcome = true,
|
||||
petHungry = false,
|
||||
beastTrainingScan = true,
|
||||
spellbookScan = false,
|
||||
trainerScan = true,
|
||||
hunterLevelUp = true,
|
||||
hunterLevelQuiet = false,
|
||||
petLevelUp = true,
|
||||
petLoyaltyUp = true,
|
||||
petRanAway = true,
|
||||
mapMarkers = true,
|
||||
stableScan = true,
|
||||
@@ -189,6 +205,62 @@ function MTH:ApplyClassGate(_source)
|
||||
return blocked
|
||||
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")
|
||||
|
||||
if not MTH._classGateLifecycleFrame then
|
||||
@@ -202,6 +274,11 @@ if not MTH._classGateLifecycleFrame then
|
||||
MTH._classGateAnnounced = nil
|
||||
MTH._classGateAnnouncePending = nil
|
||||
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
|
||||
this:UnregisterAllEvents()
|
||||
this:SetScript("OnEvent", nil)
|
||||
@@ -1120,8 +1197,6 @@ function MTH:InitSavedVariables()
|
||||
if not MTH_SavedVariables then
|
||||
MTH_SavedVariables = {
|
||||
modules = {},
|
||||
feedomatic = {},
|
||||
zhunter = {},
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1131,12 +1206,6 @@ function MTH:InitSavedVariables()
|
||||
if not MTH_SavedVariables.moduleStates then
|
||||
MTH_SavedVariables.moduleStates = {}
|
||||
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
|
||||
MTH_SavedVariables.messages = {}
|
||||
end
|
||||
@@ -1148,9 +1217,6 @@ function MTH:InitSavedVariables()
|
||||
end
|
||||
end
|
||||
|
||||
MTH_SavedVariables.feedomatic = MTH_SavedVariables.modules.feedomatic
|
||||
MTH_SavedVariables.zhunter = MTH_SavedVariables.modules.zhunter
|
||||
|
||||
if not MTH_CharSavedVariables then
|
||||
MTH_CharSavedVariables = {}
|
||||
end
|
||||
@@ -1204,6 +1270,24 @@ function MTH:InitSavedVariables()
|
||||
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
|
||||
|
||||
function MTH:GetModuleSavedVariables(name)
|
||||
@@ -1311,7 +1395,17 @@ function MTH:AnnounceLoadComplete()
|
||||
moduleList = table.concat(enabled, ", ")
|
||||
end
|
||||
|
||||
self:Print("Check ignition: OK! Version " .. tostring(self.version or "unknown") .. " loaded.")
|
||||
local namPowerStatus = ""
|
||||
if type(GetNampowerVersion) == "function" then
|
||||
local maj, min = GetNampowerVersion()
|
||||
maj = tonumber(maj) or 0
|
||||
min = tonumber(min) or 0
|
||||
if maj > 2 or (maj == 2 and min >= 40) then
|
||||
namPowerStatus = " | NamPower detected"
|
||||
self.nampower = true
|
||||
end
|
||||
end
|
||||
self:Print("Check Ignition.... OK! Version " .. tostring(self.version or "unknown") .. " loaded." .. namPowerStatus)
|
||||
if self:IsMessageEnabled("initModulesLoaded", false) then
|
||||
self:Print("Modules enabled : " .. moduleList .. ".")
|
||||
end
|
||||
|
||||
@@ -0,0 +1,395 @@
|
||||
-- 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_SuppressUntil = 0
|
||||
|
||||
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
|
||||
-- Just after a loading screen the pet's real level/loyalty can arrive a beat
|
||||
-- late; record them as the baseline silently so we never announce progress
|
||||
-- that actually happened in an earlier session (e.g. a fresh install).
|
||||
if type(GetTime) == "function" and MTH_LT_SuppressUntil > 0 and GetTime() < MTH_LT_SuppressUntil 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
|
||||
if type(GetTime) == "function" then MTH_LT_SuppressUntil = GetTime() + 8 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()
|
||||
@@ -168,7 +168,10 @@ function MTH_PT_HasBeastTrainingRows()
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
local function MTH_PT_IsScanMessageEnabled()
|
||||
@@ -335,6 +338,39 @@ local function MTH_PT_IsHunterTokenKnown(token)
|
||||
return false
|
||||
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 message = tostring(rawMessage or "")
|
||||
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+)%.?$")
|
||||
end
|
||||
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
|
||||
end
|
||||
|
||||
@@ -368,7 +408,7 @@ local function MTH_PT_ProcessLearnSystemMessage(rawMessage)
|
||||
local token = MTH_PT_MakeTokenFromAbilityRank(abilityName, rankNumber)
|
||||
if token == "" then return false end
|
||||
|
||||
local alreadyKnown = MTH_PT_IsHunterTokenKnown(token)
|
||||
local alreadyKnown = MTH_PT_IsExactTokenKnown(token)
|
||||
local changed = false
|
||||
if not alreadyKnown then
|
||||
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
|
||||
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 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))
|
||||
@@ -399,6 +445,9 @@ local function MTH_PT_ProcessLearnSystemMessage(rawMessage)
|
||||
if changed then
|
||||
MTH_PT_ApplyScan("learn-msg")
|
||||
end
|
||||
if changed and type(MTH_CelebratePetAbility) == "function" then
|
||||
MTH_CelebratePetAbility(label)
|
||||
end
|
||||
if type(MTH_PSP_RequestScan) == "function" then
|
||||
MTH_PSP_RequestScan("train-learn-msg")
|
||||
end
|
||||
@@ -656,6 +705,24 @@ MTH_PT_IsPetBookVisible = function()
|
||||
return false
|
||||
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 triggerText = tostring(triggerBase or "")
|
||||
local feedbackOnOpen = (
|
||||
@@ -665,6 +732,10 @@ local function MTH_PT_RunDoubleScan(triggerBase)
|
||||
or triggerText == "beast-training-rows-open"
|
||||
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 MTH_PT_DebugLog then
|
||||
MTH_PT_DebugLog("scan skipped: no beast-training rows, trigger=" .. tostring(triggerBase or ""))
|
||||
@@ -675,9 +746,15 @@ local function MTH_PT_RunDoubleScan(triggerBase)
|
||||
return false
|
||||
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 ok, added, baselines, ranks = MTH_PT_ApplyScan(triggerText .. ":single")
|
||||
local t1 = time()
|
||||
if ok and signature then MTH_PT_LastScanSignature = signature end
|
||||
|
||||
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")
|
||||
@@ -832,6 +909,10 @@ function MTH_PT_InitService()
|
||||
MTH_PT_RunDoubleScanThrottled("beast-training-rows-present", 8)
|
||||
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_LastTrainingRowCount = rowCount
|
||||
|
||||
|
||||
+583
-119
@@ -2,8 +2,9 @@ if not MTH then
|
||||
error("MetaHunt core framework missing: api/core-framework.lua must load before api/core-scan-stablemaster.lua")
|
||||
end
|
||||
|
||||
local MTH_PETS_SCHEMA_VERSION = 2
|
||||
local MTH_PETS_SCHEMA_VERSION = 3
|
||||
local MTH_PETS_CORE_HOOK_BOUNDARY_KEY = "core-pet-rename-hook"
|
||||
local MTH_ST_FULL_DEBUG_TRACE = false
|
||||
MTH_PETS_TRACE_CONSISTENCY = false
|
||||
|
||||
local MTH_PetLifecycleEventFrame = nil
|
||||
@@ -24,8 +25,8 @@ local MTH_ST_Profile = {
|
||||
runawayMaxMs = 0,
|
||||
}
|
||||
local MTH_ST_Trace = {
|
||||
enabled = false,
|
||||
verbose = false,
|
||||
enabled = MTH_ST_FULL_DEBUG_TRACE and true or false,
|
||||
verbose = MTH_ST_FULL_DEBUG_TRACE and true or false,
|
||||
windowStart = 0,
|
||||
windowSeconds = 5,
|
||||
eventCount = 0,
|
||||
@@ -36,7 +37,7 @@ local MTH_ST_Trace = {
|
||||
local MTH_ST_Diag = {
|
||||
lines = {},
|
||||
maxLines = 200,
|
||||
chatEcho = false,
|
||||
chatEcho = MTH_ST_FULL_DEBUG_TRACE and true or false,
|
||||
}
|
||||
local MTH_ST_DiagCopyFrame = nil
|
||||
local MTH_ST_PrintStableDiag
|
||||
@@ -268,21 +269,27 @@ local function MTH_ST_EnsureDiagCopyFrame()
|
||||
end
|
||||
|
||||
function MTH_CommandStableDiag(mode)
|
||||
local lowerMode = string.lower(tostring(mode or "on"))
|
||||
MTH_ST_Diag.lines = {}
|
||||
MTH_ST_Diag.chatEcho = false
|
||||
MTH_ST_Diag.chatEcho = (lowerMode ~= "off") and true or false
|
||||
if MTH and MTH.Print then
|
||||
MTH:Print("Stable diagnostics are disabled.")
|
||||
MTH:Print("Stable diagnostics chat echo " .. (MTH_ST_Diag.chatEcho and "enabled" or "disabled") .. ".", "debug")
|
||||
end
|
||||
return false
|
||||
return MTH_ST_Diag.chatEcho
|
||||
end
|
||||
|
||||
function MTH_CommandStableTrace(mode)
|
||||
MTH_ST_Trace.enabled = false
|
||||
MTH_ST_Trace.verbose = false
|
||||
if MTH and MTH.Print then
|
||||
MTH:Print("Stable trace is disabled.")
|
||||
local lowerMode = string.lower(tostring(mode or "verbose"))
|
||||
MTH_ST_Trace.enabled = (lowerMode ~= "off") and true or false
|
||||
MTH_ST_Trace.verbose = (lowerMode == "verbose" or lowerMode == "on") and MTH_ST_Trace.enabled or false
|
||||
if MTH_ST_Trace.enabled then
|
||||
MTH_ST_TraceResetCounters()
|
||||
end
|
||||
return false
|
||||
if MTH and MTH.Print then
|
||||
MTH:Print("Stable trace " .. (MTH_ST_Trace.enabled and "enabled" or "disabled")
|
||||
.. " (verbose=" .. tostring(MTH_ST_Trace.verbose) .. ").", "debug")
|
||||
end
|
||||
return MTH_ST_Trace.enabled
|
||||
end
|
||||
|
||||
local function MTH_ST_ProfileNow()
|
||||
@@ -418,18 +425,29 @@ local function MTH_ST_ProfileFlushIfDue(force)
|
||||
end
|
||||
|
||||
function MTH_CommandStableProfile(mode)
|
||||
MTH_ST_Profile.enabled = false
|
||||
if MTH and MTH.Print then
|
||||
MTH:Print("Stable profiler is disabled.")
|
||||
local lowerMode = string.lower(tostring(mode or "off"))
|
||||
MTH_ST_Profile.enabled = (lowerMode ~= "off") and true or false
|
||||
if MTH_ST_Profile.enabled then
|
||||
MTH_ST_ProfileResetCounters()
|
||||
end
|
||||
return false
|
||||
if MTH and MTH.Print then
|
||||
MTH:Print("Stable profiler " .. (MTH_ST_Profile.enabled and "enabled" or "disabled") .. ".", "debug")
|
||||
end
|
||||
return MTH_ST_Profile.enabled
|
||||
end
|
||||
|
||||
local function MTH_PETS_LogConsistency(line)
|
||||
return
|
||||
if not MTH_PETS_TRACE_CONSISTENCY then
|
||||
return
|
||||
end
|
||||
if MTH and MTH.Print then
|
||||
MTH:Print("[PETS] " .. tostring(line or ""), "debug")
|
||||
end
|
||||
end
|
||||
|
||||
local function MTH_PETS_LogTame(_) end
|
||||
local function MTH_PETS_LogTame(_)
|
||||
return
|
||||
end
|
||||
|
||||
local function MTH_PETS_FormatRowConsistency(row)
|
||||
if type(row) ~= "table" then
|
||||
@@ -814,6 +832,30 @@ local function MTH_PETS_NormalizeSystemMessageKey(rawMessage)
|
||||
return message
|
||||
end
|
||||
|
||||
local function MTH_PETS_GetUnitGuid(unitToken)
|
||||
local token = tostring(unitToken or "")
|
||||
if token == "" then
|
||||
return nil
|
||||
end
|
||||
|
||||
local getUnitGuid = (type(getglobal) == "function" and getglobal("GetUnitGUID")) or (_G and _G["GetUnitGUID"])
|
||||
if type(getUnitGuid) == "function" then
|
||||
local ok, guid = pcall(getUnitGuid, token)
|
||||
if ok and type(guid) == "string" and guid ~= "" then
|
||||
return guid
|
||||
end
|
||||
end
|
||||
|
||||
if type(UnitGUID) == "function" then
|
||||
local ok, guid = pcall(UnitGUID, token)
|
||||
if ok and type(guid) == "string" and guid ~= "" then
|
||||
return guid
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
local function MTH_PETS_IsRunawaySystemMessage(rawMessage)
|
||||
local message = MTH_PETS_NormalizeSystemMessage(rawMessage)
|
||||
if message == "" then
|
||||
@@ -872,6 +914,21 @@ local function MTH_PETS_ParseCreatureIdFromGuid(guid)
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Canonical pet key: the pet GUID with its low 16 bits (last 4 hex chars) zeroed.
|
||||
-- On this core the low 16 bits change every session while the high portion stays
|
||||
-- stable per physical pet, so the full GUID cannot be used as a durable identity
|
||||
-- key. The canonical key is that durable identity, derived purely from the GUID.
|
||||
function MTH_PETS_CanonicalKey(guid)
|
||||
if type(guid) ~= "string" or guid == "" then
|
||||
return nil
|
||||
end
|
||||
local body = string.gsub(guid, "^0[xX]", "")
|
||||
if string.len(body) < 5 then
|
||||
return nil
|
||||
end
|
||||
return "0x" .. string.sub(body, 1, string.len(body) - 4) .. "0000"
|
||||
end
|
||||
|
||||
local function MTH_PETS_ParseBeastLevelBounds(levelField)
|
||||
local levelText = tostring(levelField or "")
|
||||
if levelText == "" then
|
||||
@@ -979,7 +1036,7 @@ local function MTH_PETS_CaptureTargetSnapshot()
|
||||
if MTH_PETS_IsPlaceholderPetName(targetName) then
|
||||
return nil
|
||||
end
|
||||
local targetGuid = (type(UnitGUID) == "function") and UnitGUID("target") or nil
|
||||
local targetGuid = MTH_PETS_GetUnitGuid("target")
|
||||
local beastId = MTH_PETS_ParseCreatureIdFromGuid(targetGuid)
|
||||
local targetFamily = (type(UnitCreatureFamily) == "function") and UnitCreatureFamily("target") or nil
|
||||
local targetLevel = (type(UnitLevel) == "function") and UnitLevel("target") or nil
|
||||
@@ -1193,14 +1250,31 @@ local function MTH_PETS_ResolveTameBeastIdForRow(row, snapshot)
|
||||
return nil
|
||||
end
|
||||
|
||||
local function MTH_PETS_MakeSignature(name, family, level)
|
||||
local cleanName = MTH_PETS_SafeLower(MTH_PETS_NormalizeText(name))
|
||||
local cleanFamily = MTH_PETS_SafeLower(MTH_PETS_NormalizeText(family))
|
||||
local numericLevel = tonumber(level) or 0
|
||||
return cleanName .. "|" .. cleanFamily .. "|" .. tostring(numericLevel)
|
||||
local function MTH_PETS_IsTameConfirmationSource(source)
|
||||
local sourceText = tostring(source or "")
|
||||
return sourceText == "unit-pet-acquire" or sourceText == "refresh-current-pet"
|
||||
end
|
||||
|
||||
local function MTH_PETS_RowHasTameRecord(row)
|
||||
local function MTH_PETS_ResolveConfirmedTameBeastId(source, snapshot)
|
||||
if not MTH_PETS_IsTameConfirmationSource(source) then
|
||||
return nil
|
||||
end
|
||||
local resolved = MTH_PETS_GetPendingTameBeastId(snapshot)
|
||||
if resolved and resolved > 0 then
|
||||
return resolved
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function MTH_PETS_MakeSignature(name, family, level)
|
||||
-- Level intentionally excluded: it mutates on level-up, so baking it into the
|
||||
-- identity signature made the same pet look like a new one after each level.
|
||||
local cleanName = MTH_PETS_SafeLower(MTH_PETS_NormalizeText(name))
|
||||
local cleanFamily = MTH_PETS_SafeLower(MTH_PETS_NormalizeText(family))
|
||||
return cleanName .. "|" .. cleanFamily
|
||||
end
|
||||
|
||||
local function MTH_PETS_RowHasAnyTameMetadata(row)
|
||||
if type(row) ~= "table" then
|
||||
return false
|
||||
end
|
||||
@@ -1219,11 +1293,33 @@ local function MTH_PETS_RowHasTameRecord(row)
|
||||
return false
|
||||
end
|
||||
|
||||
function MTH_PETS_HasVerifiedTameRecord(row)
|
||||
if type(row) ~= "table" then
|
||||
return false
|
||||
end
|
||||
if row.tameVerified == true then
|
||||
return true
|
||||
end
|
||||
local legacyTameContext = type(row.tameContext) == "table" and row.tameContext or nil
|
||||
if legacyTameContext and (legacyTameContext.name or legacyTameContext.zone or legacyTameContext.timestamp) then
|
||||
return true
|
||||
end
|
||||
if type(row.events) == "table" then
|
||||
for i = 1, table.getn(row.events) do
|
||||
local ev = row.events[i]
|
||||
if type(ev) == "table" and tostring(ev.type or "") == "pet-tamed" then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function MTH_PETS_BackfillTameMetadataFromEvents(row)
|
||||
if type(row) ~= "table" then
|
||||
return false
|
||||
end
|
||||
if MTH_PETS_RowHasTameRecord(row) then
|
||||
if MTH_PETS_HasVerifiedTameRecord(row) then
|
||||
return false
|
||||
end
|
||||
if type(row.events) ~= "table" then
|
||||
@@ -1234,7 +1330,7 @@ local function MTH_PETS_BackfillTameMetadataFromEvents(row)
|
||||
local stableFirstSeenAt = tonumber(row.stableFirstSeenAt) or tonumber(row.stabledAt) or 0
|
||||
for i = 1, table.getn(row.events) do
|
||||
local ev = row.events[i]
|
||||
if type(ev) == "table" and tostring(ev.type or "") == "pet-acquired" then
|
||||
if type(ev) == "table" and tostring(ev.type or "") == "pet-tamed" then
|
||||
local evAt = tonumber(ev.at) or 0
|
||||
local timelineOk = true
|
||||
if stableFirstSeenAt > 0 and evAt > 0 and evAt > stableFirstSeenAt then
|
||||
@@ -1302,12 +1398,207 @@ local function MTH_PETS_BackfillTameMetadataFromEvents(row)
|
||||
|
||||
if changed then
|
||||
row.tameRecorded = true
|
||||
row.tameVerified = true
|
||||
row.lastUpdated = time()
|
||||
end
|
||||
|
||||
return changed
|
||||
end
|
||||
|
||||
local function MTH_PETS_ParseNumericPetId(petId)
|
||||
local _, _, numericId = string.find(tostring(petId or ""), "^pet%-(%d+)$")
|
||||
return tonumber(numericId)
|
||||
end
|
||||
|
||||
local function MTH_PETS_GetActiveRowRepairScore(pets, petId, row)
|
||||
if type(row) ~= "table" then
|
||||
return -1, 0
|
||||
end
|
||||
local score = 0
|
||||
local resolvedPetId = tostring(petId or "")
|
||||
local petStore = type(pets) == "table" and pets.petStore or nil
|
||||
if resolvedPetId ~= "" and tostring((pets and pets.currentPetId) or "") == resolvedPetId then
|
||||
score = score + 1000000
|
||||
end
|
||||
if resolvedPetId ~= "" and type(petStore) == "table" and tostring(petStore.activeCurrentId or "") == resolvedPetId then
|
||||
score = score + 900000
|
||||
end
|
||||
if tonumber(row.stableSlot) and tonumber(row.stableSlot) > 0 then
|
||||
score = score + 500000
|
||||
end
|
||||
if type(row.guid) == "string" and row.guid ~= "" then
|
||||
score = score + 100000
|
||||
end
|
||||
if row.tameVerified == true then
|
||||
score = score + 50000
|
||||
end
|
||||
if row.tameRecorded == true then
|
||||
score = score + 10000
|
||||
end
|
||||
score = score + (tonumber(row.lastSeen) or tonumber(row.lastUpdated) or 0)
|
||||
return score, tonumber(MTH_PETS_ParseNumericPetId(resolvedPetId)) or 0
|
||||
end
|
||||
|
||||
local function MTH_PETS_MergeActiveRows(pets, winnerId, loserId)
|
||||
if type(pets) ~= "table" or type(pets.petStore) ~= "table" then
|
||||
return false
|
||||
end
|
||||
local petStore = pets.petStore
|
||||
local winnerRow = type(petStore.activeById) == "table" and petStore.activeById[winnerId] or nil
|
||||
local loserRow = type(petStore.activeById) == "table" and petStore.activeById[loserId] or nil
|
||||
if type(winnerRow) ~= "table" or type(loserRow) ~= "table" or tostring(winnerId) == tostring(loserId) then
|
||||
return false
|
||||
end
|
||||
|
||||
local copyIfMissingFields = {
|
||||
"guid", "signature", "beastId", "name", "family", "level",
|
||||
"happiness", "loyalty", "loyaltyLevel", "xp", "xpMax", "xpPercent",
|
||||
"origin", "originContext", "previousName", "stableRaw", "stableInfo",
|
||||
"zone", "subZone", "x", "y", "hunterLevel", "lastSource",
|
||||
"tameHunterLevel", "tameZone", "tameSubZone", "tameX", "tameY",
|
||||
"tameBeastId", "tameContext",
|
||||
}
|
||||
for i = 1, table.getn(copyIfMissingFields) do
|
||||
local field = copyIfMissingFields[i]
|
||||
local winnerValue = winnerRow[field]
|
||||
if winnerValue == nil or winnerValue == "" then
|
||||
local loserValue = loserRow[field]
|
||||
if loserValue ~= nil and loserValue ~= "" then
|
||||
winnerRow[field] = loserValue
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if type(winnerRow.petSpellbook) ~= "table" and type(loserRow.petSpellbook) == "table" then
|
||||
winnerRow.petSpellbook = loserRow.petSpellbook
|
||||
winnerRow.lastPetSpellbookSkipAt = winnerRow.lastPetSpellbookSkipAt or loserRow.lastPetSpellbookSkipAt
|
||||
winnerRow.lastPetSpellbookSkipSource = winnerRow.lastPetSpellbookSkipSource or loserRow.lastPetSpellbookSkipSource
|
||||
winnerRow.lastPetSpellbookSkipReason = winnerRow.lastPetSpellbookSkipReason or loserRow.lastPetSpellbookSkipReason
|
||||
end
|
||||
if type(winnerRow.abilities) ~= "table" then
|
||||
winnerRow.abilities = {}
|
||||
end
|
||||
if type(loserRow.abilities) == "table" then
|
||||
for token, ability in pairs(loserRow.abilities) do
|
||||
if winnerRow.abilities[token] == nil then
|
||||
winnerRow.abilities[token] = ability
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if type(winnerRow.events) ~= "table" then
|
||||
winnerRow.events = {}
|
||||
end
|
||||
if type(loserRow.events) == "table" then
|
||||
for i = 1, table.getn(loserRow.events) do
|
||||
table.insert(winnerRow.events, loserRow.events[i])
|
||||
end
|
||||
end
|
||||
|
||||
local winnerCreatedAt = tonumber(winnerRow.createdAt) or 0
|
||||
local loserCreatedAt = tonumber(loserRow.createdAt) or 0
|
||||
if winnerCreatedAt <= 0 or (loserCreatedAt > 0 and loserCreatedAt < winnerCreatedAt) then
|
||||
winnerRow.createdAt = loserCreatedAt > 0 and loserCreatedAt or winnerRow.createdAt
|
||||
end
|
||||
local winnerFirstSeen = tonumber(winnerRow.firstSeen) or 0
|
||||
local loserFirstSeen = tonumber(loserRow.firstSeen) or 0
|
||||
if winnerFirstSeen <= 0 or (loserFirstSeen > 0 and loserFirstSeen < winnerFirstSeen) then
|
||||
winnerRow.firstSeen = loserFirstSeen > 0 and loserFirstSeen or winnerRow.firstSeen
|
||||
end
|
||||
local winnerLastSeen = tonumber(winnerRow.lastSeen) or 0
|
||||
local loserLastSeen = tonumber(loserRow.lastSeen) or 0
|
||||
if loserLastSeen > winnerLastSeen then
|
||||
winnerRow.lastSeen = loserLastSeen
|
||||
end
|
||||
local winnerLastUpdated = tonumber(winnerRow.lastUpdated) or 0
|
||||
local loserLastUpdated = tonumber(loserRow.lastUpdated) or 0
|
||||
if loserLastUpdated > winnerLastUpdated then
|
||||
winnerRow.lastUpdated = loserLastUpdated
|
||||
end
|
||||
|
||||
if winnerRow.tameVerified ~= true and loserRow.tameVerified == true then
|
||||
winnerRow.tameVerified = true
|
||||
winnerRow.tameRecorded = true
|
||||
winnerRow.tamedAt = winnerRow.tamedAt or loserRow.tamedAt
|
||||
winnerRow.tameHunterLevel = winnerRow.tameHunterLevel or loserRow.tameHunterLevel
|
||||
winnerRow.tameZone = winnerRow.tameZone or loserRow.tameZone
|
||||
winnerRow.tameSubZone = winnerRow.tameSubZone or loserRow.tameSubZone
|
||||
winnerRow.tameX = winnerRow.tameX or loserRow.tameX
|
||||
winnerRow.tameY = winnerRow.tameY or loserRow.tameY
|
||||
winnerRow.tameBeastId = winnerRow.tameBeastId or loserRow.tameBeastId
|
||||
end
|
||||
|
||||
if (winnerRow.stableSlot == nil or tonumber(winnerRow.stableSlot) == nil or tonumber(winnerRow.stableSlot) <= 0)
|
||||
and tonumber(loserRow.stableSlot) and tonumber(loserRow.stableSlot) > 0 then
|
||||
winnerRow.stableSlot = tonumber(loserRow.stableSlot)
|
||||
end
|
||||
|
||||
if type(petStore.stableSlotIndex) == "table" then
|
||||
for slotNumber, mappedPetId in pairs(petStore.stableSlotIndex) do
|
||||
if tostring(mappedPetId or "") == tostring(loserId) then
|
||||
petStore.stableSlotIndex[slotNumber] = tostring(winnerId)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if tostring(petStore.activeCurrentId or "") == tostring(loserId) then
|
||||
petStore.activeCurrentId = tostring(winnerId)
|
||||
end
|
||||
if tostring(pets.currentPetId or "") == tostring(loserId) then
|
||||
pets.currentPetId = tostring(winnerId)
|
||||
end
|
||||
if type(pets.currentPet) == "table" and tostring(pets.currentPet.id or "") == tostring(loserId) then
|
||||
pets.currentPet.id = tostring(winnerId)
|
||||
end
|
||||
|
||||
petStore.activeById[loserId] = nil
|
||||
return true
|
||||
end
|
||||
|
||||
local function MTH_PETS_RepairActiveRowsByGuid(pets)
|
||||
if type(pets) ~= "table" or type(pets.petStore) ~= "table" or type(pets.petStore.activeById) ~= "table" then
|
||||
return
|
||||
end
|
||||
local groupedByGuid = {}
|
||||
for petId, row in pairs(pets.petStore.activeById) do
|
||||
if type(row) == "table" and type(row.guid) == "string" and row.guid ~= "" then
|
||||
-- Group by the CANONICAL key, not the full guid: the full guid is volatile
|
||||
-- across sessions, so grouping on it never catches cross-session duplicates
|
||||
-- of the same physical pet (the root cause of the duplicate-row bug).
|
||||
local key = MTH_PETS_CanonicalKey(row.guid) or row.guid
|
||||
if type(groupedByGuid[key]) ~= "table" then
|
||||
groupedByGuid[key] = {}
|
||||
end
|
||||
table.insert(groupedByGuid[key], tostring(petId))
|
||||
end
|
||||
end
|
||||
|
||||
for _, petIds in pairs(groupedByGuid) do
|
||||
if type(petIds) == "table" and table.getn(petIds) > 1 then
|
||||
table.sort(petIds, function(a, b)
|
||||
local rowA = pets.petStore.activeById[a]
|
||||
local rowB = pets.petStore.activeById[b]
|
||||
local scoreA, numericA = MTH_PETS_GetActiveRowRepairScore(pets, a, rowA)
|
||||
local scoreB, numericB = MTH_PETS_GetActiveRowRepairScore(pets, b, rowB)
|
||||
if scoreA ~= scoreB then
|
||||
return scoreA > scoreB
|
||||
end
|
||||
if numericA ~= numericB then
|
||||
return numericA < numericB
|
||||
end
|
||||
return tostring(a) < tostring(b)
|
||||
end)
|
||||
local winnerId = petIds[1]
|
||||
for i = 2, table.getn(petIds) do
|
||||
local loserId = petIds[i]
|
||||
if pets.petStore.activeById[winnerId] and pets.petStore.activeById[loserId] then
|
||||
MTH_PETS_MergeActiveRows(pets, winnerId, loserId)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function MTH_PETS_EnsurePetStoreSchema(pets)
|
||||
if type(pets.petStore) ~= "table" then
|
||||
pets.petStore = {}
|
||||
@@ -1334,9 +1625,44 @@ local function MTH_PETS_EnsurePetStoreSchema(pets)
|
||||
if petStore.activeCurrentId == nil then
|
||||
petStore.activeCurrentId = nil
|
||||
end
|
||||
MTH_PETS_RepairActiveRowsByGuid(pets)
|
||||
petStore.signatureIndex = {}
|
||||
petStore.guidIndex = {}
|
||||
local highestPetNumericId = 0
|
||||
|
||||
for _, row in pairs(petStore.activeById) do
|
||||
for petId, row in pairs(petStore.activeById) do
|
||||
if type(row) == "table" then
|
||||
local resolvedPetId = tostring(petId)
|
||||
if row.id == nil or tostring(row.id) == "" then
|
||||
row.id = resolvedPetId
|
||||
end
|
||||
local numericPetId = MTH_PETS_ParseNumericPetId(resolvedPetId)
|
||||
if numericPetId and numericPetId > highestPetNumericId then
|
||||
highestPetNumericId = numericPetId
|
||||
end
|
||||
if row.tameVerified ~= true then
|
||||
local legacyTameContext = type(row.tameContext) == "table" and row.tameContext or nil
|
||||
if legacyTameContext and (legacyTameContext.name or legacyTameContext.zone or legacyTameContext.timestamp) then
|
||||
row.tameVerified = true
|
||||
end
|
||||
end
|
||||
-- Re-sign level-free every load so the signature index can never retain a
|
||||
-- level-baked key from older data.
|
||||
row.signature = MTH_PETS_MakeSignature(row.name, row.family)
|
||||
if type(row.signature) == "string" and row.signature ~= "" then
|
||||
if type(petStore.signatureIndex[row.signature]) ~= "table" then
|
||||
petStore.signatureIndex[row.signature] = {}
|
||||
end
|
||||
table.insert(petStore.signatureIndex[row.signature], resolvedPetId)
|
||||
end
|
||||
if type(row.guid) == "string" and row.guid ~= "" then
|
||||
petStore.guidIndex[MTH_PETS_CanonicalKey(row.guid) or row.guid] = resolvedPetId
|
||||
end
|
||||
-- beastId is mislabeled (per-pet number, not creature id); expose petNumber
|
||||
-- additively without breaking existing readers.
|
||||
if row.beastId ~= nil and row.petNumber == nil then
|
||||
row.petNumber = row.beastId
|
||||
end
|
||||
if row.loyaltyLevel == nil and type(row.loyalty) == "string" then
|
||||
row.loyaltyLevel = MTH_PETS_ParseLoyaltyLevelFromText(row.loyalty)
|
||||
end
|
||||
@@ -1351,9 +1677,21 @@ local function MTH_PETS_EnsurePetStoreSchema(pets)
|
||||
end
|
||||
row.stableInfo.loyalty = nil
|
||||
end
|
||||
if row.tameVerified ~= true and MTH_PETS_RowHasAnyTameMetadata(row) then
|
||||
row.tameRecorded = nil
|
||||
end
|
||||
MTH_PETS_BackfillTameMetadataFromEvents(row)
|
||||
end
|
||||
end
|
||||
if highestPetNumericId >= tonumber(petStore.nextId) then
|
||||
petStore.nextId = highestPetNumericId + 1
|
||||
end
|
||||
if petStore.activeCurrentId ~= nil and petStore.activeById[tostring(petStore.activeCurrentId)] == nil then
|
||||
petStore.activeCurrentId = nil
|
||||
end
|
||||
if pets.currentPetId ~= nil and petStore.activeById[tostring(pets.currentPetId)] == nil then
|
||||
pets.currentPetId = petStore.activeCurrentId
|
||||
end
|
||||
end
|
||||
|
||||
local function MTH_PETS_EnsureCurrentPetSchema(pets)
|
||||
@@ -1489,8 +1827,16 @@ local function MTH_PETS_ApplyCurrentPetFromActiveRow(cp, petId, row)
|
||||
end
|
||||
end
|
||||
|
||||
-- Forward declaration: the one-shot orphan cleanup is defined later (it reuses
|
||||
-- MTH_PETS_MoveToHistory, declared further down), but MTH_PETS_EnsureSchema needs
|
||||
-- to reference it here. By call time (a load-time event) it is assigned.
|
||||
local MTH_PETS_ArchiveOrphanActiveRows
|
||||
|
||||
local function MTH_PETS_EnsureSchema(pets)
|
||||
local now = time()
|
||||
-- Capture BEFORE the stamp block rewrites schemaVersion, so we know whether the
|
||||
-- one-time v3 orphan cleanup still needs to run for this character.
|
||||
local needsOrphanCleanup = (tonumber(pets.schemaVersion) or 0) < 3
|
||||
if pets.schemaVersion ~= MTH_PETS_SCHEMA_VERSION then
|
||||
pets.schemaVersion = MTH_PETS_SCHEMA_VERSION
|
||||
pets.schemaMigratedAt = now
|
||||
@@ -1499,7 +1845,7 @@ local function MTH_PETS_EnsureSchema(pets)
|
||||
end
|
||||
pets.schemaMigration.version = MTH_PETS_SCHEMA_VERSION
|
||||
pets.schemaMigration.at = now
|
||||
pets.schemaMigration.note = "beta canonical store"
|
||||
pets.schemaMigration.note = "canonical identity keys + orphan cleanup"
|
||||
end
|
||||
if pets.updatedAt == nil then
|
||||
pets.updatedAt = 0
|
||||
@@ -1528,6 +1874,11 @@ local function MTH_PETS_EnsureSchema(pets)
|
||||
end
|
||||
MTH_PETS_EnsureCurrentPetSchema(pets)
|
||||
MTH_PETS_EnsurePetStoreSchema(pets)
|
||||
-- After dedup + index rebuild (done inside EnsurePetStoreSchema), sweep legacy
|
||||
-- orphan rows (active but neither the current pet nor stabled) into history once.
|
||||
if needsOrphanCleanup and type(MTH_PETS_ArchiveOrphanActiveRows) == "function" then
|
||||
MTH_PETS_ArchiveOrphanActiveRows(pets, "migration-recovered")
|
||||
end
|
||||
end
|
||||
|
||||
local function MTH_PETS_MarkStableVisited(pets, source)
|
||||
@@ -1727,10 +2078,10 @@ local function MTH_PETS_MakeSnapshotFromLivePet()
|
||||
xpPercent = math.floor((xp / xpMax) * 1000 + 0.5) / 10
|
||||
end
|
||||
end
|
||||
local petGuid = (type(UnitGUID) == "function") and UnitGUID("pet") or nil
|
||||
local petGuid = MTH_PETS_GetUnitGuid("pet")
|
||||
local beastId = MTH_PETS_ParseCreatureIdFromGuid(petGuid)
|
||||
if beastId == nil and type(UnitGUID) == "function" then
|
||||
beastId = MTH_PETS_ParseCreatureIdFromGuid(UnitGUID("target"))
|
||||
if beastId == nil then
|
||||
beastId = MTH_PETS_ParseCreatureIdFromGuid(MTH_PETS_GetUnitGuid("target"))
|
||||
end
|
||||
local signature = MTH_PETS_MakeSignature(petName, petFamily, petLevel)
|
||||
return {
|
||||
@@ -1756,16 +2107,32 @@ local function MTH_PETS_SelectPetIdBySnapshot(store, snapshot, previousPetId)
|
||||
|
||||
if previousPetId and store.activeById[previousPetId] then
|
||||
local previousRow = store.activeById[previousPetId]
|
||||
if previousRow.guid and snapshot.guid and previousRow.guid == snapshot.guid then
|
||||
return previousPetId
|
||||
end
|
||||
if previousRow.signature and snapshot.signature and previousRow.signature == snapshot.signature then
|
||||
if previousRow.guid and snapshot.guid
|
||||
and MTH_PETS_CanonicalKey(previousRow.guid) == MTH_PETS_CanonicalKey(snapshot.guid) then
|
||||
return previousPetId
|
||||
end
|
||||
end
|
||||
|
||||
if snapshot.guid and store.guidIndex[snapshot.guid] and store.activeById[store.guidIndex[snapshot.guid]] then
|
||||
return store.guidIndex[snapshot.guid]
|
||||
local snapshotKey = snapshot.guid and MTH_PETS_CanonicalKey(snapshot.guid) or nil
|
||||
if snapshotKey and store.guidIndex[snapshotKey] and store.activeById[store.guidIndex[snapshotKey]] then
|
||||
return store.guidIndex[snapshotKey]
|
||||
end
|
||||
|
||||
if snapshotKey then
|
||||
for petId, row in pairs(store.activeById or {}) do
|
||||
if type(row) == "table" and type(row.guid) == "string"
|
||||
and MTH_PETS_CanonicalKey(row.guid) == snapshotKey then
|
||||
store.guidIndex[snapshotKey] = tostring(petId)
|
||||
return tostring(petId)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if previousPetId and store.activeById[previousPetId] then
|
||||
local previousRow = store.activeById[previousPetId]
|
||||
if previousRow.signature and snapshot.signature and previousRow.signature == snapshot.signature then
|
||||
return previousPetId
|
||||
end
|
||||
end
|
||||
|
||||
if snapshot.signature and snapshot.signature ~= "" then
|
||||
@@ -1831,21 +2198,17 @@ local function MTH_PETS_ApplySnapshotToRow(row, snapshot, source, context)
|
||||
end
|
||||
row.guid = snapshot.guid or row.guid
|
||||
row.beastId = snapshot.beastId or row.beastId
|
||||
local hasPendingTame = type(MTH_PETS_LastTameAttempt) == "table"
|
||||
if hasPendingTame and (row.tameBeastId == nil or tonumber(row.tameBeastId) == nil) then
|
||||
local resolvedTameBeastId = MTH_PETS_ResolveTameBeastIdForRow(row, snapshot)
|
||||
if resolvedTameBeastId and resolvedTameBeastId > 0 then
|
||||
row.tameBeastId = resolvedTameBeastId
|
||||
end
|
||||
end
|
||||
if hasPendingTame and (source == "unit-pet-acquire" or source == "refresh-current-pet") and type(context) == "table" then
|
||||
local confirmedTameBeastId = MTH_PETS_ResolveConfirmedTameBeastId(source, snapshot)
|
||||
if confirmedTameBeastId and type(context) == "table" then
|
||||
row.tameRecorded = true
|
||||
row.tameVerified = true
|
||||
row.tamedAt = row.tamedAt or context.timestamp
|
||||
row.tameHunterLevel = row.tameHunterLevel or context.hunterLevel
|
||||
row.tameZone = row.tameZone or context.zone
|
||||
row.tameSubZone = row.tameSubZone or context.subZone
|
||||
row.tameX = row.tameX or context.x
|
||||
row.tameY = row.tameY or context.y
|
||||
row.tameBeastId = row.tameBeastId or confirmedTameBeastId
|
||||
end
|
||||
row.signature = snapshot.signature
|
||||
row.lastUpdated = now
|
||||
@@ -1870,30 +2233,6 @@ local function MTH_PETS_ApplySnapshotToRow(row, snapshot, source, context)
|
||||
row.hunterLevel = context.hunterLevel or row.hunterLevel
|
||||
end
|
||||
end
|
||||
if not isStableScan and type(context) == "table" then
|
||||
local hasTameRecord = (row.tameRecorded == true)
|
||||
or tonumber(row.tamedAt) ~= nil
|
||||
or tonumber(row.tameBeastId) ~= nil
|
||||
or (type(row.tameZone) == "string" and row.tameZone ~= "")
|
||||
if not hasTameRecord and hasPendingTame then
|
||||
local sourceText = tostring(source or "")
|
||||
if sourceText == "unit-pet-acquire" or sourceText == "refresh-current-pet" then
|
||||
row.tameRecorded = true
|
||||
row.tamedAt = row.tamedAt or context.timestamp
|
||||
row.tameHunterLevel = row.tameHunterLevel or context.hunterLevel
|
||||
row.tameZone = row.tameZone or context.zone
|
||||
row.tameSubZone = row.tameSubZone or context.subZone
|
||||
row.tameX = row.tameX or context.x
|
||||
row.tameY = row.tameY or context.y
|
||||
if tonumber(row.tameBeastId) == nil then
|
||||
local resolvedTameBeastId = MTH_PETS_ResolveTameBeastIdForRow(row, snapshot)
|
||||
if resolvedTameBeastId and resolvedTameBeastId > 0 then
|
||||
row.tameBeastId = resolvedTameBeastId
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if type(row.abilities) ~= "table" then
|
||||
row.abilities = {}
|
||||
end
|
||||
@@ -1944,16 +2283,18 @@ local function MTH_PETS_UpsertActivePetFromSnapshot(pets, snapshot, source, opti
|
||||
petStore.stableSlotIndex[row.stableSlot] = nil
|
||||
end
|
||||
row.stableSlot = nil
|
||||
if row.guid and row.guid ~= snapshot.guid and petStore.guidIndex[row.guid] == petId then
|
||||
petStore.guidIndex[row.guid] = nil
|
||||
local oldGuidKey = row.guid and MTH_PETS_CanonicalKey(row.guid) or nil
|
||||
local newGuidKey = snapshot.guid and MTH_PETS_CanonicalKey(snapshot.guid) or nil
|
||||
if oldGuidKey and oldGuidKey ~= newGuidKey and petStore.guidIndex[oldGuidKey] == petId then
|
||||
petStore.guidIndex[oldGuidKey] = nil
|
||||
end
|
||||
|
||||
local context = MTH_PETS_CaptureContext()
|
||||
MTH_PETS_ApplySnapshotToRow(row, snapshot, source, context)
|
||||
row.firstSeen = row.firstSeen or time()
|
||||
|
||||
if snapshot.guid and snapshot.guid ~= "" then
|
||||
petStore.guidIndex[snapshot.guid] = petId
|
||||
if newGuidKey then
|
||||
petStore.guidIndex[newGuidKey] = petId
|
||||
end
|
||||
if snapshot.signature and snapshot.signature ~= "" then
|
||||
MTH_PETS_AddSignatureIndex(petStore, snapshot.signature, petId)
|
||||
@@ -1962,18 +2303,6 @@ local function MTH_PETS_UpsertActivePetFromSnapshot(pets, snapshot, source, opti
|
||||
if created then
|
||||
row.origin = source or "unknown"
|
||||
row.originContext = context
|
||||
local hasPendingTame = type(MTH_PETS_LastTameAttempt) == "table"
|
||||
local shouldRecordTame = hasPendingTame and (source == "unit-pet-acquire" or source == "refresh-current-pet")
|
||||
if shouldRecordTame then
|
||||
row.tamedAt = context.timestamp
|
||||
row.tameHunterLevel = context.hunterLevel
|
||||
row.tameZone = context.zone
|
||||
row.tameSubZone = context.subZone
|
||||
row.tameX = context.x
|
||||
row.tameY = context.y
|
||||
row.tameBeastId = snapshot.beastId
|
||||
row.tameRecorded = true
|
||||
end
|
||||
if type(options) == "table" and options.stableSlot then
|
||||
row.origin = "stable-slot"
|
||||
end
|
||||
@@ -2131,6 +2460,45 @@ local function MTH_PETS_RecordStableSlot(pets, slot, raw1, raw2, raw3, raw4, raw
|
||||
pets.updatedAt = time()
|
||||
end
|
||||
|
||||
local function MTH_PETS_ReconcileStableSlots(pets, slotCount)
|
||||
if type(pets) ~= "table" or type(pets.petStore) ~= "table" then
|
||||
return
|
||||
end
|
||||
local petStore = pets.petStore
|
||||
if type(petStore.activeById) ~= "table" or type(petStore.stableSlotIndex) ~= "table" then
|
||||
return
|
||||
end
|
||||
|
||||
local authoritativeByPetId = {}
|
||||
for slotNumber, petId in pairs(petStore.stableSlotIndex) do
|
||||
local numericSlot = tonumber(slotNumber)
|
||||
local mappedPetId = tostring(petId or "")
|
||||
if numericSlot and numericSlot > 0 and mappedPetId ~= "" and type(petStore.activeById[mappedPetId]) == "table" then
|
||||
if not slotCount or numericSlot <= slotCount then
|
||||
authoritativeByPetId[mappedPetId] = numericSlot
|
||||
else
|
||||
petStore.stableSlotIndex[slotNumber] = nil
|
||||
end
|
||||
else
|
||||
petStore.stableSlotIndex[slotNumber] = nil
|
||||
end
|
||||
end
|
||||
|
||||
for petId, row in pairs(petStore.activeById) do
|
||||
if type(row) == "table" then
|
||||
local resolvedPetId = tostring(petId)
|
||||
local authoritativeSlot = authoritativeByPetId[resolvedPetId]
|
||||
if authoritativeSlot then
|
||||
row.stableSlot = authoritativeSlot
|
||||
elseif tonumber(row.stableSlot) and tonumber(row.stableSlot) > 0 then
|
||||
row.stableSlot = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
pets.updatedAt = time()
|
||||
end
|
||||
|
||||
function MTH_PETS_RecordCurrentPetLearnedAbility(abilityName, rankNumber, source)
|
||||
local pets = MTH_PETS_GetRootStore()
|
||||
if type(pets) ~= "table" then
|
||||
@@ -2250,8 +2618,9 @@ local function MTH_PETS_MoveToHistory(pets, petId, source, context, reason)
|
||||
if row.signature then
|
||||
MTH_PETS_RemoveSignatureIndex(petStore, row.signature, resolvedPetId)
|
||||
end
|
||||
if row.guid and petStore.guidIndex[row.guid] == resolvedPetId then
|
||||
petStore.guidIndex[row.guid] = nil
|
||||
local guidKey = row.guid and MTH_PETS_CanonicalKey(row.guid) or nil
|
||||
if guidKey and petStore.guidIndex[guidKey] == resolvedPetId then
|
||||
petStore.guidIndex[guidKey] = nil
|
||||
end
|
||||
if row.stableSlot and petStore.stableSlotIndex[row.stableSlot] == resolvedPetId then
|
||||
petStore.stableSlotIndex[row.stableSlot] = nil
|
||||
@@ -2274,6 +2643,56 @@ local function MTH_PETS_MoveToHistory(pets, petId, source, context, reason)
|
||||
return true
|
||||
end
|
||||
|
||||
-- One-shot legacy cleanup (v3 migration): move "orphan" active rows into history.
|
||||
-- An orphan is an active row that is neither the current pet nor associated with a
|
||||
-- stable slot in any way. In normal play a pet is always either summoned or stabled,
|
||||
-- so such rows are abandoned pets that older code failed to archive. They are moved
|
||||
-- (not deleted): all tame history is preserved. Assigned to the forward-declared
|
||||
-- local so MTH_PETS_EnsureSchema (defined earlier) can call it.
|
||||
function MTH_PETS_ArchiveOrphanActiveRows(pets, reason)
|
||||
local petStore = type(pets) == "table" and pets.petStore or nil
|
||||
if type(petStore) ~= "table" or type(petStore.activeById) ~= "table" then
|
||||
return 0
|
||||
end
|
||||
local currentId = tostring(pets.currentPetId or petStore.activeCurrentId or "")
|
||||
local orphanIds = {}
|
||||
for petId, row in pairs(petStore.activeById) do
|
||||
if type(row) == "table" then
|
||||
local id = tostring(petId)
|
||||
local isCurrent = (currentId ~= "" and id == currentId)
|
||||
local inSlotIndex = false
|
||||
if type(petStore.stableSlotIndex) == "table" then
|
||||
for _, mappedId in pairs(petStore.stableSlotIndex) do
|
||||
if tostring(mappedId or "") == id then
|
||||
inSlotIndex = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
local hasStableSlot = tonumber(row.stableSlot) and tonumber(row.stableSlot) > 0
|
||||
local hasStableInfo = type(row.stableInfo) == "table"
|
||||
if not isCurrent and not inSlotIndex and not hasStableSlot and not hasStableInfo then
|
||||
table.insert(orphanIds, id)
|
||||
end
|
||||
end
|
||||
end
|
||||
local archived = 0
|
||||
for i = 1, table.getn(orphanIds) do
|
||||
local id = orphanIds[i]
|
||||
local row = petStore.activeById[id]
|
||||
if type(row) == "table" then
|
||||
row.migrationNote = "Auto-archived by 2.0 pet migration (orphaned active row)."
|
||||
if MTH_PETS_MoveToHistory(pets, id, "migration-v3", nil, reason or "migration-recovered") then
|
||||
archived = archived + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
if archived > 0 and MTH and MTH.DebugPrint then
|
||||
MTH:DebugPrint("Pet migration: archived " .. tostring(archived) .. " orphan pet row(s) to history.")
|
||||
end
|
||||
return archived
|
||||
end
|
||||
|
||||
function MTH_PETS_RecordPetAbandon(source, explicitName)
|
||||
local pets = MTH_PETS_GetRootStore()
|
||||
if type(pets) ~= "table" then
|
||||
@@ -2372,6 +2791,9 @@ function MTH_PETS_RecordPetRunaway(source, rawMessage)
|
||||
if MTH:IsMessageEnabled("petRanAway", true) then
|
||||
MTH:Print("YOUR PET " .. tostring(displayName) .. " HAS RUNAWAY FOREVER :-( Try to feed it better next time.")
|
||||
end
|
||||
if type(MTH_CelebrateRunaway) == "function" then
|
||||
MTH_CelebrateRunaway(displayName)
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
@@ -2661,13 +3083,16 @@ function MTH_PETS_RefreshCurrentPet()
|
||||
row.events = {}
|
||||
end
|
||||
|
||||
local eventType = "pet-acquired"
|
||||
if not (created and (not hadPetBeforeRefresh)) then
|
||||
if cp.id ~= nil and previousCurrentId ~= nil and cp.id ~= previousCurrentId then
|
||||
eventType = "pet-swapped"
|
||||
local eventType = nil
|
||||
local confirmedTameBeastId = MTH_PETS_ResolveConfirmedTameBeastId("refresh-current-pet", snapshot)
|
||||
if created and (not hadPetBeforeRefresh) then
|
||||
if confirmedTameBeastId then
|
||||
eventType = "pet-tamed"
|
||||
else
|
||||
eventType = nil -- routine update, don't record
|
||||
eventType = "pet-acquired"
|
||||
end
|
||||
elseif cp.id ~= nil and previousCurrentId ~= nil and cp.id ~= previousCurrentId then
|
||||
eventType = "pet-swapped"
|
||||
end
|
||||
|
||||
local eventContext = nil
|
||||
@@ -2679,28 +3104,11 @@ function MTH_PETS_RefreshCurrentPet()
|
||||
at = now,
|
||||
context = eventContext,
|
||||
})
|
||||
end
|
||||
|
||||
if eventType == "pet-acquired" then
|
||||
local hasTameRecord = (row.tameRecorded == true)
|
||||
or (tonumber(row.tamedAt) and tonumber(row.tamedAt) > 0)
|
||||
or (type(row.tameZone) == "string" and row.tameZone ~= "")
|
||||
if not hasTameRecord and type(eventContext) == "table" then
|
||||
row.tameRecorded = true
|
||||
row.tamedAt = row.tamedAt or eventContext.timestamp or now
|
||||
row.tameHunterLevel = row.tameHunterLevel or eventContext.hunterLevel
|
||||
row.tameZone = row.tameZone or eventContext.zone
|
||||
row.tameSubZone = row.tameSubZone or eventContext.subZone
|
||||
row.tameX = row.tameX or eventContext.x
|
||||
row.tameY = row.tameY or eventContext.y
|
||||
if tonumber(row.tameBeastId) == nil then
|
||||
local resolvedTameBeastId = MTH_PETS_ResolveTameBeastIdForRow(row, snapshot)
|
||||
if resolvedTameBeastId and resolvedTameBeastId > 0 then
|
||||
row.tameBeastId = resolvedTameBeastId
|
||||
end
|
||||
end
|
||||
if eventType == "pet-tamed" and type(MTH_Growl_OfferForTamedPet) == "function" then
|
||||
MTH_Growl_OfferForTamedPet(snapshot.name, snapshot.level)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if hadPendingTame and cp.id and cp.id ~= previousCurrentId then
|
||||
@@ -3117,6 +3525,10 @@ function MTH_ST_Scan(reason)
|
||||
if currentPetId and type(petsRoot.petStore) == "table" and type(petsRoot.petStore.activeById) == "table" then
|
||||
local currentRow = petsRoot.petStore.activeById[currentPetId]
|
||||
if type(currentRow) == "table" then
|
||||
MTH_ST_PrintStableDiag("scan currentRow petId=" .. tostring(currentPetId)
|
||||
.. " guid='" .. tostring(currentRow.guid or "") .. "'"
|
||||
.. " signature='" .. tostring(currentRow.signature or "") .. "'"
|
||||
.. " beastId='" .. tostring(currentRow.beastId or "") .. "'")
|
||||
local currentIcon = tostring(c1 or "")
|
||||
if currentIcon ~= "" then
|
||||
currentRow.icon = currentIcon
|
||||
@@ -3141,6 +3553,8 @@ function MTH_ST_Scan(reason)
|
||||
MTH_ST_PrintStableDiag("scan slots " .. table.concat(slotDiagParts, " | "))
|
||||
end
|
||||
|
||||
MTH_PETS_ReconcileStableSlots(petsRoot, slotCount)
|
||||
|
||||
MTH_PETS_MarkStableVisited(petsRoot, tostring(reason or "stable-scan"))
|
||||
|
||||
MTH_PETS_RefreshCurrentPet()
|
||||
@@ -3308,7 +3722,11 @@ local function MTH_PETS_HandleUnitPetTransition(source)
|
||||
if type(row.events) ~= "table" then
|
||||
row.events = {}
|
||||
end
|
||||
local confirmedTameBeastId = MTH_PETS_ResolveConfirmedTameBeastId(transitionSource, snapshot)
|
||||
local eventType = (not hadPet) and "pet-acquired" or "pet-updated"
|
||||
if not hadPet and confirmedTameBeastId then
|
||||
eventType = "pet-tamed"
|
||||
end
|
||||
-- Only record lifecycle events (acquire, rename, abandon), not routine updates
|
||||
if eventType ~= "pet-updated" then
|
||||
table.insert(row.events, {
|
||||
@@ -3318,6 +3736,9 @@ local function MTH_PETS_HandleUnitPetTransition(source)
|
||||
context = MTH_PETS_CaptureContext(),
|
||||
})
|
||||
end
|
||||
if eventType == "pet-tamed" and type(MTH_Growl_OfferForTamedPet) == "function" then
|
||||
MTH_Growl_OfferForTamedPet(snapshot.name, snapshot.level)
|
||||
end
|
||||
end
|
||||
if not hadPet and hasPendingTame then
|
||||
MTH_PETS_LogTame("Acquire completed; clearing pending tame attempt")
|
||||
@@ -3636,6 +4057,43 @@ function MTH_CommandPetsReset()
|
||||
return true
|
||||
end
|
||||
|
||||
-- Dev-only: force the v3 pet-store migration to run again (dedup by canonical key,
|
||||
-- level-free re-sign, index rebuild, orphan cleanup). Resets the pet schema flag
|
||||
-- and re-runs EnsureSchema. Does NOT touch MTH.version, so nothing is broadcast.
|
||||
-- Non-destructive: history is preserved. Invoke via /mth dev petmigrate or /run.
|
||||
function MTH_CommandPetsMigrate()
|
||||
local pets = MTH_PETS_GetRootStore()
|
||||
if type(pets) ~= "table" then
|
||||
MTH:Print("Pet migration failed: datastore unavailable.")
|
||||
return false
|
||||
end
|
||||
local store = MTH_PETS_GetStoreTables(pets)
|
||||
local function countTable(t)
|
||||
local n = 0
|
||||
if type(t) == "table" then for _ in pairs(t) do n = n + 1 end end
|
||||
return n
|
||||
end
|
||||
local beforeActive = countTable(store and store.activeById)
|
||||
local beforeHistory = countTable(store and store.historyById)
|
||||
|
||||
-- Force the one-shot path to re-run regardless of current stamp.
|
||||
pets.schemaVersion = 2
|
||||
MTH_PETS_EnsureSchema(pets)
|
||||
|
||||
store = MTH_PETS_GetStoreTables(pets)
|
||||
local afterActive = countTable(store and store.activeById)
|
||||
local afterHistory = countTable(store and store.historyById)
|
||||
pets.updatedAt = time()
|
||||
if type(MTH_CharSavedVariables) == "table" then
|
||||
MTH_CharSavedVariables.MTH_Pets = pets
|
||||
MTH_CharSavedVariables.petStore = pets.petStore
|
||||
end
|
||||
MTH:Print("Pet migration (v3) complete: active " .. tostring(beforeActive) .. "->" .. tostring(afterActive)
|
||||
.. ", history " .. tostring(beforeHistory) .. "->" .. tostring(afterHistory)
|
||||
.. ", schemaVersion=" .. tostring(pets.schemaVersion))
|
||||
return true
|
||||
end
|
||||
|
||||
function MTH_CommandPetsDump()
|
||||
local pets = MTH_PETS_GetRootStore()
|
||||
MTH_PETS_RefreshCurrentPet()
|
||||
@@ -3777,8 +4235,20 @@ function MTH_ST_HandleSpellcastEvent(evt, eventArg1, eventArg2)
|
||||
local isUnitSpellcast = (evt == "UNIT_SPELLCAST_START" or evt == "UNIT_SPELLCAST_STOP"
|
||||
or evt == "UNIT_SPELLCAST_FAILED" or evt == "UNIT_SPELLCAST_INTERRUPTED"
|
||||
or evt == "UNIT_SPELLCAST_CHANNEL_START" or evt == "UNIT_SPELLCAST_CHANNEL_STOP")
|
||||
local isSpellcast = isUnitSpellcast
|
||||
or evt == "SPELLCAST_START" or evt == "SPELLCAST_STOP"
|
||||
or evt == "SPELLCAST_FAILED" or evt == "SPELLCAST_INTERRUPTED"
|
||||
or evt == "SPELLCAST_CHANNEL_START" or evt == "SPELLCAST_CHANNEL_STOP"
|
||||
if not isSpellcast then
|
||||
return false
|
||||
end
|
||||
local castSpellName = eventArg1
|
||||
local hasPendingTame = type(MTH_PETS_LastTameAttempt) == "table"
|
||||
MTH_PETS_LogTame("Spellcast evt=" .. tostring(evt)
|
||||
.. " arg1='" .. tostring(eventArg1 or "") .. "'"
|
||||
.. " arg2='" .. tostring(eventArg2 or "") .. "'"
|
||||
.. " unitCast=" .. tostring(isUnitSpellcast)
|
||||
.. " pending=" .. tostring(hasPendingTame))
|
||||
if isUnitSpellcast then
|
||||
if eventArg1 ~= "player" then
|
||||
return true
|
||||
@@ -4115,12 +4585,6 @@ local function MTH_ST_OnEvent(frame, evt, eventArg1, eventArg2)
|
||||
if isStableUiEvt then
|
||||
stableDiagStart = MTH_ST_ProfileNow()
|
||||
MTH_ST_PrintStableDiag("evt=" .. tostring(evt) .. " phase=begin")
|
||||
if MTH_ST_Trace and not MTH_ST_Trace.enabled then
|
||||
MTH_ST_Trace.enabled = true
|
||||
MTH_ST_Trace.verbose = false
|
||||
MTH_ST_TraceResetCounters()
|
||||
MTH_ST_PrintStableDiag("trace=auto-enabled")
|
||||
end
|
||||
end
|
||||
|
||||
MTH_ST_DispatchEvent(frame, evt, eventArg1, eventArg2)
|
||||
|
||||
@@ -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()
|
||||
+192
-8
@@ -312,7 +312,7 @@ function SlashCmdList.MTH(msg, editbox)
|
||||
local lowerMsg = string.lower(msg)
|
||||
local _, _, lowerCmd, lowerArg = string.find(lowerMsg, "^(%S+)%s*(.-)%s*$")
|
||||
if msg == "" then
|
||||
MTH:Print("Available: /mth options, /mth book")
|
||||
MTH:Print("Available: /mth options, /mth book, /mth food")
|
||||
elseif lowerMsg == "err" or lowerMsg == "errors" or lowerMsg == "debug" then
|
||||
if MTH_DebugFrame and MTH_DebugFrame.Toggle then
|
||||
MTH_DebugFrame:Toggle()
|
||||
@@ -325,6 +325,57 @@ function SlashCmdList.MTH(msg, editbox)
|
||||
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)
|
||||
@@ -422,18 +473,151 @@ function SlashCmdList.MTH(msg, editbox)
|
||||
MTH:Print("|cffaaffaaArmed.|r Open any vendor, trainer, or stable master to capture their ID.")
|
||||
end
|
||||
end
|
||||
elseif lowerCmd == "bls" then
|
||||
if lowerArg == "reset" then
|
||||
if type(MTH_SavedVariables) == "table" then
|
||||
MTH_SavedVariables.beastLoreScan = { entries = {} }
|
||||
elseif lowerCmd == "dev" then
|
||||
if lowerArg == "petmigrate" then
|
||||
if type(MTH_CommandPetsMigrate) == "function" then
|
||||
MTH_CommandPetsMigrate()
|
||||
else
|
||||
MTH:Print("Pet migration command unavailable.")
|
||||
end
|
||||
MTH:Print("|cffff4444Beast Lore scan data wiped.|r All recorded beasts cleared.")
|
||||
else
|
||||
MTH:Print("Beast Lore Scan: |cffffff00/mth bls reset|r — wipe all recorded beast data")
|
||||
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
|
||||
MTH:Print("Unknown command: " .. tostring(msg))
|
||||
MTH:Print("Available: /mth options, /mth book, /mth npcid, /mth bls reset")
|
||||
MTH:Print("Available: /mth options, /mth book, /mth npcid, /mth food")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -6,6 +6,18 @@
|
||||
-- Uses MTH_DS_BeastSkins[familyName] generated by .tools/generators/gen_beast_skins.py
|
||||
-- Skin rendering: PlayerModel:SetUnit(CreatureDisplayInfo.ID)
|
||||
|
||||
local MTH_MV_Sauce = PizzaSauce
|
||||
|
||||
-- Fade a model frame in when a new skin is displayed, unless disabled.
|
||||
local function MTH_MV_AnimateModel(model)
|
||||
if not MTH_MV_Sauce or not model then return 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
|
||||
|
||||
local MTH_MV = {
|
||||
inited = false,
|
||||
family = nil,
|
||||
@@ -51,6 +63,8 @@ local function MTH_MV_DisplayCurrent()
|
||||
|
||||
MTH_MV_ApplyModel(MTH_MV.previewModel, 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)
|
||||
if MTH_MV.popupSkinLabel then
|
||||
|
||||
+157
-23
@@ -2,13 +2,16 @@ if type(MTH_HUNTERBOOK_TABS) ~= "table" then MTH_HUNTERBOOK_TABS = {} end
|
||||
|
||||
MTH_HUNTERBOOK_TABS.families = {
|
||||
headerLabel = "Families",
|
||||
columnLabels = { "Family", "Named", "Coords", "Abilities", "Diet" },
|
||||
columnLabels = { "Family", "Named", "Coords", "Abilities", "Diet", "HP", "dmg", "Arm" },
|
||||
columnLayout = {
|
||||
{ x = 10, width = 106, align = "LEFT" },
|
||||
{ x = 122, width = 52, align = "LEFT" },
|
||||
{ x = 176, width = 52, align = "LEFT" },
|
||||
{ x = 232, width = 364, align = "LEFT" },
|
||||
{ x = 532, width = 112, align = "LEFT" },
|
||||
{ x = 10, width = 100, align = "LEFT" },
|
||||
{ x = 114, width = 38, align = "LEFT" },
|
||||
{ x = 154, width = 38, align = "LEFT" },
|
||||
{ x = 196, width = 364, align = "LEFT" },
|
||||
{ x = 496, width = 102, align = "LEFT" },
|
||||
{ x = 602, width = 30, align = "CENTER" },
|
||||
{ x = 636, width = 36, align = "CENTER" },
|
||||
{ x = 674, width = 38, align = "CENTER" },
|
||||
},
|
||||
}
|
||||
|
||||
@@ -26,6 +29,28 @@ local function MTH_BOOKTAB_FamiliesTrim(value)
|
||||
return text
|
||||
end
|
||||
|
||||
local function MTH_BOOKTAB_FormatFamilyStat(value)
|
||||
local numeric = tonumber(value)
|
||||
if not numeric then
|
||||
return "-"
|
||||
end
|
||||
local colorPrefix = "|cFF4DA6FF"
|
||||
if numeric < 1 then
|
||||
colorPrefix = "|cFFFF4040"
|
||||
elseif numeric > 1 then
|
||||
colorPrefix = "|cFF40FF40"
|
||||
end
|
||||
local text = nil
|
||||
if MTH_BOOK_STATE and MTH_BOOK_STATE.familyStatsPercent == true then
|
||||
text = tostring(math.floor((numeric * 100) + 0.5)) .. "%"
|
||||
return colorPrefix .. text .. "|r"
|
||||
end
|
||||
text = string.format("%.3f", numeric)
|
||||
text = string.gsub(text, "(%..-)0+$", "%1")
|
||||
text = string.gsub(text, "%.$", "")
|
||||
return colorPrefix .. text .. "|r"
|
||||
end
|
||||
|
||||
local function MTH_BOOKTAB_ShouldDisplayAllDiet(familyName)
|
||||
local token = MTH_BOOKTAB_FamiliesSafeLower(MTH_BOOKTAB_FamiliesTrim(familyName))
|
||||
return token == "bears" or token == "boars"
|
||||
@@ -51,12 +76,41 @@ end
|
||||
function MTH_BOOKTAB_BuildFamiliesRows()
|
||||
local results = {}
|
||||
local families = MTH_DS_Families
|
||||
local beasts = MTH_DS_Beasts
|
||||
if type(families) ~= "table" then
|
||||
return results
|
||||
end
|
||||
|
||||
local abilityCounts = {}
|
||||
local canonicalMap = {}
|
||||
local familyCounts = {}
|
||||
|
||||
if type(beasts) == "table" then
|
||||
for _, beastRow in pairs(beasts) do
|
||||
if type(beastRow) == "table" then
|
||||
local familyName = MTH_BOOKTAB_FamiliesTrim(beastRow.family)
|
||||
if familyName ~= "" then
|
||||
local counts = familyCounts[familyName]
|
||||
if not counts then
|
||||
counts = { named = 0, coords = 0 }
|
||||
familyCounts[familyName] = counts
|
||||
end
|
||||
counts.named = counts.named + 1
|
||||
|
||||
if type(beastRow.coords) == "table" then
|
||||
for coordIndex = 1, table.getn(beastRow.coords) do
|
||||
local coord = beastRow.coords[coordIndex]
|
||||
if type(coord) == "table" then
|
||||
if coord[1] ~= nil and coord[2] ~= nil then
|
||||
counts.coords = counts.coords + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, familyRow in pairs(families) do
|
||||
if type(familyRow) == "table" and type(familyRow.abilities) == "table" then
|
||||
@@ -82,6 +136,7 @@ function MTH_BOOKTAB_BuildFamiliesRows()
|
||||
|
||||
for familyName, familyRow in pairs(families) do
|
||||
if type(familyRow) == "table" then
|
||||
local beastCounts = familyCounts[familyName] or nil
|
||||
MTH_BOOKTAB_FamiliesTrace("BUILD " .. tostring(familyName) .. " icon=" .. tostring(familyRow.icon))
|
||||
local abilities = {}
|
||||
local seen = {}
|
||||
@@ -171,6 +226,10 @@ function MTH_BOOKTAB_BuildFamiliesRows()
|
||||
end)
|
||||
|
||||
local dietText = ""
|
||||
local stats = type(familyRow.stats) == "table" and familyRow.stats or nil
|
||||
local healthStat = stats and tonumber(stats.health) or nil
|
||||
local damageStat = stats and tonumber(stats.damage) or nil
|
||||
local armorStat = stats and tonumber(stats.armor) or nil
|
||||
if MTH_BOOKTAB_ShouldDisplayAllDiet(familyName) then
|
||||
dietText = "ALL"
|
||||
else
|
||||
@@ -178,7 +237,8 @@ function MTH_BOOKTAB_BuildFamiliesRows()
|
||||
if type(familyRow.food) == "table" then
|
||||
for i = 1, table.getn(familyRow.food) do
|
||||
local food = MTH_BOOKTAB_FamiliesTrim(familyRow.food[i])
|
||||
if food ~= "" then
|
||||
local token = MTH_BOOKTAB_FamiliesSafeLower(food)
|
||||
if food ~= "" and token ~= "raw fish" and token ~= "raw meat" then
|
||||
table.insert(dietItems, string.upper(string.sub(food, 1, 1)) .. string.sub(food, 2))
|
||||
end
|
||||
end
|
||||
@@ -186,16 +246,22 @@ function MTH_BOOKTAB_BuildFamiliesRows()
|
||||
return MTH_BOOKTAB_FamiliesSafeLower(a) < MTH_BOOKTAB_FamiliesSafeLower(b)
|
||||
end)
|
||||
end
|
||||
dietText = table.concat(dietItems, ", ")
|
||||
dietText = table.concat(dietItems, "|")
|
||||
end
|
||||
|
||||
table.insert(results, {
|
||||
family = tostring(familyName),
|
||||
icon = familyRow.icon or nil,
|
||||
named = tonumber(familyRow.named) or 0,
|
||||
coords = tonumber(familyRow.coords) or 0,
|
||||
named = beastCounts and (tonumber(beastCounts.named) or 0) or 0,
|
||||
coords = beastCounts and (tonumber(beastCounts.coords) or 0) or 0,
|
||||
abilities = abilities,
|
||||
dietText = dietText,
|
||||
healthStat = healthStat,
|
||||
healthText = MTH_BOOKTAB_FormatFamilyStat(healthStat),
|
||||
damageStat = damageStat,
|
||||
damageText = MTH_BOOKTAB_FormatFamilyStat(damageStat),
|
||||
armorStat = armorStat,
|
||||
armorText = MTH_BOOKTAB_FormatFamilyStat(armorStat),
|
||||
})
|
||||
end
|
||||
end
|
||||
@@ -253,31 +319,76 @@ function MTH_BOOKTAB_EnsureFamiliesUI()
|
||||
ui.frame:SetPoint("BOTTOMRIGHT", listParent, "BOTTOMRIGHT", -6, 6)
|
||||
ui.frame:Hide()
|
||||
|
||||
local statsText = getglobal("MTH_BOOK_StatsText")
|
||||
local statsParent = statsText and statsText:GetParent() or nil
|
||||
if statsParent then
|
||||
ui.percentCheckbox = CreateFrame("CheckButton", "MTH_BOOK_FamiliesPercentCheckbox", statsParent, "UICheckButtonTemplate")
|
||||
ui.percentCheckbox:SetFrameStrata(statsParent:GetFrameStrata())
|
||||
ui.percentCheckbox:SetFrameLevel((statsParent:GetFrameLevel() or 0) + 20)
|
||||
ui.percentCheckbox:ClearAllPoints()
|
||||
if statsText then
|
||||
ui.percentCheckbox:SetPoint("LEFT", statsText, "RIGHT", 260, 0)
|
||||
else
|
||||
ui.percentCheckbox:SetPoint("TOPLEFT", statsParent, "TOPLEFT", 538, -526)
|
||||
end
|
||||
ui.percentCheckbox.label = ui.percentCheckbox:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
|
||||
ui.percentCheckbox.label:SetPoint("LEFT", ui.percentCheckbox, "RIGHT", 4, 0)
|
||||
ui.percentCheckbox.label:SetTextColor(1.00, 0.82, 0.00)
|
||||
ui.percentCheckbox.label:SetText("Show modifiers in %")
|
||||
ui.percentCheckbox:SetScript("OnClick", function()
|
||||
if not this then return end
|
||||
MTH_BOOK_STATE.familyStatsPercent = this:GetChecked() == 1
|
||||
MTH_BOOKTAB_RenderFamiliesList()
|
||||
end)
|
||||
ui.percentCheckbox:Hide()
|
||||
end
|
||||
|
||||
ui.headerFamily = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
|
||||
ui.headerFamily:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 4, -2)
|
||||
ui.headerFamily:SetTextColor(1.00, 0.82, 0.00)
|
||||
ui.headerFamily:SetText("Family")
|
||||
|
||||
ui.headerNamed = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
|
||||
ui.headerNamed:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 122, -2)
|
||||
ui.headerNamed:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 114, -2)
|
||||
ui.headerNamed:SetTextColor(1.00, 0.82, 0.00)
|
||||
ui.headerNamed:SetText("Named")
|
||||
|
||||
ui.headerCoords = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
|
||||
ui.headerCoords:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 176, -2)
|
||||
ui.headerCoords:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 154, -2)
|
||||
ui.headerCoords:SetTextColor(1.00, 0.82, 0.00)
|
||||
ui.headerCoords:SetText("Coords")
|
||||
|
||||
ui.headerAbilities = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
|
||||
ui.headerAbilities:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 232, -2)
|
||||
ui.headerAbilities:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 196, -2)
|
||||
ui.headerAbilities:SetTextColor(1.00, 0.82, 0.00)
|
||||
ui.headerAbilities:SetText("Abilities")
|
||||
|
||||
ui.headerDiet = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
|
||||
ui.headerDiet:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 532, -2)
|
||||
ui.headerDiet:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 496, -2)
|
||||
ui.headerDiet:SetTextColor(1.00, 0.82, 0.00)
|
||||
ui.headerDiet:SetText("Diet")
|
||||
|
||||
ui.headerHP = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
|
||||
ui.headerHP:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 602, -2)
|
||||
ui.headerHP:SetWidth(30)
|
||||
ui.headerHP:SetJustifyH("CENTER")
|
||||
ui.headerHP:SetTextColor(1.00, 0.82, 0.00)
|
||||
ui.headerHP:SetText("HP")
|
||||
|
||||
ui.headerDmg = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
|
||||
ui.headerDmg:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 636, -2)
|
||||
ui.headerDmg:SetWidth(36)
|
||||
ui.headerDmg:SetJustifyH("CENTER")
|
||||
ui.headerDmg:SetTextColor(1.00, 0.82, 0.00)
|
||||
ui.headerDmg:SetText("dmg")
|
||||
|
||||
ui.headerArm = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
|
||||
ui.headerArm:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 674, -2)
|
||||
ui.headerArm:SetWidth(38)
|
||||
ui.headerArm:SetJustifyH("CENTER")
|
||||
ui.headerArm:SetTextColor(1.00, 0.82, 0.00)
|
||||
ui.headerArm:SetText("Arm")
|
||||
|
||||
ui.rows = {}
|
||||
for i = 1, 28 do
|
||||
local row = CreateFrame("Frame", nil, ui.frame)
|
||||
@@ -298,24 +409,39 @@ function MTH_BOOKTAB_EnsureFamiliesUI()
|
||||
|
||||
row.family = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
|
||||
row.family:SetPoint("LEFT", row, "LEFT", 24, 0)
|
||||
row.family:SetWidth(88)
|
||||
row.family:SetWidth(82)
|
||||
row.family:SetJustifyH("LEFT")
|
||||
|
||||
row.named = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
|
||||
row.named:SetPoint("LEFT", row, "LEFT", 122, 0)
|
||||
row.named:SetWidth(52)
|
||||
row.named:SetPoint("LEFT", row, "LEFT", 114, 0)
|
||||
row.named:SetWidth(38)
|
||||
row.named:SetJustifyH("LEFT")
|
||||
|
||||
row.coords = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
|
||||
row.coords:SetPoint("LEFT", row, "LEFT", 176, 0)
|
||||
row.coords:SetWidth(52)
|
||||
row.coords:SetPoint("LEFT", row, "LEFT", 154, 0)
|
||||
row.coords:SetWidth(38)
|
||||
row.coords:SetJustifyH("LEFT")
|
||||
|
||||
row.diet = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
|
||||
row.diet:SetPoint("LEFT", row, "LEFT", 532, 0)
|
||||
row.diet:SetWidth(112)
|
||||
row.diet:SetPoint("LEFT", row, "LEFT", 496, 0)
|
||||
row.diet:SetWidth(102)
|
||||
row.diet:SetJustifyH("LEFT")
|
||||
|
||||
row.hp = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
|
||||
row.hp:SetPoint("LEFT", row, "LEFT", 602, 0)
|
||||
row.hp:SetWidth(30)
|
||||
row.hp:SetJustifyH("CENTER")
|
||||
|
||||
row.dmg = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
|
||||
row.dmg:SetPoint("LEFT", row, "LEFT", 636, 0)
|
||||
row.dmg:SetWidth(36)
|
||||
row.dmg:SetJustifyH("CENTER")
|
||||
|
||||
row.arm = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
|
||||
row.arm:SetPoint("LEFT", row, "LEFT", 674, 0)
|
||||
row.arm:SetWidth(38)
|
||||
row.arm:SetJustifyH("CENTER")
|
||||
|
||||
row.abilityButtons = {}
|
||||
for b = 1, 16 do
|
||||
local button = CreateFrame("Button", nil, row)
|
||||
@@ -379,9 +505,14 @@ function MTH_BOOKTAB_SetFamiliesUIVisible(visible)
|
||||
if detailParent then detailParent:Hide() end
|
||||
if detailText then detailText:Hide() end
|
||||
if openMapButton then openMapButton:Hide() end
|
||||
if ui.percentCheckbox then
|
||||
ui.percentCheckbox:SetChecked(MTH_BOOK_STATE and MTH_BOOK_STATE.familyStatsPercent == true and 1 or nil)
|
||||
ui.percentCheckbox:Show()
|
||||
end
|
||||
ui.frame:Show()
|
||||
else
|
||||
ui.frame:Hide()
|
||||
if ui.percentCheckbox then ui.percentCheckbox:Hide() end
|
||||
if listParent then listParent:Show() end
|
||||
if detailParent then detailParent:Show() end
|
||||
if detailText then detailText:Show() end
|
||||
@@ -411,9 +542,12 @@ function MTH_BOOKTAB_RenderFamiliesList()
|
||||
rowFrame.named:SetText(tostring(row.named or 0))
|
||||
rowFrame.coords:SetText(tostring(row.coords or 0))
|
||||
rowFrame.diet:SetText(tostring(row.dietText or "-"))
|
||||
rowFrame.hp:SetText(MTH_BOOKTAB_FormatFamilyStat(row.healthStat))
|
||||
rowFrame.dmg:SetText(MTH_BOOKTAB_FormatFamilyStat(row.damageStat))
|
||||
rowFrame.arm:SetText(MTH_BOOKTAB_FormatFamilyStat(row.armorStat))
|
||||
|
||||
local offsetX = 232
|
||||
local abilityMaxRight = 526
|
||||
local offsetX = 196
|
||||
local abilityMaxRight = 490
|
||||
for b = 1, table.getn(rowFrame.abilityButtons) do
|
||||
local button = rowFrame.abilityButtons[b]
|
||||
local ability = row.abilities and row.abilities[b] or nil
|
||||
|
||||
@@ -69,8 +69,14 @@ local function MTH_BOOKTAB_GetZoneAndSubzone(zoneId)
|
||||
local zid = tonumber(zoneId)
|
||||
if not zid then return "-", "-" end
|
||||
local zoneName = MTH_BOOK_GetZoneName(zid)
|
||||
local row = MTH_DS_Zones and (MTH_DS_Zones[zid] or MTH_DS_Zones[tostring(zid)])
|
||||
if row and row.parent and tonumber(row.parent) and tonumber(row.parent) ~= zid then
|
||||
-- Coord zone IDs are WMA; MTH_DS_Zones is keyed by pfQuest area IDs. Translate
|
||||
-- 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)
|
||||
if parentName and string.sub(parentName, 1, 5) ~= "Zone " then
|
||||
return parentName, zoneName
|
||||
@@ -105,6 +111,47 @@ function MTH_BOOKTAB_GetNPCZoneSummary(vendor)
|
||||
end
|
||||
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)
|
||||
local zid = tonumber(zoneId)
|
||||
if not zid or not vendor or not vendor.coords then
|
||||
|
||||
+110
-80
@@ -547,7 +547,75 @@ MTH_BOOK_NormalizeStableFoodLink = function(itemId, itemLink, itemName)
|
||||
return nil
|
||||
end
|
||||
|
||||
local function MTH_BOOK_FindStableFoodBagIcon(itemId, itemName)
|
||||
local getContainerNumSlots = (type(getglobal) == "function" and getglobal("GetContainerNumSlots")) or (_G and _G["GetContainerNumSlots"])
|
||||
local getContainerItemLink = (type(getglobal) == "function" and getglobal("GetContainerItemLink")) or (_G and _G["GetContainerItemLink"])
|
||||
local getContainerItemInfo = (type(getglobal) == "function" and getglobal("GetContainerItemInfo")) or (_G and _G["GetContainerItemInfo"])
|
||||
if type(getContainerNumSlots) ~= "function" or type(getContainerItemLink) ~= "function" or type(getContainerItemInfo) ~= "function" then
|
||||
return nil
|
||||
end
|
||||
local wantedItemId = tonumber(itemId)
|
||||
local wantedName = string.lower(tostring(itemName or ""))
|
||||
for bag = 0, 4 do
|
||||
local slotCount = tonumber(getContainerNumSlots(bag)) or 0
|
||||
for slot = 1, slotCount do
|
||||
local link = getContainerItemLink(bag, slot)
|
||||
if link then
|
||||
local matched = false
|
||||
if wantedItemId then
|
||||
local _, _, parsedId = string.find(tostring(link), "item:(%d+)")
|
||||
if tonumber(parsedId) == wantedItemId then
|
||||
matched = true
|
||||
end
|
||||
end
|
||||
if not matched and wantedName ~= "" then
|
||||
local _, _, parsedName = string.find(tostring(link), "|h%[([^%]]+)%]|h")
|
||||
if parsedName and string.lower(tostring(parsedName)) == wantedName then
|
||||
matched = true
|
||||
end
|
||||
end
|
||||
if matched then
|
||||
local bagTexture = getContainerItemInfo(bag, slot)
|
||||
if bagTexture and bagTexture ~= "" then
|
||||
if type(bagTexture) == "string" and string.find(bagTexture, "\\", 1, true) then
|
||||
return bagTexture
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function MTH_BOOK_ResolveStableFoodIcon(itemId, iconHint, itemName, itemLink)
|
||||
local function resolveTextureCandidate(value)
|
||||
if value == nil or value == "" then
|
||||
return nil
|
||||
end
|
||||
if type(value) == "number" then
|
||||
return nil
|
||||
end
|
||||
local text = tostring(value)
|
||||
if text == "" then
|
||||
return nil
|
||||
end
|
||||
if string.find(text, "|Hitem:", 1, true) or string.find(text, "^item:%d+") then
|
||||
return nil
|
||||
end
|
||||
if string.find(text, "^table:", 1, true) then
|
||||
return nil
|
||||
end
|
||||
if string.find(text, "\\", 1, true) then
|
||||
return text
|
||||
end
|
||||
local asNumber = tonumber(text)
|
||||
if asNumber then
|
||||
return nil
|
||||
end
|
||||
return "Interface\\Icons\\" .. text
|
||||
end
|
||||
|
||||
local linkText = tostring(itemLink or "")
|
||||
local linkItemId = nil
|
||||
if linkText ~= "" then
|
||||
@@ -559,104 +627,55 @@ local function MTH_BOOK_ResolveStableFoodIcon(itemId, iconHint, itemName, itemLi
|
||||
|
||||
local numericItemId = tonumber(itemId) or linkItemId
|
||||
|
||||
if linkText ~= "" and type(GetItemInfo) == "function" then
|
||||
local _, _, _, _, _, _, _, _, _, linkTexture = GetItemInfo(linkText)
|
||||
if linkTexture and linkTexture ~= "" then
|
||||
if type(linkTexture) == "number" then
|
||||
return linkTexture
|
||||
end
|
||||
if string.find(tostring(linkTexture), "\\", 1, true) then
|
||||
return tostring(linkTexture)
|
||||
end
|
||||
local asNumber = tonumber(tostring(linkTexture))
|
||||
if asNumber then
|
||||
return asNumber
|
||||
end
|
||||
return "Interface\\Icons\\" .. tostring(linkTexture)
|
||||
end
|
||||
end
|
||||
|
||||
if type(iconHint) == "number" then
|
||||
return iconHint
|
||||
end
|
||||
|
||||
local iconHintText = tostring(iconHint or "")
|
||||
if iconHintText ~= "" then
|
||||
local numericHint = tonumber(iconHintText)
|
||||
if numericHint then
|
||||
return numericHint
|
||||
end
|
||||
end
|
||||
|
||||
local iconPath = iconHintText
|
||||
if iconPath ~= "" then
|
||||
if string.find(iconPath, "\\", 1, true) then
|
||||
return iconPath
|
||||
end
|
||||
return "Interface\\Icons\\" .. iconPath
|
||||
end
|
||||
|
||||
if numericItemId and type(GetItemInfo) == "function" then
|
||||
local _, _, _, _, _, _, _, _, _, infoTexture = GetItemInfo(numericItemId)
|
||||
if infoTexture and infoTexture ~= "" then
|
||||
if type(infoTexture) == "number" then
|
||||
return infoTexture
|
||||
end
|
||||
if string.find(tostring(infoTexture), "\\", 1, true) then
|
||||
return tostring(infoTexture)
|
||||
end
|
||||
local asNumber = tonumber(tostring(infoTexture))
|
||||
if asNumber then
|
||||
return asNumber
|
||||
end
|
||||
return "Interface\\Icons\\" .. tostring(infoTexture)
|
||||
local resolvedInfoTexture = resolveTextureCandidate(infoTexture)
|
||||
if resolvedInfoTexture then
|
||||
return resolvedInfoTexture
|
||||
end
|
||||
end
|
||||
|
||||
if numericItemId and type(GetItemIcon) == "function" then
|
||||
local directIcon = GetItemIcon(numericItemId)
|
||||
if directIcon and directIcon ~= "" then
|
||||
if type(directIcon) == "number" then
|
||||
return directIcon
|
||||
end
|
||||
if string.find(tostring(directIcon), "\\", 1, true) then
|
||||
return tostring(directIcon)
|
||||
end
|
||||
local asNumber = tonumber(tostring(directIcon))
|
||||
if asNumber then
|
||||
return asNumber
|
||||
end
|
||||
return "Interface\\Icons\\" .. tostring(directIcon)
|
||||
local resolvedDirectIcon = resolveTextureCandidate(directIcon)
|
||||
if resolvedDirectIcon then
|
||||
return resolvedDirectIcon
|
||||
end
|
||||
end
|
||||
|
||||
local resolvedHintIcon = resolveTextureCandidate(iconHint)
|
||||
if resolvedHintIcon then
|
||||
return resolvedHintIcon
|
||||
end
|
||||
|
||||
if linkText ~= "" and type(GetItemInfo) == "function" then
|
||||
local _, _, _, _, _, _, _, _, _, linkTexture = GetItemInfo(linkText)
|
||||
local resolvedLinkTexture = resolveTextureCandidate(linkTexture)
|
||||
if resolvedLinkTexture then
|
||||
return resolvedLinkTexture
|
||||
end
|
||||
end
|
||||
|
||||
if itemName and itemName ~= "" and type(GetItemIcon) == "function" then
|
||||
local namedIcon = GetItemIcon(itemName)
|
||||
if namedIcon and namedIcon ~= "" then
|
||||
if type(namedIcon) == "number" then
|
||||
return namedIcon
|
||||
end
|
||||
if string.find(tostring(namedIcon), "\\", 1, true) then
|
||||
return tostring(namedIcon)
|
||||
end
|
||||
local asNumber = tonumber(tostring(namedIcon))
|
||||
if asNumber then
|
||||
return asNumber
|
||||
end
|
||||
return "Interface\\Icons\\" .. tostring(namedIcon)
|
||||
local resolvedNamedIcon = resolveTextureCandidate(namedIcon)
|
||||
if resolvedNamedIcon then
|
||||
return resolvedNamedIcon
|
||||
end
|
||||
end
|
||||
|
||||
if numericItemId and type(MTH_DS_Items) == "table" and type(MTH_DS_Items[numericItemId]) == "table" then
|
||||
local dsIcon = tostring(MTH_DS_Items[numericItemId].icon or "")
|
||||
if dsIcon ~= "" then
|
||||
if string.find(dsIcon, "\\", 1, true) then
|
||||
return dsIcon
|
||||
end
|
||||
return "Interface\\Icons\\" .. dsIcon
|
||||
local resolvedDsIcon = resolveTextureCandidate(MTH_DS_Items[numericItemId].icon)
|
||||
if resolvedDsIcon then
|
||||
return resolvedDsIcon
|
||||
end
|
||||
end
|
||||
|
||||
local bagIcon = MTH_BOOK_FindStableFoodBagIcon(numericItemId, itemName)
|
||||
if bagIcon then
|
||||
return bagIcon
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -1226,6 +1245,17 @@ function MTH_BOOKTAB_RenderStableCards()
|
||||
local row = slotEntry and slotEntry.row or nil
|
||||
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:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 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 abilitiesText = MTH_BOOK_GetStableAbilitySummary(row.abilities)
|
||||
local legacyTameContext = type(row.tameContext) == "table" and row.tameContext or nil
|
||||
local hasRecordedTame = row.tameRecorded == true
|
||||
local hasRecordedTame = type(MTH_PETS_HasVerifiedTameRecord) == "function" and MTH_PETS_HasVerifiedTameRecord(row) or (row.tameRecorded == true)
|
||||
if not hasRecordedTame and legacyTameContext then
|
||||
if legacyTameContext.name or legacyTameContext.zone or legacyTameContext.timestamp then
|
||||
hasRecordedTame = true
|
||||
|
||||
@@ -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
|
||||
+580
-70
File diff suppressed because it is too large
Load Diff
+20
-2
@@ -2,7 +2,7 @@
|
||||
..\FrameXML\UI.xsd">
|
||||
<Frame name="MTH_BOOK_Template" toplevel="true" frameStrata="DIALOG" movable="true" enableMouse="true" hidden="true" parent="UIParent" virtual="true">
|
||||
<Size>
|
||||
<AbsDimension x="760" y="560"/>
|
||||
<AbsDimension x="880" y="560"/>
|
||||
</Size>
|
||||
<Anchors>
|
||||
<Anchor point="CENTER"/>
|
||||
@@ -128,7 +128,7 @@
|
||||
</Frame>
|
||||
|
||||
<Frame name="$parentTabBar">
|
||||
<Size><AbsDimension x="728" y="24"/></Size>
|
||||
<Size><AbsDimension x="96" y="472"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="18" y="-44"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
@@ -188,6 +188,18 @@
|
||||
<Anchor point="LEFT" relativeTo="$parentSectionAmmoBags" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</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">
|
||||
<Size><AbsDimension x="180" y="20"/></Size>
|
||||
@@ -235,6 +247,12 @@
|
||||
</Anchors>
|
||||
</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">
|
||||
<Size><AbsDimension x="130" y="20"/></Size>
|
||||
<Anchors>
|
||||
|
||||
@@ -56,6 +56,8 @@ BINDING_HEADER_ZTrackHeader = MTH_L("BINDING_HEADER_ZTrackHeader", "[MetaHunt] Z
|
||||
BINDING_HEADER_ZTrapHeader = MTH_L("BINDING_HEADER_ZTrapHeader", "[MetaHunt] ZTrap Buttons")
|
||||
BINDING_HEADER_ZPetHeader = MTH_L("BINDING_HEADER_ZPetHeader", "[MetaHunt] ZPet Buttons")
|
||||
BINDING_HEADER_ZAmmoHeader = MTH_L("BINDING_HEADER_ZAmmoHeader", "[MetaHunt] ZAmmo Buttons")
|
||||
BINDING_HEADER_ExpAmmoHeader = MTH_L("BINDING_HEADER_ExpAmmoHeader", "[MetaHunt] MM Widget")
|
||||
BINDING_NAME_EXPAMMOACTION = MTH_L("BINDING_NAME_EXPAMMOACTION", "MM Widget Action")
|
||||
|
||||
function MTH:GetLocalization(key, default)
|
||||
return MTH_L(key, default)
|
||||
|
||||
+154
-45
@@ -5,6 +5,7 @@ MTH_Map = {
|
||||
activeSource = "focus",
|
||||
providers = {},
|
||||
nodesByZone = {},
|
||||
minimapNodesByZone = {},
|
||||
worldPins = {},
|
||||
minimapPins = {},
|
||||
zoneNameToId = {},
|
||||
@@ -24,10 +25,37 @@ MTH_Map = {
|
||||
nodeRevision = 0,
|
||||
minimapForceRefreshInterval = 1.0,
|
||||
verboseMiniReasons = false,
|
||||
minimapTickActive = 0.20,
|
||||
minimapTickActive = 0.05,
|
||||
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 = {
|
||||
[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 },
|
||||
@@ -122,15 +150,47 @@ local function MTH_Map_StripThePrefix(name)
|
||||
return nil
|
||||
end
|
||||
|
||||
local function MTH_Map_WmaToPfq(zoneId)
|
||||
if type(MTH_MAP_WMA_TO_PFQ) == "table" then
|
||||
return MTH_MAP_WMA_TO_PFQ[zoneId] or zoneId
|
||||
end
|
||||
return zoneId
|
||||
end
|
||||
|
||||
local function MTH_Map_PfqToWma(pfqId)
|
||||
if type(MTH_MAP_PFQ_TO_WMA) == "table" then
|
||||
return MTH_MAP_PFQ_TO_WMA[pfqId] or pfqId
|
||||
end
|
||||
return pfqId
|
||||
end
|
||||
|
||||
local function MTH_Map_GetMinimapSizes()
|
||||
if pfDB and pfDB["minimap"] and MTH_DS_MinimapSizes then
|
||||
if not MTH_Map._mergedMinimapSizes then
|
||||
local merged = {}
|
||||
for zoneId, size in pairs(pfDB["minimap"]) do
|
||||
merged[zoneId] = size
|
||||
end
|
||||
for zoneId, size in pairs(MTH_DS_MinimapSizes) do
|
||||
if merged[zoneId] == nil then
|
||||
merged[zoneId] = size
|
||||
end
|
||||
end
|
||||
MTH_Map._mergedMinimapSizes = merged
|
||||
end
|
||||
return MTH_Map._mergedMinimapSizes
|
||||
elseif pfDB and pfDB["minimap"] then
|
||||
return pfDB["minimap"]
|
||||
end
|
||||
return MTH_DS_MinimapSizes or {}
|
||||
end
|
||||
|
||||
local function MTH_Map_GetZoneNameById(zoneId)
|
||||
local normalizedId = tonumber(zoneId) or zoneId
|
||||
if not normalizedId then return nil end
|
||||
|
||||
-- Translate WMA→pfQ first so WMA IDs don't accidentally hit wrong pfQ entries.
|
||||
local pfqId = normalizedId
|
||||
if type(MTH_MAP_WMA_TO_PFQ) == "table" then
|
||||
pfqId = MTH_MAP_WMA_TO_PFQ[normalizedId] or normalizedId
|
||||
end
|
||||
local pfqId = MTH_Map_WmaToPfq(normalizedId)
|
||||
|
||||
if MTH_DS_ZoneNamesFallback and MTH_DS_ZoneNamesFallback[pfqId] then
|
||||
return MTH_DS_ZoneNamesFallback[pfqId]
|
||||
@@ -364,6 +424,8 @@ end
|
||||
|
||||
function MTH_Map:ResolveZoneHierarchy(zoneId)
|
||||
local inputId = tonumber(zoneId)
|
||||
local pfqInputId = MTH_Map_WmaToPfq(inputId)
|
||||
local inputIsWma = inputId and pfqInputId ~= inputId or false
|
||||
local resolved = {
|
||||
inputId = inputId,
|
||||
zoneId = inputId,
|
||||
@@ -377,9 +439,9 @@ function MTH_Map:ResolveZoneHierarchy(zoneId)
|
||||
return resolved
|
||||
end
|
||||
|
||||
local sizes = MTH_DS_MinimapSizes or (pfDB and pfDB["minimap"]) or {}
|
||||
local sizes = MTH_Map_GetMinimapSizes()
|
||||
local visited = {}
|
||||
local cursor = inputId
|
||||
local cursor = pfqInputId
|
||||
local depth = 0
|
||||
|
||||
while cursor and not visited[cursor] and depth < 8 do
|
||||
@@ -390,15 +452,17 @@ function MTH_Map:ResolveZoneHierarchy(zoneId)
|
||||
end
|
||||
|
||||
local parent = tonumber(row.parent or row.continent)
|
||||
local outputCursor = inputIsWma and MTH_Map_PfqToWma(cursor) or cursor
|
||||
local outputParent = parent and (inputIsWma and MTH_Map_PfqToWma(parent) or parent) or nil
|
||||
if parent and not resolved.continentId then
|
||||
resolved.continentId = parent
|
||||
resolved.continentId = outputParent
|
||||
end
|
||||
|
||||
if parent and parent > 0 and parent ~= cursor then
|
||||
resolved.subzoneId = resolved.subzoneId or cursor
|
||||
resolved.parentId = parent
|
||||
resolved.subzoneId = resolved.subzoneId or outputCursor
|
||||
resolved.parentId = outputParent
|
||||
cursor = parent
|
||||
resolved.zoneId = cursor
|
||||
resolved.zoneId = inputIsWma and MTH_Map_PfqToWma(cursor) or cursor
|
||||
depth = depth + 1
|
||||
else
|
||||
break
|
||||
@@ -406,11 +470,17 @@ function MTH_Map:ResolveZoneHierarchy(zoneId)
|
||||
end
|
||||
|
||||
local renderZoneId = resolved.zoneId
|
||||
local pfqRenderId = tonumber(cursor or pfqInputId)
|
||||
local pfqParentId = resolved.parentId and MTH_Map_WmaToPfq(resolved.parentId) or nil
|
||||
if not sizes[renderZoneId] then
|
||||
if sizes[inputId] then
|
||||
renderZoneId = inputId
|
||||
elseif resolved.parentId and sizes[resolved.parentId] then
|
||||
renderZoneId = resolved.parentId
|
||||
elseif pfqRenderId and sizes[pfqRenderId] then
|
||||
renderZoneId = pfqRenderId
|
||||
elseif pfqParentId and sizes[pfqParentId] then
|
||||
renderZoneId = pfqParentId
|
||||
end
|
||||
end
|
||||
|
||||
@@ -419,21 +489,13 @@ function MTH_Map:ResolveZoneHierarchy(zoneId)
|
||||
return resolved
|
||||
end
|
||||
|
||||
-- Translate a pfQ AreaTable ID to a WMA ID (for zone matching against coord tuples).
|
||||
-- Falls back to the pfQ ID itself if no mapping exists (sub-zones, custom zones).
|
||||
local function MTH_Map_PfqToWma(pfqId)
|
||||
if type(MTH_MAP_PFQ_TO_WMA) == "table" then
|
||||
return MTH_MAP_PFQ_TO_WMA[pfqId] or pfqId
|
||||
end
|
||||
return pfqId
|
||||
end
|
||||
|
||||
function MTH_Map:GetCurrentMapID()
|
||||
function MTH_Map:GetCurrentMapID(space)
|
||||
local continent = GetCurrentMapContinent and GetCurrentMapContinent() or nil
|
||||
local zone = GetCurrentMapZone and GetCurrentMapZone() or nil
|
||||
local zoneName = nil
|
||||
local mapId = nil
|
||||
local worldMapShown = WorldMapFrame and WorldMapFrame.IsShown and WorldMapFrame:IsShown() or false
|
||||
local preferPfq = space == "pfq"
|
||||
|
||||
if continent and zone and continent > 0 and zone > 0 and GetMapZones then
|
||||
if not self.mapZoneCache[continent] then
|
||||
@@ -444,7 +506,9 @@ function MTH_Map:GetCurrentMapID()
|
||||
if mapId then
|
||||
local resolved = self:ResolveZoneHierarchy(mapId)
|
||||
local normalized = resolved.zoneId or mapId
|
||||
normalized = MTH_Map_PfqToWma(normalized)
|
||||
if not preferPfq then
|
||||
normalized = MTH_Map_PfqToWma(normalized)
|
||||
end
|
||||
self.lastMapContext = string.format("continent=%s zone=%s mapZone='%s' real=nil mapId=%s normalized=%s", tostring(continent), tostring(zone), tostring(zoneName), tostring(mapId), tostring(normalized))
|
||||
return normalized
|
||||
end
|
||||
@@ -460,7 +524,9 @@ function MTH_Map:GetCurrentMapID()
|
||||
mapId = self:GetMapIDByName(real)
|
||||
local resolved = self:ResolveZoneHierarchy(mapId)
|
||||
local normalized = resolved.zoneId or mapId
|
||||
normalized = MTH_Map_PfqToWma(normalized)
|
||||
if not preferPfq then
|
||||
normalized = MTH_Map_PfqToWma(normalized)
|
||||
end
|
||||
self.lastMapContext = string.format("continent=%s zone=%s mapZone='%s' real='%s' mapId=%s normalized=%s", tostring(continent), tostring(zone), tostring(zoneName), tostring(real), tostring(mapId), tostring(normalized))
|
||||
return normalized
|
||||
end
|
||||
@@ -470,6 +536,30 @@ function MTH_Map:GetCurrentMapID()
|
||||
return nil
|
||||
end
|
||||
|
||||
function MTH_Map:GetCurrentMinimapMapID()
|
||||
local realZoneName = GetRealZoneText and GetRealZoneText() or nil
|
||||
if realZoneName and realZoneName ~= "" then
|
||||
local mapId = self:GetMapIDByName(realZoneName)
|
||||
if mapId then
|
||||
return mapId
|
||||
end
|
||||
end
|
||||
|
||||
local continent = GetCurrentMapContinent and GetCurrentMapContinent() or nil
|
||||
local zone = GetCurrentMapZone and GetCurrentMapZone() or nil
|
||||
if continent and zone and continent > 0 and zone > 0 and GetMapZones then
|
||||
if not self.mapZoneCache[continent] then
|
||||
self.mapZoneCache[continent] = { GetMapZones(continent) }
|
||||
end
|
||||
local zoneName = self.mapZoneCache[continent][zone]
|
||||
if zoneName and zoneName ~= "" then
|
||||
return self:GetMapIDByName(zoneName)
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
function MTH_Map:RegisterProvider(key, provider)
|
||||
if not key or type(key) ~= "string" then return false end
|
||||
if type(provider) ~= "table" or type(provider.buildNodes) ~= "function" then return false end
|
||||
@@ -876,6 +966,7 @@ function MTH_Map:HideMinimapPins()
|
||||
for i = 1, table.getn(self.minimapPins) do
|
||||
if self.minimapPins[i] then self.minimapPins[i]:Hide() end
|
||||
end
|
||||
self._miniShownSet = nil
|
||||
end
|
||||
|
||||
function MTH_Map:HideAllPins()
|
||||
@@ -886,6 +977,7 @@ end
|
||||
function MTH_Map:RebuildNodes()
|
||||
self:BuildZoneLookup()
|
||||
self.nodesByZone = {}
|
||||
self.minimapNodesByZone = {}
|
||||
self.nodeRevision = (tonumber(self.nodeRevision) or 0) + 1
|
||||
self._lastWorldRenderKey = nil
|
||||
self._lastMiniState = nil
|
||||
@@ -899,15 +991,22 @@ function MTH_Map:RebuildNodes()
|
||||
local node = allNodes[i]
|
||||
if node and node.zoneId then
|
||||
local sourceZoneId = tonumber(node.zoneId)
|
||||
local resolved = self:ResolveZoneHierarchy(sourceZoneId)
|
||||
local zoneId = tonumber(resolved.zoneId or sourceZoneId)
|
||||
if zoneId then
|
||||
local worldResolved = self:ResolveZoneHierarchy(sourceZoneId)
|
||||
local worldZoneId = tonumber(worldResolved.zoneId or sourceZoneId)
|
||||
local pfqSourceZoneId = MTH_Map_WmaToPfq(sourceZoneId)
|
||||
local minimapZoneId = tonumber(pfqSourceZoneId)
|
||||
if worldZoneId then
|
||||
node.sourceZoneId = sourceZoneId
|
||||
node.zoneId = zoneId
|
||||
node.parentZoneId = resolved.parentId
|
||||
node.subzoneId = resolved.subzoneId
|
||||
if not self.nodesByZone[zoneId] then self.nodesByZone[zoneId] = {} end
|
||||
table.insert(self.nodesByZone[zoneId], node)
|
||||
node.zoneId = worldZoneId
|
||||
node.parentZoneId = worldResolved.parentId
|
||||
node.subzoneId = worldResolved.subzoneId
|
||||
node.minimapZoneId = minimapZoneId
|
||||
if not self.nodesByZone[worldZoneId] then self.nodesByZone[worldZoneId] = {} end
|
||||
table.insert(self.nodesByZone[worldZoneId], node)
|
||||
if minimapZoneId then
|
||||
if not self.minimapNodesByZone[minimapZoneId] then self.minimapNodesByZone[minimapZoneId] = {} end
|
||||
table.insert(self.minimapNodesByZone[minimapZoneId], node)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -970,6 +1069,7 @@ function MTH_Map:UpdateWorldMap()
|
||||
pin:ClearAllPoints()
|
||||
pin:SetPoint("CENTER", worldButton, "TOPLEFT", x, -y)
|
||||
pin:Show()
|
||||
MTH_Map_AnimatePin(pin)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -996,8 +1096,8 @@ function MTH_Map:UpdateMinimap()
|
||||
return
|
||||
end
|
||||
|
||||
local mapId = self:GetCurrentMapID()
|
||||
local nodes = mapId and self.nodesByZone[mapId] or nil
|
||||
local mapId = self:GetCurrentMinimapMapID()
|
||||
local nodes = mapId and self.minimapNodesByZone[mapId] or nil
|
||||
if not mapId or not nodes then
|
||||
self.lastMiniReason = "no-mapid-or-nodes"
|
||||
self._lastMiniState = nil
|
||||
@@ -1030,20 +1130,17 @@ function MTH_Map:UpdateMinimap()
|
||||
and miniState.zoom == zoom
|
||||
and miniState.nodeRevision == (self.nodeRevision or 0)
|
||||
then
|
||||
local dx = math.abs((xPlayer or 0) - (miniState.xPlayer or 0))
|
||||
local dy = math.abs((yPlayer or 0) - (miniState.yPlayer or 0))
|
||||
if dx < 0.05 and dy < 0.05 and now < (miniState.nextForceAt or 0) then
|
||||
self.lastMiniReason = self.verboseMiniReasons and "throttled-small-move" or "throttle"
|
||||
if xPlayer == miniState.xPlayer and yPlayer == miniState.yPlayer and now < (miniState.nextForceAt or 0) then
|
||||
self.lastMiniReason = self.verboseMiniReasons and "throttled-still" or "throttle"
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local minimapSizes = MTH_DS_MinimapSizes or (pfDB and pfDB["minimap"])
|
||||
local minimapSizes = MTH_Map_GetMinimapSizes()
|
||||
local mapSize = minimapSizes and minimapSizes[mapId]
|
||||
if not mapSize and type(MTH_MAP_WMA_TO_PFQ) == "table" then
|
||||
-- Fallback: try the pfQ key (for sub-zones not yet mapped to WMA)
|
||||
local pfqId = MTH_MAP_WMA_TO_PFQ[mapId]
|
||||
mapSize = pfqId and minimapSizes and minimapSizes[pfqId]
|
||||
if not mapSize then
|
||||
local wmaId = MTH_Map_PfqToWma(mapId)
|
||||
mapSize = wmaId and minimapSizes and minimapSizes[wmaId]
|
||||
end
|
||||
if not mapSize then
|
||||
self.lastMiniReason = self.verboseMiniReasons and "missing-minimap-size" or "missing-size"
|
||||
@@ -1083,6 +1180,8 @@ function MTH_Map:UpdateMinimap()
|
||||
local xDraw = Minimap:GetWidth() / xScale / 100
|
||||
local yDraw = Minimap:GetHeight() / yScale / 100
|
||||
local shown = 0
|
||||
local prevShown = self._miniShownSet or {}
|
||||
local nowShown = {}
|
||||
|
||||
for i = 1, table.getn(nodes) do
|
||||
local node = nodes[i]
|
||||
@@ -1104,9 +1203,16 @@ function MTH_Map:UpdateMinimap()
|
||||
pin:ClearAllPoints()
|
||||
pin:SetPoint("CENTER", Minimap, "CENTER", xPos, -yPos)
|
||||
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
|
||||
self._miniShownSet = nowShown
|
||||
|
||||
for i = shown + 1, table.getn(self.minimapPins) do
|
||||
if self.minimapPins[i] then self.minimapPins[i]:Hide() end
|
||||
@@ -1278,6 +1384,9 @@ function MTH_Map:Init()
|
||||
|
||||
self.controller:SetScript("OnEvent", function(frame, eventName)
|
||||
eventName = eventName or event
|
||||
if eventName == "WORLD_MAP_UPDATE" and MTH_Map._syncingMapContext then
|
||||
return
|
||||
end
|
||||
if eventName == "PLAYER_ENTERING_WORLD" or eventName == "ZONE_CHANGED" or eventName == "ZONE_CHANGED_NEW_AREA" or eventName == "MINIMAP_ZONE_CHANGED" then
|
||||
if SetMapToCurrentZone and (not WorldMapFrame or not WorldMapFrame:IsShown()) then
|
||||
SetMapToCurrentZone()
|
||||
@@ -1320,15 +1429,15 @@ function MTH_Map:Init()
|
||||
end
|
||||
|
||||
if MTH_Map.enabled and MTH_Map.showMinimap then
|
||||
local mapId = MTH_Map:GetCurrentMapID()
|
||||
local mapId = MTH_Map:GetCurrentMinimapMapID()
|
||||
if not mapId and (frame._mthMapRecoverAt or 0) <= now then
|
||||
frame._mthMapRecoverAt = now + 2.0
|
||||
if SetMapToCurrentZone and (not WorldMapFrame or not WorldMapFrame:IsShown()) then
|
||||
SetMapToCurrentZone()
|
||||
mapId = MTH_Map:GetCurrentMapID()
|
||||
mapId = MTH_Map:GetCurrentMinimapMapID()
|
||||
end
|
||||
end
|
||||
local nodes = mapId and MTH_Map.nodesByZone and MTH_Map.nodesByZone[mapId] or nil
|
||||
local nodes = mapId and MTH_Map.minimapNodesByZone and MTH_Map.minimapNodesByZone[mapId] or nil
|
||||
local hasNodes = nodes and table.getn(nodes) > 0
|
||||
frame._mthTick = now + (hasNodes and (tonumber(MTH_Map.minimapTickActive) or 0.20) or (tonumber(MTH_Map.minimapTickIdle) or 0.75))
|
||||
if hasNodes then
|
||||
|
||||
+26
-3
@@ -14,6 +14,19 @@ if not MTH_MinimapButton then
|
||||
MTH_MinimapButton = {}
|
||||
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)
|
||||
if MTH and MTH.GetLocalization then
|
||||
return MTH:GetLocalization(key, default)
|
||||
@@ -25,10 +38,19 @@ local function MTH_MB_GetStore()
|
||||
if not MTH_CharSavedVariables then
|
||||
MTH_CharSavedVariables = {}
|
||||
end
|
||||
if type(MTH_CharSavedVariables.minimapButton) ~= "table" then
|
||||
MTH_CharSavedVariables.minimapButton = {}
|
||||
local store
|
||||
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
|
||||
local store = MTH_CharSavedVariables.minimapButton
|
||||
if store.angle == nil then
|
||||
store.angle = 220
|
||||
end
|
||||
@@ -155,6 +177,7 @@ function MTH_MinimapButton:Initialize()
|
||||
|
||||
button:SetScript("OnClick", function()
|
||||
local mouseButton = arg1
|
||||
MTH_MB_AnimateClick()
|
||||
if mouseButton == "RightButton" then
|
||||
if type(MTH_OpenOptions) == "function" then
|
||||
MTH_OpenOptions("General")
|
||||
|
||||
@@ -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
@@ -589,7 +589,9 @@ local function MTH_AB_RefreshOptionsUI()
|
||||
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 = {
|
||||
subtype = subtype,
|
||||
index = index,
|
||||
@@ -597,14 +599,15 @@ local function MTH_AB_CreateRow(container, subtype, index, xBase, yOffset)
|
||||
}
|
||||
local nameBase = "MetaHuntOptionsAutoBuy_" .. subtype .. "_" .. tostring(index)
|
||||
|
||||
row.check = MTH_CreateCheckbox(container, nameBase .. "Check", "", yOffset, xBase + MTH_AB_LAYOUT.ROW_CHECK_X)
|
||||
row.label = container:CreateFontString(nameBase .. "Label", "ARTWORK", "GameFontNormalSmall")
|
||||
row.label:SetPoint("TOPLEFT", container, "TOPLEFT", xBase + MTH_AB_LAYOUT.ROW_CHECK_LABEL_X, yOffset - 5)
|
||||
row.check = MTH_CreateCheckbox(f, nameBase .. "Check", "", y, padX)
|
||||
row.label = getglobal(nameBase .. "Label") or f:CreateFontString(nameBase .. "Label", "ARTWORK", "GameFontNormalSmall")
|
||||
row.label:ClearAllPoints()
|
||||
row.label:SetPoint("TOPLEFT", f, "TOPLEFT", padX + 27, y - 5)
|
||||
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: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
|
||||
UIDropDownMenu_SetWidth(MTH_AB_LAYOUT.ROW_DROPDOWN_WIDTH, row.dropdown)
|
||||
end
|
||||
@@ -640,7 +643,8 @@ local function MTH_AB_CreateRow(container, subtype, index, xBase, yOffset)
|
||||
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")
|
||||
if dropButton then
|
||||
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: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:SetText("Stacks")
|
||||
|
||||
@@ -687,32 +692,18 @@ local function MTH_AB_BuildUI(container)
|
||||
return
|
||||
end
|
||||
|
||||
MTH_ClearContainer(container)
|
||||
local panel = MTH_Layout.Panel(container)
|
||||
MTH_AB_STATE.container = container
|
||||
MTH_AB_STATE.built = true
|
||||
|
||||
local controls = MTH_AB_STATE.controls
|
||||
|
||||
controls.title = container:CreateFontString("MetaHuntOptionsAutoBuyTitle", "ARTWORK", "GameFontHighlight")
|
||||
if controls.title then
|
||||
if controls.title.SetPoint then controls.title:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -10) end
|
||||
if controls.title.SetText then controls.title:SetText("Auto Buy") end
|
||||
end
|
||||
panel:Title("Auto Buy")
|
||||
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.",
|
||||
{ lines = 2, color = MTH_Layout.Theme.colText })
|
||||
|
||||
controls.body = container:CreateFontString("MetaHuntOptionsAutoBuyBody", "ARTWORK", "GameFontNormalSmall")
|
||||
if controls.body then
|
||||
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()
|
||||
controls.moduleEnabled = panel:TopCheckbox("MetaHuntOptionsAutoBuyModuleEnabled", "Enable Auto Buy module", {
|
||||
onClick = function()
|
||||
if not this or MTH_AB_STATE.syncing then return end
|
||||
if MTH and MTH.SetModuleEnabled then
|
||||
local ok, err = MTH:SetModuleEnabled("autobuy", MTH_AB_IsChecked(this))
|
||||
@@ -721,70 +712,44 @@ local function MTH_AB_BuildUI(container)
|
||||
end
|
||||
end
|
||||
MTH_AB_RefreshOptionsUI()
|
||||
end)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
controls.projectilesEnabled = MTH_CreateCheckbox(container, "MetaHuntOptionsAutoBuyProjectilesEnabled", "Enable for Projectiles", -74)
|
||||
if controls.projectilesEnabled then
|
||||
controls.projectilesEnabled:SetScript("OnClick", function()
|
||||
controls.projectilesEnabled = panel:TopCheckbox("MetaHuntOptionsAutoBuyProjectilesEnabled", "Enable for Projectiles (arrows & bullets)", {
|
||||
onClick = function()
|
||||
if not this or MTH_AB_STATE.syncing then return end
|
||||
local store = MTH_AB_EnsureConfig()
|
||||
store.projectiles.enabled = MTH_AB_IsChecked(this)
|
||||
MTH_AB_RefreshOptionsUI()
|
||||
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
|
||||
end,
|
||||
})
|
||||
|
||||
-- ===== LEFT: Arrows / RIGHT: Bullets (3 rules each) =====
|
||||
local arrowBox = panel:Box("left", "MetaHuntOptionsAutoBuyArrowsBox", "Arrows")
|
||||
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(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)
|
||||
controls.rows.arrow[i] = MTH_AB_CreateRow(arrowBox, "arrow", i)
|
||||
end
|
||||
|
||||
controls.petFoodTitle = container:CreateFontString("MetaHuntOptionsAutoBuyPetFoodTitle", "ARTWORK", "GameFontHighlight")
|
||||
if controls.petFoodTitle then
|
||||
controls.petFoodTitle:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_AB_LAYOUT.LEFT_SECTION_X, -300)
|
||||
controls.petFoodTitle:SetText("Pet Food")
|
||||
local bulletBox = panel:Box("right", "MetaHuntOptionsAutoBuyBulletsBox", "Bullets")
|
||||
for i = 1, 3 do
|
||||
controls.rows.bullet[i] = MTH_AB_CreateRow(bulletBox, "bullet", i)
|
||||
end
|
||||
|
||||
controls.petFoodEnabled = MTH_CreateCheckbox(container, "MetaHuntOptionsAutoBuyPetFoodEnabled", "Enable for Pet Food", -322, MTH_AB_LAYOUT.LEFT_SECTION_X)
|
||||
if controls.petFoodEnabled then
|
||||
controls.petFoodEnabled:SetScript("OnClick", function()
|
||||
-- ===== Pet Food =====
|
||||
local petBox = panel:Box(panel:ShorterSide(), "MetaHuntOptionsAutoBuyPetFoodBox", "Pet Food")
|
||||
|
||||
controls.petFoodEnabled = petBox:Checkbox("MetaHuntOptionsAutoBuyPetFoodEnabled", "Enable for Pet Food", {
|
||||
onClick = function()
|
||||
if not this or MTH_AB_STATE.syncing then return end
|
||||
local store = MTH_AB_EnsureConfig()
|
||||
store.petFood.enabled = MTH_AB_IsChecked(this)
|
||||
MTH_AB_RefreshOptionsUI()
|
||||
end)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
controls.petFoodQtyLabel = container:CreateFontString("MetaHuntOptionsAutoBuyPetFoodQtyLabel", "ARTWORK", "GameFontNormalSmall")
|
||||
if controls.petFoodQtyLabel then
|
||||
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")
|
||||
controls.petFoodQty = petBox:EditRow("MetaHuntOptionsAutoBuyPetFoodQtyLabel", "Quantity:",
|
||||
"MetaHuntOptionsAutoBuyPetFoodQty", 40, { numeric = true, height = 20 })
|
||||
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()
|
||||
if not this then return end
|
||||
local store = MTH_AB_EnsureConfig()
|
||||
@@ -798,43 +763,52 @@ local function MTH_AB_BuildUI(container)
|
||||
MTH_AB_SetPetFoodStacks(store, this:GetText())
|
||||
MTH_AB_RefreshOptionsUI()
|
||||
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
|
||||
|
||||
controls.petFoodQtySuffix = container:CreateFontString("MetaHuntOptionsAutoBuyPetFoodQtySuffix", "ARTWORK", "GameFontNormalSmall")
|
||||
if controls.petFoodQtySuffix then
|
||||
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()
|
||||
controls.petFoodScopeCurrent = petBox:Checkbox("MetaHuntOptionsAutoBuyPetFoodScopeCurrent", "Buy only for my current pet", {
|
||||
onClick = function()
|
||||
if not this or MTH_AB_STATE.syncing then return end
|
||||
local store = MTH_AB_EnsureConfig()
|
||||
store.petFood.scope = "current"
|
||||
MTH_AB_RefreshOptionsUI()
|
||||
end)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
controls.petFoodScopeAll = MTH_CreateCheckbox(container, "MetaHuntOptionsAutoBuyPetFoodScopeAll", "Buy for all my pets", -402, MTH_AB_LAYOUT.LEFT_SECTION_X + 28)
|
||||
if controls.petFoodScopeAll then
|
||||
controls.petFoodScopeAll:SetScript("OnClick", function()
|
||||
controls.petFoodScopeAll = petBox:Checkbox("MetaHuntOptionsAutoBuyPetFoodScopeAll", "Buy for all my pets", {
|
||||
onClick = function()
|
||||
if not this or MTH_AB_STATE.syncing then return end
|
||||
local store = MTH_AB_EnsureConfig()
|
||||
store.petFood.scope = "all"
|
||||
MTH_AB_RefreshOptionsUI()
|
||||
end)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
controls.petFoodPetLines = {}
|
||||
for i = 1, 5 do
|
||||
local rowY = -434 - ((i - 1) * 22)
|
||||
local icon = container:CreateTexture("MetaHuntOptionsAutoBuyPetFoodPetIcon" .. tostring(i), "ARTWORK")
|
||||
local f, y = petBox:Row(22)
|
||||
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: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")
|
||||
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:SetJustifyH("LEFT")
|
||||
text:SetTextColor(1, 1, 1)
|
||||
@@ -842,6 +816,7 @@ local function MTH_AB_BuildUI(container)
|
||||
controls.petFoodPetLines[i] = { icon = icon, text = text }
|
||||
end
|
||||
|
||||
panel:Finish()
|
||||
end
|
||||
|
||||
function MTH_SetupAutoBuyOptions()
|
||||
|
||||
+86
-166
@@ -16,26 +16,30 @@ local function MTH_AQ_EnsureStore()
|
||||
if type(MTH_CharSavedVariables) ~= "table" then
|
||||
MTH_CharSavedVariables = {}
|
||||
end
|
||||
if type(MTH_CharSavedVariables.autoquest) ~= "table" then
|
||||
MTH_CharSavedVariables.autoquest = {}
|
||||
local store = nil
|
||||
if MTH and MTH.GetModuleCharSavedVariables then
|
||||
store = MTH:GetModuleCharSavedVariables("autoquest")
|
||||
end
|
||||
local store = MTH_CharSavedVariables.autoquest
|
||||
if type(MTH_CharSavedVariables.questautomation) == "table" then
|
||||
local legacy = MTH_CharSavedVariables.questautomation
|
||||
if store.scorpokDrazial == nil and legacy.scorpokDrazial ~= nil then
|
||||
store.scorpokDrazial = legacy.scorpokDrazial and true or false
|
||||
if type(store) ~= "table" then
|
||||
if type(MTH_CharSavedVariables.modules) ~= "table" then
|
||||
MTH_CharSavedVariables.modules = {}
|
||||
end
|
||||
if store.scorpokTooltip == nil and legacy.scorpokTooltip ~= nil then
|
||||
store.scorpokTooltip = legacy.scorpokTooltip and true or false
|
||||
if type(MTH_CharSavedVariables.modules.autoquest) ~= "table" then
|
||||
MTH_CharSavedVariables.modules.autoquest = {}
|
||||
end
|
||||
store = MTH_CharSavedVariables.modules.autoquest
|
||||
end
|
||||
if store.scorpokDrazial == nil then
|
||||
store.scorpokDrazial = false
|
||||
if type(store.settings) ~= "table" then
|
||||
store.settings = {}
|
||||
end
|
||||
if store.arrowsForSissies == nil then
|
||||
store.arrowsForSissies = false
|
||||
local settings = store.settings
|
||||
if settings.scorpokDrazial == nil then
|
||||
settings.scorpokDrazial = false
|
||||
end
|
||||
return store
|
||||
if settings.arrowsForSissies == nil then
|
||||
settings.arrowsForSissies = false
|
||||
end
|
||||
return settings
|
||||
end
|
||||
|
||||
function MTH_SetupAutoQuestOptions()
|
||||
@@ -44,19 +48,10 @@ function MTH_SetupAutoQuestOptions()
|
||||
local container = MTH_GetFrame("MetaHuntOptionsAutoQuest")
|
||||
if not container then return end
|
||||
|
||||
MTH_ClearContainer(container)
|
||||
|
||||
local title = container:CreateFontString("MetaHuntOptionsAutoQuestTitle", "ARTWORK", "GameFontHighlight")
|
||||
title:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -10)
|
||||
title:SetText("Auto Quest")
|
||||
|
||||
local body = container:CreateFontString("MetaHuntOptionsAutoQuestBody", "ARTWORK", "GameFontNormalSmall")
|
||||
body:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -30)
|
||||
body:SetWidth(560)
|
||||
body:SetJustifyH("LEFT")
|
||||
body:SetJustifyV("TOP")
|
||||
body:SetTextColor(0.45, 0.75, 1)
|
||||
body:SetText("Automation for specific quest interactions.")
|
||||
local store = MTH_AQ_EnsureStore()
|
||||
if store.scorpokTooltip == nil then
|
||||
store.scorpokTooltip = false
|
||||
end
|
||||
|
||||
local moduleEnabled = true
|
||||
if MTH and MTH.GetModule then
|
||||
@@ -69,10 +64,14 @@ function MTH_SetupAutoQuestOptions()
|
||||
elseif MTH and MTH.IsModuleEnabled then
|
||||
moduleEnabled = MTH:IsModuleEnabled("autoquest", false) and true or false
|
||||
end
|
||||
local moduleToggle = MTH_CreateCheckbox(container, "MetaHuntOptionsAutoQuestModuleToggle", "Enable Auto Quest module", -70, 20)
|
||||
if moduleToggle then
|
||||
moduleToggle:SetChecked(moduleEnabled and true or false)
|
||||
moduleToggle:SetScript("OnClick", function()
|
||||
|
||||
local panel = MTH_Layout.Panel(container)
|
||||
panel:Title("Auto Quest")
|
||||
panel:Note("Automation for specific quest interactions.")
|
||||
|
||||
panel:TopCheckbox("MetaHuntOptionsAutoQuestModuleToggle", "Enable Auto Quest module", {
|
||||
checked = moduleEnabled,
|
||||
onClick = function()
|
||||
if not this then return end
|
||||
local enabled = MTH_AQ_IsChecked(this)
|
||||
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 actual = (type(module) == "table" and module.enabled ~= nil) and (module.enabled and true or false) or enabled
|
||||
this:SetChecked(actual and true or false)
|
||||
end)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
local store = MTH_AQ_EnsureStore()
|
||||
if store.scorpokTooltip == nil then
|
||||
store.scorpokTooltip = false
|
||||
end
|
||||
|
||||
local sectionWidth = (container:GetWidth() or 600) - 40
|
||||
if sectionWidth < 360 then
|
||||
sectionWidth = 360
|
||||
end
|
||||
|
||||
local function ensureSection(name, sectionTitle, yOffset, height)
|
||||
local section = getglobal(name)
|
||||
if not section then
|
||||
section = CreateFrame("Frame", name, container, "OptionFrameBoxTemplate")
|
||||
end
|
||||
if not section then
|
||||
return nil
|
||||
end
|
||||
section:SetParent(container)
|
||||
section:ClearAllPoints()
|
||||
section:SetPoint("TOPLEFT", container, "TOPLEFT", 20, yOffset)
|
||||
section:SetWidth(sectionWidth)
|
||||
section:SetHeight(height)
|
||||
section:Show()
|
||||
|
||||
local titleFrame = getglobal(name .. "Title")
|
||||
if titleFrame then
|
||||
titleFrame:SetText(sectionTitle)
|
||||
end
|
||||
return section
|
||||
end
|
||||
|
||||
local function ensureCheckbox(section, name, label, yOffset, checked)
|
||||
local check = getglobal(name)
|
||||
if not check then
|
||||
check = CreateFrame("CheckButton", name, section, "OptionsCheckButtonTemplate")
|
||||
end
|
||||
if not check then
|
||||
return nil
|
||||
end
|
||||
check:SetParent(section)
|
||||
check:ClearAllPoints()
|
||||
check:SetPoint("TOPLEFT", section, "TOPLEFT", 12, yOffset)
|
||||
check:SetChecked(checked and true or false)
|
||||
check:Show()
|
||||
|
||||
local text = getglobal(name .. "Text")
|
||||
if text then
|
||||
text:SetText(label)
|
||||
end
|
||||
return check
|
||||
end
|
||||
|
||||
local scorpokSection = ensureSection("MetaHuntAutoQuestScorpokBox", "Salt of the Scorpok", -130, 112)
|
||||
if scorpokSection then
|
||||
local scorpokToggle = ensureCheckbox(
|
||||
scorpokSection,
|
||||
"MetaHuntOptionsAutoQuestScorpokToggle",
|
||||
"Enable SHIFT-rightclick for Bloodmage Drazial",
|
||||
-10,
|
||||
store.scorpokDrazial and true or false
|
||||
)
|
||||
if scorpokToggle then
|
||||
scorpokToggle:SetScript("OnClick", function()
|
||||
local enabled = MTH_AQ_IsChecked(this)
|
||||
store.scorpokDrazial = enabled and true or false
|
||||
if MTH and MTH.GetModuleCharSavedVariables then
|
||||
local moduleStore = MTH:GetModuleCharSavedVariables("autoquest")
|
||||
if type(moduleStore) == "table" then
|
||||
moduleStore.scorpokDrazial = store.scorpokDrazial and true or false
|
||||
end
|
||||
-- Salt of the Scorpok (left column)
|
||||
local scorpok = panel:Box("left", "MetaHuntAutoQuestScorpokBox", "Salt of the Scorpok")
|
||||
scorpok:Checkbox("MetaHuntOptionsAutoQuestScorpokToggle",
|
||||
"Enable SHIFT-rightclick for Bloodmage Drazial", {
|
||||
checked = store.scorpokDrazial and true or false,
|
||||
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
|
||||
local module = MTH and MTH.GetModule and MTH:GetModule("autoquest")
|
||||
if module and module.SetScorpokDrazialEnabled then
|
||||
module:SetScorpokDrazialEnabled(enabled)
|
||||
end
|
||||
local module = MTH and MTH.GetModule and MTH:GetModule("autoquest")
|
||||
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,
|
||||
})
|
||||
|
||||
local tooltipToggle = ensureCheckbox(
|
||||
scorpokSection,
|
||||
"MetaHuntOptionsAutoQuestScorpokTooltipToggle",
|
||||
"Enhance Tooltip on Drazial and related beasts (requires Tooltips module enabled)",
|
||||
-42,
|
||||
store.scorpokTooltip and true or false
|
||||
)
|
||||
if tooltipToggle then
|
||||
tooltipToggle:SetScript("OnClick", function()
|
||||
local enabled = MTH_AQ_IsChecked(this)
|
||||
store.scorpokTooltip = enabled and true or false
|
||||
if MTH and MTH.GetModuleCharSavedVariables then
|
||||
local moduleStore = MTH:GetModuleCharSavedVariables("autoquest")
|
||||
if type(moduleStore) == "table" then
|
||||
moduleStore.scorpokTooltip = store.scorpokTooltip and true or false
|
||||
end
|
||||
-- Arrows Are For Sissies (right column)
|
||||
local sissies = panel:Box("right", "MetaHuntAutoQuestSissiesBox", "Arrows Are For Sissies")
|
||||
sissies:Note("MetaHuntOptionsAutoQuestSissiesNote",
|
||||
"Note that you should not activate this if you are using LazyPig", { lines = 2 })
|
||||
sissies:Checkbox("MetaHuntOptionsAutoQuestSissiesToggle",
|
||||
"Enable SHIFT-rightclick for Artilleryman Sheldonore", {
|
||||
checked = store.arrowsForSissies and true or false,
|
||||
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)
|
||||
end
|
||||
end
|
||||
end
|
||||
local module = MTH and MTH.GetModule and MTH:GetModule("autoquest")
|
||||
if module and module.SetArrowsForSissiesEnabled then
|
||||
module:SetArrowsForSissiesEnabled(enabled)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
local sissiesSection = ensureSection("MetaHuntAutoQuestSissiesBox", "Arrows Are For Sissies", -255, 108)
|
||||
if sissiesSection then
|
||||
local sissiesNote = getglobal("MetaHuntOptionsAutoQuestSissiesNote")
|
||||
if not sissiesNote then
|
||||
sissiesNote = sissiesSection:CreateFontString("MetaHuntOptionsAutoQuestSissiesNote", "ARTWORK", "GameFontNormalSmall")
|
||||
end
|
||||
sissiesNote:ClearAllPoints()
|
||||
sissiesNote:SetPoint("TOPLEFT", sissiesSection, "TOPLEFT", 14, -10)
|
||||
sissiesNote:SetWidth(sectionWidth - 28)
|
||||
sissiesNote:SetJustifyH("LEFT")
|
||||
sissiesNote:SetTextColor(0.45, 0.75, 1)
|
||||
sissiesNote:SetText("Note that you should not activate this if you are using LazyPig")
|
||||
sissiesNote:Show()
|
||||
|
||||
local sissiesToggle = ensureCheckbox(
|
||||
sissiesSection,
|
||||
"MetaHuntOptionsAutoQuestSissiesToggle",
|
||||
"Enable SHIFT-rightclick for Artilleryman Sheldonore",
|
||||
-40,
|
||||
store.arrowsForSissies and true or false
|
||||
)
|
||||
if sissiesToggle then
|
||||
sissiesToggle:SetScript("OnClick", function()
|
||||
local enabled = MTH_AQ_IsChecked(this)
|
||||
store.arrowsForSissies = enabled and true or false
|
||||
if MTH and MTH.GetModuleCharSavedVariables then
|
||||
local moduleStore = MTH:GetModuleCharSavedVariables("autoquest")
|
||||
if type(moduleStore) == "table" then
|
||||
moduleStore.arrowsForSissies = store.arrowsForSissies and true or false
|
||||
end
|
||||
end
|
||||
local module = MTH and MTH.GetModule and MTH:GetModule("autoquest")
|
||||
if module and module.SetArrowsForSissiesEnabled then
|
||||
module:SetArrowsForSissiesEnabled(enabled)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
panel:Finish()
|
||||
end
|
||||
|
||||
+145
-178
@@ -252,8 +252,14 @@ local function MTH_CHRON_ApplyLiveProfile(profile)
|
||||
end
|
||||
|
||||
engine.profile = profile
|
||||
if engine.SetCandyBarGroupGrowth then
|
||||
engine:SetCandyBarGroupGrowth("MTHChronometer", profile.growup and true or false)
|
||||
if engine.SetCandyBarGroupGrowth and engine.SetCandyBarGroupPoint and engine.anchor then
|
||||
local growup = profile.growup and true or false
|
||||
if growup then
|
||||
engine:SetCandyBarGroupPoint("MTHChronometer", "BOTTOM", engine.anchor, "TOP", 0, 0)
|
||||
else
|
||||
engine:SetCandyBarGroupPoint("MTHChronometer", "TOP", engine.anchor, "BOTTOM", 0, 0)
|
||||
end
|
||||
engine:SetCandyBarGroupGrowth("MTHChronometer", growup)
|
||||
end
|
||||
if engine.SetCandyBarGroupVerticalSpacing then
|
||||
engine:SetCandyBarGroupVerticalSpacing("MTHChronometer", profile.spacing or 0)
|
||||
@@ -352,25 +358,14 @@ local function MTH_CHRON_NormalizeSection(tabKey)
|
||||
return "general"
|
||||
end
|
||||
|
||||
local function MTH_CHRON_RenderTimerSection(container, profile, title, items, bucket, opts)
|
||||
opts = opts or {}
|
||||
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
|
||||
|
||||
local function MTH_CHRON_RenderTimerBox(box, profile, items, bucket, prefix)
|
||||
prefix = tostring(prefix or "Main")
|
||||
if table.getn(items) == 0 then
|
||||
MTH_CHRON_MakeSmall(container, "MetaHuntOptionsChronometerSectionEmpty" .. prefix, "(none)", timerX + 8, timerY)
|
||||
box:Text("MetaHuntOptionsChronometerSectionEmpty" .. prefix, "(none)")
|
||||
return
|
||||
end
|
||||
|
||||
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 timerName = row
|
||||
local label = row
|
||||
@@ -381,11 +376,9 @@ local function MTH_CHRON_RenderTimerSection(container, profile, title, items, bu
|
||||
timerName = tostring(timerName or "")
|
||||
label = tostring(label or timerName)
|
||||
local controlName = "MetaHuntOptionsChronometerTimer" .. prefix .. tostring(bucket) .. tostring(i)
|
||||
local check = MTH_CreateCheckbox(container, controlName, label, timerY, timerX)
|
||||
if check then
|
||||
check:SetChecked(profile.disabledSpells[bucket][timerName] == nil and true or false)
|
||||
check:SetScript("OnClick", function()
|
||||
if not this then return end
|
||||
box:Checkbox(controlName, label, {
|
||||
checked = profile.disabledSpells[bucket][timerName] == nil,
|
||||
onClick = function()
|
||||
local currentlyDisabled = profile.disabledSpells[bucket][timerName] ~= nil
|
||||
if currentlyDisabled then
|
||||
profile.disabledSpells[bucket][timerName] = nil
|
||||
@@ -396,9 +389,8 @@ local function MTH_CHRON_RenderTimerSection(container, profile, title, items, bu
|
||||
end
|
||||
|
||||
MTH_CHRON_ApplyLiveProfile(profile)
|
||||
end)
|
||||
end
|
||||
timerY = timerY - 20
|
||||
end,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -455,210 +447,185 @@ function MTH_SetupChronometerOptions(tabKey)
|
||||
local container = MTH_GetFrame("MetaHuntOptionsChronometer")
|
||||
if not container then return end
|
||||
|
||||
MTH_ClearContainer(container)
|
||||
MTH_CHRON_STATE.controls = {}
|
||||
|
||||
local profile = MTH_CHRON_GetProfile()
|
||||
local _, class = UnitClass("player")
|
||||
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)
|
||||
if moduleCheck then
|
||||
local moduleEnabled = false
|
||||
do
|
||||
local module = MTH and MTH:GetModule("chronometer")
|
||||
moduleCheck:SetChecked(module and module.enabled and true or false)
|
||||
moduleCheck:SetScript("OnClick", function()
|
||||
if not this then return end
|
||||
local enabled = MTH_CHRON_IsChecked(this)
|
||||
moduleEnabled = module and module.enabled and true or false
|
||||
end
|
||||
panel:TopCheckbox("MetaHuntOptionsChronometerEnabled", "Enable Chronometer module", {
|
||||
checked = moduleEnabled,
|
||||
onClick = function()
|
||||
local enabled = this:GetChecked() == 1
|
||||
if MTH and MTH.SetModuleEnabled then
|
||||
MTH:SetModuleEnabled("chronometer", enabled)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
if section == "general" then
|
||||
MTH_CHRON_MakeTitle(container, "MetaHuntOptionsChronometerGeneralHeader", "General", 20, -84)
|
||||
|
||||
local ghostSlider = MTH_CreateSlider(container, "MetaHuntOptionsChronometerGhost", "Ghost Duration", 0, 30, 1, -112)
|
||||
if ghostSlider then
|
||||
ghostSlider:SetValue(tonumber(profile.ghost) or 0)
|
||||
ghostSlider.onChange = function(value)
|
||||
local genBox = panel:Box("left", "MetaHuntOptionsChronometerGeneralBox", "General")
|
||||
genBox:Slider("MetaHuntOptionsChronometerGhost", {
|
||||
label = "Ghost Duration", min = 0, max = 30, step = 1,
|
||||
value = tonumber(profile.ghost) or 0,
|
||||
onChange = function(value)
|
||||
profile.ghost = tonumber(value) or 0
|
||||
MTH_CHRON_ApplyLiveProfile(profile)
|
||||
end
|
||||
end
|
||||
|
||||
local killCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerFadeOnKill", "Fade on kill", -154)
|
||||
if killCheck then
|
||||
killCheck:SetChecked(profile.fadeonkill and true or false)
|
||||
killCheck:SetScript("OnClick", function()
|
||||
if not this then return end
|
||||
profile.fadeonkill = MTH_CHRON_IsChecked(this)
|
||||
end)
|
||||
end
|
||||
|
||||
local fadeCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerFadeOnFade", "Fade on aura fade", -179)
|
||||
if fadeCheck then
|
||||
fadeCheck:SetChecked(profile.fadeonfade and true or false)
|
||||
fadeCheck:SetScript("OnClick", function()
|
||||
if not this then return end
|
||||
profile.fadeonfade = MTH_CHRON_IsChecked(this)
|
||||
end)
|
||||
end
|
||||
|
||||
local selfCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerSelfBars", "Show self bars", -204)
|
||||
if selfCheck then
|
||||
selfCheck:SetChecked(profile.selfbars and true or false)
|
||||
selfCheck:SetScript("OnClick", function()
|
||||
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
|
||||
end,
|
||||
})
|
||||
genBox:Checkbox("MetaHuntOptionsChronometerFadeOnKill", "Fade on kill", {
|
||||
checked = profile.fadeonkill and true or false,
|
||||
onClick = function() profile.fadeonkill = this:GetChecked() == 1 end,
|
||||
})
|
||||
genBox:Checkbox("MetaHuntOptionsChronometerFadeOnFade", "Fade on aura fade", {
|
||||
checked = profile.fadeonfade and true or false,
|
||||
onClick = function() profile.fadeonfade = this:GetChecked() == 1 end,
|
||||
})
|
||||
genBox:Checkbox("MetaHuntOptionsChronometerSelfBars", "Show self bars", {
|
||||
checked = profile.selfbars and true or false,
|
||||
onClick = function() profile.selfbars = this:GetChecked() == 1 end,
|
||||
})
|
||||
genBox:Checkbox("MetaHuntOptionsChronometerOnlySelf", "Only self target", {
|
||||
checked = profile.onlyself and true or false,
|
||||
onClick = function() profile.onlyself = this:GetChecked() == 1 end,
|
||||
})
|
||||
elseif section == "bar" then
|
||||
MTH_CHRON_MakeTitle(container, "MetaHuntOptionsChronometerBarHeader", "Bars", 20, -84)
|
||||
|
||||
local runTestButton = MTH_CreateActionButton(container, "MetaHuntOptionsChronometerRunTest", "Run Test", 20, -112, 130, 22, function()
|
||||
local engine = MTH_ChronometerHunter
|
||||
if engine and engine.RunTest then
|
||||
engine:RunTest()
|
||||
end
|
||||
end)
|
||||
local anchorButton = MTH_CreateActionButton(container, "MetaHuntOptionsChronometerAnchor", "Toggle Anchor", 165, -112, 130, 22, function()
|
||||
local engine = MTH_ChronometerHunter
|
||||
if engine and engine.ToggleAnchor then
|
||||
engine:ToggleAnchor()
|
||||
end
|
||||
end)
|
||||
if runTestButton then runTestButton:Show() end
|
||||
if anchorButton then anchorButton:Show() end
|
||||
|
||||
local ctrlBox = panel:Box("left", "MetaHuntOptionsChronometerControlsBox", "Controls")
|
||||
ctrlBox:Buttons({
|
||||
{ name = "MetaHuntOptionsChronometerRunTest", label = "Run Test", width = 120,
|
||||
onClick = function()
|
||||
local engine = MTH_ChronometerHunter
|
||||
if engine and engine.RunTest then engine:RunTest() end
|
||||
end },
|
||||
{ name = "MetaHuntOptionsChronometerAnchor", label = "Toggle Anchor", width = 120,
|
||||
onClick = function()
|
||||
local engine = MTH_ChronometerHunter
|
||||
if engine and engine.ToggleAnchor then engine:ToggleAnchor() end
|
||||
end },
|
||||
})
|
||||
local anchorX = tonumber(profile.barposition and profile.barposition.x) 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)
|
||||
if widthSlider then
|
||||
widthSlider:SetValue(tonumber(profile.barwidth) or 220)
|
||||
widthSlider.onChange = function(value)
|
||||
local dimBox = panel:Box("left", "MetaHuntOptionsChronometerDimBox", "Dimensions")
|
||||
dimBox:Slider("MetaHuntOptionsChronometerBarWidth", {
|
||||
label = "Bar Width", min = 80, max = 320, step = 1,
|
||||
value = tonumber(profile.barwidth) or 220,
|
||||
onChange = function(value)
|
||||
profile.barwidth = tonumber(value) or 220
|
||||
MTH_CHRON_ApplyLiveProfile(profile)
|
||||
end
|
||||
end
|
||||
|
||||
local scaleSlider = MTH_CreateSlider(container, "MetaHuntOptionsChronometerBarScale", "Bar Scale", 0.5, 1.5, 0.1, -222)
|
||||
if scaleSlider then
|
||||
scaleSlider:SetValue(tonumber(profile.barscale) or 1)
|
||||
scaleSlider.onChange = function(value)
|
||||
end,
|
||||
})
|
||||
dimBox:Slider("MetaHuntOptionsChronometerBarScale", {
|
||||
label = "Bar Scale", min = 0.5, max = 1.5, step = 0.1,
|
||||
value = tonumber(profile.barscale) or 1,
|
||||
format = function(v) return string.format("%.1f", v) end,
|
||||
onChange = function(value)
|
||||
profile.barscale = tonumber(value) or 1
|
||||
MTH_CHRON_ApplyLiveProfile(profile)
|
||||
end
|
||||
end
|
||||
|
||||
local heightSlider = MTH_CreateSlider(container, "MetaHuntOptionsChronometerBarHeight", "Bar Height", 8, 30, 1, -264)
|
||||
if heightSlider then
|
||||
heightSlider:SetValue(tonumber(profile.barheight) or 16)
|
||||
heightSlider.onChange = function(value)
|
||||
end,
|
||||
})
|
||||
dimBox:Slider("MetaHuntOptionsChronometerBarHeight", {
|
||||
label = "Bar Height", min = 8, max = 30, step = 1,
|
||||
value = tonumber(profile.barheight) or 16,
|
||||
onChange = function(value)
|
||||
profile.barheight = tonumber(value) or 16
|
||||
MTH_CHRON_ApplyLiveProfile(profile)
|
||||
end
|
||||
end
|
||||
|
||||
local spacingSlider = MTH_CreateSlider(container, "MetaHuntOptionsChronometerBarSpacing", "Bar Spacing", 0, 15, 1, -306)
|
||||
if spacingSlider then
|
||||
spacingSlider:SetValue(tonumber(profile.spacing) or 0)
|
||||
spacingSlider.onChange = function(value)
|
||||
end,
|
||||
})
|
||||
dimBox:Slider("MetaHuntOptionsChronometerBarSpacing", {
|
||||
label = "Bar Spacing", min = 0, max = 15, step = 1,
|
||||
value = tonumber(profile.spacing) or 0,
|
||||
onChange = function(value)
|
||||
profile.spacing = tonumber(value) or 0
|
||||
MTH_CHRON_ApplyLiveProfile(profile)
|
||||
end
|
||||
end
|
||||
|
||||
local textSizeSlider = MTH_CreateSlider(container, "MetaHuntOptionsChronometerTextSize", "Text Size", 8, 20, 1, -348)
|
||||
if textSizeSlider then
|
||||
textSizeSlider:SetValue(tonumber(profile.textsize) or 10)
|
||||
textSizeSlider.onChange = function(value)
|
||||
end,
|
||||
})
|
||||
dimBox:Slider("MetaHuntOptionsChronometerTextSize", {
|
||||
label = "Text Size", min = 8, max = 20, step = 1,
|
||||
value = tonumber(profile.textsize) or 10,
|
||||
onChange = function(value)
|
||||
profile.textsize = tonumber(value) or 10
|
||||
MTH_CHRON_ApplyLiveProfile(profile)
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
MTH_CHRON_MakeSmall(container, "MetaHuntOptionsChronometerTextPatternLabel", "Bar Text ($s spell, $t target)", 20, -390)
|
||||
MTH_CHRON_MakeInput(container, "MetaHuntOptionsChronometerTextPattern", 20, -408, 220, 20, profile.text or "$t", function(newText)
|
||||
if not newText or newText == "" then
|
||||
newText = "$t"
|
||||
end
|
||||
profile.text = tostring(newText)
|
||||
MTH_CHRON_ApplyLiveProfile(profile)
|
||||
end)
|
||||
|
||||
local growCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerGrowUp", "Grow bars upward", -438)
|
||||
if growCheck then
|
||||
growCheck:SetChecked(profile.growup and true or false)
|
||||
growCheck:SetScript("OnClick", function()
|
||||
if not this then return end
|
||||
profile.growup = MTH_CHRON_IsChecked(this)
|
||||
local textBox = panel:Box("right", "MetaHuntOptionsChronometerTextBox", "Text & Growth")
|
||||
textBox:Note("MetaHuntOptionsChronometerTextPatternHint",
|
||||
"Bar text tokens: $s = spell, $t = target", { lines = 2 })
|
||||
local patternEdit = textBox:EditRow("MetaHuntOptionsChronometerTextPatternLabel", "Bar Text",
|
||||
"MetaHuntOptionsChronometerTextPattern", 150, { height = 20 })
|
||||
if patternEdit then
|
||||
patternEdit:SetText(tostring(profile.text or "$t"))
|
||||
patternEdit:SetScript("OnEnterPressed", function()
|
||||
local newText = this:GetText() or ""
|
||||
if newText == "" then newText = "$t" end
|
||||
profile.text = tostring(newText)
|
||||
MTH_CHRON_ApplyLiveProfile(profile)
|
||||
this:ClearFocus()
|
||||
end)
|
||||
end
|
||||
|
||||
local reverseCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerReverse", "Reverse bars", -463)
|
||||
if reverseCheck then
|
||||
reverseCheck:SetChecked(profile.reverse and true or false)
|
||||
reverseCheck:SetScript("OnClick", function()
|
||||
if not this then return end
|
||||
profile.reverse = MTH_CHRON_IsChecked(this)
|
||||
textBox:Checkbox("MetaHuntOptionsChronometerGrowUp", "Grow bars upward", {
|
||||
checked = profile.growup and true or false,
|
||||
onClick = function()
|
||||
profile.growup = this:GetChecked() == 1
|
||||
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 iconLeft = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerIconLeft", "Left", -514)
|
||||
local iconRight = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerIconRight", "Right", -538)
|
||||
if iconLeft and iconRight then
|
||||
iconLeft:SetChecked(profile.iconposition ~= "RIGHT")
|
||||
iconRight:SetChecked(profile.iconposition == "RIGHT")
|
||||
iconLeft:SetScript("OnClick", function()
|
||||
local iconBox = panel:Box("right", "MetaHuntOptionsChronometerIconBox", "Icon Position")
|
||||
local iconLeft, iconRight
|
||||
iconLeft = iconBox:Checkbox("MetaHuntOptionsChronometerIconLeft", "Left", {
|
||||
checked = profile.iconposition ~= "RIGHT",
|
||||
onClick = function()
|
||||
profile.iconposition = "LEFT"
|
||||
iconLeft:SetChecked(true)
|
||||
iconRight:SetChecked(nil)
|
||||
MTH_CHRON_ApplyLiveProfile(profile)
|
||||
end)
|
||||
iconRight:SetScript("OnClick", function()
|
||||
end,
|
||||
})
|
||||
iconRight = iconBox:Checkbox("MetaHuntOptionsChronometerIconRight", "Right", {
|
||||
checked = profile.iconposition == "RIGHT",
|
||||
onClick = function()
|
||||
profile.iconposition = "RIGHT"
|
||||
iconRight:SetChecked(true)
|
||||
iconLeft:SetChecked(nil)
|
||||
MTH_CHRON_ApplyLiveProfile(profile)
|
||||
end)
|
||||
end
|
||||
|
||||
end,
|
||||
})
|
||||
else
|
||||
local classSpells, classEvents, racial = MTH_CHRON_BuildTimerLists()
|
||||
if section == "classspells" then
|
||||
MTH_CHRON_RenderTimerSection(container, profile, "Hunter Spells", classSpells, class, {
|
||||
x = 20,
|
||||
y = -84,
|
||||
prefix = "ClassSpells",
|
||||
})
|
||||
MTH_CHRON_RenderTimerSection(container, profile, "Hunter Trinkets", MTH_CHRON_BuildHunterTrinketItems(classEvents), class, {
|
||||
x = 300,
|
||||
y = -84,
|
||||
prefix = "ClassTrinkets",
|
||||
})
|
||||
local spellsBox = panel:Box("left", "MetaHuntOptionsChronometerSpellsBox", "Hunter Spells")
|
||||
MTH_CHRON_RenderTimerBox(spellsBox, profile, classSpells, class, "ClassSpells")
|
||||
local trinketBox = panel:Box("right", "MetaHuntOptionsChronometerTrinketsBox", "Hunter Trinkets")
|
||||
MTH_CHRON_RenderTimerBox(trinketBox, profile, MTH_CHRON_BuildHunterTrinketItems(classEvents), class, "ClassTrinkets")
|
||||
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
|
||||
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
|
||||
|
||||
panel:Finish()
|
||||
end
|
||||
|
||||
if _G then
|
||||
|
||||
+30
-48
@@ -5,13 +5,6 @@ local MTH_CREDITS_READY = MTH_OptionsRequire and MTH_OptionsRequire("options-cre
|
||||
|
||||
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)
|
||||
if not fs or not fs.GetFont or not fs.SetFont then return end
|
||||
local path, size, flags = fs:GetFont()
|
||||
@@ -25,12 +18,12 @@ local function MTH_CreditsInsertLink(url)
|
||||
if link == "" then return end
|
||||
if type(MTH_InsertLinkToChat) == "function" and MTH_InsertLinkToChat(link) 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
|
||||
return
|
||||
end
|
||||
if MTH and MTH.Print then
|
||||
MTH:Print(MTH_CR_L("CREDITS_LINK_FALLBACK", "Link: ") .. link)
|
||||
MTH:Print("Link: " .. link)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -96,12 +89,8 @@ function MTH_SetupCreditsOptions()
|
||||
local container = MTH_GetFrame("MetaHuntOptionsCredits")
|
||||
if not container then return end
|
||||
|
||||
MTH_ClearContainer(container)
|
||||
|
||||
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 panel = MTH_Layout.Panel(container)
|
||||
local title = panel:Title("Credits")
|
||||
|
||||
local scrollFrame = MTH_GetFrame("MetaHuntOptionsCreditsScroll")
|
||||
if not scrollFrame then
|
||||
@@ -120,20 +109,27 @@ function MTH_SetupCreditsOptions()
|
||||
MTH_CreditsClearFrame(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,
|
||||
MTH_CR_L("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.\n"
|
||||
.. "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:"),
|
||||
"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"
|
||||
.. "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:",
|
||||
"GameFontNormalSmall", -10)
|
||||
|
||||
cursor = MTH_CreditsCreateText(content, cursor,
|
||||
MTH_CR_L("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."),
|
||||
"- MetaHunt animations are powered by the great PizzaSauce library of Pizzahawaii! Thank you man.",
|
||||
"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)
|
||||
|
||||
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)
|
||||
cursor = MTH_CreditsCreateLink(content,
|
||||
"https://www.wowinterface.com/downloads/info4160-FizzwidgetFeed-O-Matic.html",
|
||||
@@ -142,7 +138,7 @@ function MTH_SetupCreditsOptions()
|
||||
-4)
|
||||
|
||||
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)
|
||||
cursor = MTH_CreditsCreateLink(content,
|
||||
"https://legacy-wow.com/vanilla-addons/fizzwidget-hunter-helper/",
|
||||
@@ -151,7 +147,7 @@ function MTH_SetupCreditsOptions()
|
||||
-4)
|
||||
|
||||
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)
|
||||
cursor = MTH_CreditsCreateLink(content,
|
||||
"https://github.com/wigan91/Chronometer-TWoW",
|
||||
@@ -161,7 +157,7 @@ function MTH_SetupCreditsOptions()
|
||||
|
||||
|
||||
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)
|
||||
cursor = MTH_CreditsCreateLink(content,
|
||||
"https://github.com/wigan91/ICU-TWoW",
|
||||
@@ -170,43 +166,29 @@ function MTH_SetupCreditsOptions()
|
||||
-4)
|
||||
|
||||
cursor = MTH_CreditsCreateText(content, cursor,
|
||||
MTH_CR_L("CREDITS_ABOUT_MAP", "- The Map/Marker system is the one of pfQuest from Master Shagu. Unfortunately he is no more reachable for some months and I could not have some talk with him about this. I mostly let it untouched, I just made slight modifications so it can integrate better within Metahunt, and doesn't conflict with pfQuest."),
|
||||
"- 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)
|
||||
|
||||
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)
|
||||
|
||||
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,
|
||||
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"
|
||||
.. "Sources used to build the data include:"),
|
||||
"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"
|
||||
.. "Other sources used to build the data include:",
|
||||
"GameFontNormalSmall", -10)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
cursor = MTH_CreditsCreateText(content, cursor,
|
||||
MTH_CR_L("CREDITS_SIGNATURE", "\n"
|
||||
.. "Metasploit <Atlantis>, of Nordaanar."),
|
||||
"\n" .. "Metasploit <Atlantis>, of Nordaanar, now N'Zoth",
|
||||
"GameFontNormalSmall", -10)
|
||||
end
|
||||
|
||||
+235
-83
@@ -15,111 +15,263 @@ local function MTH_EA_OPT_GetCfg()
|
||||
return MTH_SavedVariables.modules["expammo"] or {}
|
||||
end
|
||||
|
||||
local MTH_EA_OPT_built = false
|
||||
|
||||
-- Helper: create a pre-wired checkbox
|
||||
local function MTH_EA_OPT_CB(container, name, label, yOff, isChecked, onClick)
|
||||
local cb = MTH_CreateCheckbox(container, name, label, yOff, 20)
|
||||
if cb then
|
||||
cb:SetChecked(isChecked and 1 or 0)
|
||||
cb:SetScript("OnClick", function()
|
||||
local v = this:GetChecked()
|
||||
onClick(v == 1 or v == true)
|
||||
end)
|
||||
end
|
||||
return cb
|
||||
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
|
||||
|
||||
-- Helper: section header label
|
||||
local function MTH_EA_OPT_Header(container, text, yOff)
|
||||
local fs = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
|
||||
fs:SetPoint("TOPLEFT", container, "TOPLEFT", 20, yOff)
|
||||
fs:SetText("|cffff9900" .. text .. "|r")
|
||||
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)
|
||||
if MTH_EA_OPT_built then return end
|
||||
MTH_EA_OPT_built = true
|
||||
|
||||
local cfg = MTH_EA_OPT_GetCfg()
|
||||
local showHint = cfg.showHint ~= false
|
||||
|
||||
-- Title
|
||||
local title = container:CreateFontString(nil, "ARTWORK", "GameFontHighlightLarge")
|
||||
title:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -16)
|
||||
title:SetText("MM Widget — Experimental Ammunition Tracker")
|
||||
local panel = MTH_Layout.Panel(container)
|
||||
panel:Title("MM Widget — Experimental Ammunition Tracker")
|
||||
|
||||
-- Short description
|
||||
local desc = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
|
||||
desc:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -46)
|
||||
desc:SetWidth(540)
|
||||
desc:SetJustifyH("LEFT")
|
||||
desc:SetJustifyV("TOP")
|
||||
desc:SetText(
|
||||
"Tracks the 1.18.1 ammo cycling mechanic.\n"
|
||||
.. "Aimed Shot starts a 60s cycle: "
|
||||
.. "|cffff8833Explosive|r \226\134\146 |cff33ff55Poisonous|r \226\134\146 |cff6633ffEnchanted|r\n"
|
||||
.. "Each state is consumed by its matching shot (Multi-Shot / Serpent Sting / Arcane Shot)."
|
||||
)
|
||||
|
||||
-- ── Section: Module ──────────────────────────────────────
|
||||
MTH_EA_OPT_Header(container, "Module", -112)
|
||||
-- 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
|
||||
MTH_EA_OPT_CB(container, "MTH_ExpAmmoModuleCB",
|
||||
"Enable MM Widget module",
|
||||
-132, moduleEnabled,
|
||||
function(v)
|
||||
if MTH and MTH.SetModuleEnabled then
|
||||
MTH:SetModuleEnabled("expammo", v)
|
||||
end
|
||||
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)
|
||||
end,
|
||||
})
|
||||
|
||||
-- ── Section: Tracker Visibility ─────────────────────────
|
||||
MTH_EA_OPT_Header(container, "Tracker Visibility", -166)
|
||||
-- ===== 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,
|
||||
})
|
||||
|
||||
MTH_EA_OPT_CB(container, "MTH_ExpAmmoShowLnLCB",
|
||||
"Show Lock and Load cell (top)",
|
||||
-186, cfg.showLnL ~= false,
|
||||
function(v) if MTH_ExpAmmo then MTH_ExpAmmo.SetShowLnL(v) end end)
|
||||
-- ===== 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,
|
||||
})
|
||||
|
||||
MTH_EA_OPT_CB(container, "MTH_ExpAmmoShowHintCB",
|
||||
"Show action hint cell (bottom)",
|
||||
-210, cfg.showHint ~= false,
|
||||
function(v) if MTH_ExpAmmo then MTH_ExpAmmo.SetShowHint(v) end end)
|
||||
-- ===== 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 })
|
||||
|
||||
MTH_EA_OPT_CB(container, "MTH_ExpAmmoHideOOCCB",
|
||||
"Hide widget when out of combat",
|
||||
-234, cfg.hideOOC == true,
|
||||
function(v) if MTH_ExpAmmo then MTH_ExpAmmo.SetHideOOC(v) end end)
|
||||
-- ===== 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 })
|
||||
|
||||
-- ── Section: Appearance ──────────────────────────────────
|
||||
MTH_EA_OPT_Header(container, "Appearance", -268)
|
||||
-- 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])
|
||||
|
||||
local sizeSlider = MTH_CreateSlider(container, "MTH_ExpAmmoCellSizeSlider",
|
||||
"Cell Size (px)", 20, 80, 4, -288)
|
||||
if sizeSlider then
|
||||
sizeSlider:SetValue(math.max(20, math.min(80, cfg.cellSize or 40)))
|
||||
sizeSlider.onChange = function(val)
|
||||
if MTH_ExpAmmo then MTH_ExpAmmo.SetCellSize(val) end
|
||||
end
|
||||
end
|
||||
-- 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 })
|
||||
|
||||
MTH_EA_OPT_CB(container, "MTH_ExpAmmoBigCDCB",
|
||||
"Large cooldown numbers (centered in cell)",
|
||||
-352, cfg.bigCD == true,
|
||||
function(v) if MTH_ExpAmmo then MTH_ExpAmmo.SetBigCD(v) end end)
|
||||
-- ===== 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)
|
||||
|
||||
-- ── Notes ───────────────────────────────────────────────
|
||||
local tip = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
|
||||
tip:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -388)
|
||||
tip:SetWidth(540)
|
||||
tip:SetJustifyH("LEFT")
|
||||
tip:SetTextColor(0.3, 0.7, 1.0)
|
||||
tip:SetText("Tip: hold ALT and drag the tracker widget to reposition it.")
|
||||
panel:Finish()
|
||||
|
||||
-- Apply initial gating state.
|
||||
setRightEnabled(showHint)
|
||||
end
|
||||
|
||||
function MTH_SetupExpAmmoOptions()
|
||||
|
||||
+173
-169
@@ -10,7 +10,7 @@ local MTH_FOM_STATE = {
|
||||
bindStatus = nil,
|
||||
bindButton = nil,
|
||||
bindClearButton = nil,
|
||||
bindingCapture = false,
|
||||
captureFrame = nil,
|
||||
ctrl = {},
|
||||
}
|
||||
|
||||
@@ -93,46 +93,86 @@ local function MTH_FOM_UpdateBindingStatus()
|
||||
if not bindStatus or not bindStatus.SetText then
|
||||
return
|
||||
end
|
||||
if MTH_FOM_STATE.bindingCapture then
|
||||
bindStatus:SetText(MTH_FOM_L("FOM_BIND_STATUS_PROMPT", "Feed key: press a key... (ESC to clear)"))
|
||||
else
|
||||
bindStatus:SetText(string.format(MTH_FOM_L("FOM_BIND_STATUS_VALUE", "Feed key: %s"), MTH_FOM_GetBindingDisplayText()))
|
||||
end
|
||||
bindStatus:SetText(string.format(MTH_FOM_L("FOM_BIND_STATUS_VALUE", "Feed key: %s"), MTH_FOM_GetBindingDisplayText()))
|
||||
end
|
||||
|
||||
local function MTH_FOM_StopBindingCapture()
|
||||
MTH_FOM_STATE.bindingCapture = false
|
||||
if MTH_FOM_STATE.captureFrame then
|
||||
MTH_FOM_STATE.captureFrame:Hide()
|
||||
end
|
||||
if MTH_FOM_STATE.bindButton then
|
||||
if MTH_FOM_STATE.bindButton.SetPropagateKeyboardInput then
|
||||
MTH_FOM_STATE.bindButton:SetPropagateKeyboardInput(true)
|
||||
end
|
||||
MTH_FOM_STATE.bindButton:EnableKeyboard(false)
|
||||
MTH_FOM_STATE.bindButton:SetText(MTH_FOM_L("FOM_BIND_SET_KEY", "Set Key"))
|
||||
end
|
||||
MTH_FOM_UpdateBindingStatus()
|
||||
end
|
||||
|
||||
local function MTH_FOM_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()
|
||||
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
|
||||
MTH_FOM_STATE.bindButton:SetText(MTH_FOM_L("FOM_BIND_PRESS_KEY", "Press key..."))
|
||||
MTH_FOM_STATE.bindButton:EnableKeyboard(true)
|
||||
if MTH_FOM_STATE.bindButton.SetPropagateKeyboardInput then
|
||||
MTH_FOM_STATE.bindButton:SetPropagateKeyboardInput(false)
|
||||
end
|
||||
end
|
||||
MTH_FOM_UpdateBindingStatus()
|
||||
end
|
||||
|
||||
local function MTH_FOM_BuildBindingChord(key)
|
||||
local chord = ""
|
||||
if IsControlKeyDown and IsControlKeyDown() and key ~= "LCTRL" and key ~= "RCTRL" then chord = chord .. "CTRL-" end
|
||||
if IsAltKeyDown and IsAltKeyDown() and key ~= "LALT" and key ~= "RALT" then chord = chord .. "ALT-" end
|
||||
if IsShiftKeyDown and IsShiftKeyDown() and key ~= "LSHIFT" and key ~= "RSHIFT" then chord = chord .. "SHIFT-" end
|
||||
return chord .. key
|
||||
end
|
||||
|
||||
local function MTH_FOM_SaveBinding(key)
|
||||
function MTH_FOM_SaveBinding(key)
|
||||
if not SetBinding or not SaveBindings or not GetBindingKey then
|
||||
if MTH and MTH.Print then
|
||||
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
|
||||
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()
|
||||
if not MTH_FOM_READY then return end
|
||||
local container = MTH_GetFrame("MetaHuntOptionsFeedOMatic")
|
||||
if not container then return end
|
||||
local rightColumnX = 300
|
||||
local yAdjust = -64
|
||||
|
||||
MTH_ClearContainer(container)
|
||||
MTH_FOM_STATE.ctrl = {}
|
||||
|
||||
local title = container:CreateFontString("MetaHuntOptionsFeedOMaticTitle", "ARTWORK", "GameFontHighlight")
|
||||
title:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -10)
|
||||
title:SetText(MTH_FOM_L("FOM_TITLE", "Feed-O-Matic"))
|
||||
local panel = MTH_Layout.Panel(container)
|
||||
panel:Title(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")
|
||||
statusNotice:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -34)
|
||||
statusNotice:SetWidth(600)
|
||||
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()
|
||||
MTH_FOM_STATE.enableCheck = panel:TopCheckbox("MetaHuntOptionsFeedOMaticEnabled",
|
||||
MTH_FOM_L("FOM_ENABLE_MODULE", "Enable FeedOMatic module"), {
|
||||
onClick = function()
|
||||
if not this then return end
|
||||
local enabled = this:GetChecked() == 1
|
||||
if MTH and MTH.SetModuleEnabled then
|
||||
@@ -236,143 +270,113 @@ function MTH_SetupFeedOMaticOptions()
|
||||
end
|
||||
end
|
||||
MTH_RefreshFeedOMaticOptions()
|
||||
end)
|
||||
MTH_FOM_STATE.enableCheck = moduleCheck
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
local leftHeader = container:CreateFontString("MetaHuntOptionsFeedOMaticGeneralHeader", "ARTWORK", "GameFontHighlight")
|
||||
leftHeader:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -62 + yAdjust)
|
||||
leftHeader:SetText(MTH_FOM_L("FOM_HEADER_GENERAL", "General"))
|
||||
-- ===== General (left column) =====
|
||||
local general = panel:Box("left", "MetaHuntOptionsFOMGeneralBox", MTH_FOM_L("FOM_HEADER_GENERAL", "General"))
|
||||
|
||||
local bindStatus = container:CreateFontString("MetaHuntOptionsFOMBindStatus", "ARTWORK", "GameFontNormalSmall")
|
||||
bindStatus:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -80 + yAdjust)
|
||||
bindStatus:SetText(string.format(MTH_FOM_L("FOM_BIND_STATUS_VALUE", "Feed key: %s"), MTH_FOM_GetBindingDisplayText()))
|
||||
MTH_FOM_STATE.bindStatus = bindStatus
|
||||
MTH_FOM_STATE.bindStatus = general:Text("MetaHuntOptionsFOMBindStatus",
|
||||
string.format(MTH_FOM_L("FOM_BIND_STATUS_VALUE", "Feed key: %s"), MTH_FOM_GetBindingDisplayText()))
|
||||
|
||||
local bindButton = CreateFrame("Button", "MetaHuntOptionsFOMBindButton", container, "UIPanelButtonTemplate")
|
||||
bindButton:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -100 + yAdjust)
|
||||
bindButton:SetWidth(90)
|
||||
bindButton:SetHeight(22)
|
||||
bindButton:SetText(MTH_FOM_L("FOM_BIND_SET_KEY", "Set Key"))
|
||||
bindButton:EnableKeyboard(false)
|
||||
bindButton:SetScript("OnClick", function()
|
||||
if MTH_FOM_STATE.bindingCapture then
|
||||
MTH_FOM_StopBindingCapture()
|
||||
else
|
||||
MTH_FOM_StartBindingCapture()
|
||||
end
|
||||
end)
|
||||
bindButton:SetScript("OnKeyDown", function()
|
||||
if not MTH_FOM_STATE.bindingCapture then
|
||||
return
|
||||
end
|
||||
local key = arg1
|
||||
if not key or key == "" then
|
||||
return
|
||||
end
|
||||
if key == "ESCAPE" then
|
||||
MTH_FOM_SaveBinding(nil)
|
||||
return
|
||||
end
|
||||
if key == "UNKNOWN" or key == "PRINTSCREEN" then
|
||||
return
|
||||
end
|
||||
MTH_FOM_SaveBinding(MTH_FOM_BuildBindingChord(key))
|
||||
end)
|
||||
MTH_FOM_STATE.bindButton = bindButton
|
||||
local bindButtons = general:Buttons({
|
||||
{ name = "MetaHuntOptionsFOMBindButton", label = MTH_FOM_L("FOM_BIND_SET_KEY", "Set Key"), width = 90,
|
||||
onClick = function()
|
||||
if MTH_FOM_STATE.captureFrame and MTH_FOM_STATE.captureFrame:IsShown() then
|
||||
MTH_FOM_StopBindingCapture()
|
||||
else
|
||||
MTH_FOM_StartBindingCapture()
|
||||
end
|
||||
end },
|
||||
{ name = "MetaHuntOptionsFOMBindClearButton", label = MTH_FOM_L("FOM_BIND_CLEAR", "Clear"), width = 70,
|
||||
onClick = function() MTH_FOM_SaveBinding(nil) end },
|
||||
})
|
||||
MTH_FOM_STATE.bindButton = bindButtons[1]
|
||||
MTH_FOM_STATE.bindClearButton = bindButtons[2]
|
||||
|
||||
local bindClearButton = CreateFrame("Button", "MetaHuntOptionsFOMBindClearButton", container, "UIPanelButtonTemplate")
|
||||
bindClearButton:SetPoint("LEFT", bindButton, "RIGHT", 8, 0)
|
||||
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
|
||||
general:Note("MetaHuntOptionsFOMBindHint",
|
||||
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 })
|
||||
|
||||
local bindHint = container:CreateFontString("MetaHuntOptionsFOMBindHint", "ARTWORK", "GameFontNormalSmall")
|
||||
bindHint:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -128 + yAdjust)
|
||||
bindHint:SetWidth(360)
|
||||
bindHint:SetJustifyH("LEFT")
|
||||
bindHint:SetJustifyV("TOP")
|
||||
bindHint:SetTextColor(0.35, 0.65, 1)
|
||||
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.AvoidQuestFood = general:Checkbox("MetaHuntOptionsFOMAvoidQuest",
|
||||
MTH_FOM_Label("AvoidQuestFood", "FOM_LABEL_AVOID_QUEST_FOODS", "Avoid quest foods"),
|
||||
{ onClick = function() if not this then return end FOM_Config.AvoidQuestFood = this:GetChecked() == 1 end })
|
||||
MTH_FOM_STATE.ctrl.AvoidBonusFood = general:Checkbox("MetaHuntOptionsFOMAvoidBonus",
|
||||
MTH_FOM_Label("AvoidBonusFood", "FOM_LABEL_AVOID_BONUS_FOODS", "Avoid bonus foods"),
|
||||
{ onClick = function() if not this then return end FOM_Config.AvoidBonusFood = this:GetChecked() == 1 end })
|
||||
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.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_STATE.ctrl.PreferHigherQuality = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMPreferQuality", FOM_OptionsButtonText and FOM_OptionsButtonText["PreferHigherQuality"] or MTH_FOM_L("FOM_LABEL_PREFER_HIGHER_QUALITY_FOOD", "Prefer higher quality food"), -220 + yAdjust)
|
||||
MTH_FOM_STATE.ctrl.Fallback = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMFallback", FOM_OptionsButtonText and FOM_OptionsButtonText["Fallback"] or MTH_FOM_L("FOM_LABEL_FALLBACK_TO_AVOIDED_FOODS", "Fallback to avoided foods"), -245 + yAdjust)
|
||||
MTH_FOM_STATE.keepOpenEdit = general:EditRow("MetaHuntOptionsFOMKeepOpenLabel",
|
||||
MTH_FOM_L("FOM_KEEP_OPEN_BAG_SLOTS", "Keep open bag slots:"),
|
||||
"MetaHuntOptionsFOMKeepOpen", 40, {
|
||||
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
|
||||
if MTH_FOM_STATE.ctrl.AvoidBonusFood then MTH_FOM_STATE.ctrl.AvoidBonusFood:SetScript("OnClick", function() if not this then return end FOM_Config.AvoidBonusFood = this:GetChecked() == 1 end) end
|
||||
if MTH_FOM_STATE.ctrl.PreferHigherQuality then MTH_FOM_STATE.ctrl.PreferHigherQuality:SetScript("OnClick", function() if not this then return end FOM_Config.PreferHigherQuality = this:GetChecked() == 1 end) end
|
||||
if MTH_FOM_STATE.ctrl.Fallback then MTH_FOM_STATE.ctrl.Fallback:SetScript("OnClick", function() if not this then return end FOM_Config.Fallback = this:GetChecked() == 1 end) end
|
||||
-- ===== Notify when feeding (left column) =====
|
||||
local notify = panel:Box("left", "MetaHuntOptionsFOMNotifyBox", MTH_FOM_L("FOM_HEADER_NOTIFY_WHEN_FEEDING", "Notify when feeding"))
|
||||
MTH_FOM_STATE.ctrl.AlertEmote = notify:Checkbox("MetaHuntOptionsFOMAlertEmote",
|
||||
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")
|
||||
keepOpenLabel:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -302 + yAdjust)
|
||||
keepOpenLabel:SetText(MTH_FOM_L("FOM_KEEP_OPEN_BAG_SLOTS", "Keep open bag slots:"))
|
||||
-- ===== Avoid foods used in cooking (right column) =====
|
||||
local avoid = panel:Box("right", "MetaHuntOptionsFOMAvoidBox", MTH_FOM_L("FOM_HEADER_AVOID_FOODS_USED_IN_COOKING", "Avoid foods used in cooking"))
|
||||
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")
|
||||
keepOpen:SetPoint("LEFT", keepOpenLabel, "RIGHT", 8, 0)
|
||||
keepOpen:SetWidth(40)
|
||||
keepOpen:SetHeight(20)
|
||||
keepOpen:SetNumeric(true)
|
||||
keepOpen:SetAutoFocus(false)
|
||||
keepOpen:SetScript("OnTextChanged", function()
|
||||
if not this then return end
|
||||
local value = tonumber(this:GetText()) or 0
|
||||
FOM_Config.KeepOpenSlots = value
|
||||
end)
|
||||
keepOpen:SetScript("OnEnterPressed", function() if this then this:ClearFocus() end end)
|
||||
keepOpen:SetScript("OnEscapePressed", function() if this then this:ClearFocus() end end)
|
||||
MTH_FOM_STATE.keepOpenEdit = keepOpen
|
||||
-- ===== Warn when pet needs feeding (right column) =====
|
||||
local warn = panel:Box("right", "MetaHuntOptionsFOMWarnBox", MTH_FOM_L("FOM_HEADER_WARN_WHEN_PET_NEEDS_FEEDING", "Warn when pet needs feeding"))
|
||||
MTH_FOM_STATE.ctrl.LevelContent = warn:Checkbox("MetaHuntOptionsFOMLevelContent",
|
||||
MTH_FOM_Label("LevelContent", "FOM_LABEL_WARN_WHEN_CONTENT", "When content"),
|
||||
{ onClick = function() MTH_FOM_SetLevel("content"); MTH_RefreshFeedOMaticOptions() end })
|
||||
MTH_FOM_STATE.ctrl.LevelUnhappy = warn:Checkbox("MetaHuntOptionsFOMLevelUnhappy",
|
||||
MTH_FOM_Label("LevelUnhappy", "FOM_LABEL_WARN_WHEN_UNHAPPY", "When unhappy"),
|
||||
{ onClick = function() MTH_FOM_SetLevel("unhappy"); MTH_RefreshFeedOMaticOptions() end })
|
||||
MTH_FOM_STATE.ctrl.LevelOff = warn:Checkbox("MetaHuntOptionsFOMLevelOff",
|
||||
MTH_FOM_Label("LevelOff", "FOM_LABEL_DONT_WARN", "Don't warn"),
|
||||
{ onClick = function() MTH_FOM_SetLevel("off"); MTH_RefreshFeedOMaticOptions() end })
|
||||
|
||||
local notifyHeader = container:CreateFontString("MetaHuntOptionsFOMNotifyHeader", "ARTWORK", "GameFontHighlight")
|
||||
notifyHeader:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -336 + yAdjust)
|
||||
notifyHeader:SetText(MTH_FOM_L("FOM_HEADER_NOTIFY_WHEN_FEEDING", "Notify when feeding"))
|
||||
warn:Gap(6)
|
||||
|
||||
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.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_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)
|
||||
if MTH_FOM_STATE.ctrl.AlertEmote then MTH_FOM_STATE.ctrl.AlertEmote:SetScript("OnClick", function() MTH_FOM_SetAlert("emote"); MTH_RefreshFeedOMaticOptions() end) end
|
||||
if MTH_FOM_STATE.ctrl.AlertChat then MTH_FOM_STATE.ctrl.AlertChat:SetScript("OnClick", function() MTH_FOM_SetAlert("chat"); MTH_RefreshFeedOMaticOptions() end) end
|
||||
if MTH_FOM_STATE.ctrl.AlertNone then MTH_FOM_STATE.ctrl.AlertNone:SetScript("OnClick", function() MTH_FOM_SetAlert("none"); MTH_RefreshFeedOMaticOptions() end) end
|
||||
|
||||
local warnHeader = container:CreateFontString("MetaHuntOptionsFOMWarnHeader", "ARTWORK", "GameFontHighlight")
|
||||
warnHeader:SetPoint("TOPLEFT", container, "TOPLEFT", rightColumnX, -276 + yAdjust)
|
||||
warnHeader:SetText(MTH_FOM_L("FOM_HEADER_WARN_WHEN_PET_NEEDS_FEEDING", "Warn when pet needs feeding"))
|
||||
|
||||
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)
|
||||
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
|
||||
MTH_FOM_STATE.ctrl.AudioWarning = warn:Checkbox("MetaHuntOptionsFOMAudioWarning",
|
||||
MTH_FOM_Label("AudioWarning", "FOM_LABEL_PLAY_SOUND", "Play sound"),
|
||||
{ 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 })
|
||||
MTH_FOM_STATE.ctrl.AudioWarningBell = warn:Checkbox("MetaHuntOptionsFOMAudioWarningBell",
|
||||
MTH_FOM_Label("AudioWarningBell", "FOM_LABEL_USE_BELL_SOUND", "Use bell sound"),
|
||||
{ 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",
|
||||
MTH_FOM_Label("TextWarning", "FOM_LABEL_SHOW_TEXT", "Show text"),
|
||||
{ onClick = function() if not this then return end FOM_Config.TextWarning = this:GetChecked() == 1 end })
|
||||
MTH_FOM_STATE.ctrl.IconWarning = warn:Checkbox("MetaHuntOptionsFOMIconWarning",
|
||||
MTH_FOM_Label("IconWarning", "FOM_LABEL_FLASH_ICON", "Flash icon"),
|
||||
{ onClick = function() if not this then return end FOM_Config.IconWarning = this:GetChecked() == 1 end })
|
||||
|
||||
panel:Finish()
|
||||
MTH_RefreshFeedOMaticOptions()
|
||||
end
|
||||
|
||||
+103
-195
@@ -197,31 +197,25 @@ local function MTH_ICU_OpenColorPicker(colorKey)
|
||||
ColorPickerFrame:Show()
|
||||
end
|
||||
|
||||
local function MTH_ICU_CreateOptionRow(container, rowName, label, key, y, labelX, dropdownX)
|
||||
labelX = labelX or MTH_ICU_LAYOUT.LEFT_X
|
||||
dropdownX = dropdownX or MTH_ICU_LAYOUT.LEFT_DROPDOWN_X
|
||||
local function MTH_ICU_BuildUI(container)
|
||||
MTH_ICU_OPT_STATE.controls = {}
|
||||
MTH_ICU_OPT_STATE.container = container
|
||||
MTH_ICU_OPT_STATE.built = true
|
||||
|
||||
local labelFs = container:CreateFontString(rowName .. "Label", "ARTWORK", "GameFontNormalSmall")
|
||||
labelFs:SetPoint("TOPLEFT", container, "TOPLEFT", labelX, y)
|
||||
labelFs:SetText(label)
|
||||
local controls = MTH_ICU_OPT_STATE.controls
|
||||
|
||||
local dropdown = CreateFrame("Frame", rowName .. "Drop", container, "UIDropDownMenuTemplate")
|
||||
dropdown:ClearAllPoints()
|
||||
dropdown:SetPoint("TOPLEFT", container, "TOPLEFT", dropdownX, y + 12)
|
||||
if UIDropDownMenu_SetWidth then
|
||||
UIDropDownMenu_SetWidth(MTH_ICU_LAYOUT.DROPDOWN_WIDTH, dropdown)
|
||||
end
|
||||
if UIDropDownMenu_JustifyText then
|
||||
UIDropDownMenu_JustifyText("LEFT", dropdown)
|
||||
end
|
||||
local panel = MTH_Layout.Panel(container)
|
||||
panel:Title("MTH ICU")
|
||||
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.",
|
||||
{ lines = 2 })
|
||||
|
||||
if UIDropDownMenu_Initialize then
|
||||
UIDropDownMenu_Initialize(dropdown, function()
|
||||
-- Standard UIDropDownMenu initialize factory for a stored option key.
|
||||
local function makeInit(key)
|
||||
return function()
|
||||
local values = MTH_ICU_GetOptionValues(key)
|
||||
local store = MTH_ICU_GetStore()
|
||||
local current = tostring(store[key] or "")
|
||||
local infoFactory = UIDropDownMenu_CreateInfo
|
||||
|
||||
for i = 1, table.getn(values) do
|
||||
local optionValue = tostring(values[i])
|
||||
local info = type(infoFactory) == "function" and infoFactory() or {}
|
||||
@@ -229,76 +223,23 @@ local function MTH_ICU_CreateOptionRow(container, rowName, label, key, y, labelX
|
||||
info.checked = optionValue == current and 1 or nil
|
||||
info.func = function()
|
||||
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
|
||||
if UIDropDownMenu_AddButton then UIDropDownMenu_AddButton(info) end
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
MTH_ICU_OPT_STATE.controls[key] = {
|
||||
label = labelFs,
|
||||
dropdown = dropdown,
|
||||
}
|
||||
end
|
||||
|
||||
local function MTH_ICU_CreateColorRow(container, rowName, label, colorKey, y, labelX, buttonX)
|
||||
labelX = labelX or MTH_ICU_LAYOUT.RIGHT_X
|
||||
buttonX = buttonX or MTH_ICU_LAYOUT.COLOR_BUTTON_X
|
||||
|
||||
local labelFs = container:CreateFontString(rowName .. "Label", "ARTWORK", "GameFontNormalSmall")
|
||||
labelFs:SetPoint("TOPLEFT", container, "TOPLEFT", labelX, y)
|
||||
labelFs:SetText(label)
|
||||
|
||||
local button = CreateFrame("Button", rowName .. "Button", container, "UIPanelButtonTemplate")
|
||||
button:SetPoint("TOPLEFT", container, "TOPLEFT", buttonX, y + 8)
|
||||
button:SetWidth(MTH_ICU_LAYOUT.COLOR_BUTTON_WIDTH)
|
||||
button:SetHeight(22)
|
||||
button:SetText("Pick")
|
||||
button:SetScript("OnClick", function()
|
||||
MTH_ICU_OpenColorPicker(colorKey)
|
||||
end)
|
||||
|
||||
local swatch = button:CreateTexture(rowName .. "Swatch", "ARTWORK")
|
||||
swatch:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||||
swatch:SetPoint("LEFT", button, "LEFT", 6, 0)
|
||||
swatch:SetWidth(14)
|
||||
swatch:SetHeight(14)
|
||||
|
||||
MTH_ICU_OPT_STATE.controls[colorKey] = {
|
||||
label = labelFs,
|
||||
button = button,
|
||||
swatch = swatch,
|
||||
isColor = true,
|
||||
}
|
||||
end
|
||||
|
||||
local function MTH_ICU_BuildUI(container)
|
||||
if MTH_ICU_OPT_STATE.built and MTH_ICU_OPT_STATE.container == container then
|
||||
return
|
||||
end
|
||||
|
||||
MTH_ClearContainer(container)
|
||||
MTH_ICU_OPT_STATE.controls = {}
|
||||
MTH_ICU_OPT_STATE.container = container
|
||||
MTH_ICU_OPT_STATE.built = true
|
||||
|
||||
local title = container:CreateFontString("MetaHuntOptionsICUTitle", "ARTWORK", "GameFontHighlight")
|
||||
title:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -10)
|
||||
title:SetText("MTH ICU")
|
||||
|
||||
local note = container:CreateFontString("MetaHuntOptionsICUNote", "ARTWORK", "GameFontNormalSmall")
|
||||
note:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -32)
|
||||
note:SetWidth(560)
|
||||
note:SetJustifyH("LEFT")
|
||||
note:SetJustifyV("TOP")
|
||||
note:SetTextColor(0.35, 0.65, 1)
|
||||
note:SetText("ICU is a minimap targeting utility module for your Trackings.\nIt provides enhanced target information and click actions for players and NPCs on the minimap.")
|
||||
|
||||
local moduleEnabled = MTH_CreateCheckbox(container, "MetaHuntOptionsICUModuleEnabled", "Enable ICU module", -72, MTH_ICU_LAYOUT.LEFT_X)
|
||||
if moduleEnabled then
|
||||
moduleEnabled:SetScript("OnClick", function()
|
||||
if not this then return end
|
||||
-- ===== LEFT: Module =====
|
||||
local moduleBox = panel:Box("left", "MTH_ICUModuleBox", "Module")
|
||||
controls.moduleEnabled = moduleBox:Checkbox("MetaHuntOptionsICUModuleEnabled", "Enable ICU module", {
|
||||
onClick = function()
|
||||
if MTH and MTH.SetModuleEnabled then
|
||||
local ok, err = MTH:SetModuleEnabled("icu", this:GetChecked() == 1)
|
||||
if not ok and MTH and MTH.Print then
|
||||
@@ -306,130 +247,97 @@ local function MTH_ICU_BuildUI(container)
|
||||
end
|
||||
end
|
||||
MTH_RefreshICUOptions()
|
||||
end)
|
||||
MTH_ICU_OPT_STATE.controls.moduleEnabled = moduleEnabled
|
||||
end
|
||||
|
||||
local mouseOver = MTH_CreateCheckbox(container, "MetaHuntOptionsICUMouseOver", "Enable CTRL mouseover scan", -98, MTH_ICU_LAYOUT.LEFT_X)
|
||||
if mouseOver then
|
||||
mouseOver:SetScript("OnClick", function()
|
||||
if not this then return end
|
||||
end,
|
||||
})
|
||||
controls.mouseOver = moduleBox:Checkbox("MetaHuntOptionsICUMouseOver", "Enable CTRL mouseover scan", {
|
||||
onClick = function()
|
||||
local store = MTH_ICU_GetStore()
|
||||
store.mouseOver = this:GetChecked() == 1 and true or false
|
||||
end)
|
||||
MTH_ICU_OPT_STATE.controls.mouseOver = mouseOver
|
||||
end,
|
||||
})
|
||||
|
||||
-- ===== 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
|
||||
|
||||
local sectionHeader = container:CreateFontString("MetaHuntOptionsICUSectionHeader", "ARTWORK", "GameFontHighlight")
|
||||
sectionHeader:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_ICU_LAYOUT.LEFT_X, -146)
|
||||
sectionHeader:SetText("General Settings")
|
||||
|
||||
MTH_ICU_CreateOptionRow(container, "MetaHuntOptionsICUAlert", "ALERT PVP", "ALERT", -172, MTH_ICU_LAYOUT.LEFT_X, MTH_ICU_LAYOUT.LEFT_DROPDOWN_X)
|
||||
MTH_ICU_CreateOptionRow(container, "MetaHuntOptionsICUAnnounce", "ANNOUNCE on click", "ANNOUNCE", -198, MTH_ICU_LAYOUT.LEFT_X, MTH_ICU_LAYOUT.LEFT_DROPDOWN_X)
|
||||
MTH_ICU_CreateOptionRow(container, "MetaHuntOptionsICUHealthMode", "Health text", "HEALTH_TEXT_MODE", -224, MTH_ICU_LAYOUT.LEFT_X, MTH_ICU_LAYOUT.LEFT_DROPDOWN_X)
|
||||
MTH_ICU_CreateOptionRow(container, "MetaHuntOptionsICUHideDelay", "List hide delay", "POPUP_HIDE_DELAY", -250, MTH_ICU_LAYOUT.LEFT_X, MTH_ICU_LAYOUT.LEFT_DROPDOWN_X)
|
||||
MTH_ICU_CreateOptionRow(container, "MetaHuntOptionsICUAnchor", "ANCHOR", "ANCHOR", -276, MTH_ICU_LAYOUT.LEFT_X, MTH_ICU_LAYOUT.LEFT_DROPDOWN_X)
|
||||
|
||||
local toggleAnchorButton = CreateFrame("Button", "MetaHuntOptionsICUToggleAnchorButton", container, "UIPanelButtonTemplate")
|
||||
toggleAnchorButton:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_ICU_LAYOUT.LEFT_X, -302)
|
||||
toggleAnchorButton:SetWidth(116)
|
||||
toggleAnchorButton:SetHeight(22)
|
||||
toggleAnchorButton:SetText("Toggle Anchor")
|
||||
toggleAnchorButton:SetScript("OnClick", function()
|
||||
if type(ICU_ToggleAnchor) == "function" then
|
||||
ICU_ToggleAnchor()
|
||||
end
|
||||
end)
|
||||
MTH_ICU_OPT_STATE.controls.customAnchorButton = toggleAnchorButton
|
||||
|
||||
local expandUp = MTH_CreateCheckbox(container, "MetaHuntOptionsICUExpandUp", "Expand Up", -301, MTH_ICU_LAYOUT.LEFT_X + 132)
|
||||
if expandUp then
|
||||
expandUp:SetScript("OnClick", function()
|
||||
if not this then return end
|
||||
local store = MTH_ICU_GetStore()
|
||||
store.EXPAND_UP = this:GetChecked() == 1 and true or false
|
||||
if type(ICU_SetPoints) == "function" then
|
||||
ICU_SetPoints()
|
||||
end
|
||||
end)
|
||||
MTH_ICU_OPT_STATE.controls.expandUp = expandUp
|
||||
end
|
||||
|
||||
local showGuild = MTH_CreateCheckbox(container, "MetaHuntOptionsICUShowGuild", "Show player guild names", -344, MTH_ICU_LAYOUT.LEFT_X)
|
||||
if showGuild then
|
||||
showGuild:SetScript("OnClick", function()
|
||||
if not this then return end
|
||||
local store = MTH_ICU_GetStore()
|
||||
store.SHOW_GUILD_NAME = this:GetChecked() == 1 and true or false
|
||||
end)
|
||||
MTH_ICU_OPT_STATE.controls.showGuildName = showGuild
|
||||
end
|
||||
|
||||
local showClass = MTH_CreateCheckbox(container, "MetaHuntOptionsICUShowClass", "Show player class", -368, MTH_ICU_LAYOUT.LEFT_X)
|
||||
if showClass then
|
||||
showClass:SetScript("OnClick", function()
|
||||
if not this then return end
|
||||
local store = MTH_ICU_GetStore()
|
||||
store.SHOW_PLAYER_CLASS = this:GetChecked() == 1 and true or false
|
||||
end)
|
||||
MTH_ICU_OPT_STATE.controls.showPlayerClass = showClass
|
||||
end
|
||||
|
||||
local showRace = MTH_CreateCheckbox(container, "MetaHuntOptionsICUShowRace", "Show player race", -392, MTH_ICU_LAYOUT.LEFT_X)
|
||||
if showRace then
|
||||
showRace:SetScript("OnClick", function()
|
||||
if not this then return end
|
||||
local store = MTH_ICU_GetStore()
|
||||
store.SHOW_PLAYER_RACE = this:GetChecked() == 1 and true or false
|
||||
end)
|
||||
MTH_ICU_OPT_STATE.controls.showPlayerRace = showRace
|
||||
end
|
||||
|
||||
local showTitles = MTH_CreateCheckbox(container, "MetaHuntOptionsICUShowTitles", "Show custom titles in alerts", -416, MTH_ICU_LAYOUT.LEFT_X)
|
||||
if showTitles then
|
||||
showTitles:SetScript("OnClick", function()
|
||||
if not this then return end
|
||||
local store = MTH_ICU_GetStore()
|
||||
store.SHOW_CUSTOM_TITLES = this:GetChecked() == 1 and true or false
|
||||
end)
|
||||
MTH_ICU_OPT_STATE.controls.showCustomTitles = showTitles
|
||||
end
|
||||
|
||||
local rightHeader = container:CreateFontString("MetaHuntOptionsICURightHeader", "ARTWORK", "GameFontHighlight")
|
||||
rightHeader:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_ICU_LAYOUT.RIGHT_X, -146)
|
||||
rightHeader:SetText("Background Colors")
|
||||
|
||||
MTH_ICU_CreateOptionRow(container, "MetaHuntOptionsICUPlayerColorMode", "Player colors", "PLAYER_COLOR_MODE", -172, MTH_ICU_LAYOUT.RIGHT_X, MTH_ICU_LAYOUT.RIGHT_DROPDOWN_X)
|
||||
|
||||
local reactionOnlyPvp = MTH_CreateCheckbox(container, "MetaHuntOptionsICUReactionOnlyPvp", "Reaction only when PvP-flagged", -200, MTH_ICU_LAYOUT.RIGHT_X)
|
||||
if reactionOnlyPvp then
|
||||
reactionOnlyPvp:SetScript("OnClick", function()
|
||||
if not this then return end
|
||||
-- ===== RIGHT: Background Colors =====
|
||||
local bgBox = panel:Box("right", "MTH_ICUBgBox", "Background Colors")
|
||||
controls.PLAYER_COLOR_MODE = { dropdown = bgBox:Dropdown("MetaHuntOptionsICUPlayerColorModeDrop",
|
||||
{ label = "Player colors", width = 130, initialize = makeInit("PLAYER_COLOR_MODE") }) }
|
||||
controls.reactionOnlyPvp = bgBox:Checkbox("MetaHuntOptionsICUReactionOnlyPvp", "Reaction only when PvP-flagged", {
|
||||
onClick = function()
|
||||
local store = MTH_ICU_GetStore()
|
||||
store.REACTION_ONLY_PVP_PLAYERS = this:GetChecked() == 1 and true or false
|
||||
end)
|
||||
MTH_ICU_OPT_STATE.controls.reactionOnlyPvp = reactionOnlyPvp
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
local colorHeader = container:CreateFontString("MetaHuntOptionsICUColorsHeader", "ARTWORK", "GameFontHighlight")
|
||||
colorHeader:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_ICU_LAYOUT.RIGHT_X, -224)
|
||||
|
||||
local colorScope = container:CreateFontString("MetaHuntOptionsICUColorsScope", "ARTWORK", "GameFontNormalSmall")
|
||||
colorScope:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_ICU_LAYOUT.RIGHT_X, -242)
|
||||
colorScope:SetWidth(250)
|
||||
colorScope:SetJustifyH("LEFT")
|
||||
colorScope:SetJustifyV("TOP")
|
||||
colorScope:SetTextColor(0.35, 0.65, 1)
|
||||
colorScope:SetText("Player pickers: REACTION / FACTION / CUSTOM\nNPC pickers: always")
|
||||
|
||||
local colorY = -284
|
||||
-- ===== RIGHT: Color Pickers =====
|
||||
local colorBox = panel:Box("right", "MTH_ICUColorBox", "Color Pickers")
|
||||
colorBox:Note("MetaHuntOptionsICUColorsScope",
|
||||
"Player pickers: REACTION / FACTION / CUSTOM\nNPC pickers: always", { lines = 2 })
|
||||
for i = 1, table.getn(MTH_ICU_COLOR_ROWS) do
|
||||
local row = MTH_ICU_COLOR_ROWS[i]
|
||||
MTH_ICU_CreateColorRow(container, "MetaHuntOptionsICUColor" .. tostring(i), row.label, row.key, colorY, MTH_ICU_LAYOUT.RIGHT_X, MTH_ICU_LAYOUT.COLOR_BUTTON_X)
|
||||
colorY = colorY - MTH_ICU_LAYOUT.COLOR_ROW_STEP
|
||||
local btn, swatch = colorBox:ColorRow("MetaHuntOptionsICUColor" .. tostring(i), {
|
||||
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
|
||||
|
||||
-- ===== 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
|
||||
|
||||
function MTH_RefreshICUOptions()
|
||||
|
||||
@@ -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
|
||||
@@ -300,11 +300,10 @@ function MTH_SetupProfilesOptions()
|
||||
if not container then return end
|
||||
|
||||
if not gSetupDone then
|
||||
-- Title
|
||||
local title = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
|
||||
title:SetPoint("TOPLEFT", container, "TOPLEFT", 6, -6)
|
||||
title:SetText("Profiles")
|
||||
title:SetTextColor(1.00, 0.82, 0.00)
|
||||
local panel = MTH_Layout.Panel(container)
|
||||
|
||||
-- Title (engine header, consistent gold styling)
|
||||
local title = panel:Title("Profiles")
|
||||
|
||||
-- Active profile indicator
|
||||
gActiveLabel = container:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
|
||||
|
||||
+125
-3
@@ -3,8 +3,8 @@ local MTH_OPTIONS_SETUP = {} -- Track which frames have been set up
|
||||
MTH_OPTIONS_CONST = MTH_OPTIONS_CONST or {}
|
||||
MTH_OPTIONS_CONST.NAV_DEFAULT_WIDTH = MTH_OPTIONS_CONST.NAV_DEFAULT_WIDTH or 120
|
||||
MTH_OPTIONS_CONST.WINDOW_PADDING = MTH_OPTIONS_CONST.WINDOW_PADDING or 12
|
||||
MTH_OPTIONS_CONST.CONTENT_TOP_OFFSET = MTH_OPTIONS_CONST.CONTENT_TOP_OFFSET or -58
|
||||
MTH_OPTIONS_CONST.CONTENT_HEIGHT = MTH_OPTIONS_CONST.CONTENT_HEIGHT or 566
|
||||
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 600
|
||||
MTH_OPTIONS_CONST.CLOSE_BTN_SIZE = MTH_OPTIONS_CONST.CLOSE_BTN_SIZE or 22
|
||||
MTH_OPTIONS_CONST.CLOSE_BTN_OFFSET_X = MTH_OPTIONS_CONST.CLOSE_BTN_OFFSET_X or -8
|
||||
MTH_OPTIONS_CONST.CLOSE_BTN_OFFSET_Y = MTH_OPTIONS_CONST.CLOSE_BTN_OFFSET_Y or -8
|
||||
@@ -20,6 +20,62 @@ local function MTH_OPT_L(key, default)
|
||||
return default or key
|
||||
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)
|
||||
if not (frameName and UISpecialFrames) then
|
||||
return
|
||||
@@ -76,7 +132,7 @@ local function MTH_EnsureOptionsFrame()
|
||||
closeBtn:SetWidth(MTH_OPTIONS_CONST.CLOSE_BTN_SIZE)
|
||||
closeBtn:SetHeight(MTH_OPTIONS_CONST.CLOSE_BTN_SIZE)
|
||||
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
|
||||
|
||||
MTH_BuildOptionsTree()
|
||||
@@ -133,6 +189,11 @@ function MTH_SelectOptionsTab(tabKey)
|
||||
MTH_SetupMessagesOptions()
|
||||
end
|
||||
end
|
||||
elseif tabKey == "ZBar" then
|
||||
if type(MTH_SetupZBarOptions) == "function" then
|
||||
MTH_SetupZBarOptions()
|
||||
MTH_OPTIONS_SETUP["ZBar"] = true
|
||||
end
|
||||
elseif tabKey == "Pet" then
|
||||
if not MTH_OPTIONS_SETUP["Pet"] then
|
||||
MTH_SetupPetOptions()
|
||||
@@ -182,6 +243,11 @@ function MTH_SelectOptionsTab(tabKey)
|
||||
MTH_SetupToysOptions()
|
||||
MTH_OPTIONS_SETUP["Toys"] = true
|
||||
end
|
||||
elseif tabKey == "Craft" then
|
||||
if not MTH_OPTIONS_SETUP["Craft"] then
|
||||
MTH_SetupCraftOptions()
|
||||
MTH_OPTIONS_SETUP["Craft"] = true
|
||||
end
|
||||
elseif tabKey == "SmartAmmo" then
|
||||
if not MTH_OPTIONS_SETUP["SmartAmmo"] then
|
||||
MTH_SetupSmartAmmoOptions()
|
||||
@@ -222,6 +288,13 @@ function MTH_SelectOptionsTab(tabKey)
|
||||
elseif MTH and MTH.Print then
|
||||
MTH:Print("ICU options setup function is not loaded", "error")
|
||||
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
|
||||
local setupCredits = _G and _G["MTH_SetupCreditsOptions"]
|
||||
if type(setupCredits) == "function" then
|
||||
@@ -250,6 +323,7 @@ function MTH_SelectOptionsTab(tabKey)
|
||||
end
|
||||
end
|
||||
end
|
||||
MTH_OPT_AnimateTabContent(tabKey)
|
||||
end
|
||||
|
||||
function MTH_ResetAndSelectOptionsTab(tabKey)
|
||||
@@ -261,6 +335,52 @@ function MTH_ResetAndSelectOptionsTab(tabKey)
|
||||
MTH_SelectOptionsTab(tabKey)
|
||||
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)
|
||||
if not MTH_OPTIONS_SHELL_READY then return end
|
||||
local optionsFrame = MTH_EnsureOptionsFrame()
|
||||
@@ -268,5 +388,7 @@ function MTH_OpenOptions(tabKey)
|
||||
return
|
||||
end
|
||||
optionsFrame:Show()
|
||||
MTH_OPT_AnimateOpen(optionsFrame)
|
||||
if MTH_OPTIONS_UpdateRealmGateOverlay(optionsFrame) then return end
|
||||
MTH_SelectOptionsTab(tabKey or "General")
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
MTH_OPTIONS_TREE_STATE = MTH_OPTIONS_TREE_STATE or { zButtons = true, chronometer = true }
|
||||
MTH_OPTIONS_TREE_STATE = MTH_OPTIONS_TREE_STATE or { zButtons = false, chronometer = false }
|
||||
MTH_OPTIONS_TREE_BUTTONS = MTH_OPTIONS_TREE_BUTTONS or {}
|
||||
MTH_OPTIONS_STATE = MTH_OPTIONS_STATE or {}
|
||||
if MTH_OPTIONS_STATE.activeTab == nil and MTH_OPTIONS_ACTIVE_TAB ~= nil then
|
||||
@@ -30,7 +30,8 @@ MTH_OPTIONS_TABS = MTH_OPTIONS_TABS or {
|
||||
{ key = "General", label = "General", frame = "MetaHuntOptionsGeneral" },
|
||||
{ key = "Profiles", label = "Profiles", frame = "MetaHuntOptionsProfiles" },
|
||||
{ key = "Messages", label = "Messages", frame = "MetaHuntOptionsMessages" },
|
||||
{ key = "Pet", label = "ZPet", frame = "MetaHuntOptionsPet" },
|
||||
{ key = "ZBar", label = "zBar", frame = "MetaHuntOptionsZBar" },
|
||||
{ key = "Pet", label = "ZPet", frame = "MetaHuntOptionsPet" },
|
||||
{ key = "Track", label = "ZTrack", frame = "MetaHuntOptionsTrack" },
|
||||
{ key = "Aspect", label = "ZAspect", frame = "MetaHuntOptionsAspect" },
|
||||
{ key = "Trap", label = "ZTrap", frame = "MetaHuntOptionsTrap" },
|
||||
@@ -39,6 +40,7 @@ MTH_OPTIONS_TABS = MTH_OPTIONS_TABS or {
|
||||
{ key = "Mounts", label = "ZMounts", frame = "MetaHuntOptionsMounts" },
|
||||
{ key = "Companions", label = "ZCompanions", frame = "MetaHuntOptionsCompanions" },
|
||||
{ key = "Toys", label = "ZToys", frame = "MetaHuntOptionsToys" },
|
||||
{ key = "Craft", label = "ZCraft", frame = "MetaHuntOptionsCraft" },
|
||||
{ key = "SmartAmmo", label = "Smart Ammo", frame = "MetaHuntOptionsSmartAmmo" },
|
||||
{ key = "FeedOMatic", label = "FeedOMatic", frame = "MetaHuntOptionsFeedOMatic" },
|
||||
{ key = "AutoBuy", label = "Auto Buy", frame = "MetaHuntOptionsAutoBuy" },
|
||||
@@ -50,6 +52,7 @@ MTH_OPTIONS_TABS = MTH_OPTIONS_TABS or {
|
||||
{ key = "ChronometerClassSpells", label = "Hunter Spells", frame = "MetaHuntOptionsChronometer" },
|
||||
{ key = "ChronometerClassEvents", label = "Hunter Events", frame = "MetaHuntOptionsChronometer" },
|
||||
{ key = "ChronometerRacial", label = "Racial", frame = "MetaHuntOptionsChronometer" },
|
||||
{ key = "Animations", label = "Animations", frame = "MetaHuntOptionsAnimations" },
|
||||
{ key = "Credits", label = "Credits", frame = "MetaHuntOptionsCredits" },
|
||||
}
|
||||
|
||||
@@ -66,7 +69,8 @@ MTH_OPTIONS_TREE = MTH_OPTIONS_TREE or {
|
||||
label = "zButtons",
|
||||
node = "zButtons",
|
||||
children = {
|
||||
{ label = "zAmmo", key = "Ammo" },
|
||||
{ label = "zBar", key = "ZBar" },
|
||||
{ label = "zAmmo", key = "Ammo" },
|
||||
{ label = "zPet", key = "Pet" },
|
||||
{ label = "zAspect", key = "Aspect" },
|
||||
{ label = "zTrack", key = "Track" },
|
||||
@@ -75,6 +79,7 @@ MTH_OPTIONS_TREE = MTH_OPTIONS_TREE or {
|
||||
{ label = "zMounts", key = "Mounts" },
|
||||
{ label = "zCompanions", key = "Companions" },
|
||||
{ label = "zToys", key = "Toys" },
|
||||
{ label = "zCraft", key = "Craft" },
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -88,6 +93,7 @@ MTH_OPTIONS_TREE = MTH_OPTIONS_TREE or {
|
||||
{ label = "Racial", key = "ChronometerRacial" },
|
||||
},
|
||||
},
|
||||
{ label = "Animations", key = "Animations" },
|
||||
{ label = "Credits", key = "Credits" },
|
||||
}
|
||||
|
||||
|
||||
+835
-565
File diff suppressed because it is too large
Load Diff
+5
-3
@@ -46,12 +46,12 @@
|
||||
<Frames>
|
||||
<Frame name="$parentNav" enableMouse="true">
|
||||
<Size>
|
||||
<AbsDimension x="120" y="566"/>
|
||||
<AbsDimension x="120" y="600"/>
|
||||
</Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT">
|
||||
<Offset>
|
||||
<AbsDimension x="12" y="-58"/>
|
||||
<AbsDimension x="12" y="-30"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
@@ -64,7 +64,7 @@
|
||||
</Backdrop>
|
||||
</Frame>
|
||||
|
||||
<!-- Content Areas -->
|
||||
<Frame name="$parentZBar" hidden="true" enableMouse="true"/>
|
||||
<Frame name="$parentGeneral" hidden="true" enableMouse="true"/>
|
||||
<Frame name="$parentProfiles" hidden="true" enableMouse="true"/>
|
||||
<Frame name="$parentMessages" hidden="true" enableMouse="true"/>
|
||||
@@ -77,6 +77,7 @@
|
||||
<Frame name="$parentMounts" hidden="true" enableMouse="true"/>
|
||||
<Frame name="$parentCompanions" hidden="true" enableMouse="true"/>
|
||||
<Frame name="$parentToys" hidden="true" enableMouse="true"/>
|
||||
<Frame name="$parentCraft" hidden="true" enableMouse="true"/>
|
||||
<Frame name="$parentSmartAmmo" hidden="true" enableMouse="true"/>
|
||||
<Frame name="$parentFeedOMatic" hidden="true" enableMouse="true"/>
|
||||
<Frame name="$parentAutoBuy" hidden="true" enableMouse="true"/>
|
||||
@@ -84,6 +85,7 @@
|
||||
<Frame name="$parentICU" hidden="true" enableMouse="true"/>
|
||||
<Frame name="$parentExpAmmo" 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"/>
|
||||
|
||||
<!-- Top-right close button -->
|
||||
|
||||
@@ -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
|
||||
+26
-2
@@ -19,6 +19,30 @@ local function MTH_Profile_DeepCopy(orig)
|
||||
return copy
|
||||
end
|
||||
|
||||
-- Shallow-copy a module's saved data, skipping keys that hold large operational
|
||||
-- blobs (e.g. feedomatic's "legacy" table which mirrors FOM_Cooking, FOM_QuestFood
|
||||
-- etc.) — those are runtime migration artifacts, not user configuration settings.
|
||||
local MTH_PROFILE_SKIP_KEYS = { legacy = true, history = true }
|
||||
|
||||
local function MTH_Profile_CopyModules(src)
|
||||
if type(src) ~= "table" then return {} end
|
||||
local out = {}
|
||||
for modName, modData in src do
|
||||
if type(modData) == "table" then
|
||||
local modCopy = {}
|
||||
for k, v in modData do
|
||||
if not MTH_PROFILE_SKIP_KEYS[k] then
|
||||
modCopy[MTH_Profile_DeepCopy(k)] = MTH_Profile_DeepCopy(v)
|
||||
end
|
||||
end
|
||||
out[modName] = modCopy
|
||||
else
|
||||
out[modName] = modData
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
local function MTH_Profile_EnsureStore()
|
||||
if type(MTH_SavedVariables) ~= "table" then
|
||||
MTH_SavedVariables = {}
|
||||
@@ -47,13 +71,13 @@ local function MTH_Profile_BuildSnapshot()
|
||||
-- Account-wide module option values (e.g. feedomatic)
|
||||
snap.config = (type(MTH_SavedVariables) == "table"
|
||||
and type(MTH_SavedVariables.modules) == "table")
|
||||
and MTH_Profile_DeepCopy(MTH_SavedVariables.modules)
|
||||
and MTH_Profile_CopyModules(MTH_SavedVariables.modules)
|
||||
or {}
|
||||
|
||||
-- Per-character module option values (ICU, Chronometer, AutoBuy, SmartAmmo, etc.)
|
||||
snap.charConfig = (type(MTH_CharSavedVariables) == "table"
|
||||
and type(MTH_CharSavedVariables.modules) == "table")
|
||||
and MTH_Profile_DeepCopy(MTH_CharSavedVariables.modules)
|
||||
and MTH_Profile_CopyModules(MTH_CharSavedVariables.modules)
|
||||
or {}
|
||||
|
||||
-- Per-character module enabled/disabled states
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -4,13 +4,13 @@ MTH.VersionCheck = MTH.VersionCheck or {}
|
||||
local VC = MTH.VersionCheck
|
||||
VC.abbrev = "MTH"
|
||||
VC.channelName = "LFT"
|
||||
VC.updateUrl = "https://github.com/DuvelCorp/MetaHunt"
|
||||
VC.updateUrl = "https://codeberg.org/Duvelcorp/MetaHunt"
|
||||
VC.nextPublishAt = nil
|
||||
VC.lastPublishedAt = nil
|
||||
VC.joinAt = nil
|
||||
VC.notified = false
|
||||
VC._invalidVersionWarned = false
|
||||
VC.maxPersistedPeers = 200
|
||||
VC.maxPersistedPeers = 10000
|
||||
VC.seenPeers = VC.seenPeers or {}
|
||||
|
||||
local function VC_GetTimeNow()
|
||||
@@ -141,7 +141,7 @@ end
|
||||
function VC:PrunePersistedPeers(limit)
|
||||
local maxCount = tonumber(limit) or tonumber(self.maxPersistedPeers) or 200
|
||||
if maxCount <= 0 then
|
||||
maxCount = 200
|
||||
maxCount = 10000
|
||||
end
|
||||
if type(self.seenPeers) ~= "table" then
|
||||
return
|
||||
@@ -221,8 +221,8 @@ end
|
||||
|
||||
function VC:ResetPublishDelay()
|
||||
local now = VC_GetTimeNow()
|
||||
if self.lastPublishedAt and (now - self.lastPublishedAt) < 600 then
|
||||
return -- 10-minute cooldown between publishes
|
||||
if self.lastPublishedAt and (now - self.lastPublishedAt) < 3600 then
|
||||
return -- 1-hour cooldown between publishes
|
||||
end
|
||||
self.nextPublishAt = now + math.random(10, 20)
|
||||
end
|
||||
|
||||
+23
-23
@@ -1,5 +1,5 @@
|
||||
-- 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.
|
||||
|
||||
if not MTH_DS then MTH_DS = {} end
|
||||
@@ -14,7 +14,7 @@ MTH_DS_BagItems = {
|
||||
["subtype"] = "quiver",
|
||||
["description"] = "",
|
||||
["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"] = {
|
||||
["sold-by"] = {
|
||||
["template"] = "npc",
|
||||
@@ -89,7 +89,7 @@ MTH_DS_BagItems = {
|
||||
["subtype"] = "ammo pouch",
|
||||
["description"] = "",
|
||||
["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"] = {
|
||||
["sold-by"] = {
|
||||
["template"] = "npc",
|
||||
@@ -144,7 +144,7 @@ MTH_DS_BagItems = {
|
||||
["subtype"] = "quiver",
|
||||
["description"] = "",
|
||||
["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"] = {
|
||||
["dropped-by"] = {
|
||||
["template"] = "npc",
|
||||
@@ -171,7 +171,7 @@ MTH_DS_BagItems = {
|
||||
["subtype"] = "ammo pouch",
|
||||
["description"] = "",
|
||||
["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"] = {
|
||||
["dropped-by"] = {
|
||||
["template"] = "npc",
|
||||
@@ -197,7 +197,7 @@ MTH_DS_BagItems = {
|
||||
["subtype"] = "quiver",
|
||||
["description"] = "",
|
||||
["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"] = {
|
||||
["reward-of"] = {
|
||||
["template"] = "quest",
|
||||
@@ -216,7 +216,7 @@ MTH_DS_BagItems = {
|
||||
["subtype"] = "ammo pouch",
|
||||
["description"] = "",
|
||||
["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"] = {
|
||||
["reward-of"] = {
|
||||
["template"] = "quest",
|
||||
@@ -235,7 +235,7 @@ MTH_DS_BagItems = {
|
||||
["subtype"] = "ammo pouch",
|
||||
["description"] = "",
|
||||
["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"] = {
|
||||
["reward-of"] = {
|
||||
["template"] = "quest",
|
||||
@@ -254,7 +254,7 @@ MTH_DS_BagItems = {
|
||||
["subtype"] = "quiver",
|
||||
["description"] = "",
|
||||
["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"] = {
|
||||
["reward-of"] = {
|
||||
["template"] = "quest",
|
||||
@@ -273,7 +273,7 @@ MTH_DS_BagItems = {
|
||||
["subtype"] = "quiver",
|
||||
["description"] = "",
|
||||
["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"] = {
|
||||
["sold-by"] = {
|
||||
["template"] = "npc",
|
||||
@@ -621,7 +621,7 @@ MTH_DS_BagItems = {
|
||||
["subtype"] = "ammo pouch",
|
||||
["description"] = "",
|
||||
["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"] = {
|
||||
["sold-by"] = {
|
||||
["template"] = "npc",
|
||||
@@ -773,7 +773,7 @@ MTH_DS_BagItems = {
|
||||
["subtype"] = "quiver",
|
||||
["description"] = "",
|
||||
["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"] = {
|
||||
["created-by"] = {
|
||||
["template"] = "spell",
|
||||
@@ -796,7 +796,7 @@ MTH_DS_BagItems = {
|
||||
["subtype"] = "ammo pouch",
|
||||
["description"] = "",
|
||||
["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"] = {
|
||||
["created-by"] = {
|
||||
["template"] = "spell",
|
||||
@@ -820,7 +820,7 @@ MTH_DS_BagItems = {
|
||||
["subtype"] = "quiver",
|
||||
["description"] = "",
|
||||
["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"] = {
|
||||
["sold-by"] = {
|
||||
["template"] = "npc",
|
||||
@@ -871,7 +871,7 @@ MTH_DS_BagItems = {
|
||||
["subtype"] = "ammo pouch",
|
||||
["description"] = "",
|
||||
["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"] = {
|
||||
["created-by"] = {
|
||||
["template"] = "spell",
|
||||
@@ -895,7 +895,7 @@ MTH_DS_BagItems = {
|
||||
["subtype"] = "quiver",
|
||||
["description"] = "",
|
||||
["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"] = {
|
||||
["created-by"] = {
|
||||
["template"] = "spell",
|
||||
@@ -919,7 +919,7 @@ MTH_DS_BagItems = {
|
||||
["subtype"] = "ammo pouch",
|
||||
["description"] = "",
|
||||
["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"] = {
|
||||
["created-by"] = {
|
||||
["template"] = "spell",
|
||||
@@ -943,7 +943,7 @@ MTH_DS_BagItems = {
|
||||
["subtype"] = "quiver",
|
||||
["description"] = "",
|
||||
["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"] = {
|
||||
["sold-by"] = {
|
||||
["template"] = "npc",
|
||||
@@ -1327,7 +1327,7 @@ MTH_DS_BagItems = {
|
||||
["subtype"] = "ammo pouch",
|
||||
["description"] = "",
|
||||
["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"] = {
|
||||
["sold-by"] = {
|
||||
["template"] = "npc",
|
||||
@@ -1368,7 +1368,7 @@ MTH_DS_BagItems = {
|
||||
["subtype"] = "quiver",
|
||||
["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%.",
|
||||
["sourceUrl"] = "https://database.turtlecraft.gg/?item=18714",
|
||||
["sourceUrl"] = "https://octowow.st/db/?item=18714",
|
||||
["sources"] = {
|
||||
["reward-of"] = {
|
||||
["template"] = "quest",
|
||||
@@ -1388,7 +1388,7 @@ MTH_DS_BagItems = {
|
||||
["subtype"] = "quiver",
|
||||
["description"] = "",
|
||||
["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"] = {
|
||||
["sold-by"] = {
|
||||
["template"] = "npc",
|
||||
@@ -1440,7 +1440,7 @@ MTH_DS_BagItems = {
|
||||
["subtype"] = "ammo pouch",
|
||||
["description"] = "",
|
||||
["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"] = {
|
||||
["sold-by"] = {
|
||||
["template"] = "npc",
|
||||
@@ -1491,7 +1491,7 @@ MTH_DS_BagItems = {
|
||||
["subtype"] = "quiver",
|
||||
["description"] = "",
|
||||
["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"] = {
|
||||
["dropped-by"] = {
|
||||
["template"] = "npc",
|
||||
|
||||
@@ -9,6 +9,7 @@ MTH_DS_Families = {
|
||||
["icon"] = "Ability_Hunter_Pet_Bat",
|
||||
["named"] = 20,
|
||||
["coords"] = 18,
|
||||
["stats"] = { ["health"] = 1, ["armor"] = 1, ["damage"] = 1.07 },
|
||||
["food"] = {
|
||||
"fruit",
|
||||
"fungus",
|
||||
@@ -25,6 +26,7 @@ MTH_DS_Families = {
|
||||
["icon"] = "Ability_Hunter_Pet_Bear",
|
||||
["named"] = 45,
|
||||
["coords"] = 45,
|
||||
["stats"] = { ["health"] = 1.08, ["armor"] = 1.05, ["damage"] = 0.91 },
|
||||
["food"] = {
|
||||
"meat",
|
||||
"fish",
|
||||
@@ -47,6 +49,7 @@ MTH_DS_Families = {
|
||||
["icon"] = "Ability_Hunter_Pet_Boar",
|
||||
["named"] = 45,
|
||||
["coords"] = 87,
|
||||
["stats"] = { ["health"] = 1.04, ["armor"] = 1.09, ["damage"] = 0.9 },
|
||||
["food"] = {
|
||||
"meat",
|
||||
"fish",
|
||||
@@ -69,6 +72,7 @@ MTH_DS_Families = {
|
||||
["icon"] = "Ability_Hunter_Pet_Vulture",
|
||||
["named"] = 36,
|
||||
["coords"] = 101,
|
||||
["stats"] = { ["health"] = 1, ["armor"] = 1.05, ["damage"] = 1 },
|
||||
["food"] = {
|
||||
"meat",
|
||||
"raw meat",
|
||||
@@ -86,6 +90,7 @@ MTH_DS_Families = {
|
||||
["icon"] = "Ability_Hunter_Pet_Cat",
|
||||
["named"] = 85,
|
||||
["coords"] = 88,
|
||||
["stats"] = { ["health"] = 0.98, ["armor"] = 1, ["damage"] = 1.1 },
|
||||
["food"] = {
|
||||
"meat",
|
||||
"fish",
|
||||
@@ -105,6 +110,7 @@ MTH_DS_Families = {
|
||||
["icon"] = "Ability_Hunter_Pet_Crab",
|
||||
["named"] = 32,
|
||||
["coords"] = 74,
|
||||
["stats"] = { ["health"] = 0.96, ["armor"] = 1.13, ["damage"] = 0.95 },
|
||||
["food"] = {
|
||||
"fruit",
|
||||
"fish",
|
||||
@@ -123,6 +129,7 @@ MTH_DS_Families = {
|
||||
["icon"] = "Ability_Hunter_Pet_Crocolisk",
|
||||
["named"] = 27,
|
||||
["coords"] = 71,
|
||||
["stats"] = { ["health"] = 0.95, ["armor"] = 1.1, ["damage"] = 1 },
|
||||
["food"] = {
|
||||
"meat",
|
||||
"fish",
|
||||
@@ -140,6 +147,7 @@ MTH_DS_Families = {
|
||||
["icon"] = "ability_hunter_aspectofthefox",
|
||||
["named"] = 9,
|
||||
["coords"] = 51,
|
||||
["stats"] = { ["health"] = 1, ["armor"] = 1, ["damage"] = 1.07 },
|
||||
["food"] = {
|
||||
"meat",
|
||||
"fruit",
|
||||
@@ -157,6 +165,7 @@ MTH_DS_Families = {
|
||||
["icon"] = "Ability_Hunter_Pet_Gorilla",
|
||||
["named"] = 24,
|
||||
["coords"] = 117,
|
||||
["stats"] = { ["health"] = 1.04, ["armor"] = 1, ["damage"] = 1.02 },
|
||||
["food"] = {
|
||||
"fruit",
|
||||
"fungus",
|
||||
@@ -172,6 +181,7 @@ MTH_DS_Families = {
|
||||
["icon"] = "Ability_Hunter_Pet_Hyena",
|
||||
["named"] = 26,
|
||||
["coords"] = 36,
|
||||
["stats"] = { ["health"] = 1, ["armor"] = 1.05, ["damage"] = 1 },
|
||||
["food"] = {
|
||||
"meat",
|
||||
"fruit",
|
||||
@@ -189,6 +199,7 @@ MTH_DS_Families = {
|
||||
["icon"] = "Ability_Hunter_Pet_Owl",
|
||||
["named"] = 16,
|
||||
["coords"] = 59,
|
||||
["stats"] = { ["health"] = 1, ["armor"] = 1, ["damage"] = 1.07 },
|
||||
["food"] = {
|
||||
"meat",
|
||||
"raw meat",
|
||||
@@ -205,6 +216,7 @@ MTH_DS_Families = {
|
||||
["icon"] = "Ability_Hunter_Pet_Raptor",
|
||||
["named"] = 51,
|
||||
["coords"] = 115,
|
||||
["stats"] = { ["health"] = 0.95, ["armor"] = 1.03, ["damage"] = 1.1 },
|
||||
["food"] = {
|
||||
"meat",
|
||||
"raw meat",
|
||||
@@ -222,6 +234,7 @@ MTH_DS_Families = {
|
||||
["icon"] = "Ability_Hunter_Pet_Scorpid",
|
||||
["named"] = 38,
|
||||
["coords"] = 36,
|
||||
["stats"] = { ["health"] = 1, ["armor"] = 1.1, ["damage"] = 0.94 },
|
||||
["food"] = {
|
||||
"meat",
|
||||
"raw meat",
|
||||
@@ -237,6 +250,7 @@ MTH_DS_Families = {
|
||||
["icon"] = "Ability_Hunter_Pet_Serpent1",
|
||||
["named"] = 10,
|
||||
["coords"] = 13,
|
||||
["stats"] = { ["health"] = 1.03, ["armor"] = 1.03, ["damage"] = 1.135 },
|
||||
["food"] = {
|
||||
"meat",
|
||||
"fish",
|
||||
@@ -254,6 +268,7 @@ MTH_DS_Families = {
|
||||
["icon"] = "Ability_Hunter_Pet_Spider",
|
||||
["named"] = 91,
|
||||
["coords"] = 213,
|
||||
["stats"] = { ["health"] = 1, ["armor"] = 1, ["damage"] = 1.07 },
|
||||
["food"] = {
|
||||
"meat",
|
||||
"raw meat",
|
||||
@@ -269,6 +284,7 @@ MTH_DS_Families = {
|
||||
["icon"] = "Ability_Hunter_Pet_TallStrider",
|
||||
["named"] = 15,
|
||||
["coords"] = 112,
|
||||
["stats"] = { ["health"] = 1.05, ["armor"] = 1, ["damage"] = 1 },
|
||||
["food"] = {
|
||||
"fruit",
|
||||
"fungus",
|
||||
@@ -286,6 +302,7 @@ MTH_DS_Families = {
|
||||
["icon"] = "Ability_Hunter_Pet_Turtle",
|
||||
["named"] = 25,
|
||||
["coords"] = 42,
|
||||
["stats"] = { ["health"] = 1, ["armor"] = 1.13, ["damage"] = 0.9 },
|
||||
["food"] = {
|
||||
"fruit",
|
||||
"fish",
|
||||
@@ -303,6 +320,7 @@ MTH_DS_Families = {
|
||||
["icon"] = "Ability_Hunter_Pet_WindSerpent",
|
||||
["named"] = 27,
|
||||
["coords"] = 42,
|
||||
["stats"] = { ["health"] = 1, ["armor"] = 1, ["damage"] = 1.07 },
|
||||
["food"] = {
|
||||
"fish",
|
||||
"cheese",
|
||||
@@ -321,6 +339,7 @@ MTH_DS_Families = {
|
||||
["icon"] = "Ability_Hunter_Pet_Wolf",
|
||||
["named"] = 81,
|
||||
["coords"] = 99,
|
||||
["stats"] = { ["health"] = 1, ["armor"] = 1.05, ["damage"] = 1 },
|
||||
["food"] = {
|
||||
"meat",
|
||||
"raw meat",
|
||||
|
||||
+1207
-1207
File diff suppressed because it is too large
Load Diff
+324
-324
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@ MTH_DS_AmmoItems = {
|
||||
["level"] = 5,
|
||||
["reqlevel"] = 1,
|
||||
["icon"] = "INV_Ammo_Arrow_02",
|
||||
["dps"] = 1.5,
|
||||
["subtype"] = "arrow",
|
||||
["vendors"] = {
|
||||
[150] = 0,
|
||||
@@ -118,6 +119,7 @@ MTH_DS_AmmoItems = {
|
||||
["level"] = 15,
|
||||
["reqlevel"] = 10,
|
||||
["icon"] = "INV_Ammo_Arrow_02",
|
||||
["dps"] = 3.5,
|
||||
["subtype"] = "arrow",
|
||||
["vendors"] = {
|
||||
[150] = 0,
|
||||
@@ -275,6 +277,7 @@ MTH_DS_AmmoItems = {
|
||||
["level"] = 5,
|
||||
["reqlevel"] = 1,
|
||||
["icon"] = "INV_Ammo_Bullet_02",
|
||||
["dps"] = 2,
|
||||
["subtype"] = "bullet",
|
||||
["vendors"] = {
|
||||
[151] = 0,
|
||||
@@ -359,6 +362,7 @@ MTH_DS_AmmoItems = {
|
||||
["level"] = 15,
|
||||
["reqlevel"] = 10,
|
||||
["icon"] = "INV_Ammo_Bullet_02",
|
||||
["dps"] = 5.5,
|
||||
["subtype"] = "bullet",
|
||||
["vendors"] = {
|
||||
[227] = 0,
|
||||
@@ -494,6 +498,7 @@ MTH_DS_AmmoItems = {
|
||||
["level"] = 30,
|
||||
["reqlevel"] = 25,
|
||||
["icon"] = "INV_Ammo_Arrow_02",
|
||||
["dps"] = 5.5,
|
||||
["subtype"] = "arrow",
|
||||
["vendors"] = {
|
||||
[227] = 0,
|
||||
@@ -621,6 +626,7 @@ MTH_DS_AmmoItems = {
|
||||
["level"] = 30,
|
||||
["reqlevel"] = 25,
|
||||
["icon"] = "INV_Ammo_Bullet_02",
|
||||
["dps"] = 7.5,
|
||||
["subtype"] = "bullet",
|
||||
["vendors"] = {
|
||||
[227] = 0,
|
||||
@@ -736,18 +742,21 @@ MTH_DS_AmmoItems = {
|
||||
["name"] = "Feathered Arrow",
|
||||
["level"] = 35,
|
||||
["icon"] = "INV_Ammo_Arrow_02",
|
||||
["dps"] = 7.5,
|
||||
["subtype"] = "arrow",
|
||||
},
|
||||
[3465] = {
|
||||
["name"] = "Exploding Shot",
|
||||
["level"] = 36,
|
||||
["icon"] = "INV_Ammo_Bullet_02",
|
||||
["dps"] = 7.5,
|
||||
["subtype"] = "bullet",
|
||||
},
|
||||
[4960] = {
|
||||
["name"] = "Flash Pellet",
|
||||
["level"] = 7,
|
||||
["icon"] = "INV_Ammo_Bullet_02",
|
||||
["dps"] = 3.5,
|
||||
["subtype"] = "bullet",
|
||||
},
|
||||
[5568] = {
|
||||
@@ -755,6 +764,7 @@ MTH_DS_AmmoItems = {
|
||||
["level"] = 18,
|
||||
["reqlevel"] = 13,
|
||||
["icon"] = "INV_Ammo_Bullet_02",
|
||||
["dps"] = 3.5,
|
||||
["subtype"] = "bullet",
|
||||
["drops"] = {
|
||||
[2156] = 30.97,
|
||||
@@ -781,6 +791,7 @@ MTH_DS_AmmoItems = {
|
||||
["level"] = 10,
|
||||
["reqlevel"] = 5,
|
||||
["icon"] = "INV_Ammo_Bullet_02",
|
||||
["dps"] = 2,
|
||||
["subtype"] = "bullet",
|
||||
},
|
||||
[8068] = {
|
||||
@@ -788,6 +799,7 @@ MTH_DS_AmmoItems = {
|
||||
["level"] = 20,
|
||||
["reqlevel"] = 15,
|
||||
["icon"] = "INV_Ammo_Bullet_02",
|
||||
["dps"] = 5.5,
|
||||
["subtype"] = "bullet",
|
||||
},
|
||||
[8069] = {
|
||||
@@ -795,6 +807,7 @@ MTH_DS_AmmoItems = {
|
||||
["level"] = 35,
|
||||
["reqlevel"] = 30,
|
||||
["icon"] = "INV_Ammo_Bullet_02",
|
||||
["dps"] = 7.5,
|
||||
["subtype"] = "bullet",
|
||||
},
|
||||
[9399] = {
|
||||
@@ -802,6 +815,7 @@ MTH_DS_AmmoItems = {
|
||||
["level"] = 40,
|
||||
["reqlevel"] = 35,
|
||||
["icon"] = "INV_Ammo_Arrow_01",
|
||||
["dps"] = 7.5,
|
||||
["subtype"] = "arrow",
|
||||
["drops"] = {
|
||||
[6906] = 100,
|
||||
@@ -812,6 +826,7 @@ MTH_DS_AmmoItems = {
|
||||
["level"] = 42,
|
||||
["reqlevel"] = 37,
|
||||
["icon"] = "INV_Ammo_Bullet_01",
|
||||
["dps"] = 12.5,
|
||||
["subtype"] = "bullet",
|
||||
},
|
||||
[10513] = {
|
||||
@@ -819,6 +834,7 @@ MTH_DS_AmmoItems = {
|
||||
["level"] = 49,
|
||||
["reqlevel"] = 44,
|
||||
["icon"] = "INV_Ammo_Bullet_01",
|
||||
["dps"] = 12.5,
|
||||
["subtype"] = "bullet",
|
||||
},
|
||||
[10579] = {
|
||||
@@ -826,6 +842,7 @@ MTH_DS_AmmoItems = {
|
||||
["level"] = 42,
|
||||
["reqlevel"] = 37,
|
||||
["icon"] = "INV_Ammo_Arrow_02",
|
||||
["dps"] = 12.5,
|
||||
["subtype"] = "arrow",
|
||||
},
|
||||
[11284] = {
|
||||
@@ -833,6 +850,7 @@ MTH_DS_AmmoItems = {
|
||||
["level"] = 45,
|
||||
["reqlevel"] = 40,
|
||||
["icon"] = "INV_Ammo_Bullet_01",
|
||||
["dps"] = 10.5,
|
||||
["subtype"] = "bullet",
|
||||
["vendors"] = {
|
||||
[734] = 0,
|
||||
@@ -934,6 +952,7 @@ MTH_DS_AmmoItems = {
|
||||
["level"] = 45,
|
||||
["reqlevel"] = 40,
|
||||
["icon"] = "INV_Weapon_ShortBlade_25",
|
||||
["dps"] = 10.5,
|
||||
["subtype"] = "arrow",
|
||||
["vendors"] = {
|
||||
[228] = 0,
|
||||
@@ -1043,6 +1062,7 @@ MTH_DS_AmmoItems = {
|
||||
["level"] = 52,
|
||||
["reqlevel"] = 47,
|
||||
["icon"] = "INV_Ammo_Bullet_02",
|
||||
["dps"] = 10.5,
|
||||
["subtype"] = "bullet",
|
||||
["drops"] = {
|
||||
[9025] = 16,
|
||||
@@ -1053,6 +1073,7 @@ MTH_DS_AmmoItems = {
|
||||
["level"] = 59,
|
||||
["reqlevel"] = 54,
|
||||
["icon"] = "Ability_Hunter_CriticalShot",
|
||||
["dps"] = 20,
|
||||
["subtype"] = "arrow",
|
||||
["drops"] = {
|
||||
[9236] = 100,
|
||||
@@ -1063,6 +1084,7 @@ MTH_DS_AmmoItems = {
|
||||
["level"] = 61,
|
||||
["reqlevel"] = 56,
|
||||
["icon"] = "INV_Ammo_Bullet_02",
|
||||
["dps"] = 20.5,
|
||||
["subtype"] = "bullet",
|
||||
["drops"] = {
|
||||
[10997] = 100,
|
||||
@@ -1073,6 +1095,7 @@ MTH_DS_AmmoItems = {
|
||||
["level"] = 57,
|
||||
["reqlevel"] = 52,
|
||||
["icon"] = "INV_Ammo_Bullet_03",
|
||||
["dps"] = 17.5,
|
||||
["subtype"] = "bullet",
|
||||
["vendors"] = {
|
||||
[17078] = 0,
|
||||
@@ -1083,6 +1106,7 @@ MTH_DS_AmmoItems = {
|
||||
["level"] = 57,
|
||||
["reqlevel"] = 52,
|
||||
["icon"] = "INV_Ammo_Arrow_02",
|
||||
["dps"] = 17.5,
|
||||
["subtype"] = "arrow",
|
||||
["vendors"] = {
|
||||
[17078] = 0,
|
||||
@@ -1093,6 +1117,7 @@ MTH_DS_AmmoItems = {
|
||||
["level"] = 1,
|
||||
["reqlevel"] = 1,
|
||||
["icon"] = "INV_Ammo_Arrow_02",
|
||||
["dps"] = 1.5,
|
||||
["subtype"] = "arrow",
|
||||
},
|
||||
[19316] = {
|
||||
@@ -1100,6 +1125,7 @@ MTH_DS_AmmoItems = {
|
||||
["level"] = 54,
|
||||
["reqlevel"] = 51,
|
||||
["icon"] = "Spell_Frost_IceShard",
|
||||
["dps"] = 14.5,
|
||||
["subtype"] = "arrow",
|
||||
["vendors"] = {
|
||||
[13216] = 0,
|
||||
@@ -1113,6 +1139,7 @@ MTH_DS_AmmoItems = {
|
||||
["level"] = 54,
|
||||
["reqlevel"] = 51,
|
||||
["icon"] = "Spell_Frost_FrostBlast",
|
||||
["dps"] = 14.5,
|
||||
["subtype"] = "bullet",
|
||||
["vendors"] = {
|
||||
[13216] = 0,
|
||||
|
||||
@@ -1,140 +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-20 13:57 UTC
|
||||
|
||||
-- This file patches MTH_DS_PetSpells.allSpells entries with DBC-authoritative
|
||||
-- learnMethod, minRank, and nextSpellId values.
|
||||
-- Load order: after ds-pet-spells.lua and ds-pet-spells-trainer.lua
|
||||
|
||||
if not MTH_DS_PetSpells or not MTH_DS_PetSpells.allSpells then return end
|
||||
|
||||
local _acquire = {
|
||||
[1747] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[1748] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[1749] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[1750] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[1751] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[1853] = { learnMethod="unknown(1)", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[2975] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[2976] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[2977] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[2980] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[2981] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[2982] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[3666] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[3667] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[4195] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[4196] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[4197] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[4198] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[4199] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[4200] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[4201] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[4202] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[5048] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[5049] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[5149] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[7370] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[14922] = { learnMethod="unknown(1)", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[14923] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[14924] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[14925] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[14926] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[14927] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[16698] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[17254] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[17262] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[17263] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[17264] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[17265] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[17266] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[17267] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[17268] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[23100] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[23111] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[23112] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[23146] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[23149] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[23150] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[23163] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[23166] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[23167] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24424] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24440] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24441] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24451] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24454] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24455] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24463] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24464] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24475] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24476] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24477] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24478] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24490] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24494] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24495] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24508] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24509] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24510] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24511] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24512] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24513] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24514] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24515] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24516] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24547] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24556] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24557] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24558] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24559] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24560] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24561] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24562] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24580] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24581] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24582] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24584] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24588] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24589] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24599] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24607] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24608] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24609] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24631] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24632] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24641] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[24845] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[25013] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[25014] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[25015] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[25016] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[25017] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[25077] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[26065] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[26094] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[26184] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[26185] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[26186] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[26189] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[26190] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[26202] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[28343] = { learnMethod="trainer", minRank=1, nextSpellId=0, skillLineId=261 },
|
||||
[46023] = { learnMethod="trainer", minRank=0, nextSpellId=0, skillLineId=261 },
|
||||
[46303] = { learnMethod="trainer", minRank=0, nextSpellId=0, skillLineId=261 },
|
||||
[46307] = { learnMethod="trainer", minRank=0, nextSpellId=0, skillLineId=261 },
|
||||
[51154] = { learnMethod="trainer", minRank=0, nextSpellId=0, skillLineId=261 },
|
||||
[51155] = { learnMethod="trainer", minRank=0, nextSpellId=0, skillLineId=261 },
|
||||
[51157] = { learnMethod="trainer", minRank=0, nextSpellId=0, skillLineId=261 },
|
||||
}
|
||||
|
||||
for _, spell in ipairs(MTH_DS_PetSpells.allSpells) do
|
||||
local meta = _acquire[spell.id]
|
||||
if meta then
|
||||
spell.learnMethod = meta.learnMethod
|
||||
spell.minRank = meta.minRank
|
||||
spell.nextSpellId = meta.nextSpellId
|
||||
spell.skillLineId = meta.skillLineId
|
||||
end
|
||||
end
|
||||
@@ -51,7 +51,7 @@ local growlTrainerRows = {
|
||||
["rank"] = "Rank 1",
|
||||
["rankNumber"] = 1,
|
||||
["school"] = "Physical",
|
||||
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=2649",
|
||||
["sourceUrl"] = "https://octowow.st/db/?spell=2649",
|
||||
["trainLevel"] = 1,
|
||||
},
|
||||
{
|
||||
@@ -72,7 +72,7 @@ local growlTrainerRows = {
|
||||
["rank"] = "Rank 2",
|
||||
["rankNumber"] = 2,
|
||||
["school"] = "Physical",
|
||||
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=14916",
|
||||
["sourceUrl"] = "https://octowow.st/db/?spell=14916",
|
||||
["trainLevel"] = 10,
|
||||
},
|
||||
{
|
||||
@@ -93,7 +93,7 @@ local growlTrainerRows = {
|
||||
["rank"] = "Rank 3",
|
||||
["rankNumber"] = 3,
|
||||
["school"] = "Physical",
|
||||
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=14917",
|
||||
["sourceUrl"] = "https://octowow.st/db/?spell=14917",
|
||||
["trainLevel"] = 20,
|
||||
},
|
||||
{
|
||||
@@ -114,7 +114,7 @@ local growlTrainerRows = {
|
||||
["rank"] = "Rank 4",
|
||||
["rankNumber"] = 4,
|
||||
["school"] = "Physical",
|
||||
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=14918",
|
||||
["sourceUrl"] = "https://octowow.st/db/?spell=14918",
|
||||
["trainLevel"] = 30,
|
||||
},
|
||||
{
|
||||
@@ -135,7 +135,7 @@ local growlTrainerRows = {
|
||||
["rank"] = "Rank 5",
|
||||
["rankNumber"] = 5,
|
||||
["school"] = "Physical",
|
||||
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=14919",
|
||||
["sourceUrl"] = "https://octowow.st/db/?spell=14919",
|
||||
["trainLevel"] = 40,
|
||||
},
|
||||
{
|
||||
@@ -156,7 +156,7 @@ local growlTrainerRows = {
|
||||
["rank"] = "Rank 6",
|
||||
["rankNumber"] = 6,
|
||||
["school"] = "Physical",
|
||||
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=14920",
|
||||
["sourceUrl"] = "https://octowow.st/db/?spell=14920",
|
||||
["trainLevel"] = 50,
|
||||
},
|
||||
{
|
||||
@@ -177,7 +177,7 @@ local growlTrainerRows = {
|
||||
["rank"] = "Rank 7",
|
||||
["rankNumber"] = 7,
|
||||
["school"] = "Physical",
|
||||
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=14921",
|
||||
["sourceUrl"] = "https://octowow.st/db/?spell=14921",
|
||||
["trainLevel"] = 60,
|
||||
},
|
||||
}
|
||||
@@ -228,7 +228,7 @@ end)
|
||||
|
||||
MTH_DS_PetSpells.byAbility["Growl"] = {
|
||||
["ability"] = "Growl",
|
||||
["searchUrl"] = "https://database.turtlecraft.gg/?search=Growl",
|
||||
["searchUrl"] = "https://octowow.st/db/?search=Growl",
|
||||
["spellCount"] = table.getn(growlTrainerRows),
|
||||
["spells"] = growlTrainerRows,
|
||||
}
|
||||
|
||||
+204
-204
File diff suppressed because it is too large
Load Diff
+42
-42
@@ -6,7 +6,7 @@ MTH_DS_Racial = {
|
||||
version = "1.0",
|
||||
generatedAtUTC = "2026-02-20T00:00:00Z",
|
||||
source = {
|
||||
site = "https://database.turtlecraft.gg",
|
||||
site = "https://octowow.st/db",
|
||||
runRoot = ".TurtleDBparsing/racial-audit/runs/20260220-164554Z",
|
||||
selection = "user-curated-approved",
|
||||
},
|
||||
@@ -25,46 +25,46 @@ MTH_DS_Racial = {
|
||||
["Human"] = { 20599, 20600, 20597, 20864, 20598 },
|
||||
},
|
||||
spellsById = {
|
||||
[2481] = { id = 2481, name = "Find Treasure", rank = "Racial", level = 1, school = 0, race = "Dwarf", sourceUrl = "https://database.turtlecraft.gg/?spell=2481" },
|
||||
[5227] = { id = 5227, name = "Underwater Breathing", rank = "Racial Passive", level = 0, school = 0, race = "Undead", sourceUrl = "https://database.turtlecraft.gg/?spell=5227" },
|
||||
[7340] = { id = 7340, name = "Language Gnomish", rank = "", level = 1, school = 0, race = "Gnome", sourceUrl = "https://database.turtlecraft.gg/?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" },
|
||||
[20549] = { id = 20549, name = "War Stomp", rank = "Racial", level = 0, school = 0, race = "Tauren", sourceUrl = "https://database.turtlecraft.gg/?spell=20549" },
|
||||
[20550] = { id = 20550, name = "Endurance", rank = "Racial Passive", level = 0, school = 0, race = "Tauren", sourceUrl = "https://database.turtlecraft.gg/?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" },
|
||||
[20552] = { id = 20552, name = "Cultivation", rank = "Racial Passive", level = 0, school = 0, race = "Tauren", sourceUrl = "https://database.turtlecraft.gg/?spell=20552" },
|
||||
[20554] = { id = 20554, name = "Berserking", rank = "Racial", level = 0, school = 0, race = "Troll", sourceUrl = "https://database.turtlecraft.gg/?spell=20554" },
|
||||
[20555] = { id = 20555, name = "Regeneration", rank = "Racial Passive", level = 0, school = 0, race = "Troll", sourceUrl = "https://database.turtlecraft.gg/?spell=20555" },
|
||||
[20557] = { id = 20557, name = "Beast Slaying", rank = "Racial Passive", level = 0, school = 0, race = "Troll", sourceUrl = "https://database.turtlecraft.gg/?spell=20557" },
|
||||
[20558] = { id = 20558, name = "Throwing Specialization", rank = "Racial Passive", level = 0, school = 0, race = "Troll", sourceUrl = "https://database.turtlecraft.gg/?spell=20558" },
|
||||
[20572] = { id = 20572, name = "Blood Fury", rank = "Racial", level = 0, school = 0, race = "Orc", sourceUrl = "https://database.turtlecraft.gg/?spell=20572" },
|
||||
[20573] = { id = 20573, name = "Hardiness", rank = "Racial Passive", level = 0, school = 0, race = "Orc", sourceUrl = "https://database.turtlecraft.gg/?spell=20573" },
|
||||
[20574] = { id = 20574, name = "Axe Specialization", rank = "Racial Passive", level = 0, school = 0, race = "Orc", sourceUrl = "https://database.turtlecraft.gg/?spell=20574" },
|
||||
[20575] = { id = 20575, name = "Command", rank = "Racial Passive", level = 0, school = 0, race = "Orc", sourceUrl = "https://database.turtlecraft.gg/?spell=20575" },
|
||||
[20576] = { id = 20576, name = "Command", rank = "Racial Passive", level = 0, school = 0, race = "Orc", sourceUrl = "https://database.turtlecraft.gg/?spell=20576" },
|
||||
[20577] = { id = 20577, name = "Cannibalize", rank = "Racial", level = 0, school = 0, race = "Undead", sourceUrl = "https://database.turtlecraft.gg/?spell=20577" },
|
||||
[20579] = { id = 20579, name = "Shadow Resistance", rank = "Racial Passive", level = 0, school = 0, race = "Undead", sourceUrl = "https://database.turtlecraft.gg/?spell=20579" },
|
||||
[20580] = { id = 20580, name = "Shadowmeld", rank = "Racial", level = 1, school = 0, race = "Night Elf", sourceUrl = "https://database.turtlecraft.gg/?spell=20580" },
|
||||
[20582] = { id = 20582, name = "Quickness", rank = "Racial Passive", level = 0, school = 0, race = "Night Elf", sourceUrl = "https://database.turtlecraft.gg/?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" },
|
||||
[20585] = { id = 20585, name = "Wisp Spirit", rank = "Racial Passive", level = 0, school = 0, race = "Night Elf", sourceUrl = "https://database.turtlecraft.gg/?spell=20585" },
|
||||
[20589] = { id = 20589, name = "Escape Artist", rank = "Racial", level = 0, school = 0, race = "Gnome", sourceUrl = "https://database.turtlecraft.gg/?spell=20589" },
|
||||
[20591] = { id = 20591, name = "Expansive Mind", rank = "Racial Passive", level = 0, school = 0, race = "Gnome", sourceUrl = "https://database.turtlecraft.gg/?spell=20591" },
|
||||
[20592] = { id = 20592, name = "Arcane Resistance", rank = "Racial Passive", level = 0, school = 0, race = "Gnome", sourceUrl = "https://database.turtlecraft.gg/?spell=20592" },
|
||||
[20593] = { id = 20593, name = "Engineering Specialization", rank = "Racial Passive", level = 0, school = 0, race = "Gnome", sourceUrl = "https://database.turtlecraft.gg/?spell=20593" },
|
||||
[20594] = { id = 20594, name = "Stoneform", rank = "Racial", level = 0, school = 0, race = "Dwarf", sourceUrl = "https://database.turtlecraft.gg/?spell=20594" },
|
||||
[20595] = { id = 20595, name = "Gun Specialization", rank = "Racial Passive", level = 0, school = 0, race = "Dwarf", sourceUrl = "https://database.turtlecraft.gg/?spell=20595" },
|
||||
[20596] = { id = 20596, name = "Frost Resistance", rank = "Racial Passive", level = 0, school = 0, race = "Dwarf", sourceUrl = "https://database.turtlecraft.gg/?spell=20596" },
|
||||
[20597] = { id = 20597, name = "Sword Specialization", rank = "Racial Passive", level = 0, school = 0, race = "Human", sourceUrl = "https://database.turtlecraft.gg/?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" },
|
||||
[20599] = { id = 20599, name = "Diplomacy", rank = "Racial Passive", level = 0, school = 0, race = "Human", sourceUrl = "https://database.turtlecraft.gg/?spell=20599" },
|
||||
[20600] = { id = 20600, name = "Perception", rank = "Racial", level = 0, school = 0, race = "Human", sourceUrl = "https://database.turtlecraft.gg/?spell=20600" },
|
||||
[20864] = { id = 20864, name = "Mace Specialization", rank = "Racial Passive", level = 0, school = 0, race = "Human", sourceUrl = "https://database.turtlecraft.gg/?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" },
|
||||
[21563] = { id = 21563, name = "Command", rank = "Racial Passive", level = 0, school = 0, race = "Orc", sourceUrl = "https://database.turtlecraft.gg/?spell=21563" },
|
||||
[26290] = { id = 26290, name = "Bow Specialization", rank = "Racial Passive", level = 0, school = 0, race = "Troll", sourceUrl = "https://database.turtlecraft.gg/?spell=26290" },
|
||||
[26296] = { id = 26296, name = "Berserking", rank = "Racial", level = 0, school = 0, race = "Troll", sourceUrl = "https://database.turtlecraft.gg/?spell=26296" },
|
||||
[26297] = { id = 26297, name = "Berserking", rank = "Racial", level = 0, school = 0, race = "Troll", sourceUrl = "https://database.turtlecraft.gg/?spell=26297" },
|
||||
[52522] = { id = 52522, name = "Vengeance", rank = "Racial Passive", level = 0, school = 0, race = "Undead", sourceUrl = "https://database.turtlecraft.gg/?spell=52522" },
|
||||
[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://octowow.st/db/?spell=5227" },
|
||||
[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://octowow.st/db/?spell=7744" },
|
||||
[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://octowow.st/db/?spell=20550" },
|
||||
[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://octowow.st/db/?spell=20552" },
|
||||
[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://octowow.st/db/?spell=20555" },
|
||||
[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://octowow.st/db/?spell=20558" },
|
||||
[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://octowow.st/db/?spell=20573" },
|
||||
[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://octowow.st/db/?spell=20575" },
|
||||
[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://octowow.st/db/?spell=20577" },
|
||||
[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://octowow.st/db/?spell=20580" },
|
||||
[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://octowow.st/db/?spell=20583" },
|
||||
[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://octowow.st/db/?spell=20589" },
|
||||
[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://octowow.st/db/?spell=20592" },
|
||||
[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://octowow.st/db/?spell=20594" },
|
||||
[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://octowow.st/db/?spell=20596" },
|
||||
[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://octowow.st/db/?spell=20598" },
|
||||
[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://octowow.st/db/?spell=20600" },
|
||||
[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://octowow.st/db/?spell=21009" },
|
||||
[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://octowow.st/db/?spell=26290" },
|
||||
[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://octowow.st/db/?spell=26297" },
|
||||
[52522] = { id = 52522, name = "Vengeance", rank = "Racial Passive", level = 0, school = 0, race = "Undead", sourceUrl = "https://octowow.st/db/?spell=52522" },
|
||||
},
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
-- MetaHunt: Stable slot prices
|
||||
-- AUTO-GENERATED by .tools — do not edit by hand.
|
||||
-- Source DBC(s): StableSlotPrices.dbc
|
||||
-- Generated: 2026-03-20 13:57 UTC
|
||||
|
||||
if not MTH_DS then MTH_DS = {} end
|
||||
|
||||
MTH_DS_StableSlotPrices = {
|
||||
[1] = {
|
||||
["cost_copper"] = 500,
|
||||
["cost_gold"] = 0,
|
||||
["cost_silver"] = 5,
|
||||
["cost_copper_remainder"] = 0,
|
||||
["display"] = "0g 5s 0c",
|
||||
["acquireMethod"] = "gold",
|
||||
},
|
||||
[2] = {
|
||||
["cost_copper"] = 50000,
|
||||
["cost_gold"] = 5,
|
||||
["cost_silver"] = 0,
|
||||
["cost_copper_remainder"] = 0,
|
||||
["display"] = "5g 0s 0c",
|
||||
["acquireMethod"] = "gold",
|
||||
},
|
||||
[3] = {
|
||||
["cost_copper"] = 2500000,
|
||||
["cost_gold"] = 250,
|
||||
["cost_silver"] = 0,
|
||||
["cost_copper_remainder"] = 0,
|
||||
["display"] = "250g 0s 0c",
|
||||
["acquireMethod"] = "gold",
|
||||
},
|
||||
[4] = {
|
||||
["cost_copper"] = 0,
|
||||
["cost_gold"] = 0,
|
||||
["cost_silver"] = 0,
|
||||
["cost_copper_remainder"] = 0,
|
||||
["display"] = "Shop",
|
||||
["acquireMethod"] = "shop",
|
||||
},
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,97 +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-20 13:57 UTC
|
||||
|
||||
if not MTH_DS_Zones then return end
|
||||
|
||||
-- WMA ID=301 map=0 'Stormwind'
|
||||
if MTH_DS_Zones[1519] then
|
||||
MTH_DS_Zones[1519]["dbcAreaId"] = 1519
|
||||
MTH_DS_Zones[1519]["dbcMapId"] = 0
|
||||
MTH_DS_Zones[1519]["dbcName"] = "Stormwind"
|
||||
MTH_DS_Zones[1519]["worldBounds"] = {
|
||||
["left"] = 1722.92,
|
||||
["right"] = -14.58,
|
||||
["top"] = -7995.8301,
|
||||
["bottom"] = -9154.1699,
|
||||
}
|
||||
end
|
||||
|
||||
-- WMA ID=321 map=1 'Ogrimmar'
|
||||
if MTH_DS_Zones[1637] then
|
||||
MTH_DS_Zones[1637]["dbcAreaId"] = 1637
|
||||
MTH_DS_Zones[1637]["dbcMapId"] = 1
|
||||
MTH_DS_Zones[1637]["dbcName"] = "Ogrimmar"
|
||||
MTH_DS_Zones[1637]["worldBounds"] = {
|
||||
["left"] = -3680.6001,
|
||||
["right"] = -5083.21,
|
||||
["top"] = 2273.8799,
|
||||
["bottom"] = 1338.46,
|
||||
}
|
||||
end
|
||||
|
||||
-- WMA ID=362 map=1 'ThunderBluff'
|
||||
if MTH_DS_Zones[1638] then
|
||||
MTH_DS_Zones[1638]["dbcAreaId"] = 1638
|
||||
MTH_DS_Zones[1638]["dbcMapId"] = 1
|
||||
MTH_DS_Zones[1638]["dbcName"] = "ThunderBluff"
|
||||
MTH_DS_Zones[1638]["worldBounds"] = {
|
||||
["left"] = 516.67,
|
||||
["right"] = -527.08,
|
||||
["top"] = -850.0,
|
||||
["bottom"] = -1545.83,
|
||||
}
|
||||
end
|
||||
|
||||
-- WMA ID=381 map=1 'Darnassis'
|
||||
if MTH_DS_Zones[1657] then
|
||||
MTH_DS_Zones[1657]["dbcAreaId"] = 1657
|
||||
MTH_DS_Zones[1657]["dbcMapId"] = 1
|
||||
MTH_DS_Zones[1657]["dbcName"] = "Darnassis"
|
||||
MTH_DS_Zones[1657]["worldBounds"] = {
|
||||
["left"] = 2938.3601,
|
||||
["right"] = 1880.03,
|
||||
["top"] = 10238.3203,
|
||||
["bottom"] = 9532.5898,
|
||||
}
|
||||
end
|
||||
|
||||
-- WMA ID=382 map=0 'Undercity'
|
||||
if MTH_DS_Zones[1497] then
|
||||
MTH_DS_Zones[1497]["dbcAreaId"] = 1497
|
||||
MTH_DS_Zones[1497]["dbcMapId"] = 0
|
||||
MTH_DS_Zones[1497]["dbcName"] = "Undercity"
|
||||
MTH_DS_Zones[1497]["worldBounds"] = {
|
||||
["left"] = 873.19,
|
||||
["right"] = -86.18,
|
||||
["top"] = 1877.95,
|
||||
["bottom"] = 1237.84,
|
||||
}
|
||||
end
|
||||
|
||||
-- WMA ID=508 map=1 'AmaniAlor'
|
||||
if MTH_DS_Zones[2041] then
|
||||
MTH_DS_Zones[2041]["dbcAreaId"] = 2041
|
||||
MTH_DS_Zones[2041]["dbcMapId"] = 1
|
||||
MTH_DS_Zones[2041]["dbcName"] = "AmaniAlor"
|
||||
MTH_DS_Zones[2041]["worldBounds"] = {
|
||||
["left"] = 3112.0,
|
||||
["right"] = 1599.0,
|
||||
["top"] = 3437.0,
|
||||
["bottom"] = 2434.0,
|
||||
}
|
||||
end
|
||||
|
||||
-- WMA ID=509 map=0 'AlahThalas'
|
||||
if MTH_DS_Zones[2040] then
|
||||
MTH_DS_Zones[2040]["dbcAreaId"] = 2040
|
||||
MTH_DS_Zones[2040]["dbcMapId"] = 0
|
||||
MTH_DS_Zones[2040]["dbcName"] = "AlahThalas"
|
||||
MTH_DS_Zones[2040]["worldBounds"] = {
|
||||
["left"] = -2169.0,
|
||||
["right"] = -3637.0,
|
||||
["top"] = 4907.0,
|
||||
["bottom"] = 3931.0,
|
||||
}
|
||||
end
|
||||
@@ -54,20 +54,27 @@ MTH_DS_ZoneNamesFallback = {
|
||||
[1519] = "Stormwind City",
|
||||
[1537] = "Ironforge",
|
||||
[1583] = "Blackrock Spire",
|
||||
[1584] = "Blackrock Depths",
|
||||
[1941] = "Caverns of Time",
|
||||
[1977] = "Zul'Gurub",
|
||||
[2040] = "Alah'Thalas",
|
||||
[2100] = "Maraudon",
|
||||
[2717] = "Molten Core",
|
||||
[2597] = "Alterac Valley",
|
||||
[3456] = "Naxxramas",
|
||||
[3457] = "Tower of Karazhan",
|
||||
[4012] = "Scarlet Enclave",
|
||||
[5023] = "Sunnyglade",
|
||||
[5024] = "Icepoint Rock",
|
||||
[5077] = "Crescent Grove",
|
||||
[5121] = "Tel'Abim",
|
||||
[5130] = "Winter Veil Vale",
|
||||
[5135] = "Scarlet Monastery Library",
|
||||
[5179] = "Gilneas",
|
||||
[5208] = "Gilneas City",
|
||||
[5225] = "Thalassian Highlands",
|
||||
[5536] = "Blackstone Island",
|
||||
[5723] = "Karazhan",
|
||||
[5561] = "Balor",
|
||||
[5581] = "Northwind",
|
||||
[5601] = "Dragonmaw Retreat",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -216,12 +216,15 @@ MTH_MAP_WMA_TO_PFQ = {
|
||||
[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
|
||||
@@ -298,7 +301,7 @@ MTH_DS_WMAZones = {
|
||||
[42] = { pfqAreaId=148, mapId=1, left=2941.67, right=-3608.33, top=8333.33, bottom=3966.67, width=6550.00, height=4366.66 }, -- Darkshore
|
||||
[43] = { pfqAreaId=331, mapId=1, left=1700.00, right=-4066.67, top=4672.92, bottom=829.17, width=5766.67, height=3843.75 }, -- Ashenvale
|
||||
[61] = { pfqAreaId=400, mapId=1, left=-433.33, right=-4833.33, top=-3966.67, bottom=-6900.00, width=4400.00, height=2933.33 }, -- ThousandNeedles
|
||||
[81] = { pfqAreaId=406, mapId=1, left=3949.00, right=-2069.00, top=3441.00, bottom=-569.00, width=6018.00, height=4010.00 }, -- StonetalonMountains
|
||||
[81] = { pfqAreaId=406, mapId=1, left=3245.83, right=-1637.50, top=2916.67, bottom=-339.58, width=4883.33, height=3256.25 }, -- StonetalonMountains
|
||||
[101] = { pfqAreaId=405, mapId=1, left=4233.33, right=-262.50, top=452.08, bottom=-2545.83, width=4495.83, height=2997.91 }, -- Desolace
|
||||
[121] = { pfqAreaId=357, mapId=1, left=5441.67, right=-1508.33, top=-2366.67, bottom=-7000.00, width=6950.00, height=4633.33 }, -- Feralas
|
||||
[141] = { pfqAreaId=15, mapId=1, left=-975.00, right=-6225.00, top=-2033.33, bottom=-5533.33, width=5250.00, height=3500.00 }, -- Dustwallow
|
||||
|
||||
+77
-16
@@ -212,7 +212,7 @@ local function MTH_DS_EnsurePetSpellsTrainerGrowl()
|
||||
rank = src.rank,
|
||||
rankNumber = src.rankNumber,
|
||||
school = "Physical",
|
||||
sourceUrl = "https://database.turtlecraft.gg/?spell=" .. tostring(src.id),
|
||||
sourceUrl = "https://octowow.st/db/?spell=" .. tostring(src.id),
|
||||
trainLevel = src.trainLevel,
|
||||
}
|
||||
table.insert(growlSpells, row)
|
||||
@@ -262,7 +262,7 @@ local function MTH_DS_EnsurePetSpellsTrainerGrowl()
|
||||
rank = src.rank,
|
||||
rankNumber = src.rankNumber,
|
||||
school = "Physical",
|
||||
sourceUrl = "https://database.turtlecraft.gg/?spell=" .. tostring(src.id),
|
||||
sourceUrl = "https://octowow.st/db/?spell=" .. tostring(src.id),
|
||||
trainLevel = src.trainLevel,
|
||||
}
|
||||
table.insert(staminaSpells, row)
|
||||
@@ -312,7 +312,7 @@ local function MTH_DS_EnsurePetSpellsTrainerGrowl()
|
||||
rank = src.rank,
|
||||
rankNumber = src.rankNumber,
|
||||
school = "Physical",
|
||||
sourceUrl = "https://database.turtlecraft.gg/?spell=" .. tostring(src.id),
|
||||
sourceUrl = "https://octowow.st/db/?spell=" .. tostring(src.id),
|
||||
trainLevel = src.trainLevel,
|
||||
}
|
||||
table.insert(armorSpells, row)
|
||||
@@ -356,7 +356,7 @@ local function MTH_DS_EnsurePetSpellsTrainerGrowl()
|
||||
rank = src.rank,
|
||||
rankNumber = src.rankNumber,
|
||||
school = "Physical",
|
||||
sourceUrl = "https://database.turtlecraft.gg/?spell=" .. tostring(src.id),
|
||||
sourceUrl = "https://octowow.st/db/?spell=" .. tostring(src.id),
|
||||
trainLevel = src.trainLevel,
|
||||
}
|
||||
table.insert(fireResSpells, row)
|
||||
@@ -400,7 +400,7 @@ local function MTH_DS_EnsurePetSpellsTrainerGrowl()
|
||||
rank = src.rank,
|
||||
rankNumber = src.rankNumber,
|
||||
school = "Physical",
|
||||
sourceUrl = "https://database.turtlecraft.gg/?spell=" .. tostring(src.id),
|
||||
sourceUrl = "https://octowow.st/db/?spell=" .. tostring(src.id),
|
||||
trainLevel = src.trainLevel,
|
||||
}
|
||||
table.insert(frostResSpells, row)
|
||||
@@ -444,7 +444,7 @@ local function MTH_DS_EnsurePetSpellsTrainerGrowl()
|
||||
rank = src.rank,
|
||||
rankNumber = src.rankNumber,
|
||||
school = "Physical",
|
||||
sourceUrl = "https://database.turtlecraft.gg/?spell=" .. tostring(src.id),
|
||||
sourceUrl = "https://octowow.st/db/?spell=" .. tostring(src.id),
|
||||
trainLevel = src.trainLevel,
|
||||
}
|
||||
table.insert(arcaneResSpells, row)
|
||||
@@ -488,7 +488,7 @@ local function MTH_DS_EnsurePetSpellsTrainerGrowl()
|
||||
rank = src.rank,
|
||||
rankNumber = src.rankNumber,
|
||||
school = "Physical",
|
||||
sourceUrl = "https://database.turtlecraft.gg/?spell=" .. tostring(src.id),
|
||||
sourceUrl = "https://octowow.st/db/?spell=" .. tostring(src.id),
|
||||
trainLevel = src.trainLevel,
|
||||
}
|
||||
table.insert(natureResSpells, row)
|
||||
@@ -532,7 +532,7 @@ local function MTH_DS_EnsurePetSpellsTrainerGrowl()
|
||||
rank = src.rank,
|
||||
rankNumber = src.rankNumber,
|
||||
school = "Physical",
|
||||
sourceUrl = "https://database.turtlecraft.gg/?spell=" .. tostring(src.id),
|
||||
sourceUrl = "https://octowow.st/db/?spell=" .. tostring(src.id),
|
||||
trainLevel = src.trainLevel,
|
||||
}
|
||||
table.insert(shadowResSpells, row)
|
||||
@@ -556,56 +556,56 @@ local function MTH_DS_EnsurePetSpellsTrainerGrowl()
|
||||
|
||||
MTH_DS_PetSpells.byAbility["Growl"] = {
|
||||
ability = "Growl",
|
||||
searchUrl = "https://database.turtlecraft.gg/?search=Growl",
|
||||
searchUrl = "https://octowow.st/db/?search=Growl",
|
||||
spellCount = table.getn(growlSpells),
|
||||
spells = growlSpells,
|
||||
}
|
||||
|
||||
MTH_DS_PetSpells.byAbility["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),
|
||||
spells = staminaSpells,
|
||||
}
|
||||
|
||||
MTH_DS_PetSpells.byAbility["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),
|
||||
spells = armorSpells,
|
||||
}
|
||||
|
||||
MTH_DS_PetSpells.byAbility["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),
|
||||
spells = fireResSpells,
|
||||
}
|
||||
|
||||
MTH_DS_PetSpells.byAbility["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),
|
||||
spells = frostResSpells,
|
||||
}
|
||||
|
||||
MTH_DS_PetSpells.byAbility["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),
|
||||
spells = arcaneResSpells,
|
||||
}
|
||||
|
||||
MTH_DS_PetSpells.byAbility["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),
|
||||
spells = natureResSpells,
|
||||
}
|
||||
|
||||
MTH_DS_PetSpells.byAbility["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),
|
||||
spells = shadowResSpells,
|
||||
}
|
||||
@@ -613,6 +613,66 @@ local function MTH_DS_EnsurePetSpellsTrainerGrowl()
|
||||
MTH_DS_PetSpells.totalSpellRows = table.getn(MTH_DS_PetSpells.allSpells)
|
||||
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()
|
||||
if MTH_DS.loaded then
|
||||
return
|
||||
@@ -623,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
|
||||
MTH_DS_EnsurePetSpellsTrainerGrowl()
|
||||
MTH_DS_DeriveBeastUnique()
|
||||
MTH_DS.loaded = true
|
||||
|
||||
-- Count entries
|
||||
|
||||
+11
-1
@@ -1,6 +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/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Include file="..\api\PizzaSauce.lua"/>
|
||||
<Include file="..\api\constants.lua"/>
|
||||
<Include file="..\api\savedvariables.lua"/>
|
||||
<Include file="..\api\fx-celebrate.lua"/>
|
||||
<Include file="..\api\minimap-button.lua"/>
|
||||
<Include file="..\api\AutoStrip.xml"/>
|
||||
<Include file="..\api\AutoStrip.lua"/>
|
||||
@@ -11,8 +14,10 @@
|
||||
<Include file="..\api\options-tree.lua"/>
|
||||
<Include file="..\api\options.xml"/>
|
||||
<Include file="..\api\options.lua"/>
|
||||
<Include file="..\api\options-layout.lua"/>
|
||||
<Include file="..\api\options-chronometer.lua"/>
|
||||
<Include file="..\api\options-credits.lua"/>
|
||||
<Include file="..\api\options-animations.lua"/>
|
||||
<Include file="..\api\options-autobuy.lua"/>
|
||||
<Include file="..\api\options-autoquest.lua"/>
|
||||
<Include file="..\api\options-icu.lua"/>
|
||||
@@ -21,6 +26,7 @@
|
||||
<Include file="..\api\options-profiles.lua"/>
|
||||
<Include file="..\api\options-shell.lua"/>
|
||||
<Include file="..\api\options-zbuttons.lua"/>
|
||||
<Include file="..\api\bag-ammo-labels.lua"/>
|
||||
<Include file="..\api\options-feedomatic.lua"/>
|
||||
<Include file="..\api\hunterbook.xml"/>
|
||||
<Include file="..\api\hunterbook.lua"/>
|
||||
@@ -31,6 +37,7 @@
|
||||
<Include file="..\api\hunterbook-tab-npcfinder.lua"/>
|
||||
<Include file="..\api\hunterbook-tab-stable.lua"/>
|
||||
<Include file="..\api\hunterbook-tab-pethistory.lua"/>
|
||||
<Include file="..\api\hunterbook-tab-trainers.lua"/>
|
||||
<Include file="..\api\hunterbook-model-viewer.lua"/>
|
||||
<Include file="..\api\map-markers.lua"/>
|
||||
<Include file="..\api\debug-frame.lua"/>
|
||||
@@ -40,10 +47,13 @@
|
||||
<Include file="..\api\pets-service-vitals.lua"/>
|
||||
<Include file="..\api\pets-service-runaway.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-trainer.lua"/>
|
||||
<Include file="..\api\core-scan-spellbookpet.lua"/>
|
||||
<Include file="..\api\beastlore-scan.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\version-check.lua"/>
|
||||
<Include file="..\api\localization.lua"/>
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/">
|
||||
<!-- MetaHunt Compatibility Layer -->
|
||||
<!-- Handles version compatibility with Turtle WoW -->
|
||||
</Ui>
|
||||
@@ -1,4 +0,0 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/">
|
||||
<!-- MetaHunt Environment Setup -->
|
||||
<!-- Global tables, localization, and configuration -->
|
||||
</Ui>
|
||||
@@ -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>
|
||||
@@ -1,4 +0,0 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/">
|
||||
<!-- MetaHunt UI Skins and Styling -->
|
||||
<!-- Placeholder for custom UI theming -->
|
||||
</Ui>
|
||||
+5
-3
@@ -8,6 +8,8 @@ MTH_LocaleData.ui.enUS = {
|
||||
BINDING_HEADER_ZTrapHeader = "[MetaHunt] ZTrap Buttons",
|
||||
BINDING_HEADER_ZPetHeader = "[MetaHunt] ZPet Buttons",
|
||||
BINDING_HEADER_ZAmmoHeader = "[MetaHunt] ZAmmo Buttons",
|
||||
BINDING_HEADER_ExpAmmoHeader = "[MetaHunt] MM Widget",
|
||||
BINDING_NAME_EXPAMMOACTION = "MM Widget Action",
|
||||
|
||||
FEEDOMATIC_NAME = "Feed-O-Matic",
|
||||
FEEDOMATIC_DESC = "Helps a Hunter keep his pets well fed",
|
||||
@@ -43,7 +45,7 @@ MTH_LocaleData.ui.enUS = {
|
||||
FOM_ERROR_TOGGLE_FAILED = "Failed to change FeedOMatic state: %s",
|
||||
|
||||
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_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 !",
|
||||
@@ -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_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_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_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_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_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)",
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
local MTH_AutoBuy = {
|
||||
name = "autobuy",
|
||||
enabled = false,
|
||||
version = "1.4.0",
|
||||
events = {
|
||||
"VARIABLES_LOADED",
|
||||
"MERCHANT_SHOW",
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
local MTH_AutoQuest = {
|
||||
name = "autoquest",
|
||||
enabled = false,
|
||||
version = "1.4.0",
|
||||
events = {
|
||||
"GOSSIP_SHOW",
|
||||
"QUEST_GREETING",
|
||||
@@ -57,70 +56,33 @@ local function QA_GetOptionsStore()
|
||||
if type(MTH_CharSavedVariables) ~= "table" then
|
||||
MTH_CharSavedVariables = {}
|
||||
end
|
||||
if type(MTH_CharSavedVariables.autoquest) ~= "table" then
|
||||
MTH_CharSavedVariables.autoquest = {}
|
||||
end
|
||||
local store = MTH_CharSavedVariables.autoquest
|
||||
|
||||
if store._migrated ~= true then
|
||||
if type(MTH_CharSavedVariables.questautomation) == "table" then
|
||||
local legacyStore = MTH_CharSavedVariables.questautomation
|
||||
if legacyStore.scorpokDrazial ~= nil and store.scorpokDrazial == nil then
|
||||
store.scorpokDrazial = legacyStore.scorpokDrazial and true or false
|
||||
end
|
||||
if legacyStore.scorpokTooltip ~= nil and store.scorpokTooltip == nil then
|
||||
store.scorpokTooltip = legacyStore.scorpokTooltip and true or false
|
||||
end
|
||||
end
|
||||
local legacyChar = nil
|
||||
local legacyAccount = nil
|
||||
if MTH and MTH.GetModuleCharSavedVariables then
|
||||
legacyChar = MTH:GetModuleCharSavedVariables("autoquest")
|
||||
if type(legacyChar) ~= "table" then
|
||||
legacyChar = MTH:GetModuleCharSavedVariables("questautomation")
|
||||
end
|
||||
end
|
||||
if MTH and MTH.GetModuleSavedVariables then
|
||||
legacyAccount = MTH:GetModuleSavedVariables("autoquest")
|
||||
if type(legacyAccount) ~= "table" then
|
||||
legacyAccount = MTH:GetModuleSavedVariables("questautomation")
|
||||
end
|
||||
end
|
||||
if type(legacyChar) == "table" and legacyChar.scorpokDrazial ~= nil and store.scorpokDrazial == nil then
|
||||
store.scorpokDrazial = legacyChar.scorpokDrazial and true or false
|
||||
elseif type(legacyAccount) == "table" and legacyAccount.scorpokDrazial ~= nil and store.scorpokDrazial == nil then
|
||||
store.scorpokDrazial = legacyAccount.scorpokDrazial and true or false
|
||||
elseif store.scorpokDrazial == nil and MTH and MTH.GetModuleCharSavedVariables then
|
||||
local autobuyStore = MTH:GetModuleCharSavedVariables("autobuy")
|
||||
if type(autobuyStore) == "table"
|
||||
and type(autobuyStore.questAutomation) == "table"
|
||||
and autobuyStore.questAutomation.scorpokDrazial ~= nil then
|
||||
store.scorpokDrazial = autobuyStore.questAutomation.scorpokDrazial and true or false
|
||||
end
|
||||
end
|
||||
store._migrated = true
|
||||
end
|
||||
|
||||
if store.scorpokDrazial == nil then
|
||||
store.scorpokDrazial = false
|
||||
end
|
||||
if store.arrowsForSissies == nil then
|
||||
store.arrowsForSissies = false
|
||||
end
|
||||
if store.scorpokTooltip == nil then
|
||||
store.scorpokTooltip = false
|
||||
end
|
||||
|
||||
local store = nil
|
||||
if MTH and MTH.GetModuleCharSavedVariables then
|
||||
local moduleStore = MTH:GetModuleCharSavedVariables("autoquest")
|
||||
if type(moduleStore) == "table" then
|
||||
moduleStore.scorpokDrazial = store.scorpokDrazial and true or false
|
||||
moduleStore.arrowsForSissies = store.arrowsForSissies and true or false
|
||||
moduleStore.scorpokTooltip = store.scorpokTooltip and true or false
|
||||
end
|
||||
store = MTH:GetModuleCharSavedVariables("autoquest")
|
||||
end
|
||||
|
||||
return store
|
||||
if type(store) ~= "table" then
|
||||
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
|
||||
|
||||
function MTH_AutoQuest:GetStore()
|
||||
@@ -130,36 +92,18 @@ end
|
||||
function MTH_AutoQuest:SetScorpokDrazialEnabled(enabled)
|
||||
local store = QA_GetOptionsStore()
|
||||
store.scorpokDrazial = enabled and true or false
|
||||
if MTH and MTH.GetModuleCharSavedVariables then
|
||||
local moduleStore = MTH:GetModuleCharSavedVariables("autoquest")
|
||||
if type(moduleStore) == "table" then
|
||||
moduleStore.scorpokDrazial = store.scorpokDrazial and true or false
|
||||
end
|
||||
end
|
||||
QA_Debug("option write: scorpokDrazial=" .. tostring(store.scorpokDrazial and true or false))
|
||||
end
|
||||
|
||||
function MTH_AutoQuest:SetArrowsForSissiesEnabled(enabled)
|
||||
local store = QA_GetOptionsStore()
|
||||
store.arrowsForSissies = enabled and true or false
|
||||
if MTH and MTH.GetModuleCharSavedVariables then
|
||||
local moduleStore = MTH:GetModuleCharSavedVariables("autoquest")
|
||||
if type(moduleStore) == "table" then
|
||||
moduleStore.arrowsForSissies = store.arrowsForSissies and true or false
|
||||
end
|
||||
end
|
||||
QA_Debug("option write: arrowsForSissies=" .. tostring(store.arrowsForSissies and true or false))
|
||||
end
|
||||
|
||||
function MTH_AutoQuest:SetScorpokTooltipEnabled(enabled)
|
||||
local store = QA_GetOptionsStore()
|
||||
store.scorpokTooltip = enabled and true or false
|
||||
if MTH and MTH.GetModuleCharSavedVariables then
|
||||
local moduleStore = MTH:GetModuleCharSavedVariables("autoquest")
|
||||
if type(moduleStore) == "table" then
|
||||
moduleStore.scorpokTooltip = store.scorpokTooltip and true or false
|
||||
end
|
||||
end
|
||||
QA_Debug("option write: scorpokTooltip=" .. tostring(store.scorpokTooltip and true or false))
|
||||
end
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ function MTH_ChronometerHunterInstallData(engine)
|
||||
engine:AddTimer(engine.SPELL, BS["Flare"], 30, 0, 0, 0, { cr = "YELLOW", rc = true })
|
||||
engine:AddTimer(engine.SPELL, BS["Hunter's Mark"], 120, 1, 0, 0, { cr = "RED", rc = true })
|
||||
engine:AddTimer(engine.SPELL, BS["Immolation Trap"], 60, 0, 0, 0, { gr = 1, cr = "ORANGE", ea = { [BS["Immolation Trap Effect"]] = 60 } })
|
||||
engine:AddTimer(engine.SPELL, BS["Aimed Shot"], 20, 0, 0, 0, { cr = "RED", rc = true, d = { tn = BS["Swiftshot"], tb = -2, ts = -2 } }) -- base 20s; Swiftshot reduces by 2s/rank (max -6s at rank 3)
|
||||
engine:AddTimer(engine.SPELL, BS["Kill Command"], 12, 0, 0, 0, { cr = "RED", rc = true })
|
||||
engine:AddTimer(engine.SPELL, BS["Rapid Fire"], 15, 0, 1, 1, { cr = "RED" })
|
||||
engine:AddTimer(engine.SPELL, BS["Scare Beast"], 10, 1, 0, 0, { cr = "BLUE", rc = true, d = { rs = 5 } })
|
||||
@@ -40,13 +39,16 @@ function MTH_ChronometerHunterInstallData(engine)
|
||||
engine:AddTimer(engine.SPELL, BS["Wyvern Sting"], 12, 1, 0, 0, { gr = 2, cr = "MAROON", rc = true })
|
||||
engine:AddTimer(engine.SPELL, BS["Feed Pet"], 20, 0, 0, 0, { cr = "MAROON" })
|
||||
|
||||
engine:AddTimer(engine.EVENT, BS["Explosive Trap Effect"], 20, 1, 0, 1, { gr = 1, cr = "ORANGE", a = 1, xn = BS["Explosive Trap"] })
|
||||
engine:AddTimer(engine.EVENT, BS["Freezing Trap Effect"], 10, 1, 0, 1, { gr = 1, cr = "CYAN", a = 1, d = { rs = 5, tn = BS["Clever Traps"], tb = 0.15, tp = 1 }, xn = BS["Freezing Trap"] })
|
||||
engine:AddTimer(engine.EVENT, BS["Frost Trap Aura"], 30, 0, 0, 0, { gr = 1, cr = "CYAN", a = 1, d = { tn = BS["Clever Traps"], tb = 0.15, tp = 1 }, xn = BS["Frost Trap"] })
|
||||
engine:AddTimer(engine.EVENT, BS["Immolation Trap Effect"], 15, 1, 0, 1, { gr = 1, cr = "ORANGE", a = 1, xn = BS["Immolation Trap"] })
|
||||
-- Trap effects: no "a=1" here — primed by the SPELL timer's "ea" chain (cast hook sets timer.v
|
||||
-- and timer.t). Using "a=1" would trigger on any hunter's affliction message since
|
||||
-- CHAT_MSG_SPELL_PERIODIC_CREATURE_BUFFS never embeds a caster name.
|
||||
engine:AddTimer(engine.EVENT, BS["Explosive Trap Effect"], 20, 1, 0, 1, { gr = 1, cr = "ORANGE", xn = BS["Explosive Trap"] })
|
||||
engine:AddTimer(engine.EVENT, BS["Freezing Trap Effect"], 10, 1, 0, 1, { gr = 1, cr = "CYAN", d = { rs = 5, tn = BS["Clever Traps"], tb = 0.15, tp = 1 }, xn = BS["Freezing Trap"] })
|
||||
engine:AddTimer(engine.EVENT, BS["Frost Trap Aura"], 30, 0, 0, 0, { gr = 1, cr = "CYAN", d = { tn = BS["Clever Traps"], tb = 0.15, tp = 1 }, xn = BS["Frost Trap"] })
|
||||
engine:AddTimer(engine.EVENT, BS["Immolation Trap Effect"], 15, 1, 0, 1, { gr = 1, cr = "ORANGE", xn = BS["Immolation Trap"] })
|
||||
engine:AddTimer(engine.EVENT, BS["Improved Concussive Shot"], 3, 1, 0, 0, { a = 1, cr = "BLUE", xn = BS["Concussive Shot"] })
|
||||
engine:AddTimer(engine.EVENT, BS["Improved Wing Clip"], 5, 1, 0, 0, { a = 1, cr = "MAROON", xn = BS["Wing Clip"] })
|
||||
engine:AddTimer(engine.EVENT, BS["Lock and Load"], 10, 0, 1, 1, { a = 1, cr = "MAROON", tx = "Interface\\Icons\\ability_hunter_lockandload" }) -- 10s (tooltip confirmed; expires on Aimed Shot cast)
|
||||
engine:AddTimer(engine.EVENT, BS["Lock and Load"], 10, 0, 1, 1, { a = 1, cr = "MAROON", tx = "Interface\\Icons\\ability_hunter_lockandload" }) -- 10s (tooltip confirmed; expires when consumed)
|
||||
engine:AddTimer(engine.EVENT, BS["Scent of Blood"], 8, 0, 1, 1, { a = 1, cr = "RED" })
|
||||
engine:AddTimer(engine.EVENT, BS["Piercing Shot"], 8, 1, 0, 1, { a = 1, cr = "PURPLE", tx = "Interface\\Icons\\Ability_Hunter_DisarmingShot" })
|
||||
engine:AddTimer(engine.EVENT, BS["Entrapment"], 5, 1, 0, 1, { a = 1, cr = "MAROON", tx = "Interface\\Icons\\Spell_Nature_StrangleVines" })
|
||||
|
||||
@@ -58,6 +58,18 @@ Chronometer.dataSetup = {}
|
||||
|
||||
local latins = { I = 1, II = 2, III = 3, IV = 4, V = 5, VI = 6, VII = 7, VIII = 8, IX = 9, X = 10, XI = 11, XII = 12, XIII = 13, XIV = 14 }
|
||||
local BAR_GROUP = "MTHChronometer"
|
||||
|
||||
-- When growing up the group attachment point must flip so bars stack above the
|
||||
-- anchor rather than below it. CandyBar uses the stored group point as-is when
|
||||
-- positioning each bar, so SetCandyBarGroupGrowth alone is not enough.
|
||||
local function applyGrowup(self, growup)
|
||||
if growup then
|
||||
self:SetCandyBarGroupPoint(BAR_GROUP, "BOTTOM", self.anchor, "TOP", 0, 0)
|
||||
else
|
||||
self:SetCandyBarGroupPoint(BAR_GROUP, "TOP", self.anchor, "BOTTOM", 0, 0)
|
||||
end
|
||||
self:SetCandyBarGroupGrowth(BAR_GROUP, growup)
|
||||
end
|
||||
local PARSER_OWNER = "MTHChronometerHunter"
|
||||
|
||||
|
||||
@@ -556,7 +568,7 @@ function Chronometer:OnEnable()
|
||||
self._mth_enabled = true
|
||||
|
||||
self.profile = ensureProfile()
|
||||
self:SetCandyBarGroupGrowth(BAR_GROUP, self.profile.growup and true or false)
|
||||
applyGrowup(self, self.profile.growup and true or false)
|
||||
self:SetCandyBarGroupVerticalSpacing(BAR_GROUP, self.profile.spacing or 0)
|
||||
if self.anchor then
|
||||
end
|
||||
@@ -580,14 +592,18 @@ function Chronometer:OnEnable()
|
||||
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_AURA_GONE_SELF", function(event, info) self:SPELL_FADE(event, info) end)
|
||||
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_AURA_GONE_OTHER", function(event, info) self:SPELL_FADE(event, info) end)
|
||||
|
||||
-- Registered events:
|
||||
-- SELF_BUFFS / SELF_DAMAGE → our own procs & self-damage (source == ParserLib_SELF confirmed)
|
||||
-- CREATURE_DAMAGE → DOT ticks on creatures ("from your X" embeds source; filtered in SPELL_PERIODIC)
|
||||
-- CREATURE_BUFFS → debuff APPLICATION on creatures ("X is afflicted by Y"; no source embedded).
|
||||
-- Needed for non-ticking debuffs: Poisonous/Enchanted Ammunition, Piercing Shot.
|
||||
-- Foreign-bar risk is mitigated in SPELL_PERIODIC by requiring victim == target
|
||||
-- when source is nil and no confirmed cast record exists.
|
||||
-- FRIENDLYPLAYER_*, HOSTILEPLAYER_* are other players' events → intentionally NOT registered.
|
||||
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE", function(event, info) self:SPELL_PERIODIC(event, info) end)
|
||||
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_PERIODIC_CREATURE_BUFFS", function(event, info) self:SPELL_PERIODIC(event, info) end)
|
||||
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_BUFFS", function(event, info) self:SPELL_PERIODIC(event, info) end)
|
||||
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE", function(event, info) self:SPELL_PERIODIC(event, info) end)
|
||||
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS", function(event, info) self:SPELL_PERIODIC(event, info) end)
|
||||
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE", function(event, info) self:SPELL_PERIODIC(event, info) end)
|
||||
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS", function(event, info) self:SPELL_PERIODIC(event, info) end)
|
||||
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_DAMAGE", function(event, info) self:SPELL_PERIODIC(event, info) end)
|
||||
|
||||
local enableRoM = false
|
||||
for _, timer in pairs(self.timers[self.SPELL] or {}) do
|
||||
@@ -646,15 +662,34 @@ function Chronometer:OnEnable()
|
||||
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_SELF_DAMAGE", function(event, info) self:SELF_DAMAGE(event, info) end)
|
||||
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_DAMAGESHIELDS_ON_SELF", function(event, info) self:SELF_DAMAGE(event, info) end)
|
||||
self.parser:RegisterEvent(PARSER_OWNER, "CHAT_MSG_SPELL_FAILED_LOCALPLAYER", function(event, info) self:SPELL_FAILED(event, info) end)
|
||||
|
||||
-- NamPower: register authoritative self-aura events for instant bar
|
||||
-- kill and duration resync. The ParserLib chat paths remain active as
|
||||
-- they handle creature/target debuffs that NamPower cannot see.
|
||||
if MTH and MTH.nampower then
|
||||
self._nampowerSpellIdCache = {}
|
||||
self:RegisterEvent("BUFF_ADDED_SELF")
|
||||
self:RegisterEvent("BUFF_REMOVED_SELF")
|
||||
self:RegisterEvent("DEBUFF_ADDED_SELF")
|
||||
self:RegisterEvent("DEBUFF_REMOVED_SELF")
|
||||
end
|
||||
end
|
||||
|
||||
function Chronometer:VARIABLES_LOADED()
|
||||
self.profile = ensureProfile()
|
||||
if self._mth_enabled then
|
||||
applyGrowup(self, self.profile.growup and true or false)
|
||||
self:SetCandyBarGroupVerticalSpacing(BAR_GROUP, self.profile.spacing or 0)
|
||||
end
|
||||
MTH_CHRON_RestoreAnchorFromProfile(self, "VARIABLES_LOADED")
|
||||
end
|
||||
|
||||
function Chronometer:PLAYER_ENTERING_WORLD()
|
||||
self.profile = ensureProfile()
|
||||
if self._mth_enabled then
|
||||
applyGrowup(self, self.profile.growup and true or false)
|
||||
self:SetCandyBarGroupVerticalSpacing(BAR_GROUP, self.profile.spacing or 0)
|
||||
end
|
||||
MTH_CHRON_RestoreAnchorFromProfile(self, "PLAYER_ENTERING_WORLD")
|
||||
end
|
||||
|
||||
@@ -1245,6 +1280,85 @@ function Chronometer:CreateAnchor(text, r, g, b)
|
||||
return frame
|
||||
end
|
||||
|
||||
-- ────────────────────────────────────────────────────────────
|
||||
-- NamPower self-aura handlers
|
||||
-- ────────────────────────────────────────────────────────────
|
||||
|
||||
-- Helper: resolve spellId → spell name, with cache
|
||||
local function NP_ResolveName(cache, spellId)
|
||||
if not spellId or spellId == 0 then return nil end
|
||||
local name = cache[spellId]
|
||||
if name then return name end
|
||||
name = GetSpellRecField(spellId, "name")
|
||||
if name and name ~= "" then
|
||||
cache[spellId] = name
|
||||
return name
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- BUFF / DEBUFF removed from the player → kill matching self-bar instantly
|
||||
function Chronometer:BUFF_REMOVED_SELF()
|
||||
if not self.profile.fadeonfade then return end
|
||||
local spellId = arg3
|
||||
local name = NP_ResolveName(self._nampowerSpellIdCache, spellId)
|
||||
if not name then return end
|
||||
self:KillBar(name)
|
||||
end
|
||||
|
||||
Chronometer.DEBUFF_REMOVED_SELF = Chronometer.BUFF_REMOVED_SELF
|
||||
|
||||
-- BUFF / DEBUFF added to the player → resync running bar duration
|
||||
function Chronometer:BUFF_ADDED_SELF()
|
||||
local spellId = arg3
|
||||
local name = NP_ResolveName(self._nampowerSpellIdCache, spellId)
|
||||
if not name then return end
|
||||
|
||||
-- Find the matching self-bar
|
||||
local bar = nil
|
||||
for i = 1, 20 do
|
||||
if self.bars[i].id and self.bars[i].name == name then
|
||||
local tgt = self.bars[i].target
|
||||
if tgt == "none" or tgt == UnitName("player") then
|
||||
bar = self.bars[i]
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if not bar then return end
|
||||
|
||||
-- Get authoritative remaining duration from NamPower aura array
|
||||
local remaining = nil
|
||||
local auras = GetUnitField("player", "aura")
|
||||
if auras then
|
||||
for slot = 1, 48 do
|
||||
if auras[slot] == spellId then
|
||||
local _, ms = GetPlayerAuraDuration(slot - 1)
|
||||
if ms and ms > 0 then
|
||||
remaining = ms / 1000
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if not remaining then
|
||||
-- Fallback: use base DBC duration
|
||||
local dms = GetSpellDuration(spellId)
|
||||
if dms and dms > 0 then
|
||||
remaining = dms / 1000
|
||||
end
|
||||
end
|
||||
if not remaining then return end
|
||||
|
||||
-- Resync: update total first (allows SetTimeLeft to extend), then set remaining
|
||||
self:SetCandyBarTime(bar.id, remaining)
|
||||
self:SetCandyBarTimeLeft(bar.id, remaining)
|
||||
end
|
||||
|
||||
Chronometer.DEBUFF_ADDED_SELF = Chronometer.BUFF_ADDED_SELF
|
||||
|
||||
-- ────────────────────────────────────────────────────────────
|
||||
|
||||
function Chronometer:COMBAT_DEATH(event, info)
|
||||
if not self.profile.fadeonkill then return end
|
||||
if info.type == "experience" then
|
||||
@@ -1274,11 +1388,20 @@ end
|
||||
function Chronometer:SPELL_PERIODIC(event, info)
|
||||
local aura, rank, unit, isgain
|
||||
|
||||
-- Ignore events not caused by the player (e.g. other players' DOTs/traps nearby)
|
||||
-- Primary filter: explicit non-self source → always ignore (other player's confirmed event)
|
||||
if info.source and info.source ~= ParserLib_SELF then return end
|
||||
|
||||
-- Secondary filter: creature/friendly periodic events do NOT embed a caster name,
|
||||
-- so info.source is nil even for other players' effects. Track whether this event
|
||||
-- was provably ours (SELF_BUFFS / SELF_DAMAGE events give source == ParserLib_SELF).
|
||||
local sourceIsConfirmedSelf = (info.source == ParserLib_SELF)
|
||||
|
||||
if info.type == "buff" then
|
||||
isgain = 1
|
||||
-- Buff gained by someone other than self with no source confirmation → another player's proc
|
||||
if not sourceIsConfirmedSelf and info.victim ~= nil and info.victim ~= ParserLib_SELF then
|
||||
return
|
||||
end
|
||||
elseif info.type == "debuff" then
|
||||
isgain = nil
|
||||
elseif not info.isDOT then
|
||||
@@ -1306,7 +1429,27 @@ function Chronometer:SPELL_PERIODIC(event, info)
|
||||
if timer.k.t then
|
||||
if not unit then unit = UnitName("player") end
|
||||
if timer.k.s then
|
||||
if timer.t and timer.t ~= unit then return end
|
||||
-- Selforselect: four-tier confirmation.
|
||||
-- 1. Confirmed cast record (timer.t set by CastSpell hook): require target match.
|
||||
-- 2. Confirmed self source (SELF_BUFFS / SELF_DAMAGE events, source == SELF): accept.
|
||||
-- 3. Cast-window confirmed (timer.v fresh, timer.t nil): untargeted spells like traps
|
||||
-- where the caster is known but the trigger victim cannot be predicted in advance.
|
||||
-- Accept unconditionally — the mob that walked into the trap may not be our target.
|
||||
-- 4. Source unconfirmed AND no cast record (CREATURE_BUFFS affliction messages never
|
||||
-- embed a caster name): fall back to target-check. This lets non-ticking debuffs
|
||||
-- (Poisonous Ammo, Enchanted Ammo, Piercing Shot…) show when applied to our current
|
||||
-- target, while blocking the same debuff on a mob we are NOT targeting (i.e. another
|
||||
-- player's debuff on a different dummy).
|
||||
if timer.t then
|
||||
if timer.t ~= unit then return end
|
||||
elseif sourceIsConfirmedSelf then
|
||||
-- source confirmed → accept unconditionally
|
||||
elseif timer.v and timer.v > GetTime() then
|
||||
-- cast-window confirmed, timer.t nil → untargeted cast (e.g. trap placed on ground)
|
||||
-- victim unknown at cast time; accept whatever mob triggered it
|
||||
elseif not UnitExists("target") or unit ~= UnitName("target") then
|
||||
return
|
||||
end
|
||||
else
|
||||
if not UnitExists("target") or unit ~= UnitName("target") then return end
|
||||
end
|
||||
@@ -1366,7 +1509,9 @@ function Chronometer:UNIT_AURA(unit)
|
||||
end
|
||||
|
||||
local hasFeedAura = false
|
||||
if type(UnitBuff) == "function" then
|
||||
if type(MTH_FEED_HasPetFeedBuff) == "function" then
|
||||
hasFeedAura = MTH_FEED_HasPetFeedBuff()
|
||||
elseif type(UnitBuff) == "function" then
|
||||
for i = 1, 32 do
|
||||
local texture = UnitBuff("pet", i)
|
||||
if not texture then
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
local MTH_ChronometerModule = {
|
||||
name = "chronometer",
|
||||
enabled = true,
|
||||
version = "1.4.0",
|
||||
events = {},
|
||||
initialized = false,
|
||||
}
|
||||
|
||||
+827
-280
File diff suppressed because it is too large
Load Diff
@@ -125,6 +125,11 @@ FOM_FamilyToLegacyMap = {
|
||||
|
||||
|
||||
|
||||
-- [DISABLED] NO-BUFF quarantine: set to true to re-enable.
|
||||
-- When false, failed feeds are still logged but items are never
|
||||
-- blacklisted and the quarantine store is never consulted.
|
||||
local FOM_QUARANTINE_ENABLED = false;
|
||||
|
||||
local function FOM_GetServerProfile()
|
||||
return FOM_ServerProfiles[FOM_SERVER_PROFILE] or {};
|
||||
end
|
||||
@@ -186,6 +191,7 @@ local function FOM_GetFamilyKeyFromInfo(petInfo)
|
||||
end
|
||||
|
||||
local function FOM_RegisterNoBuffQuarantine(itemID, petInfo)
|
||||
if (not FOM_QUARANTINE_ENABLED) then return; end
|
||||
local numericItem = tonumber(itemID);
|
||||
if (numericItem == nil) then
|
||||
return;
|
||||
@@ -217,6 +223,7 @@ local function FOM_RegisterNoBuffQuarantine(itemID, petInfo)
|
||||
end
|
||||
|
||||
local function FOM_IsQuarantined(itemID, petLevel, familyKey)
|
||||
if (not FOM_QUARANTINE_ENABLED) then return false; end
|
||||
local numericItem = tonumber(itemID);
|
||||
local numericPet = tonumber(petLevel);
|
||||
if (numericItem == nil or numericPet == nil or numericPet <= 0) then
|
||||
@@ -643,41 +650,6 @@ function MTH_GetMerchantFoodsByDiet()
|
||||
return result;
|
||||
end
|
||||
|
||||
local function FOM_DebugLog(message)
|
||||
return;
|
||||
end
|
||||
|
||||
local function FOM_DebugLogMerchantFoods(sourceEvent)
|
||||
local merchantFoods = MTH_GetMerchantFoodsByDiet();
|
||||
local eventLabel = tostring(sourceEvent or "MERCHANT");
|
||||
if (merchantFoods == nil) then
|
||||
FOM_DebugLog(eventLabel .. ": no merchant data available.");
|
||||
return;
|
||||
end
|
||||
|
||||
local knownCount = table.getn(merchantFoods.items or {});
|
||||
local unknownCount = table.getn(merchantFoods.unknown or {});
|
||||
local merchantName = UnitName("npc") or UnitName("target") or "unknown";
|
||||
|
||||
FOM_DebugLog(eventLabel .. ": " .. tostring(merchantName) .. " | known foods=" .. tostring(knownCount) .. " | unknown items=" .. tostring(unknownCount));
|
||||
|
||||
if (type(merchantFoods.byDiet) == "table") then
|
||||
for dietName, foodRows in merchantFoods.byDiet do
|
||||
local dietCount = table.getn(foodRows or {});
|
||||
FOM_DebugLog("diet " .. tostring(dietName) .. ": " .. tostring(dietCount) .. " item(s)");
|
||||
for _, row in foodRows do
|
||||
FOM_DebugLog(" - [" .. tostring(row.id) .. "] " .. tostring(row.name) .. " (merchantIndex=" .. tostring(row.index) .. ")");
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (unknownCount > 0) then
|
||||
for _, row in merchantFoods.unknown do
|
||||
FOM_DebugLog("unknown food map: [" .. tostring(row.id) .. "] " .. tostring(row.name) .. " (merchantIndex=" .. tostring(row.index) .. ")");
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function MTH_FOM_FeedButton_OnClick()
|
||||
if (arg1 == "RightButton") then
|
||||
if (MTH_OpenOptions) then
|
||||
@@ -896,10 +868,6 @@ function FOM_OnEvent(event, arg1)
|
||||
end
|
||||
return;
|
||||
|
||||
elseif ( event == "MERCHANT_SHOW" or event == "MERCHANT_UPDATE" ) then
|
||||
FOM_DebugLogMerchantFoods(event);
|
||||
return;
|
||||
|
||||
elseif ( event == "PET_ATTACK_START" ) then
|
||||
|
||||
-- Set Flag
|
||||
@@ -926,7 +894,6 @@ function FOM_OnEvent(event, arg1)
|
||||
end
|
||||
if (foodID) then
|
||||
FOM_LastFood = GFWUtils.ItemLink(foodID);
|
||||
GFWUtils.DebugLog("Manually fed "..FOM_LastFood);
|
||||
end
|
||||
end
|
||||
return;
|
||||
@@ -1187,7 +1154,7 @@ function FOM_OnUpdate(elapsed)
|
||||
FOM_CoreFinalizeAttempt(FOM_LastFeedAttempt.coreAttemptId, "rejected", "no-buff");
|
||||
local currentPetInfo = MTH_FOM_GetCorePetInfo();
|
||||
FOM_RegisterNoBuffQuarantine(FOM_LastFeedAttempt.itemId, currentPetInfo);
|
||||
if (type(MTH_FEED_BlockItemForPetLevel) == "function") then
|
||||
if (FOM_QUARANTINE_ENABLED and type(MTH_FEED_BlockItemForPetLevel) == "function") then
|
||||
pcall(MTH_FEED_BlockItemForPetLevel,
|
||||
FOM_LastFeedAttempt.itemId,
|
||||
tonumber(currentPetInfo and currentPetInfo.level) or nil,
|
||||
@@ -1245,35 +1212,63 @@ end
|
||||
function FOM_CanFeed()
|
||||
local petInfo = MTH_FOM_GetCorePetInfo();
|
||||
if ( not (petInfo and petInfo.liveExists) ) then
|
||||
GFWUtils.DebugLog("Can't feed; pet doesn't exist.");
|
||||
return false;
|
||||
end
|
||||
if ( tonumber(petInfo.health) and tonumber(petInfo.health) <= 0 ) then
|
||||
GFWUtils.DebugLog("Can't feed; pet is dead.");
|
||||
return false;
|
||||
end
|
||||
if ( UnitHealth("player") <= 0 ) then
|
||||
GFWUtils.DebugLog("Can't feed; I'm dead.");
|
||||
return false;
|
||||
end
|
||||
if ( CastingBarFrameStatusBar:IsVisible() ) then
|
||||
GFWUtils.DebugLog("Can't feed; casting a spell / tradeksill.");
|
||||
return false;
|
||||
end
|
||||
if ( UnitOnTaxi("player") ) then
|
||||
GFWUtils.DebugLog("Can't feed; flying.");
|
||||
return false;
|
||||
end
|
||||
if ( FOM_State.InCombat ) or ( PlayerFrame.inCombat ) then
|
||||
GFWUtils.DebugLog("Can't feed; in combat.");
|
||||
return false;
|
||||
end
|
||||
if ( LootFrame:IsVisible() ) then
|
||||
GFWUtils.DebugLog("Shouldn't feed; loot window is open.");
|
||||
return false;
|
||||
end
|
||||
|
||||
|
||||
-- NamPower path: scan aura array by spellId → name, no tooltip needed
|
||||
if MTH and MTH.nampower and type(GetUnitField) == "function" and type(GetSpellRecField) == "function" then
|
||||
local auras = GetUnitField("player", "aura")
|
||||
if type(auras) == "table" then
|
||||
local dontFeedNames = {
|
||||
["Shadowmeld"] = true,
|
||||
["Feign Death"] = true,
|
||||
["Food"] = true,
|
||||
["Drink"] = true,
|
||||
["Refreshment"] = true,
|
||||
}
|
||||
for i = 1, 32 do
|
||||
local sid = tonumber(auras[i])
|
||||
if sid and sid > 0 then
|
||||
local name = GetSpellRecField(sid, "name")
|
||||
if name then
|
||||
if dontFeedNames[name] then
|
||||
return false
|
||||
end
|
||||
if UnitLevel("player") >= 40 then
|
||||
local lname = string.lower(name)
|
||||
for _, mountSub in FOM_MOUNT_NAME_SUBSTRINGS do
|
||||
if string.find(lname, mountSub) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Legacy path: texture + tooltip scanning
|
||||
local buff, buffIndex;
|
||||
local dontFeedBuffTextures = {
|
||||
"Interface\\Icons\\Ability_Ambush", -- NE Shadowmeld (maybe not unique buff icon?)
|
||||
@@ -1290,7 +1285,6 @@ function FOM_CanFeed()
|
||||
if ( buff ~= nil) then
|
||||
for _, buffTexture in dontFeedBuffTextures do
|
||||
if ( buff == buffTexture ) then
|
||||
GFWUtils.DebugLog("Can't feed; currently, eating, drinking, or feigning death.");
|
||||
return false;
|
||||
end
|
||||
end
|
||||
@@ -1303,7 +1297,6 @@ function FOM_CanFeed()
|
||||
msg = string.lower(msg);
|
||||
for _, mountName in FOM_MOUNT_NAME_SUBSTRINGS do
|
||||
if (string.find(msg, mountName)) then
|
||||
GFWUtils.DebugLog("Can't feed; mounted.");
|
||||
return false;
|
||||
end
|
||||
end
|
||||
@@ -1855,15 +1848,6 @@ end
|
||||
-- Add a food to a list
|
||||
function FOM_AddFood(diet, food)
|
||||
|
||||
if (FOM_Foods[diet] == nil) then
|
||||
GFWUtils.DebugLog("FOM_Foods[diet] == nil");
|
||||
end
|
||||
if (FOM_AddedFoods == nil or FOM_AddedFoods[diet] == nil) then
|
||||
GFWUtils.DebugLog("FOM_AddedFoods == nil or FOM_AddedFoods[diet] == nil");
|
||||
end
|
||||
if (FOM_RemovedFoods == nil or FOM_RemovedFoods[diet] == nil) then
|
||||
GFWUtils.DebugLog("FOM_RemovedFoods == nil or FOM_RemovedFoods[diet] == nil");
|
||||
end
|
||||
if ( GFWTable.IndexOf(FOM_Foods[diet], food) == 0 ) then
|
||||
if (FOM_AddedFoods == nil) then
|
||||
FOM_AddedFoods = {};
|
||||
@@ -1897,15 +1881,6 @@ end
|
||||
-- Remove a food from a list
|
||||
function FOM_RemoveFood(diet, food)
|
||||
|
||||
if (FOM_Foods[diet] == nil) then
|
||||
GFWUtils.DebugLog("FOM_Foods[diet] == nil");
|
||||
end
|
||||
if (FOM_AddedFoods == nil or FOM_AddedFoods[diet] == nil) then
|
||||
GFWUtils.DebugLog("FOM_AddedFoods == nil or FOM_AddedFoods[diet] == nil");
|
||||
end
|
||||
if (FOM_RemovedFoods == nil or FOM_RemovedFoods[diet] == nil) then
|
||||
GFWUtils.DebugLog("FOM_RemovedFoods == nil or FOM_RemovedFoods[diet] == nil");
|
||||
end
|
||||
if ( GFWTable.IndexOf(FOM_Foods[diet], food) ~= 0 ) then
|
||||
if (FOM_RemovedFoods == nil) then
|
||||
FOM_RemovedFoods = {};
|
||||
@@ -1936,7 +1911,7 @@ function FOM_RemoveFood(diet, food)
|
||||
end
|
||||
|
||||
-- Clear quarantine, core exception blocks, and RemovedFoods ban for a specific item (called when user explicitly re-adds via /fom add)
|
||||
local function FOM_ClearItemBans(itemID)
|
||||
function FOM_ClearItemBans(itemID)
|
||||
local numericItem = tonumber(itemID);
|
||||
if (numericItem == nil) then return; end
|
||||
-- Clear from RemovedFoods across all diet lists
|
||||
@@ -2076,6 +2051,9 @@ end
|
||||
|
||||
-- Check Feed Effect
|
||||
function FOM_HasFeedEffect()
|
||||
if type(MTH_FEED_HasPetFeedBuff) == "function" then
|
||||
return MTH_FEED_HasPetFeedBuff()
|
||||
end
|
||||
|
||||
local i = 1;
|
||||
local buff;
|
||||
@@ -2341,12 +2319,6 @@ function FOM_Feed(aFood, options)
|
||||
foodLevel = selectedFoodLevel,
|
||||
})
|
||||
|
||||
GFWUtils.DebugLog("Picked "..FOM_LastFood.." (bag "..foodBag..", slot "..foodItem..") for feeding.");
|
||||
if (FOM_Config.Debug) then
|
||||
-- don't actually feed anything, just show what we would choose
|
||||
return false;
|
||||
end
|
||||
|
||||
-- Actually feed the item to the pet
|
||||
PickupContainerItem(foodBag, foodItem);
|
||||
if ( CursorHasItem() ) then
|
||||
@@ -2642,6 +2614,11 @@ function FOM_NewFindFood(fallback, excludedItemIds, precomputedFoodList, openSlo
|
||||
table.insert(reasonParts, "lower quality preferred");
|
||||
end
|
||||
else
|
||||
-- Bags are full: free a slot by eating a small stack, but still prefer foods
|
||||
-- the pet can actually eat (known food level) so we don't pick an
|
||||
-- unknown-level food the pet will refuse. Falls back to smallest stack
|
||||
-- when nothing has a known level.
|
||||
table.sort(FlatFoodList, FOM_SortKnownThenCount);
|
||||
table.insert(reasonParts, "small stack to free bag space");
|
||||
end
|
||||
if (FOM_Config.AvoidUsefulFood and not fallback) then
|
||||
@@ -2683,6 +2660,15 @@ function FOM_SortCount(a, b)
|
||||
return a.count < b.count;
|
||||
end
|
||||
|
||||
function FOM_SortKnownThenCount(a, b)
|
||||
local aKnown = a.knownLevel and 1 or 0;
|
||||
local bKnown = b.knownLevel and 1 or 0;
|
||||
if (aKnown ~= bKnown) then
|
||||
return aKnown > bKnown; -- edible (known-level) foods first
|
||||
end
|
||||
return a.count < b.count; -- then smallest stack to free bag space
|
||||
end
|
||||
|
||||
function FOM_SortQualityDescending(a, b)
|
||||
return a.quality > b.quality;
|
||||
end
|
||||
@@ -2708,12 +2694,10 @@ end
|
||||
function FOM_IsUsefulFood(itemID, quantity)
|
||||
local foodName = GetItemInfo(itemID);
|
||||
if (foodName == nil) then
|
||||
GFWUtils.DebugLog("Can't get info for item ID "..itemID..", assuming it's OK to eat.");
|
||||
return false;
|
||||
end
|
||||
if (FOM_Cooking and FOM_Cooking[FOM_RealmPlayer] and FOM_Cooking[FOM_RealmPlayer][itemID]) then
|
||||
if (FOM_Cooking[FOM_RealmPlayer][itemID] >= FOM_Config.SaveForCookingLevel) then
|
||||
GFWUtils.DebugLog("Skipping "..quantity.."x "..foodName.."; is good for cooking.");
|
||||
return true;
|
||||
end
|
||||
end
|
||||
@@ -2725,19 +2709,15 @@ function FOM_IsUsefulFood(itemID, quantity)
|
||||
FOM_Quantity[foodName] = FOM_Quantity[foodName] + quantity;
|
||||
end
|
||||
if (FOM_Quantity[foodName] > FOM_QuestFood[FOM_RealmPlayer][foodName]) then
|
||||
GFWUtils.DebugLog("Not skipping "..quantity.."x "..foodName.."; is needed for quest, but we have more than enough.");
|
||||
return false;
|
||||
else
|
||||
GFWUtils.DebugLog("Skipping "..quantity.."x "..foodName.."; is needed for quest.");
|
||||
return true;
|
||||
end
|
||||
end
|
||||
end
|
||||
if (FOM_Config.AvoidBonusFood and FOM_IsInDiet(itemID, FOM_DIET_BONUS)) then
|
||||
GFWUtils.DebugLog("Skipping "..quantity.."x "..foodName.."; has bonus effect when eaten by player.");
|
||||
return true;
|
||||
end
|
||||
--GFWUtils.DebugLog("Not skipping "..quantity.."x "..foodName.."; doesn't have other uses.");
|
||||
return false;
|
||||
end
|
||||
|
||||
@@ -2774,9 +2754,6 @@ function FOM_IsInDiet(food, dietList)
|
||||
if (diet == nil) then
|
||||
diet = "";
|
||||
end
|
||||
if (FOM_Foods[diet] == nil) then
|
||||
GFWUtils.DebugLog("FOM_Foods[diet] == nil");
|
||||
end
|
||||
if (FOM_RemovedFoods ~= nil and FOM_RemovedFoods[diet] ~= nil and GFWTable.IndexOf(FOM_RemovedFoods[diet], food) ~= 0) then
|
||||
return false;
|
||||
end
|
||||
|
||||
@@ -6,11 +6,8 @@
|
||||
local MTH_FeedOMatic = {
|
||||
name = "feedomatic",
|
||||
enabled = false,
|
||||
version = "1.4.0",
|
||||
events = {
|
||||
"VARIABLES_LOADED",
|
||||
"MERCHANT_SHOW",
|
||||
"MERCHANT_UPDATE",
|
||||
"MTH_PET_LIVE_STATE_CHANGED",
|
||||
"PET_ATTACK_START",
|
||||
"PET_ATTACK_STOP",
|
||||
@@ -32,7 +29,6 @@ local MTH_FeedOMatic = {
|
||||
}
|
||||
|
||||
local MTH_PETL_HOOK_BOUNDARY_KEY = "pet-lifecycle-hooks"
|
||||
local MTH_FOM_MerchantProbe = nil
|
||||
|
||||
function MTH_FeedOMatic_OnUpdate(elapsed)
|
||||
if not elapsed then
|
||||
@@ -129,104 +125,34 @@ local function MTH_FeedOMatic_SyncSavedVariables()
|
||||
end
|
||||
|
||||
local moduleStore = MTH:GetModuleSavedVariables("feedomatic")
|
||||
if not moduleStore.legacy then
|
||||
moduleStore.legacy = {}
|
||||
end
|
||||
|
||||
local function bindLegacyTable(globalName)
|
||||
local legacyValue = moduleStore.legacy[globalName]
|
||||
local globalValue = _G and _G[globalName] or nil
|
||||
|
||||
if type(legacyValue) ~= "table" then
|
||||
-- Bind each legacy FeedOMatic global to its clean nested location under
|
||||
-- modules.feedomatic. The globals are runtime aliases only (NOT persisted via
|
||||
-- the TOC any more); the real data lives in these nested tables inside
|
||||
-- MTH_SavedVariables. Adopt any pre-existing runtime global on first bind.
|
||||
local function bindGlobal(globalName, key)
|
||||
local target = moduleStore[key]
|
||||
if type(target) ~= "table" then
|
||||
local globalValue = _G and _G[globalName] or nil
|
||||
if type(globalValue) == "table" then
|
||||
legacyValue = globalValue
|
||||
target = globalValue
|
||||
else
|
||||
legacyValue = {}
|
||||
target = {}
|
||||
end
|
||||
moduleStore.legacy[globalName] = legacyValue
|
||||
moduleStore[key] = target
|
||||
end
|
||||
|
||||
if _G then
|
||||
_G[globalName] = legacyValue
|
||||
_G[globalName] = target
|
||||
end
|
||||
end
|
||||
|
||||
bindLegacyTable("FOM_Config")
|
||||
bindLegacyTable("FOM_FoodQuality")
|
||||
bindLegacyTable("FOM_AddedFoods")
|
||||
bindLegacyTable("FOM_RemovedFoods")
|
||||
bindLegacyTable("FOM_Cooking")
|
||||
bindLegacyTable("FOM_QuestFood")
|
||||
bindLegacyTable("FOM_LocaleInfo")
|
||||
end
|
||||
|
||||
local function MTH_FeedOMatic_Log(message, severity)
|
||||
if type(MTH_Log) == "function" then
|
||||
MTH_Log("[FeedOMatic] " .. tostring(message or ""), severity)
|
||||
end
|
||||
end
|
||||
|
||||
local function MTH_FeedOMatic_DebugMerchantScan(sourceEvent)
|
||||
if type(MTH_GetMerchantFoodsByDiet) ~= "function" then
|
||||
MTH_FeedOMatic_Log(tostring(sourceEvent) .. ": MTH_GetMerchantFoodsByDiet is not available yet")
|
||||
return
|
||||
end
|
||||
|
||||
local merchantFoods = MTH_GetMerchantFoodsByDiet()
|
||||
local knownCount = 0
|
||||
local unknownCount = 0
|
||||
if type(merchantFoods) == "table" then
|
||||
if type(merchantFoods.items) == "table" then
|
||||
knownCount = table.getn(merchantFoods.items)
|
||||
end
|
||||
if type(merchantFoods.unknown) == "table" then
|
||||
unknownCount = table.getn(merchantFoods.unknown)
|
||||
end
|
||||
end
|
||||
|
||||
local merchantName = UnitName("npc") or UnitName("target") or "unknown"
|
||||
MTH_FeedOMatic_Log(tostring(sourceEvent) .. ": wrapper scan for " .. tostring(merchantName) .. " | known=" .. tostring(knownCount) .. " | unknown=" .. tostring(unknownCount))
|
||||
|
||||
if type(merchantFoods.byDiet) == "table" then
|
||||
for dietName, rows in merchantFoods.byDiet do
|
||||
local dietCount = 0
|
||||
if type(rows) == "table" then
|
||||
dietCount = table.getn(rows)
|
||||
end
|
||||
if type(rows) == "table" then
|
||||
for _, row in rows do
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if unknownCount > 0 and type(merchantFoods.unknown) == "table" then
|
||||
for _, row in merchantFoods.unknown do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function MTH_FeedOMatic_SetMerchantProbeEnabled(enabled)
|
||||
if not enabled then
|
||||
if MTH_FOM_MerchantProbe then
|
||||
MTH_FOM_MerchantProbe:UnregisterEvent("MERCHANT_SHOW")
|
||||
MTH_FOM_MerchantProbe:UnregisterEvent("MERCHANT_UPDATE")
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if not MTH_FOM_MerchantProbe then
|
||||
MTH_FOM_MerchantProbe = CreateFrame("Frame", "MTH_FOM_MerchantProbe")
|
||||
MTH_FOM_MerchantProbe:SetScript("OnEvent", function()
|
||||
local evt = event
|
||||
if evt == "MERCHANT_SHOW" or evt == "MERCHANT_UPDATE" then
|
||||
MTH_FeedOMatic_DebugMerchantScan(evt)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
MTH_FOM_MerchantProbe:RegisterEvent("MERCHANT_SHOW")
|
||||
MTH_FOM_MerchantProbe:RegisterEvent("MERCHANT_UPDATE")
|
||||
bindGlobal("FOM_Config", "settings")
|
||||
bindGlobal("FOM_FoodQuality", "foodQuality")
|
||||
bindGlobal("FOM_AddedFoods", "addedFoods")
|
||||
bindGlobal("FOM_RemovedFoods", "removedFoods")
|
||||
bindGlobal("FOM_Cooking", "cooking")
|
||||
bindGlobal("FOM_QuestFood", "questFood")
|
||||
bindGlobal("FOM_LocaleInfo", "localeInfo")
|
||||
end
|
||||
|
||||
function MTH_FeedOMatic:init()
|
||||
@@ -266,7 +192,6 @@ function MTH_FeedOMatic:setEnabled(enabled)
|
||||
MTH_FeedOMatic_CaptureHookBoundary()
|
||||
MTH_FeedOMatic_SyncSavedVariables()
|
||||
MTH_FeedOMatic_EnsureVariablesLoaded(self)
|
||||
MTH_FeedOMatic_SetMerchantProbeEnabled(false)
|
||||
if MTH_FOM_FeedButton then
|
||||
MTH_FOM_FeedButton:Show()
|
||||
end
|
||||
@@ -276,7 +201,6 @@ function MTH_FeedOMatic:setEnabled(enabled)
|
||||
if type(FOM_State) == "table" then
|
||||
FOM_State.ShouldFeed = false
|
||||
end
|
||||
MTH_FeedOMatic_SetMerchantProbeEnabled(false)
|
||||
if MTH_FOM_FeedButton then
|
||||
MTH_FOM_FeedButton:Hide()
|
||||
end
|
||||
@@ -305,7 +229,6 @@ function MTH_FeedOMatic:cleanup()
|
||||
if type(FOM_State) == "table" then
|
||||
FOM_State.ShouldFeed = false
|
||||
end
|
||||
MTH_FeedOMatic_SetMerchantProbeEnabled(false)
|
||||
if MTH_FOM_FeedButton then
|
||||
MTH_FOM_FeedButton:Hide();
|
||||
end
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
local MTH_ICU = {
|
||||
name = "icu",
|
||||
enabled = false,
|
||||
version = "1.4.0",
|
||||
events = {
|
||||
"VARIABLES_LOADED",
|
||||
},
|
||||
|
||||
+184
-61
@@ -1,7 +1,3 @@
|
||||
local function MTHSmartAmmo_GetSaved()
|
||||
return MTH_SA_GetSavedTable("MTHSmartAmmo")
|
||||
end
|
||||
|
||||
local function MTHSmartAmmo_GetModuleStore()
|
||||
if MTH and MTH.GetModuleCharSavedVariables then
|
||||
return MTH:GetModuleCharSavedVariables("smartammo")
|
||||
@@ -9,49 +5,76 @@ local function MTHSmartAmmo_GetModuleStore()
|
||||
return nil
|
||||
end
|
||||
|
||||
local function MTHSmartAmmo_IsSmartEnabled()
|
||||
-- Single source of truth: moduleStore.settings.{smartEnabled,reloadEnabled,weaponSwapEnabled}
|
||||
local function MTHSmartAmmo_GetSettings()
|
||||
local moduleStore = MTHSmartAmmo_GetModuleStore()
|
||||
if type(moduleStore) == "table" and moduleStore.smartEnabled ~= nil then
|
||||
return moduleStore.smartEnabled and true or false
|
||||
if type(moduleStore) ~= "table" then
|
||||
return nil
|
||||
end
|
||||
local saved = MTHSmartAmmo_GetSaved()
|
||||
if saved["enabled"] == nil then
|
||||
return true
|
||||
if type(moduleStore.settings) ~= "table" then
|
||||
moduleStore.settings = {}
|
||||
end
|
||||
return saved["enabled"] and true or false
|
||||
return moduleStore.settings
|
||||
end
|
||||
|
||||
local function MTHSmartAmmo_ReadFlag(key)
|
||||
local settings = MTHSmartAmmo_GetSettings()
|
||||
if settings and settings[key] ~= nil then
|
||||
return settings[key] and true or false
|
||||
end
|
||||
return true -- default enabled
|
||||
end
|
||||
|
||||
local function MTHSmartAmmo_WriteFlag(key, enabled)
|
||||
local settings = MTHSmartAmmo_GetSettings()
|
||||
if settings then
|
||||
settings[key] = enabled and true or false
|
||||
end
|
||||
end
|
||||
|
||||
local function MTHSmartAmmo_IsSmartEnabled()
|
||||
return MTHSmartAmmo_ReadFlag("smartEnabled")
|
||||
end
|
||||
|
||||
local function MTHSmartAmmo_IsReloadEnabled()
|
||||
local moduleStore = MTHSmartAmmo_GetModuleStore()
|
||||
if type(moduleStore) == "table" and moduleStore.reloadEnabled ~= nil then
|
||||
return moduleStore.reloadEnabled and true or false
|
||||
end
|
||||
local saved = MTHSmartAmmo_GetSaved()
|
||||
if saved["reload"] == nil then
|
||||
return true
|
||||
end
|
||||
return saved["reload"] and true or false
|
||||
return MTHSmartAmmo_ReadFlag("reloadEnabled")
|
||||
end
|
||||
|
||||
local function MTHSmartAmmo_IsWeaponSwapEnabled()
|
||||
local moduleStore = MTHSmartAmmo_GetModuleStore()
|
||||
if type(moduleStore) == "table" and moduleStore.weaponSwapEnabled ~= nil then
|
||||
return moduleStore.weaponSwapEnabled and true or false
|
||||
end
|
||||
local saved = MTHSmartAmmo_GetSaved()
|
||||
if saved["weaponSwap"] == nil then
|
||||
return true
|
||||
end
|
||||
return saved["weaponSwap"] and true or false
|
||||
return MTHSmartAmmo_ReadFlag("weaponSwapEnabled")
|
||||
end
|
||||
|
||||
local function MTHSmartAmmo_IsLowAmmoWarningEnabled()
|
||||
return MTHSmartAmmo_ReadFlag("lowAmmoWarningEnabled")
|
||||
end
|
||||
|
||||
local function MTHSmartAmmo_ReadLowAmmoThreshold()
|
||||
local settings = MTHSmartAmmo_GetSettings()
|
||||
local threshold = tonumber(settings and settings.lowAmmoThreshold) or 200
|
||||
if threshold < 1 then threshold = 1 end
|
||||
return math.floor(threshold)
|
||||
end
|
||||
|
||||
local MTHSmartAmmo_InitializeHooks
|
||||
MTHSmartAmmo_EnsureHooks = nil
|
||||
|
||||
local MTH_SA_SPELL_EVENTS = {
|
||||
"START_AUTOREPEAT_SPELL",
|
||||
"STOP_AUTOREPEAT_SPELL",
|
||||
"SPELLCAST_INTERRUPTED",
|
||||
"SPELLCAST_FAILED",
|
||||
"SPELLCAST_STOP",
|
||||
"SPELLCAST_DELAYED",
|
||||
}
|
||||
|
||||
function MTHSmartAmmo_SetSmartEnabled(enabled, silent)
|
||||
local saved = MTHSmartAmmo_GetSaved()
|
||||
if enabled then
|
||||
saved["enabled"] = 1
|
||||
local evFrame = getglobal("MTH_SA_EventFrame")
|
||||
if evFrame then
|
||||
for _, ev in ipairs(MTH_SA_SPELL_EVENTS) do
|
||||
evFrame:RegisterEvent(ev)
|
||||
end
|
||||
end
|
||||
if MTHSmartAmmo_InitializeHooks then
|
||||
MTHSmartAmmo_InitializeHooks()
|
||||
end
|
||||
@@ -62,16 +85,18 @@ function MTHSmartAmmo_SetSmartEnabled(enabled, silent)
|
||||
MTH_SA_Print("Smart Ammo Enabled.")
|
||||
end
|
||||
else
|
||||
saved["enabled"] = false
|
||||
local evFrame = getglobal("MTH_SA_EventFrame")
|
||||
if evFrame then
|
||||
for _, ev in ipairs(MTH_SA_SPELL_EVENTS) do
|
||||
evFrame:UnregisterEvent(ev)
|
||||
end
|
||||
end
|
||||
if not silent and DEFAULT_CHAT_FRAME then
|
||||
MTH_SA_Print("Smart Ammo Disabled.")
|
||||
end
|
||||
end
|
||||
|
||||
local moduleStore = MTHSmartAmmo_GetModuleStore()
|
||||
if type(moduleStore) == "table" then
|
||||
moduleStore.smartEnabled = enabled and true or false
|
||||
end
|
||||
MTHSmartAmmo_WriteFlag("smartEnabled", enabled and true or false)
|
||||
end
|
||||
|
||||
function MTHSmartAmmo_GetSmartEnabled()
|
||||
@@ -79,16 +104,7 @@ function MTHSmartAmmo_GetSmartEnabled()
|
||||
end
|
||||
|
||||
function MTHSmartAmmo_SetReloadEnabled(enabled, silent)
|
||||
local saved = MTHSmartAmmo_GetSaved()
|
||||
if enabled then
|
||||
saved["reload"] = 1
|
||||
else
|
||||
saved["reload"] = false
|
||||
end
|
||||
local moduleStore = MTHSmartAmmo_GetModuleStore()
|
||||
if type(moduleStore) == "table" then
|
||||
moduleStore.reloadEnabled = enabled and true or false
|
||||
end
|
||||
MTHSmartAmmo_WriteFlag("reloadEnabled", enabled and true or false)
|
||||
if not silent and DEFAULT_CHAT_FRAME then
|
||||
MTH_SA_Print("Smart Ammo reload fallback " .. (enabled and "Enabled." or "Disabled."))
|
||||
end
|
||||
@@ -99,16 +115,7 @@ function MTHSmartAmmo_GetReloadEnabled()
|
||||
end
|
||||
|
||||
function MTHSmartAmmo_SetWeaponSwapEnabled(enabled, silent)
|
||||
local saved = MTHSmartAmmo_GetSaved()
|
||||
if enabled then
|
||||
saved["weaponSwap"] = 1
|
||||
else
|
||||
saved["weaponSwap"] = false
|
||||
end
|
||||
local moduleStore = MTHSmartAmmo_GetModuleStore()
|
||||
if type(moduleStore) == "table" then
|
||||
moduleStore.weaponSwapEnabled = enabled and true or false
|
||||
end
|
||||
MTHSmartAmmo_WriteFlag("weaponSwapEnabled", enabled and true or false)
|
||||
if not silent and DEFAULT_CHAT_FRAME then
|
||||
MTH_SA_Print("Smart Ammo weapon-swap auto-equip " .. (enabled and "Enabled." or "Disabled."))
|
||||
end
|
||||
@@ -118,6 +125,29 @@ function MTHSmartAmmo_GetWeaponSwapEnabled()
|
||||
return MTHSmartAmmo_IsWeaponSwapEnabled() and true or false
|
||||
end
|
||||
|
||||
function MTHSmartAmmo_SetLowAmmoWarningEnabled(enabled, silent)
|
||||
MTHSmartAmmo_WriteFlag("lowAmmoWarningEnabled", enabled and true or false)
|
||||
if not silent and DEFAULT_CHAT_FRAME then
|
||||
MTH_SA_Print("Smart Ammo low-ammo warnings " .. (enabled and "Enabled." or "Disabled."))
|
||||
end
|
||||
end
|
||||
|
||||
function MTHSmartAmmo_GetLowAmmoWarningEnabled()
|
||||
return MTHSmartAmmo_IsLowAmmoWarningEnabled() and true or false
|
||||
end
|
||||
|
||||
function MTHSmartAmmo_SetLowAmmoThreshold(value)
|
||||
local settings = MTHSmartAmmo_GetSettings()
|
||||
if not settings then return end
|
||||
local threshold = tonumber(value) or 200
|
||||
if threshold < 1 then threshold = 1 end
|
||||
settings.lowAmmoThreshold = math.floor(threshold)
|
||||
end
|
||||
|
||||
function MTHSmartAmmo_GetLowAmmoThreshold()
|
||||
return MTHSmartAmmo_ReadLowAmmoThreshold()
|
||||
end
|
||||
|
||||
local MTHSmartAmmo_CastSpell_Hook
|
||||
local MTHSmartAmmo_UseAction_Hook
|
||||
local MTHSmartAmmo_CastSpellByName_Hook
|
||||
@@ -182,6 +212,8 @@ MTH_SA_LastKnownEquippedName = nil
|
||||
MTH_SA_LastFallbackNotice = { key = nil, time = 0 }
|
||||
MTH_SA_LastKnownWeaponAmmoType = nil
|
||||
MTH_SA_LastWeaponCheck = 0
|
||||
MTH_SA_LastAmmoStockCheck = 0
|
||||
MTH_SA_AmmoStockState = nil
|
||||
MTH_SA_StartupProtectUntil = (GetTime() or 0) + 2.0
|
||||
MTH_SA_StartupPrimed = nil
|
||||
|
||||
@@ -220,11 +252,91 @@ local function MTHSmartAmmo_ShowFallbackWarning(previousAmmo, newAmmo)
|
||||
DEFAULT_CHAT_FRAME:AddMessage(warning)
|
||||
end
|
||||
|
||||
if RaidWarningFrame and RaidNotice_AddMessage and ChatTypeInfo and ChatTypeInfo["RAID_WARNING"] then
|
||||
local hasWarningAnimation = type(MTH_CelebrateAmmoFallback) == "function"
|
||||
and type(MTH_FX_IsEnabled) == "function" and MTH_FX_IsEnabled("combat.ammoout")
|
||||
if hasWarningAnimation then
|
||||
MTH_CelebrateAmmoFallback(previousAmmo, newAmmo)
|
||||
elseif RaidWarningFrame and RaidNotice_AddMessage and ChatTypeInfo and ChatTypeInfo["RAID_WARNING"] then
|
||||
RaidNotice_AddMessage(RaidWarningFrame, warning, ChatTypeInfo["RAID_WARNING"])
|
||||
elseif UIErrorsFrame and UIErrorsFrame.AddMessage then
|
||||
UIErrorsFrame:AddMessage(warning, 1.0, 0.15, 0.15, 1.0)
|
||||
end
|
||||
|
||||
if type(MTH_FX_Flash) == "function" and type(MTH_FX_IsEnabled) == "function"
|
||||
and MTH_FX_IsEnabled("combat.ammoswap") then
|
||||
local target = getglobal("zButtonAmmo")
|
||||
if target then
|
||||
MTH_FX_Flash(target, 1.00, 0.75, 0.20, 0.5)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function MTHSmartAmmo_ShowAmmoStockWarning(ammoType, total, threshold)
|
||||
local isZero = total <= 0
|
||||
local warning
|
||||
if isZero then
|
||||
warning = "Out of " .. ammoType .. "! Your ranged weapon is now a very expensive club."
|
||||
else
|
||||
warning = "Running low on " .. ammoType .. " — only " .. tostring(total) .. " left. Restock before you're just angrily pointing at things."
|
||||
end
|
||||
if MTH_SA_Print then
|
||||
MTH_SA_Print(warning)
|
||||
elseif DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.AddMessage then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(warning)
|
||||
end
|
||||
|
||||
local flag = isZero and "combat.ammozero" or "combat.lowammo"
|
||||
local celebrate = isZero and MTH_CelebrateAmmoZero or MTH_CelebrateLowAmmo
|
||||
if type(celebrate) == "function" and type(MTH_FX_IsEnabled) == "function" and MTH_FX_IsEnabled(flag) then
|
||||
celebrate(ammoType, total, threshold)
|
||||
elseif RaidWarningFrame and RaidNotice_AddMessage and ChatTypeInfo and ChatTypeInfo["RAID_WARNING"] then
|
||||
RaidNotice_AddMessage(RaidWarningFrame, warning, ChatTypeInfo["RAID_WARNING"])
|
||||
elseif UIErrorsFrame and UIErrorsFrame.AddMessage then
|
||||
UIErrorsFrame:AddMessage(warning, 1.0, 0.15, 0.15, 1.0)
|
||||
end
|
||||
end
|
||||
|
||||
local function MTHSmartAmmo_CountAmmoStock(ammoType)
|
||||
local rankMap = (ammoType == "Bullets" and MTH_AMMO_BULLET_RANK) or MTH_AMMO_ARROW_RANK or {}
|
||||
-- Bag scan already covers the quiver/ammo pouch; GetInventoryItemCount("player", 0)
|
||||
-- returns the bag copies of the loaded ammo, so adding it would double-count.
|
||||
local total = 0
|
||||
for bag = 4, 0, -1 do
|
||||
for slot = GetContainerNumSlots(bag), 1, -1 do
|
||||
local itemName = MTH_SA_GetContainerItemName(bag, slot)
|
||||
if itemName and rankMap[itemName] then
|
||||
local _, count = GetContainerItemInfo(bag, slot)
|
||||
total = total + (tonumber(count) or 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
return total
|
||||
end
|
||||
|
||||
-- ready < low < zero; a warning fires only when severity INCREASES (a real
|
||||
-- downward crossing during play), never on the cold baseline or on restock.
|
||||
local function MTHSmartAmmo_StockSeverity(state)
|
||||
if state == "zero" then return 2 end
|
||||
if state == "low" then return 1 end
|
||||
return 0
|
||||
end
|
||||
|
||||
local function MTHSmartAmmo_CheckAmmoStock()
|
||||
if not MTH_SA_IsModuleEnabled() or not MTHSmartAmmo_IsLowAmmoWarningEnabled() then return end
|
||||
local ammoType = MTH_SA_GetEquippedWeaponAmmoType()
|
||||
if not ammoType then
|
||||
MTH_SA_AmmoStockState = nil
|
||||
return
|
||||
end
|
||||
local total = MTHSmartAmmo_CountAmmoStock(ammoType)
|
||||
local threshold = MTHSmartAmmo_ReadLowAmmoThreshold()
|
||||
local state = total <= 0 and "zero" or (total <= threshold and "low" or "ready")
|
||||
|
||||
local prev = MTH_SA_AmmoStockState
|
||||
if prev ~= nil and MTHSmartAmmo_StockSeverity(state) > MTHSmartAmmo_StockSeverity(prev) then
|
||||
MTHSmartAmmo_ShowAmmoStockWarning(ammoType, total, threshold)
|
||||
end
|
||||
MTH_SA_AmmoStockState = state
|
||||
end
|
||||
|
||||
local MTHSmartAmmo_FallbackCheckFrame = nil
|
||||
@@ -375,6 +487,13 @@ local function MTHSmartAmmo_HandleWeaponSwapAutoEquip(reason)
|
||||
if MTHSmartAmmo_EquipAmmoFromSlot(bestAmmo.bag, bestAmmo.slot) then
|
||||
MTH_SA_LastKnownEquippedName = bestAmmo.name
|
||||
MTHSmartAmmo_RefreshAmmoButtonDisplay()
|
||||
if type(MTH_FX_Flash) == "function" and type(MTH_FX_IsEnabled) == "function"
|
||||
and MTH_FX_IsEnabled("combat.rangedswap") then
|
||||
local target = getglobal("zButtonAmmo")
|
||||
if target then
|
||||
MTH_FX_Flash(target, 0.30, 0.70, 1.00, 0.5)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -456,6 +575,10 @@ frame:SetScript("OnUpdate", function()
|
||||
MTH_SA_LastWeaponCheck = now
|
||||
MTHSmartAmmo_HandleWeaponSwapAutoEquip("OnUpdate-weapon-check")
|
||||
end
|
||||
if (now - (MTH_SA_LastAmmoStockCheck or 0)) >= 1.0 then
|
||||
MTH_SA_LastAmmoStockCheck = now
|
||||
MTHSmartAmmo_CheckAmmoStock()
|
||||
end
|
||||
end)
|
||||
|
||||
function MTH_SA_CountEquippedAmmo()
|
||||
@@ -786,15 +909,15 @@ SlashCmdList["MTHSmartAmmo"] = function(msg)
|
||||
return
|
||||
end
|
||||
|
||||
local saved = MTHSmartAmmo_GetSaved()
|
||||
local smartEnabled = MTHSmartAmmo_GetSmartEnabled()
|
||||
|
||||
if msg == "status" then
|
||||
local castHook, actionHook, byNameHook = MTHSmartAmmo_AreHooksInstalled()
|
||||
MTH_SA_Print("Smart Ammo status: smart=" .. tostring(saved["enabled"] and true or false) .. ", module=" .. tostring(MTH_SA_IsModuleEnabled()) .. ", hooks=" .. tostring(castHook) .. "/" .. tostring(actionHook) .. "/" .. tostring(byNameHook))
|
||||
MTH_SA_Print("Smart Ammo status: smart=" .. tostring(smartEnabled) .. ", module=" .. tostring(MTH_SA_IsModuleEnabled()) .. ", hooks=" .. tostring(castHook) .. "/" .. tostring(actionHook) .. "/" .. tostring(byNameHook))
|
||||
return
|
||||
end
|
||||
|
||||
if saved["enabled"] then
|
||||
|
||||
if smartEnabled then
|
||||
MTHSmartAmmo_SetSmartEnabled(nil)
|
||||
else
|
||||
MTHSmartAmmo_SetSmartEnabled(1)
|
||||
|
||||
@@ -7,7 +7,6 @@ MTH_SA_MANAGED_HOOKS = true
|
||||
local MTH_SmartAmmo = {
|
||||
name = "smartammo",
|
||||
enabled = true,
|
||||
version = "1.4.0",
|
||||
events = {
|
||||
"VARIABLES_LOADED",
|
||||
-- PLAYER_ENTERING_WORLD handled by engine's own frame
|
||||
@@ -16,39 +15,6 @@ local MTH_SmartAmmo = {
|
||||
|
||||
local MTH_SA_BOUNDARY_KEY = "smartammo.core"
|
||||
|
||||
function MTH_SA_GetSavedTable(tableName)
|
||||
if not MTH or not MTH.GetModuleCharSavedVariables then
|
||||
if type(_G[tableName]) ~= "table" then
|
||||
_G[tableName] = {}
|
||||
end
|
||||
return _G[tableName]
|
||||
end
|
||||
|
||||
local moduleStore = MTH:GetModuleCharSavedVariables("smartammo")
|
||||
if type(moduleStore) ~= "table" then
|
||||
moduleStore = {}
|
||||
end
|
||||
|
||||
if not moduleStore.legacy then
|
||||
moduleStore.legacy = {}
|
||||
end
|
||||
if type(moduleStore.legacy[tableName]) ~= "table" then
|
||||
moduleStore.legacy[tableName] = {}
|
||||
end
|
||||
|
||||
if type(_G[tableName]) == "table" then
|
||||
local legacyTable = moduleStore.legacy[tableName]
|
||||
if not next(legacyTable) then
|
||||
for key, value in pairs(_G[tableName]) do
|
||||
legacyTable[key] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
_G[tableName] = moduleStore.legacy[tableName]
|
||||
|
||||
return moduleStore.legacy[tableName]
|
||||
end
|
||||
|
||||
function MTH_SA_IsModuleEnabled()
|
||||
if MTH and MTH.IsModuleEnabled then
|
||||
return MTH:IsModuleEnabled("smartammo", true) and true or false
|
||||
@@ -71,43 +37,22 @@ local function MTH_SA_ApplySavedState()
|
||||
return
|
||||
end
|
||||
|
||||
local saved = MTH_SA_GetSavedTable("MTHSmartAmmo")
|
||||
local moduleStore = MTH and MTH.GetModuleCharSavedVariables and MTH:GetModuleCharSavedVariables("smartammo") or nil
|
||||
if type(moduleStore) == "table" then
|
||||
if moduleStore.smartEnabled ~= nil then
|
||||
saved["enabled"] = moduleStore.smartEnabled and 1 or false
|
||||
end
|
||||
if moduleStore.reloadEnabled ~= nil then
|
||||
saved["reload"] = moduleStore.reloadEnabled and 1 or false
|
||||
end
|
||||
if moduleStore.weaponSwapEnabled ~= nil then
|
||||
saved["weaponSwap"] = moduleStore.weaponSwapEnabled and 1 or false
|
||||
end
|
||||
end
|
||||
if saved["enabled"] == nil then
|
||||
saved["enabled"] = 1
|
||||
if type(moduleStore) == "table" then
|
||||
moduleStore.smartEnabled = true
|
||||
end
|
||||
end
|
||||
if saved["reload"] == nil and type(moduleStore) == "table" then
|
||||
moduleStore.reloadEnabled = saved["reload"] ~= false
|
||||
end
|
||||
if saved["weaponSwap"] == nil then
|
||||
saved["weaponSwap"] = 1
|
||||
if type(moduleStore) == "table" then
|
||||
moduleStore.weaponSwapEnabled = true
|
||||
end
|
||||
elseif type(moduleStore) == "table" and moduleStore.weaponSwapEnabled == nil then
|
||||
moduleStore.weaponSwapEnabled = saved["weaponSwap"] ~= false
|
||||
end
|
||||
local settings = (type(moduleStore) == "table" and type(moduleStore.settings) == "table") and moduleStore.settings or {}
|
||||
|
||||
MTHSmartAmmo_SetSmartEnabled(saved["enabled"] and 1 or nil, 1)
|
||||
local smart = settings.smartEnabled
|
||||
if smart == nil then smart = true end
|
||||
local reload = settings.reloadEnabled
|
||||
if reload == nil then reload = true end
|
||||
local swap = settings.weaponSwapEnabled
|
||||
if swap == nil then swap = true end
|
||||
|
||||
MTHSmartAmmo_SetSmartEnabled(smart and 1 or nil, 1)
|
||||
if type(MTHSmartAmmo_SetReloadEnabled) == "function" then
|
||||
MTHSmartAmmo_SetReloadEnabled(saved["reload"] ~= false and 1 or nil, 1)
|
||||
MTHSmartAmmo_SetReloadEnabled(reload and 1 or nil, 1)
|
||||
end
|
||||
if type(MTHSmartAmmo_SetWeaponSwapEnabled) == "function" then
|
||||
MTHSmartAmmo_SetWeaponSwapEnabled(saved["weaponSwap"] ~= false and 1 or nil, 1)
|
||||
MTHSmartAmmo_SetWeaponSwapEnabled(swap and 1 or nil, 1)
|
||||
end
|
||||
if type(MTHSmartAmmo_EnsureHooks) == "function" then
|
||||
MTHSmartAmmo_EnsureHooks("module-apply")
|
||||
@@ -155,7 +100,6 @@ local function MTH_SA_RestoreHooks()
|
||||
end
|
||||
|
||||
function MTH_SmartAmmo:init()
|
||||
MTH_SA_GetSavedTable("MTHSmartAmmo")
|
||||
if self.enabled then
|
||||
MTH_SA_ApplySavedState()
|
||||
end
|
||||
|
||||
+27
-34
@@ -6,7 +6,6 @@
|
||||
local MTH_Tooltips = {
|
||||
name = "tooltips",
|
||||
enabled = true,
|
||||
version = "1.4.0",
|
||||
events = {
|
||||
"UPDATE_MOUSEOVER_UNIT",
|
||||
"UNIT_NAME_UPDATE",
|
||||
@@ -116,23 +115,24 @@ local function MTH_TT_IsOwnPetTooltipsEnabled()
|
||||
end
|
||||
|
||||
local function MTH_TT_GetAutoQuestStore()
|
||||
if type(MTH_CharSavedVariables) ~= "table" then
|
||||
return nil
|
||||
end
|
||||
local store = MTH_CharSavedVariables.autoquest
|
||||
if type(store) ~= "table" then
|
||||
store = MTH_CharSavedVariables.questautomation
|
||||
local store = nil
|
||||
if MTH and MTH.GetModuleCharSavedVariables then
|
||||
store = MTH:GetModuleCharSavedVariables("autoquest")
|
||||
end
|
||||
if type(store) ~= "table" then
|
||||
return nil
|
||||
end
|
||||
if store.scorpokDrazial == nil then
|
||||
store.scorpokDrazial = false
|
||||
if type(store.settings) ~= "table" then
|
||||
store.settings = {}
|
||||
end
|
||||
if store.scorpokTooltip == nil then
|
||||
store.scorpokTooltip = false
|
||||
local settings = store.settings
|
||||
if settings.scorpokDrazial == nil then
|
||||
settings.scorpokDrazial = false
|
||||
end
|
||||
return store
|
||||
if settings.scorpokTooltip == nil then
|
||||
settings.scorpokTooltip = false
|
||||
end
|
||||
return settings
|
||||
end
|
||||
|
||||
local function MTH_TT_IsScorpokAutomationEnabled()
|
||||
@@ -1056,6 +1056,15 @@ end
|
||||
local function MTH_TT_HunterKnowsAbility(abilityLower, rankNumber)
|
||||
if abilityLower == "" then return false end
|
||||
|
||||
-- Prefer the authoritative pet-training check: it is rank-strict and also consults the
|
||||
-- hunter's learned-ability map, so a freshly learned rank counts even before a full
|
||||
-- Beast Training rescan has rebuilt the spell-map rows the fallback below relies on.
|
||||
if type(MTH_IsPetAbilityKnownByHunter) == "function" then
|
||||
if MTH_IsPetAbilityKnownByHunter(abilityLower, rankNumber) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
local spellMap = MTH_TT_GetKnownSpellMap()
|
||||
if type(spellMap) ~= "table" then
|
||||
return false
|
||||
@@ -1406,12 +1415,10 @@ local function MTH_TT_AddPetActionNotLearnedHint()
|
||||
|
||||
local canonicalLower = MTH_TT_Lower(canonical)
|
||||
local hunterKnown = MTH_TT_HunterKnowsAbility(canonicalLower, rankNumber)
|
||||
-- Only fall back to petKnown for ranked abilities: rankless abilities have no rank
|
||||
-- filter in CurrentPetKnowsAbility, so they always match on name alone -> false positive.
|
||||
local petKnown = (not hunterKnown)
|
||||
and MTH_TT_AbilityHasPositiveRanks(canonical)
|
||||
and MTH_TT_CurrentPetKnowsAbility(canonicalLower, rankNumber)
|
||||
if hunterKnown or petKnown then
|
||||
-- The hint reflects whether the HUNTER has learned this ability rank. We must NOT
|
||||
-- suppress it just because the current pet knows it: taming a new beast is exactly
|
||||
-- when you want to see which of its abilities you still have to learn.
|
||||
if hunterKnown then
|
||||
return
|
||||
end
|
||||
|
||||
@@ -1486,11 +1493,8 @@ local function MTH_TT_AddTooltip(unit)
|
||||
local ammoVendorTooltipsEnabled = MTH_TT_IsAmmoVendorTooltipsEnabled()
|
||||
local vendorInfo = ammoVendorTooltipsEnabled and MTH_TT_FindVendorInfo(name) or nil
|
||||
local row = beastTooltipsEnabled and MTH_TT_FindBeastRow(name) or nil
|
||||
local savedEntry = beastTooltipsEnabled
|
||||
and (type(MTH_BLS_FindSavedBeastByName) == "function")
|
||||
and MTH_BLS_FindSavedBeastByName(name) or nil
|
||||
local scorpokAdded = MTH_TT_AddScorpokTargetsTooltip(unit, name)
|
||||
if not row and not vendorInfo and not scorpokAdded and not savedEntry then
|
||||
if not row and not vendorInfo and not scorpokAdded then
|
||||
state.lookupMiss = (state.lookupMiss or 0) + 1
|
||||
MTH_TT_StateSet("lookup-miss", name, normalized)
|
||||
MTH_TT_Log("lookup miss: unit='" .. tostring(name) .. "' normalized='" .. tostring(normalized) .. "'")
|
||||
@@ -1514,13 +1518,7 @@ local function MTH_TT_AddTooltip(unit)
|
||||
MTH_TT_Log("vendor tooltip added: unit='" .. tostring(name) .. "'")
|
||||
end
|
||||
|
||||
-- Determine which abilities source to display.
|
||||
local abilitiesSource = nil
|
||||
if row then
|
||||
abilitiesSource = row.abilities
|
||||
elseif savedEntry and savedEntry.abilities and savedEntry.abilities ~= "" then
|
||||
abilitiesSource = savedEntry.abilities
|
||||
end
|
||||
local abilitiesSource = row and row.abilities or nil
|
||||
|
||||
if abilitiesSource then
|
||||
local abilities = MTH_TT_ParseAbilities(abilitiesSource)
|
||||
@@ -1546,11 +1544,6 @@ local function MTH_TT_AddTooltip(unit)
|
||||
end
|
||||
end
|
||||
|
||||
if savedEntry then
|
||||
GameTooltip:AddLine("Beast recorded by you", 1.0, 0.85, 0.0)
|
||||
addedAnything = 1
|
||||
end
|
||||
|
||||
if addedAnything then
|
||||
GameTooltip:Show()
|
||||
state.added = (state.added or 0) + 1
|
||||
|
||||
@@ -67,6 +67,8 @@ local function ZHunterMod_EnsureButtonFrame(buttonName)
|
||||
created:SetPoint("TOP", UIParent, "TOP", 0, -464)
|
||||
elseif buttonName == "zButtonToys" then
|
||||
created:SetPoint("TOP", UIParent, "TOP", 0, -518)
|
||||
elseif buttonName == "zButtonCraft" then
|
||||
created:SetPoint("TOP", UIParent, "TOP", 0, -572)
|
||||
else
|
||||
created:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
|
||||
end
|
||||
@@ -85,7 +87,7 @@ function ZHunterMod_DeferredInit()
|
||||
local mounts = ZHunterMod_EnsureButtonFrame("zButtonMounts")
|
||||
local companions = ZHunterMod_EnsureButtonFrame("zButtonCompanions")
|
||||
local toys = ZHunterMod_EnsureButtonFrame("zButtonToys")
|
||||
|
||||
local craft = ZHunterMod_EnsureButtonFrame("zButtonCraft")
|
||||
|
||||
-- Continue with all frames that exist; do not hard-abort when one optional frame is missing.
|
||||
local missing = {}
|
||||
@@ -98,12 +100,13 @@ function ZHunterMod_DeferredInit()
|
||||
if not mounts then table.insert(missing, "Mounts") end
|
||||
if not companions then table.insert(missing, "Companions") end
|
||||
if not toys then table.insert(missing, "Toys") end
|
||||
if not craft then table.insert(missing, "Craft") end
|
||||
|
||||
if table.getn(missing) > 0 then
|
||||
MTH_ZH_Print("[ZHUNTER] Warning: missing frames: " .. table.concat(missing, ", ") .. ". Continuing with available buttons.", "error")
|
||||
end
|
||||
|
||||
if not (pet or aspect or track or trap or ranged or ammo or mounts or companions or toys) then
|
||||
if not (pet or aspect or track or trap or ranged or ammo or mounts or companions or toys or craft) then
|
||||
MTH_ZH_Print("zhunter buttons not created - check for addon conflicts!", "error")
|
||||
return
|
||||
end
|
||||
@@ -178,6 +181,12 @@ function ZHunterMod_DeferredInit()
|
||||
end
|
||||
end
|
||||
|
||||
if craft and zButtonCraft_OnEvent then
|
||||
if ZHunterMod_SafeRunButtonInit(craft, zButtonCraft_OnEvent, "zButtonCraft") then
|
||||
initialized = initialized + 1
|
||||
end
|
||||
end
|
||||
|
||||
if initialized <= 0 then
|
||||
MTH_ZH_Print("zhunter buttons not created - check for addon conflicts!", "error")
|
||||
return
|
||||
|
||||
@@ -86,6 +86,8 @@ ZHUNTER_TRACK_GIANTS = "Track Giants"
|
||||
ZHUNTER_TRACK_MINERALS = "Find Minerals"
|
||||
ZHUNTER_TRACK_HERBS = "Find Herbs"
|
||||
ZHUNTER_TRACK_TREASURE = "Find Treasure"
|
||||
ZHUNTER_TRACK_TREES = "Find Trees"
|
||||
ZHUNTER_TRACK_FISH = "Find Fish"
|
||||
|
||||
ZHUNTER_TRAP_FREEZING = "Freezing Trap"
|
||||
ZHUNTER_TRAP_FROST = "Frost Trap"
|
||||
|
||||
@@ -43,10 +43,35 @@ function MTH_ZH_IsModuleEnabled()
|
||||
end
|
||||
|
||||
function MTH_ZH_GetSavedRoot()
|
||||
if type(ZHunterMod_Saved) ~= "table" then
|
||||
ZHunterMod_Saved = {}
|
||||
-- Canonical store is MTH_CharSavedVariables.modules.zhunter. The old global
|
||||
-- ZHunterMod_Saved is kept only as a RUNTIME alias so the many zButton files
|
||||
-- and options-zbuttons.lua can keep referencing it unchanged.
|
||||
local store = nil
|
||||
if MTH and MTH.GetModuleCharSavedVariables then
|
||||
store = MTH:GetModuleCharSavedVariables("zhunter")
|
||||
end
|
||||
return ZHunterMod_Saved
|
||||
|
||||
if type(store) ~= "table" then
|
||||
-- Framework not ready yet: fall back to a temporary global holder;
|
||||
-- MTH_ZH_SyncSavedVariables re-binds to the nested store later.
|
||||
if type(ZHunterMod_Saved) ~= "table" then
|
||||
ZHunterMod_Saved = {}
|
||||
end
|
||||
return ZHunterMod_Saved
|
||||
end
|
||||
|
||||
-- Adopt a pre-existing global (legacy TOC copy) once, only if the nested
|
||||
-- store is still empty, so no layout is lost during the transition.
|
||||
if type(ZHunterMod_Saved) == "table" and ZHunterMod_Saved ~= store then
|
||||
if next(store) == nil then
|
||||
for k, v in pairs(ZHunterMod_Saved) do
|
||||
store[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ZHunterMod_Saved = store -- pure runtime alias into the canonical store
|
||||
return store
|
||||
end
|
||||
|
||||
function MTH_ZH_GetSavedTable(key)
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
-- MAX_SPELLS = 1024
|
||||
|
||||
-- Capture our PizzaSauce instance for the expand animation (guarded; may be nil).
|
||||
local ZSB_Sauce = PizzaSauce
|
||||
local function ZSpellButton_AnimateExpand(parent)
|
||||
if not ZSB_Sauce or not parent or not parent.children then return end
|
||||
if type(MTH_FX_IsEnabled) == "function" and not MTH_FX_IsEnabled("ui.zbuttons") then return end
|
||||
-- The child cluster is anchored to the main button, not to the container, so
|
||||
-- sliding the container has no visible effect. Instead reveal the children in
|
||||
-- a quick staggered cascade so the fly-out clearly reads as motion. Keep the
|
||||
-- container fully visible and drive each child's own alpha.
|
||||
local container = parent.children
|
||||
if type(container.SetAlpha) == "function" then container:SetAlpha(1) end
|
||||
local count = parent.count or 0
|
||||
local shown = 0
|
||||
for i = 1, count do
|
||||
local btn = getglobal((parent.name or "") .. i)
|
||||
if btn and btn:IsShown() then
|
||||
if type(btn.SetAlpha) == "function" then btn:SetAlpha(0) end
|
||||
ZSB_Sauce:FadeTo(btn, 1, 0.20, "outQuad", { delay = shown * 0.05 })
|
||||
shown = shown + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Key binding prefix mapping (button name → Bindings.xml binding prefix)
|
||||
local ZHUNTER_BINDING_PREFIX = {
|
||||
zButtonAspect = "ZAspect",
|
||||
@@ -138,7 +161,6 @@ local function ZHunterMod_UpdateButtonCount(parent, found)
|
||||
end
|
||||
|
||||
function ZSpellButton_SetExpandDirection(parent, direction)
|
||||
local button = getglobal(parent.name.."1")
|
||||
local offset = parent:GetWidth() / 36
|
||||
local circleVisible = true
|
||||
if type(MTH_ZH_GetSavedTable) == "function" and parent and parent.name then
|
||||
@@ -154,11 +176,14 @@ function ZSpellButton_SetExpandDirection(parent, direction)
|
||||
parent.circle:Hide()
|
||||
end
|
||||
end
|
||||
-- Children have not been created yet; circle state is already updated above.
|
||||
if not (parent and parent.name) then return end
|
||||
if circleVisible then
|
||||
offset = offset * 5
|
||||
else
|
||||
offset = offset * 3
|
||||
end
|
||||
local button = getglobal(parent.name.."1")
|
||||
button:ClearAllPoints()
|
||||
if direction == "TOP" then
|
||||
button:SetPoint("BOTTOM", parent, "TOP", 0, offset)
|
||||
@@ -217,7 +242,7 @@ function ZSpellButton_SetSize(parent, size, setChildren)
|
||||
local cdscale = 0.75 / 36
|
||||
if setChildren then
|
||||
local button
|
||||
for i=1, parent.count do
|
||||
for i=1, (parent.count or 0) do
|
||||
button = getglobal(parent.name..i)
|
||||
if button then
|
||||
button:SetHeight(size)
|
||||
@@ -376,6 +401,7 @@ function ZSpellButton_SetChildrenExpanded(parent, expanded)
|
||||
end
|
||||
if expanded then
|
||||
saved["children"]["expanded"] = 1
|
||||
local wasCollapsed = parent.children and not parent.children:IsShown()
|
||||
if parent.children then
|
||||
parent.children:Show()
|
||||
end
|
||||
@@ -392,6 +418,9 @@ function ZSpellButton_SetChildrenExpanded(parent, expanded)
|
||||
end
|
||||
end
|
||||
ZSpellButton_StartFadeTimer(parent)
|
||||
-- Flyout reveal is instant: no stagger/cascade animation. The staggered
|
||||
-- "stacking one after the other" fly-out was slow and unwanted, so the
|
||||
-- children just appear immediately when expanded.
|
||||
else
|
||||
saved["children"]["expanded"] = 0
|
||||
if parent.children then
|
||||
@@ -415,6 +444,8 @@ function ZSpellButton_ApplyChildrenExpanded(parent)
|
||||
end
|
||||
ZSpellButton_SetChildrenExpanded(parent, ZSpellButton_GetChildrenExpanded(parent))
|
||||
ZSpellButton_UpdateKeybindingText(parent)
|
||||
-- Mark ready so future user toggles animate, but the initial login apply above does not.
|
||||
parent._zbAnimReady = true
|
||||
end
|
||||
|
||||
-- ========== Fade Timer ==========
|
||||
@@ -683,28 +714,53 @@ function ZSpellButtonParent_OnLoad()
|
||||
if this.SetScript then
|
||||
this:SetScript("OnDragStart", function()
|
||||
if IsAltKeyDown() then
|
||||
this:StartMoving()
|
||||
this.isMoving = true
|
||||
if type(MTH_ZBar_IsActive) == "function" and MTH_ZBar_IsActive() then
|
||||
-- Alt+drag any bar button to move the whole zBar via its invisible anchor
|
||||
local anchor = type(MTH_ZBar_GetAnchor) == "function" and MTH_ZBar_GetAnchor()
|
||||
if anchor then
|
||||
anchor:StartMoving()
|
||||
anchor.isMoving = true
|
||||
end
|
||||
else
|
||||
this:StartMoving()
|
||||
this.isMoving = true
|
||||
end
|
||||
end
|
||||
end)
|
||||
this:SetScript("OnDragStop", function()
|
||||
if not this then
|
||||
return
|
||||
if not this then return end
|
||||
if type(MTH_ZBar_IsActive) == "function" and MTH_ZBar_IsActive() then
|
||||
local anchor = type(MTH_ZBar_GetAnchor) == "function" and MTH_ZBar_GetAnchor()
|
||||
if anchor and anchor.isMoving then
|
||||
if anchor.StopMovingOrSizing then anchor:StopMovingOrSizing() end
|
||||
anchor.isMoving = false
|
||||
if type(MTH_ZBar_SavePosition) == "function" then MTH_ZBar_SavePosition() end
|
||||
if type(MTH_ZBar_ApplyLayout) == "function" then MTH_ZBar_ApplyLayout() end
|
||||
end
|
||||
else
|
||||
if this.StopMovingOrSizing then this:StopMovingOrSizing() end
|
||||
ZSpellButton_SaveParentPosition(this)
|
||||
this.isMoving = false
|
||||
end
|
||||
if this.StopMovingOrSizing then
|
||||
this:StopMovingOrSizing()
|
||||
end
|
||||
ZSpellButton_SaveParentPosition(this)
|
||||
this.isMoving = false
|
||||
end)
|
||||
this:SetScript("OnMouseUp", function()
|
||||
if this and this.isMoving then
|
||||
if this.StopMovingOrSizing then
|
||||
this:StopMovingOrSizing()
|
||||
if this.StopMovingOrSizing then this:StopMovingOrSizing() end
|
||||
if not (type(MTH_ZBar_IsActive) == "function" and MTH_ZBar_IsActive()) then
|
||||
ZSpellButton_SaveParentPosition(this)
|
||||
end
|
||||
ZSpellButton_SaveParentPosition(this)
|
||||
this.isMoving = false
|
||||
end
|
||||
-- Fallback: finish anchor movement started from OnDragStart
|
||||
if type(MTH_ZBar_IsActive) == "function" and MTH_ZBar_IsActive() then
|
||||
local anchor = type(MTH_ZBar_GetAnchor) == "function" and MTH_ZBar_GetAnchor()
|
||||
if anchor and anchor.isMoving then
|
||||
if anchor.StopMovingOrSizing then anchor:StopMovingOrSizing() end
|
||||
anchor.isMoving = false
|
||||
if type(MTH_ZBar_SavePosition) == "function" then MTH_ZBar_SavePosition() end
|
||||
if type(MTH_ZBar_ApplyLayout) == "function" then MTH_ZBar_ApplyLayout() end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -22,6 +22,7 @@ Loads all zhunter addon code and wraps it in the MTH module system
|
||||
<Include file="zButtonMounts.xml"/>
|
||||
<Include file="zButtonCompanions.xml"/>
|
||||
<Include file="zButtonToys.xml"/>
|
||||
<Include file="zButtonCraft.xml"/>
|
||||
|
||||
<!-- Load main ZHunterMod.lua FIRST so ZHunterMod_Saved table exists before button files use it -->
|
||||
<Include file="ZHunterMod.lua"/>
|
||||
@@ -47,6 +48,11 @@ Loads all zhunter addon code and wraps it in the MTH module system
|
||||
<Include file="zButtonCompanions.lua"/>
|
||||
|
||||
<Include file="zButtonToys.lua"/>
|
||||
|
||||
<Include file="zButtonCraft.lua"/>
|
||||
|
||||
<!-- zBar: unified draggable anchor bar (must load after all zButton files) -->
|
||||
<Include file="zBar.lua"/>
|
||||
|
||||
<!-- Deferred initialization - runs LAST after all parsing is complete -->
|
||||
<Include file="DeferredInit.lua"/>
|
||||
|
||||
+14
-14
@@ -8,7 +8,6 @@ MTH_ZH_MANAGED_HOOKS = true
|
||||
local MTH_ZHunter = {
|
||||
name = "zhunter",
|
||||
enabled = true,
|
||||
version = "1.4.0",
|
||||
events = {
|
||||
"VARIABLES_LOADED",
|
||||
-- PLAYER_ENTERING_WORLD handled by bootstrap frame + adjustment frames
|
||||
@@ -59,6 +58,7 @@ local function MTH_ZH_ApplyDefaultWidgetSpawnLayoutOnce()
|
||||
"zButtonMounts",
|
||||
"zButtonCompanions",
|
||||
"zButtonToys",
|
||||
"zButtonCraft",
|
||||
}
|
||||
|
||||
local startY = -140
|
||||
@@ -165,18 +165,11 @@ MTH_ZH_ApplyEnabledRuntimeState = function(source)
|
||||
end
|
||||
|
||||
MTH_ZH_SyncSavedVariables = function()
|
||||
if not MTH or not MTH.GetModuleCharSavedVariables then
|
||||
return
|
||||
end
|
||||
|
||||
local moduleStore = MTH:GetModuleCharSavedVariables("zhunter")
|
||||
|
||||
if type(ZHunterMod_Saved) == "table" then
|
||||
if MTH_CharSavedVariables and MTH_CharSavedVariables.modules then
|
||||
MTH_CharSavedVariables.modules.zhunter = ZHunterMod_Saved
|
||||
end
|
||||
elseif type(moduleStore) == "table" then
|
||||
ZHunterMod_Saved = moduleStore
|
||||
-- Bind the ZHunterMod_Saved global as a runtime alias into the canonical
|
||||
-- nested store (MTH_CharSavedVariables.modules.zhunter). GetSavedRoot does the
|
||||
-- adoption + aliasing; there is no second on-disk copy anymore.
|
||||
if type(MTH_ZH_GetSavedRoot) == "function" then
|
||||
MTH_ZH_GetSavedRoot()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -191,6 +184,7 @@ MTH_ZH_SetButtonsVisible = function(visible)
|
||||
"zButtonMounts",
|
||||
"zButtonCompanions",
|
||||
"zButtonToys",
|
||||
"zButtonCraft",
|
||||
}
|
||||
|
||||
local function MTH_ZH_SetChildButtonsVisible(parentButton, parentName, show)
|
||||
@@ -303,6 +297,7 @@ local function MTH_ZH_SetAdjustmentHandlersActive(active)
|
||||
{ frame = "MTH_ZH_MountsAdjust", handler = "MTH_ZH_MountsAdjust_OnEvent", getter = "zButtonMounts_GetSaved" },
|
||||
{ frame = "MTH_ZH_CompanionsAdjust", handler = "MTH_ZH_CompanionsAdjust_OnEvent", getter = "zButtonCompanions_GetSaved" },
|
||||
{ frame = "MTH_ZH_ToysAdjust", handler = "MTH_ZH_ToysAdjust_OnEvent", getter = "zButtonToys_GetSaved" },
|
||||
{ frame = "MTH_ZH_CraftAdjust", handler = "MTH_ZH_CraftAdjust_OnEvent", getter = "zButtonCraft_GetSaved" },
|
||||
}
|
||||
|
||||
local function isFeatureEnabled(getterName)
|
||||
@@ -348,7 +343,9 @@ local function MTH_ZH_ClearRuntimeFeatureFlags()
|
||||
end
|
||||
|
||||
MTH_ZH_RestoreRuntimeFeatureFlags = function()
|
||||
return
|
||||
if type(MTH_ZBar_Init) == "function" then
|
||||
MTH_ZBar_Init()
|
||||
end
|
||||
end
|
||||
|
||||
function MTH_ZHunter:init()
|
||||
@@ -372,6 +369,9 @@ function MTH_ZHunter:setEnabled(enabled)
|
||||
MTH_ZH_SetAdjustmentHandlersActive(false)
|
||||
MTH_ZH_SetButtonsVisible(false)
|
||||
MTH_ZH_SetAuxFramesVisible(false)
|
||||
-- Hide the zBar anchor so it doesn't float while the module is off
|
||||
local zbarAnchor = getglobal("MTH_ZBar_Anchor")
|
||||
if zbarAnchor then zbarAnchor:Hide() end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,361 @@
|
||||
-- zBar: Unified draggable anchor bar for all zButton parent buttons.
|
||||
-- When enabled, all included zButton parents are anchored to the zBar anchor
|
||||
-- in a configurable row (horizontal) or column (vertical).
|
||||
-- Individual Alt+drag on each zButton is suppressed while zBar is active.
|
||||
|
||||
local ZBAR_FRAME_NAME = "MTH_ZBar_Anchor"
|
||||
|
||||
local ZBar_Sauce = PizzaSauce
|
||||
|
||||
local ZBAR_ALL_BUTTONS = {
|
||||
"zButtonAspect",
|
||||
"zButtonAmmo",
|
||||
"zButtonTrack",
|
||||
"zButtonTrap",
|
||||
"zButtonPet",
|
||||
"zButtonRanged",
|
||||
"zButtonMounts",
|
||||
"zButtonCompanions",
|
||||
"zButtonToys",
|
||||
"zButtonCraft",
|
||||
}
|
||||
|
||||
local ZBAR_BUTTON_LABELS = {
|
||||
zButtonAspect = "zAspect",
|
||||
zButtonAmmo = "zAmmo",
|
||||
zButtonTrack = "zTrack",
|
||||
zButtonTrap = "zTrap",
|
||||
zButtonPet = "zPet",
|
||||
zButtonRanged = "zRanged",
|
||||
zButtonMounts = "zMounts",
|
||||
zButtonCompanions = "zCompanions",
|
||||
zButtonToys = "zToys",
|
||||
zButtonCraft = "zCraft",
|
||||
}
|
||||
|
||||
function MTH_ZBar_GetAllButtons()
|
||||
return ZBAR_ALL_BUTTONS
|
||||
end
|
||||
|
||||
function MTH_ZBar_GetAllButtonLabels()
|
||||
return ZBAR_BUTTON_LABELS
|
||||
end
|
||||
|
||||
-- Returns (and initialises on first access) the _zbar saved-variable table.
|
||||
function MTH_ZBar_GetSaved()
|
||||
local root = MTH_ZH_GetSavedRoot()
|
||||
if type(root["_zbar"]) ~= "table" then
|
||||
root["_zbar"] = {}
|
||||
end
|
||||
local s = root["_zbar"]
|
||||
|
||||
if s.enabled == nil then s.enabled = false end
|
||||
if s.direction == nil then s.direction = "HORIZONTAL" end
|
||||
if type(s.spacing) ~= "number" then s.spacing = 4 end
|
||||
if type(s.size) ~= "number" then s.size = 36 end
|
||||
-- childexpand: which side children appear on (TOP/BOTTOM for H bar, LEFT/RIGHT for V bar)
|
||||
if s.childexpand == nil then s.childexpand = "BOTTOM" end
|
||||
-- childarrange: how children #2+ are laid out (VERTICAL=column, HORIZONTAL=row)
|
||||
if s.childarrange == nil then s.childarrange = "VERTICAL" end
|
||||
if type(s.order) ~= "table" then
|
||||
s.order = {}
|
||||
for i = 1, table.getn(ZBAR_ALL_BUTTONS) do
|
||||
s.order[i] = ZBAR_ALL_BUTTONS[i]
|
||||
end
|
||||
end
|
||||
|
||||
if type(s.visible) ~= "table" then
|
||||
s.visible = {}
|
||||
end
|
||||
|
||||
-- Migration: ensure every known button is present in the order list and
|
||||
-- has an explicit visible entry.
|
||||
local present = {}
|
||||
for _, v in ipairs(s.order) do
|
||||
present[v] = true
|
||||
end
|
||||
for _, v in ipairs(ZBAR_ALL_BUTTONS) do
|
||||
if not present[v] then
|
||||
table.insert(s.order, v)
|
||||
end
|
||||
if s.visible[v] == nil then
|
||||
s.visible[v] = true
|
||||
end
|
||||
end
|
||||
|
||||
return s
|
||||
end
|
||||
|
||||
-- Returns true when the zBar feature is both enabled AND the anchor frame
|
||||
-- has been created in memory.
|
||||
function MTH_ZBar_IsActive()
|
||||
local s = MTH_ZBar_GetSaved()
|
||||
return (s.enabled == true) and (getglobal(ZBAR_FRAME_NAME) ~= nil)
|
||||
end
|
||||
|
||||
-- ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
|
||||
-- Position persistence
|
||||
-- ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
|
||||
|
||||
function MTH_ZBar_SavePosition()
|
||||
local bar = getglobal(ZBAR_FRAME_NAME)
|
||||
if not (bar and bar.GetPoint) then return end
|
||||
local s = MTH_ZBar_GetSaved()
|
||||
local point, _, relPoint, x, y = bar:GetPoint()
|
||||
if not point then return end
|
||||
s.point = tostring(point)
|
||||
s.relPoint = tostring(relPoint or point)
|
||||
s.x = math.floor((tonumber(x) or 0) + 0.5)
|
||||
s.y = math.floor((tonumber(y) or 0) + 0.5)
|
||||
end
|
||||
|
||||
-- ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
|
||||
-- Anchor frame creation
|
||||
-- ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
|
||||
|
||||
-- Returns the invisible anchor frame (used internally and by button drag code).
|
||||
function MTH_ZBar_GetAnchor()
|
||||
return getglobal(ZBAR_FRAME_NAME)
|
||||
end
|
||||
|
||||
function MTH_ZBar_EnsureAnchor()
|
||||
local existing = getglobal(ZBAR_FRAME_NAME)
|
||||
if existing then
|
||||
existing:Show()
|
||||
return existing
|
||||
end
|
||||
|
||||
local s = MTH_ZBar_GetSaved()
|
||||
|
||||
-- Invisible 1x1 movable frame — used only as a position anchor for the bar.
|
||||
-- Users interact via the zButton buttons themselves (Alt+drag any of them).
|
||||
local bar = CreateFrame("Frame", ZBAR_FRAME_NAME, UIParent)
|
||||
if not bar then return nil end
|
||||
|
||||
bar:SetWidth(1)
|
||||
bar:SetHeight(1)
|
||||
bar:SetFrameStrata("MEDIUM")
|
||||
bar:SetFrameLevel(5)
|
||||
bar:SetMovable(true)
|
||||
bar:SetClampedToScreen(true)
|
||||
|
||||
-- Restore saved position (or fall back to a sensible default)
|
||||
bar:ClearAllPoints()
|
||||
if s.point and s.x and s.y then
|
||||
bar:SetPoint(s.point, UIParent, s.relPoint or s.point, s.x, s.y)
|
||||
else
|
||||
bar:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 400, -140)
|
||||
end
|
||||
|
||||
bar:Show()
|
||||
return bar
|
||||
end
|
||||
|
||||
-- ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
|
||||
-- Layout engine
|
||||
-- ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
|
||||
|
||||
-- Anchors every included+enabled zButton parent relative to the zBar anchor
|
||||
-- (or the previous button in the chain).
|
||||
function MTH_ZBar_ApplyLayout()
|
||||
local s = MTH_ZBar_GetSaved()
|
||||
if not s.enabled then return end
|
||||
|
||||
local bar = getglobal(ZBAR_FRAME_NAME)
|
||||
if not bar then return end
|
||||
|
||||
local direction = s.direction or "HORIZONTAL"
|
||||
local spacing = tonumber(s.spacing) or 4
|
||||
|
||||
local prevFrame = bar
|
||||
local count = 0
|
||||
local placed = {}
|
||||
|
||||
for i = 1, table.getn(s.order) do
|
||||
local buttonName = s.order[i]
|
||||
if type(buttonName) == "string" then
|
||||
local btn = getglobal(buttonName)
|
||||
|
||||
local globalEnabled = ZHunterMod_Saved
|
||||
and type(ZHunterMod_Saved[buttonName]) == "table"
|
||||
and ZHunterMod_Saved[buttonName]["enabled"] ~= false
|
||||
and ZHunterMod_Saved[buttonName]["enabled"] ~= 0
|
||||
local zbarVisible = s.visible[buttonName] ~= false
|
||||
|
||||
if btn and globalEnabled and zbarVisible then
|
||||
btn:ClearAllPoints()
|
||||
if direction == "HORIZONTAL" then
|
||||
btn:SetPoint("LEFT", prevFrame, "RIGHT", spacing, 0)
|
||||
else
|
||||
btn:SetPoint("TOP", prevFrame, "BOTTOM", 0, -spacing)
|
||||
end
|
||||
-- Size, expand direction and child arrangement are handled by each
|
||||
-- bar's own _SetupSizeAndPosition, which now calls MTH_ZBar_ApplyToButton.
|
||||
local setupFunc = getglobal(buttonName .. "_SetupSizeAndPosition")
|
||||
if type(setupFunc) == "function" then
|
||||
setupFunc()
|
||||
end
|
||||
prevFrame = btn
|
||||
count = count + 1
|
||||
placed[buttonName] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- For every button NOT placed in the bar, call its own setup function so that:
|
||||
-- * disabled buttons get Hide() called on them immediately
|
||||
-- * excluded buttons get their individual size/expand/position applied
|
||||
for _, buttonName in ipairs(ZBAR_ALL_BUTTONS) do
|
||||
if not placed[buttonName] then
|
||||
local setupFunc = getglobal(buttonName .. "_SetupSizeAndPosition")
|
||||
if type(setupFunc) == "function" then
|
||||
setupFunc()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
|
||||
-- Per-button zBar override (called from each zButton's _SetupSizeAndPosition)
|
||||
-- ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
|
||||
|
||||
-- When zBar is active this is the MASTER for size, expand direction and child
|
||||
-- layout. Each zButton's _SetupSizeAndPosition calls this first; if it returns
|
||||
-- true the bar must skip its own saved settings entirely.
|
||||
function MTH_ZBar_ApplyToButton(btn, displayCount)
|
||||
if not (type(MTH_ZBar_IsActive) == "function" and MTH_ZBar_IsActive()) then
|
||||
return false
|
||||
end
|
||||
local s = MTH_ZBar_GetSaved()
|
||||
local btnKey = (btn and btn.name) or (btn and btn.GetName and btn:GetName()) or ""
|
||||
-- Only take master control if this button is actually placed in the active bar.
|
||||
local globalEnabled = ZHunterMod_Saved
|
||||
and type(ZHunterMod_Saved[btnKey]) == "table"
|
||||
and ZHunterMod_Saved[btnKey]["enabled"] ~= false
|
||||
and ZHunterMod_Saved[btnKey]["enabled"] ~= 0
|
||||
if not globalEnabled or s.visible[btnKey] == false then
|
||||
return false
|
||||
end
|
||||
local btnSize = tonumber(s.size) or 36
|
||||
local expandDir = s.childexpand or "BOTTOM"
|
||||
local arrange = s.childarrange or "VERTICAL"
|
||||
local arHoriz = (expandDir == "LEFT")
|
||||
local arVert = (expandDir == "TOP")
|
||||
ZSpellButton_SetSize(btn, btnSize)
|
||||
ZSpellButton_SetExpandDirection(btn, expandDir)
|
||||
if displayCount and displayCount > 1 then
|
||||
local rows = (arrange == "VERTICAL") and displayCount or 1
|
||||
ZSpellButton_ArrangeChildren(btn, rows, displayCount, arHoriz, arVert)
|
||||
end
|
||||
if btn.circle then btn.circle:Hide() end
|
||||
return true
|
||||
end
|
||||
|
||||
-- ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
|
||||
-- Enable / disable
|
||||
-- ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
|
||||
|
||||
-- Restores each bar to its individually saved position and hides the anchor.
|
||||
function MTH_ZBar_Release()
|
||||
local bar = getglobal(ZBAR_FRAME_NAME)
|
||||
if bar then bar:Hide() end
|
||||
|
||||
for _, buttonName in ipairs(ZBAR_ALL_BUTTONS) do
|
||||
local btn = getglobal(buttonName)
|
||||
if btn and ZHunterMod_Saved and type(ZHunterMod_Saved[buttonName]) == "table" then
|
||||
-- Restore individually saved position
|
||||
local parentSaved = ZHunterMod_Saved[buttonName]["parent"]
|
||||
if type(parentSaved) == "table" then
|
||||
local px = tonumber(parentSaved["x"])
|
||||
local py = tonumber(parentSaved["y"])
|
||||
local point = parentSaved["point"]
|
||||
local rPoint = parentSaved["relativePoint"] or point
|
||||
if px and py and type(point) == "string" and point ~= "" then
|
||||
btn:ClearAllPoints()
|
||||
btn:SetPoint(point, UIParent, rPoint, px, py)
|
||||
end
|
||||
end
|
||||
-- Restore size, circle, expand direction and child layout from the bar's own saved settings
|
||||
local setupFunc = getglobal(buttonName .. "_SetupSizeAndPosition")
|
||||
if type(setupFunc) == "function" then
|
||||
setupFunc()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Animations: a single, quick, simultaneous fade-in of the bar's buttons when
|
||||
-- it activates. NO stagger (no per-button delay) — every button fades in at the
|
||||
-- same time so it reads as one clean fade instead of a slow "stacking" cascade.
|
||||
function MTH_ZBar_AnimateActivate()
|
||||
if not ZBar_Sauce then return end
|
||||
if type(MTH_FX_IsEnabled) ~= "function" or not MTH_FX_IsEnabled("ui.zbar") then
|
||||
return
|
||||
end
|
||||
local s = MTH_ZBar_GetSaved()
|
||||
local order = s.order or {}
|
||||
for i = 1, table.getn(order) do
|
||||
local buttonName = order[i]
|
||||
if type(buttonName) == "string" then
|
||||
local btn = getglobal(buttonName)
|
||||
if btn and btn:IsShown() then
|
||||
btn:SetAlpha(0)
|
||||
ZBar_Sauce:FadeTo(btn, 1, 0.20, "outQuad")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Called when the user toggles the Enable checkbox in the options.
|
||||
function MTH_ZBar_SetEnabled(enabled)
|
||||
local s = MTH_ZBar_GetSaved()
|
||||
s.enabled = enabled and true or false
|
||||
if enabled then
|
||||
MTH_ZBar_EnsureAnchor()
|
||||
MTH_ZBar_ApplyLayout()
|
||||
MTH_ZBar_AnimateActivate()
|
||||
else
|
||||
MTH_ZBar_Release()
|
||||
end
|
||||
end
|
||||
|
||||
-- Resize all bar buttons (and persist the value).
|
||||
function MTH_ZBar_SetSize(size)
|
||||
local s = MTH_ZBar_GetSaved()
|
||||
s.size = tonumber(size) or 36
|
||||
if s.enabled and type(MTH_ZBar_ApplyLayout) == "function" then
|
||||
MTH_ZBar_ApplyLayout()
|
||||
end
|
||||
end
|
||||
|
||||
-- Called from DeferredInit / RestoreRuntimeFeatureFlags after saved vars are
|
||||
-- fully loaded; creates the anchor and applies layout only when enabled.
|
||||
function MTH_ZBar_Init()
|
||||
local s = MTH_ZBar_GetSaved()
|
||||
|
||||
-- One-time migration: fix firstbutton values corrupted to "BOTTOM" by an
|
||||
-- early version of zBar that incorrectly wrote per-bar saved variables.
|
||||
if not s._firstbutton_fixed then
|
||||
for _, buttonName in ipairs(ZBAR_ALL_BUTTONS) do
|
||||
if ZHunterMod_Saved and type(ZHunterMod_Saved[buttonName]) == "table" then
|
||||
if ZHunterMod_Saved[buttonName]["firstbutton"] == "BOTTOM" then
|
||||
ZHunterMod_Saved[buttonName]["firstbutton"] = "RIGHT"
|
||||
end
|
||||
end
|
||||
end
|
||||
s._firstbutton_fixed = true
|
||||
-- Re-run each bar's own setup now so the fix takes immediate visual effect
|
||||
for _, buttonName in ipairs(ZBAR_ALL_BUTTONS) do
|
||||
local setupFunc = getglobal(buttonName .. "_SetupSizeAndPosition")
|
||||
if type(setupFunc) == "function" then setupFunc() end
|
||||
end
|
||||
end
|
||||
|
||||
if s.enabled then
|
||||
MTH_ZBar_EnsureAnchor()
|
||||
MTH_ZBar_ApplyLayout()
|
||||
-- Play the gentle fade on login/reload too, not just on checkbox toggle.
|
||||
-- Safe here because AnimateActivate is a plain alpha fade (no re-anchoring).
|
||||
MTH_ZBar_AnimateActivate()
|
||||
end
|
||||
end
|
||||
@@ -38,7 +38,6 @@ local zButtonAmmo_LastFullScanAt = 0
|
||||
local zButtonAmmo_MinUnknownBagScanInterval = 5.0
|
||||
local zButtonAmmo_EmptyScanStreak = 0
|
||||
local zButtonAmmo_LastEquippedAmmoSeen = nil
|
||||
local zButtonAmmo_GetKnownAmmoTypes
|
||||
|
||||
local function zButtonAmmo_BagHasAmmoNow(bagId)
|
||||
local bag = tonumber(bagId)
|
||||
@@ -117,7 +116,7 @@ zButtonAmmo_GetKnownAmmoTypes = function()
|
||||
return knownAmmo
|
||||
end
|
||||
|
||||
local zButtonAmmo_ShortArrowLabels = {
|
||||
zButtonAmmo_ShortArrowLabels = {
|
||||
[ARROWS_ROUGH] = "Rough",
|
||||
[ARROWS_SHARP] = "Sharp",
|
||||
[ARROWS_RAZOR] = "Razor",
|
||||
@@ -131,7 +130,7 @@ local zButtonAmmo_ShortArrowLabels = {
|
||||
[ARROWS_DOOMSHOT] = "Doom",
|
||||
}
|
||||
|
||||
local zButtonAmmo_ShortBulletLabels = {
|
||||
zButtonAmmo_ShortBulletLabels = {
|
||||
[BULLETS_LIGHT] = "Light",
|
||||
[BULLETS_CRAFTLIGHT] = "C.Light",
|
||||
[BULLETS_FLASH] = "Flash",
|
||||
@@ -151,7 +150,7 @@ local zButtonAmmo_ShortBulletLabels = {
|
||||
[BULLETS_ICETHREADED] = "Ice",
|
||||
}
|
||||
|
||||
local function zButtonAmmo_GetColorGradient(perc)
|
||||
function zButtonAmmo_GetColorGradient(perc)
|
||||
perc = perc > 1 and 1 or perc
|
||||
perc = perc < 0 and 0 or perc
|
||||
local r1, g1, b1, r2, g2, b2
|
||||
@@ -188,7 +187,7 @@ local function zButtonAmmo_EnsureShortLabelText(button)
|
||||
return button and button.ammoShortLabelText
|
||||
end
|
||||
|
||||
local function zButtonAmmo_GetQualityColor(quality)
|
||||
function zButtonAmmo_GetQualityColor(quality)
|
||||
local tableRef = MTH_ITEM_QUALITY_COLORS or ITEM_QUALITY_COLORS
|
||||
if tableRef then
|
||||
local q = tonumber(quality)
|
||||
@@ -898,12 +897,14 @@ function zButtonAmmo_SetupSizeAndPosition()
|
||||
if arrangeCount < 0 then
|
||||
arrangeCount = 0
|
||||
end
|
||||
ZSpellButton_SetSize(zButtonAmmo, saved["parent"]["size"])
|
||||
ZSpellButton_SetSize(zButtonAmmo, saved["children"]["size"], 1)
|
||||
ZSpellButton_SetExpandDirection(zButtonAmmo, saved["firstbutton"])
|
||||
ZSpellButton_ArrangeChildren(zButtonAmmo, saved["rows"],
|
||||
arrangeCount, saved["horizontal"],
|
||||
saved["vertical"])
|
||||
if not (type(MTH_ZBar_ApplyToButton) == "function" and MTH_ZBar_ApplyToButton(zButtonAmmo, arrangeCount)) then
|
||||
ZSpellButton_SetSize(zButtonAmmo, saved["parent"]["size"])
|
||||
ZSpellButton_SetSize(zButtonAmmo, saved["children"]["size"], 1)
|
||||
ZSpellButton_SetExpandDirection(zButtonAmmo, saved["firstbutton"])
|
||||
ZSpellButton_ArrangeChildren(zButtonAmmo, saved["rows"],
|
||||
arrangeCount, saved["horizontal"],
|
||||
saved["vertical"])
|
||||
end
|
||||
end
|
||||
|
||||
function zButtonAmmo_Reset()
|
||||
|
||||
@@ -39,6 +39,7 @@ local zButtonAspect_MinAuraIntervalCombat = 0.60
|
||||
local zButtonAspect_LastAuraTexture = nil
|
||||
local zButtonAspect_LastParentId = nil
|
||||
local zButtonAspect_AuraProbeTextLeft1 = nil
|
||||
local MTH_ZH_UseNamPower = false
|
||||
|
||||
local function zButtonAspect_GetSaved()
|
||||
local currentRoot = zButtonAspect_GetRoot()
|
||||
@@ -72,6 +73,9 @@ local function zButtonAspect_EnsureConfig()
|
||||
if saved["parent"]["circle"] == nil then
|
||||
saved["parent"]["circle"] = 1
|
||||
end
|
||||
if saved["parent"]["smart"] == nil then
|
||||
saved["parent"]["smart"] = true
|
||||
end
|
||||
if not saved["children"] then
|
||||
saved["children"] = {}
|
||||
end
|
||||
@@ -167,6 +171,12 @@ function zButtonAspect_OnEvent()
|
||||
MTH_ZH_AspectAdjust:RegisterEvent("CHARACTER_POINTS_CHANGED")
|
||||
MTH_ZH_AspectAdjust:RegisterEvent("LEARNED_SPELL_IN_TAB")
|
||||
MTH_ZH_AspectAdjust:SetScript("OnEvent", MTH_ZH_AspectAdjust_OnEvent)
|
||||
if MTH and MTH.nampower then
|
||||
MTH_ZH_UseNamPower = true
|
||||
MTH_ZH_AspectAdjust:UnregisterEvent("PLAYER_AURAS_CHANGED")
|
||||
MTH_ZH_AspectAdjust:RegisterEvent("BUFF_ADDED_SELF")
|
||||
MTH_ZH_AspectAdjust:RegisterEvent("BUFF_REMOVED_SELF")
|
||||
end
|
||||
zButtonAspect_Tooltip = CreateFrame("GameTooltip", "MTH_ZH_AspectProbe", nil, "GameTooltipTemplate")
|
||||
if zButtonAspect_Tooltip and zButtonAspect_Tooltip.GetName then
|
||||
local tooltipName = zButtonAspect_Tooltip:GetName()
|
||||
@@ -221,12 +231,14 @@ function zButtonAspect_SetupSizeAndPosition()
|
||||
if displayCount < 0 then
|
||||
displayCount = 0
|
||||
end
|
||||
ZSpellButton_SetSize(zButtonAspect, saved["parent"]["size"])
|
||||
ZSpellButton_SetSize(zButtonAspect, saved["children"]["size"], 1)
|
||||
ZSpellButton_SetExpandDirection(zButtonAspect, saved["firstbutton"])
|
||||
ZSpellButton_ArrangeChildren(zButtonAspect, saved["rows"],
|
||||
displayCount, saved["horizontal"],
|
||||
saved["vertical"])
|
||||
if not (type(MTH_ZBar_ApplyToButton) == "function" and MTH_ZBar_ApplyToButton(zButtonAspect, displayCount)) then
|
||||
ZSpellButton_SetSize(zButtonAspect, saved["parent"]["size"])
|
||||
ZSpellButton_SetSize(zButtonAspect, saved["children"]["size"], 1)
|
||||
ZSpellButton_SetExpandDirection(zButtonAspect, saved["firstbutton"])
|
||||
ZSpellButton_ArrangeChildren(zButtonAspect, saved["rows"],
|
||||
displayCount, saved["horizontal"],
|
||||
saved["vertical"])
|
||||
end
|
||||
end
|
||||
|
||||
function zButtonAspect_Reset()
|
||||
@@ -255,7 +267,11 @@ function MTH_ZH_AspectAdjust_OnEvent()
|
||||
if not zButtonAspect or not zButtonAspect.count then
|
||||
return
|
||||
end
|
||||
if event == "PLAYER_AURAS_CHANGED" then
|
||||
if event == "BUFF_ADDED_SELF" or event == "BUFF_REMOVED_SELF" then
|
||||
-- NamPower: targeted event, no throttle needed.
|
||||
-- arg3 may not be a reliable DBC ID so we skip spell-ID filtering and let
|
||||
-- the existing texture/change guard below handle early-exit cheaply.
|
||||
elseif event == "PLAYER_AURAS_CHANGED" then
|
||||
local now = GetTime and GetTime() or 0
|
||||
local minInterval = zButtonAspect_MinAuraInterval
|
||||
if UnitAffectingCombat and UnitAffectingCombat("player") then
|
||||
@@ -271,7 +287,8 @@ function MTH_ZH_AspectAdjust_OnEvent()
|
||||
zButtonAspect_SetupSizeAndPosition()
|
||||
zButtonAspect_EnsureValidParentId()
|
||||
end
|
||||
if event == "PLAYER_AURAS_CHANGED" or event == "PLAYER_ENTERING_WORLD"
|
||||
if event == "PLAYER_AURAS_CHANGED" or event == "BUFF_ADDED_SELF" or event == "BUFF_REMOVED_SELF"
|
||||
or event == "PLAYER_ENTERING_WORLD"
|
||||
or event == "SPELLS_CHANGED" or event == "CHARACTER_POINTS_CHANGED" or event == "LEARNED_SPELL_IN_TAB" then
|
||||
zButtonAspect_EnsureValidParentId()
|
||||
if not zButtonAspect1.id then
|
||||
@@ -304,13 +321,19 @@ function MTH_ZH_AspectAdjust_OnEvent()
|
||||
end
|
||||
end
|
||||
local i = 1
|
||||
local texture = GetSpellTexture(zButtonAspect1.id, "spell")
|
||||
local texture1 = GetSpellTexture(zButtonAspect1.id, "spell")
|
||||
local texture2 = zButtonAspect2 and zButtonAspect2.id and GetSpellTexture(zButtonAspect2.id, "spell") or nil
|
||||
local buff = UnitBuff("player", i)
|
||||
local spellname, buffname
|
||||
local newParentId = zButtonAspect1.id
|
||||
local smartEnabled = zButtonAspect_GetSaved()["parent"]["smart"] ~= false
|
||||
while buff do
|
||||
if texture == buff and zButtonAspect2.id then
|
||||
newParentId = zButtonAspect2.id
|
||||
if smartEnabled and zButtonAspect2 and zButtonAspect2.id then
|
||||
if texture1 == buff then
|
||||
newParentId = zButtonAspect2.id
|
||||
elseif texture2 and texture2 == buff then
|
||||
newParentId = zButtonAspect1.id
|
||||
end
|
||||
end
|
||||
if needChildIconScan and buttontextures[buff] then
|
||||
if hasDuplicateChildTexture then
|
||||
@@ -332,10 +355,10 @@ function MTH_ZH_AspectAdjust_OnEvent()
|
||||
i = i + 1
|
||||
buff = UnitBuff("player", i)
|
||||
end
|
||||
if event == "PLAYER_AURAS_CHANGED" and zButtonAspect_LastAuraTexture == texture and zButtonAspect.id == newParentId and zButtonAspect_LastParentId == newParentId and not isParentTooltipOwned then
|
||||
if (event == "PLAYER_AURAS_CHANGED" or event == "BUFF_ADDED_SELF" or event == "BUFF_REMOVED_SELF") and zButtonAspect_LastAuraTexture == texture1 and zButtonAspect.id == newParentId and zButtonAspect_LastParentId == newParentId and not isParentTooltipOwned then
|
||||
return
|
||||
end
|
||||
zButtonAspect_LastAuraTexture = texture
|
||||
zButtonAspect_LastAuraTexture = texture1
|
||||
zButtonAspect_LastParentId = newParentId
|
||||
zButtonAspect.id = newParentId
|
||||
ZSpellButton_UpdateButton(zButtonAspect)
|
||||
|
||||
@@ -75,6 +75,9 @@ local function zButtonCompanions_EnsureConfig()
|
||||
if saved["parent"]["circle"] == nil then
|
||||
saved["parent"]["circle"] = 1
|
||||
end
|
||||
if saved["parent"]["random"] == nil then
|
||||
saved["parent"]["random"] = false
|
||||
end
|
||||
if not saved["children"]["size"] then
|
||||
saved["children"]["size"] = 36
|
||||
end
|
||||
@@ -177,7 +180,14 @@ local function zButtonCompanions_ApplySpellIds(spellIds)
|
||||
end
|
||||
|
||||
if found > 0 then
|
||||
parent.id = spellIds[1]
|
||||
local saved = zButtonCompanions_GetSaved()
|
||||
if saved["parent"]["random"] and found > 1 then
|
||||
local pick = math.random(1, found)
|
||||
local child = getglobal("zButtonCompanions" .. pick)
|
||||
parent.id = child and child.id or spellIds[1]
|
||||
else
|
||||
parent.id = spellIds[1]
|
||||
end
|
||||
parent.isspell = 1
|
||||
ZSpellButton_UpdateButton(parent)
|
||||
parent:Enable()
|
||||
@@ -227,6 +237,17 @@ function zButtonCompanions_OnEvent()
|
||||
return
|
||||
end
|
||||
zButtonCompanions_CreateButtons()
|
||||
zButtonCompanions.afterclick = function(button)
|
||||
local saved = zButtonCompanions_GetSaved()
|
||||
if saved["parent"]["random"] and zButtonCompanions.found and zButtonCompanions.found > 1 then
|
||||
local pick = math.random(1, zButtonCompanions.found)
|
||||
local child = getglobal("zButtonCompanions" .. pick)
|
||||
if child and child.id then
|
||||
zButtonCompanions.id = child.id
|
||||
ZSpellButton_UpdateButton(zButtonCompanions)
|
||||
end
|
||||
end
|
||||
end
|
||||
MTH_ZH_CompanionsAdjust = CreateFrame("Frame", "MTH_ZH_CompanionsAdjust")
|
||||
MTH_ZH_CompanionsAdjust:RegisterEvent("SPELLS_CHANGED")
|
||||
MTH_ZH_CompanionsAdjust:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
@@ -264,12 +285,14 @@ function zButtonCompanions_SetupSizeAndPosition()
|
||||
if displayCount < 0 then
|
||||
displayCount = 0
|
||||
end
|
||||
ZSpellButton_SetSize(zButtonCompanions, saved["parent"]["size"])
|
||||
ZSpellButton_SetSize(zButtonCompanions, saved["children"]["size"], 1)
|
||||
ZSpellButton_SetExpandDirection(zButtonCompanions, saved["firstbutton"])
|
||||
ZSpellButton_ArrangeChildren(zButtonCompanions, saved["rows"],
|
||||
displayCount, saved["horizontal"],
|
||||
saved["vertical"])
|
||||
if not (type(MTH_ZBar_ApplyToButton) == "function" and MTH_ZBar_ApplyToButton(zButtonCompanions, displayCount)) then
|
||||
ZSpellButton_SetSize(zButtonCompanions, saved["parent"]["size"])
|
||||
ZSpellButton_SetSize(zButtonCompanions, saved["children"]["size"], 1)
|
||||
ZSpellButton_SetExpandDirection(zButtonCompanions, saved["firstbutton"])
|
||||
ZSpellButton_ArrangeChildren(zButtonCompanions, saved["rows"],
|
||||
displayCount, saved["horizontal"],
|
||||
saved["vertical"])
|
||||
end
|
||||
end
|
||||
|
||||
function zButtonCompanions_Reset()
|
||||
|
||||
@@ -0,0 +1,420 @@
|
||||
local function zButtonCraft_GetRoot()
|
||||
return MTH_ZH_GetSavedRoot()
|
||||
end
|
||||
|
||||
local root = zButtonCraft_GetRoot()
|
||||
if not root["zButtonCraft"] then
|
||||
root["zButtonCraft"] = {}
|
||||
root["zButtonCraft"]["spells"] = {}
|
||||
root["zButtonCraft"]["rows"] = 1
|
||||
root["zButtonCraft"]["horizontal"] = nil
|
||||
root["zButtonCraft"]["vertical"] = nil
|
||||
root["zButtonCraft"]["firstbutton"] = "RIGHT"
|
||||
root["zButtonCraft"]["enabled"] = 0
|
||||
root["zButtonCraft"]["tooltip"] = 1
|
||||
root["zButtonCraft"]["parent"] = {}
|
||||
root["zButtonCraft"]["parent"]["size"] = 36
|
||||
root["zButtonCraft"]["parent"]["hide"] = nil
|
||||
root["zButtonCraft"]["parent"]["circle"] = 1
|
||||
root["zButtonCraft"]["children"] = {}
|
||||
root["zButtonCraft"]["children"]["size"] = 36
|
||||
root["zButtonCraft"]["children"]["hideonclick"] = 1
|
||||
end
|
||||
|
||||
local ZHUNTER_CRAFT_BUTTON_MAX = 14
|
||||
|
||||
local zButtonCraft_LastSpellSignature = nil
|
||||
local zButtonCraft_LastRefreshAt = 0
|
||||
local zButtonCraft_MinRefreshInterval = 0.50
|
||||
local zButtonCraft_PendingRefresh = false
|
||||
|
||||
-- Exact spell names as they appear in the General spellbook tab.
|
||||
-- spellName = the name returned by GetSpellName() to match against (case-insensitive)
|
||||
-- display = label used for tooltips and options
|
||||
local ZCRAFT_PROFESSIONS = {
|
||||
{ spellName = "Alchemy", display = "Alchemy" },
|
||||
{ spellName = "Blacksmithing", display = "Blacksmithing" },
|
||||
{ spellName = "Disenchanting", display = "Disenchanting" },
|
||||
{ spellName = "Enchanting", display = "Enchanting" },
|
||||
{ spellName = "Engineering", display = "Engineering" },
|
||||
{ spellName = "Leatherworking", display = "Leatherworking" },
|
||||
{ spellName = "Tailoring", display = "Tailoring" },
|
||||
{ spellName = "Jewelcrafting", display = "Jewelcrafting" },
|
||||
{ spellName = "Smelting", display = "Smelting" },
|
||||
{ spellName = "Cooking", display = "Cooking" },
|
||||
{ spellName = "First Aid", display = "First Aid" },
|
||||
{ spellName = "Herbalism", display = "Herbalism" },
|
||||
{ spellName = "Survival", display = "Survival" },
|
||||
{ spellName = "Fishing", display = "Fishing" },
|
||||
{ spellName = "Skinning", display = "Skinning" },
|
||||
}
|
||||
|
||||
-- Build a lookup: lowercase spell name → ZCRAFT_PROFESSIONS entry
|
||||
local ZCRAFT_SPELL_LOOKUP = {}
|
||||
for i = 1, table.getn(ZCRAFT_PROFESSIONS) do
|
||||
ZCRAFT_SPELL_LOOKUP[string.lower(ZCRAFT_PROFESSIONS[i].spellName)] = ZCRAFT_PROFESSIONS[i]
|
||||
end
|
||||
|
||||
function zButtonCraft_GetSaved()
|
||||
local currentRoot = zButtonCraft_GetRoot()
|
||||
if not currentRoot["zButtonCraft"] then
|
||||
currentRoot["zButtonCraft"] = {}
|
||||
end
|
||||
return currentRoot["zButtonCraft"]
|
||||
end
|
||||
|
||||
local function zButtonCraft_EnsureConfig()
|
||||
local saved = zButtonCraft_GetSaved()
|
||||
saved["count"] = nil
|
||||
if saved["enabled"] == nil then
|
||||
saved["enabled"] = 0
|
||||
end
|
||||
if saved["tooltip"] == nil then
|
||||
saved["tooltip"] = 1
|
||||
end
|
||||
if not saved["children"] then
|
||||
saved["children"] = {}
|
||||
end
|
||||
if saved["children"]["hideonclick"] == nil then
|
||||
saved["children"]["hideonclick"] = 1
|
||||
end
|
||||
if not saved["spells"] then
|
||||
saved["spells"] = {}
|
||||
end
|
||||
if not saved["parent"] then
|
||||
saved["parent"] = {}
|
||||
end
|
||||
if not saved["parent"]["size"] then
|
||||
saved["parent"]["size"] = 36
|
||||
end
|
||||
if saved["parent"]["circle"] == nil then
|
||||
saved["parent"]["circle"] = 1
|
||||
end
|
||||
if not saved["children"]["size"] then
|
||||
saved["children"]["size"] = 36
|
||||
end
|
||||
if not saved["rows"] or saved["rows"] < 1 then
|
||||
saved["rows"] = 1
|
||||
end
|
||||
if not saved["firstbutton"] then
|
||||
saved["firstbutton"] = "RIGHT"
|
||||
end
|
||||
end
|
||||
|
||||
local function zButtonCraft_ApplyRuntimeSettings()
|
||||
if not zButtonCraft then return end
|
||||
local saved = zButtonCraft_GetSaved()
|
||||
zButtonCraft.tooltip = saved["tooltip"] and true or false
|
||||
zButtonCraft.hideonclick = saved["children"] and saved["children"]["hideonclick"] and true or false
|
||||
zButtonCraft.expandonhover = saved["children"] and saved["children"]["expandonhover"] and true or false
|
||||
zButtonCraft.fadetimer = saved["children"] and tonumber(saved["children"]["fadetimer"]) or 0
|
||||
end
|
||||
|
||||
local function zButtonCraft_GetSpellSignature(spellIds)
|
||||
if type(spellIds) ~= "table" or table.getn(spellIds) == 0 then return "" end
|
||||
local parts = {}
|
||||
for i = 1, table.getn(spellIds) do
|
||||
parts[i] = tostring(spellIds[i] or "")
|
||||
end
|
||||
return table.concat(parts, ",")
|
||||
end
|
||||
|
||||
-- Scans individual spells in the General tab and returns two parallel arrays:
|
||||
-- spellIds = spellbook index of each matched craft spell
|
||||
-- spellNames = display label for that profession
|
||||
-- Results are ordered by ZCRAFT_PROFESSIONS priority.
|
||||
local function zButtonCraft_CollectProfessions()
|
||||
if type(GetNumSpellTabs) ~= "function" or type(GetSpellTabInfo) ~= "function"
|
||||
or type(GetSpellName) ~= "function" then
|
||||
return {}, {}
|
||||
end
|
||||
local bookType = BOOKTYPE_SPELL or "spell"
|
||||
|
||||
-- Find the General tab (always tab 1, but look it up to be safe)
|
||||
local generalOffset, generalEntries
|
||||
local tabCount = GetNumSpellTabs() or 0
|
||||
for tabIndex = 1, tabCount do
|
||||
local tabName, _, tabOffset, tabEntries = GetSpellTabInfo(tabIndex)
|
||||
if tabName and string.lower(tabName) == "general" then
|
||||
generalOffset = tabOffset
|
||||
generalEntries = tabEntries
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not generalOffset or not generalEntries or generalEntries <= 0 then
|
||||
return {}, {}
|
||||
end
|
||||
|
||||
-- Scan every spell in General; collect matches keyed by profession order
|
||||
local found = {} -- profession display → spellbook index (first match wins)
|
||||
for i = 1, generalEntries do
|
||||
local spellIndex = generalOffset + i
|
||||
local spellName = GetSpellName(spellIndex, bookType)
|
||||
if spellName then
|
||||
local prof = ZCRAFT_SPELL_LOOKUP[string.lower(spellName)]
|
||||
if prof and not found[prof.display] then
|
||||
found[prof.display] = spellIndex
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Return results in ZCRAFT_PROFESSIONS order
|
||||
local spellIds = {}
|
||||
local spellNames = {}
|
||||
for profIndex = 1, table.getn(ZCRAFT_PROFESSIONS) do
|
||||
local prof = ZCRAFT_PROFESSIONS[profIndex]
|
||||
local spellIndex = found[prof.display]
|
||||
if spellIndex then
|
||||
table.insert(spellIds, spellIndex)
|
||||
table.insert(spellNames, prof.display)
|
||||
end
|
||||
end
|
||||
|
||||
return spellIds, spellNames
|
||||
end
|
||||
|
||||
local function zButtonCraft_ApplySpellIds(spellIds)
|
||||
if not zButtonCraft then return 0 end
|
||||
local parent = zButtonCraft
|
||||
local childCount = parent.count or ZHUNTER_CRAFT_BUTTON_MAX
|
||||
|
||||
for i = 1, childCount do
|
||||
local child = getglobal("zButtonCraft" .. i)
|
||||
if child then
|
||||
child:Hide()
|
||||
child.id = nil
|
||||
child.icon = nil
|
||||
child.isspell = nil
|
||||
end
|
||||
end
|
||||
|
||||
local found = 0
|
||||
for i = 1, table.getn(spellIds) do
|
||||
if found >= childCount then break end
|
||||
local spellId = spellIds[i]
|
||||
if spellId then
|
||||
found = found + 1
|
||||
local child = getglobal("zButtonCraft" .. found)
|
||||
if child then
|
||||
child.id = spellId
|
||||
child.isspell = 1
|
||||
ZSpellButton_UpdateButton(child)
|
||||
child:Show()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if found > 0 then
|
||||
parent.id = spellIds[1]
|
||||
parent.isspell = 1
|
||||
ZSpellButton_UpdateButton(parent)
|
||||
parent:Enable()
|
||||
parent:Show()
|
||||
else
|
||||
parent.id = nil
|
||||
parent:Hide()
|
||||
end
|
||||
|
||||
return found
|
||||
end
|
||||
|
||||
local function zButtonCraft_RefreshProfessions()
|
||||
if not zButtonCraft then return 0 end
|
||||
local saved = zButtonCraft_GetSaved()
|
||||
local freshIds, freshNames = zButtonCraft_CollectProfessions()
|
||||
|
||||
-- Build lookup: display name → spellbook index from the fresh scan
|
||||
local nameToId = {}
|
||||
for i = 1, table.getn(freshNames) do
|
||||
nameToId[freshNames[i]] = freshIds[i]
|
||||
end
|
||||
|
||||
-- Honour saved user order when it exists
|
||||
local existing = saved["spells"]
|
||||
if existing and table.getn(existing) > 0 then
|
||||
local orderedNames = {}
|
||||
local orderedIds = {}
|
||||
local seen = {}
|
||||
-- Keep entries that still exist, in the user's order
|
||||
for i = 1, table.getn(existing) do
|
||||
local name = existing[i]
|
||||
if nameToId[name] then
|
||||
table.insert(orderedNames, name)
|
||||
table.insert(orderedIds, nameToId[name])
|
||||
seen[name] = true
|
||||
end
|
||||
end
|
||||
-- Append any newly learned professions at the end
|
||||
for i = 1, table.getn(freshNames) do
|
||||
if not seen[freshNames[i]] then
|
||||
table.insert(orderedNames, freshNames[i])
|
||||
table.insert(orderedIds, freshIds[i])
|
||||
end
|
||||
end
|
||||
freshNames = orderedNames
|
||||
freshIds = orderedIds
|
||||
end
|
||||
|
||||
saved["spellIds"] = freshIds
|
||||
saved["spells"] = freshNames
|
||||
zButtonCraft.spells = freshNames
|
||||
zButtonCraft.found = zButtonCraft_ApplySpellIds(freshIds)
|
||||
zButtonCraft_ApplyRuntimeSettings()
|
||||
return zButtonCraft.found or 0
|
||||
end
|
||||
|
||||
function zButtonCraft_OnLoad()
|
||||
this:RegisterEvent("VARIABLES_LOADED")
|
||||
end
|
||||
|
||||
function zButtonCraft_OnEvent()
|
||||
if event == "VARIABLES_LOADED" then
|
||||
if not zButtonCraft then return end
|
||||
if UnitClass("player") ~= ZHUNTER_HUNTER then
|
||||
zButtonCraft:UnregisterAllEvents()
|
||||
zButtonCraft:Hide()
|
||||
return
|
||||
end
|
||||
zButtonCraft_CreateButtons()
|
||||
MTH_ZH_CraftAdjust = CreateFrame("Frame", "MTH_ZH_CraftAdjust")
|
||||
MTH_ZH_CraftAdjust:RegisterEvent("SPELLS_CHANGED")
|
||||
MTH_ZH_CraftAdjust:RegisterEvent("LEARNED_SPELL_IN_TAB")
|
||||
MTH_ZH_CraftAdjust:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
MTH_ZH_CraftAdjust:SetScript("OnEvent", MTH_ZH_CraftAdjust_OnEvent)
|
||||
zButtonCraft_SetupSizeAndPosition()
|
||||
end
|
||||
end
|
||||
|
||||
function zButtonCraft_CreateButtons()
|
||||
zButtonCraft_EnsureConfig()
|
||||
if not zButtonCraft._mthChildrenCreated then
|
||||
ZSpellButton_CreateChildren(zButtonCraft, "zButtonCraft", ZHUNTER_CRAFT_BUTTON_MAX)
|
||||
zButtonCraft._mthChildrenCreated = 1
|
||||
end
|
||||
zButtonCraft_RefreshProfessions()
|
||||
end
|
||||
|
||||
function zButtonCraft_SetupSizeAndPosition()
|
||||
local saved = zButtonCraft_GetSaved()
|
||||
if saved["enabled"] == false or saved["enabled"] == 0 then
|
||||
if MTH_ZH_CraftAdjust then
|
||||
MTH_ZH_CraftAdjust:UnregisterAllEvents()
|
||||
MTH_ZH_CraftAdjust:SetScript("OnEvent", nil)
|
||||
end
|
||||
if zButtonCraft and zButtonCraft.Hide then
|
||||
zButtonCraft:Hide()
|
||||
end
|
||||
return
|
||||
end
|
||||
if MTH_ZH_CraftAdjust then
|
||||
MTH_ZH_CraftAdjust:RegisterEvent("SPELLS_CHANGED")
|
||||
MTH_ZH_CraftAdjust:RegisterEvent("LEARNED_SPELL_IN_TAB")
|
||||
MTH_ZH_CraftAdjust:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
MTH_ZH_CraftAdjust:SetScript("OnEvent", MTH_ZH_CraftAdjust_OnEvent)
|
||||
end
|
||||
local displayCount = zButtonCraft.found or 0
|
||||
if displayCount < 0 then displayCount = 0 end
|
||||
if not (type(MTH_ZBar_ApplyToButton) == "function" and MTH_ZBar_ApplyToButton(zButtonCraft, displayCount)) then
|
||||
ZSpellButton_SetSize(zButtonCraft, saved["parent"]["size"])
|
||||
ZSpellButton_SetSize(zButtonCraft, saved["children"]["size"], 1)
|
||||
ZSpellButton_SetExpandDirection(zButtonCraft, saved["firstbutton"])
|
||||
ZSpellButton_ArrangeChildren(zButtonCraft, saved["rows"],
|
||||
displayCount, saved["horizontal"], saved["vertical"])
|
||||
end
|
||||
end
|
||||
|
||||
function zButtonCraft_Reset()
|
||||
local currentRoot = zButtonCraft_GetRoot()
|
||||
currentRoot["zButtonCraft"] = {}
|
||||
local saved = zButtonCraft_GetSaved()
|
||||
saved["spells"] = {}
|
||||
saved["rows"] = 1
|
||||
saved["horizontal"] = nil
|
||||
saved["vertical"] = nil
|
||||
saved["firstbutton"] = "RIGHT"
|
||||
saved["enabled"] = 1
|
||||
saved["tooltip"] = 1
|
||||
saved["parent"] = {}
|
||||
saved["parent"]["size"] = 36
|
||||
saved["parent"]["hide"] = nil
|
||||
saved["parent"]["circle"] = 1
|
||||
saved["children"] = {}
|
||||
saved["children"]["size"] = 36
|
||||
saved["children"]["hideonclick"] = 1
|
||||
zButtonCraft_EnsureConfig()
|
||||
end
|
||||
|
||||
local function zButtonCraft_RefreshAfterSpellbookChange()
|
||||
if not zButtonCraft then return end
|
||||
local now = GetTime and GetTime() or 0
|
||||
if now > 0 and zButtonCraft_LastRefreshAt > 0 and (now - zButtonCraft_LastRefreshAt) < zButtonCraft_MinRefreshInterval then
|
||||
return
|
||||
end
|
||||
local spellIds, _ = zButtonCraft_CollectProfessions()
|
||||
local signature = zButtonCraft_GetSpellSignature(spellIds)
|
||||
if signature == zButtonCraft_LastSpellSignature and not GameTooltip:IsOwned(zButtonCraft) then
|
||||
return
|
||||
end
|
||||
zButtonCraft_LastSpellSignature = signature
|
||||
zButtonCraft_LastRefreshAt = now
|
||||
zButtonCraft_RefreshProfessions()
|
||||
zButtonCraft_SetupSizeAndPosition()
|
||||
if GameTooltip:IsOwned(zButtonCraft) then
|
||||
ZSpellButtonParent_OnEnter(zButtonCraft)
|
||||
end
|
||||
end
|
||||
|
||||
function MTH_ZH_CraftAdjust_OnEvent()
|
||||
if not zButtonCraft then return end
|
||||
if event == "SPELLS_CHANGED" or event == "LEARNED_SPELL_IN_TAB" then
|
||||
if zButtonCraft_PendingRefresh then return end
|
||||
zButtonCraft_PendingRefresh = true
|
||||
MTH_ZH_CraftAdjust.elapsed = 0
|
||||
MTH_ZH_CraftAdjust:SetScript("OnUpdate", function()
|
||||
this.elapsed = (this.elapsed or 0) + (arg1 or 0)
|
||||
if this.elapsed < 0.6 then return end
|
||||
this:SetScript("OnUpdate", nil)
|
||||
zButtonCraft_PendingRefresh = false
|
||||
zButtonCraft_RefreshAfterSpellbookChange()
|
||||
end)
|
||||
return
|
||||
end
|
||||
zButtonCraft_RefreshAfterSpellbookChange()
|
||||
end
|
||||
|
||||
function zButtonCraft_KeyBinding(index)
|
||||
if MTH_ZH_IsModuleEnabled and not MTH_ZH_IsModuleEnabled() then return end
|
||||
local button
|
||||
if index then
|
||||
button = getglobal("zButtonCraft" .. index)
|
||||
else
|
||||
button = zButtonCraft
|
||||
end
|
||||
if button and button.id then
|
||||
CastSpell(button.id, "spell")
|
||||
if zButtonCraft.hideonclick then
|
||||
if type(ZSpellButton_SetChildrenExpanded) == "function" then
|
||||
ZSpellButton_SetChildrenExpanded(zButtonCraft, false)
|
||||
elseif zButtonCraft.children then
|
||||
zButtonCraft.children:Hide()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
SLASH_zButtonCraft1 = "/ZCraft"
|
||||
SlashCmdList["zButtonCraft"] = function(msg)
|
||||
if MTH_ZH_HandleDisabledSlash and MTH_ZH_HandleDisabledSlash("Craft button is disabled while module 'zhunter' is disabled.") then
|
||||
return
|
||||
end
|
||||
if msg == "reset" then
|
||||
zButtonCraft_Reset()
|
||||
zButtonCraft:ClearAllPoints()
|
||||
zButtonCraft:SetPoint("TOP", UIParent, "TOP", 0, -558)
|
||||
zButtonCraft_RefreshProfessions()
|
||||
zButtonCraft_SetupSizeAndPosition()
|
||||
else
|
||||
MTH_ZH_Print("Possible Commands: \"reset\"")
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Frame name="zButtonCraftFrame">
|
||||
</Frame>
|
||||
<CheckButton name="zButtonCraft" inherits="ZSpellButtonTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="CENTER" relativeTo="UIParent" relativePoint="CENTER">
|
||||
<Offset>
|
||||
<AbsDimension x="0" y="-288"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</CheckButton>
|
||||
</Ui>
|
||||
@@ -75,6 +75,12 @@ local function zButtonMounts_EnsureConfig()
|
||||
if saved["parent"]["circle"] == nil then
|
||||
saved["parent"]["circle"] = 1
|
||||
end
|
||||
if saved["parent"]["random"] == nil then
|
||||
saved["parent"]["random"] = false
|
||||
end
|
||||
if saved["parent"]["aqfilter"] == nil then
|
||||
saved["parent"]["aqfilter"] = true
|
||||
end
|
||||
if not saved["children"]["size"] then
|
||||
saved["children"]["size"] = 36
|
||||
end
|
||||
@@ -177,7 +183,14 @@ local function zButtonMounts_ApplySpellIds(spellIds)
|
||||
end
|
||||
|
||||
if found > 0 then
|
||||
parent.id = spellIds[1]
|
||||
local saved = zButtonMounts_GetSaved()
|
||||
if saved["parent"]["random"] and found > 1 then
|
||||
local pick = math.random(1, found)
|
||||
local child = getglobal("zButtonMounts" .. pick)
|
||||
parent.id = child and child.id or spellIds[1]
|
||||
else
|
||||
parent.id = spellIds[1]
|
||||
end
|
||||
parent.isspell = 1
|
||||
ZSpellButton_UpdateButton(parent)
|
||||
parent:Enable()
|
||||
@@ -190,6 +203,49 @@ local function zButtonMounts_ApplySpellIds(spellIds)
|
||||
return found
|
||||
end
|
||||
|
||||
local function zButtonMounts_IsQirajiSpell(spellId)
|
||||
local bookType = BOOKTYPE_SPELL or "spell"
|
||||
local name = GetSpellName(spellId, bookType)
|
||||
if name and string.find(string.lower(name), "qiraji", 1, true) then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function zButtonMounts_IsInAQ40()
|
||||
local zone = GetRealZoneText and GetRealZoneText() or ""
|
||||
if zone == "Temple of Ahn'Qiraj" or zone == "Ahn'Qiraj" then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function zButtonMounts_FilterAQ(spellIds)
|
||||
local saved = zButtonMounts_GetSaved()
|
||||
if not saved["parent"]["aqfilter"] then
|
||||
return spellIds
|
||||
end
|
||||
local inAQ = zButtonMounts_IsInAQ40()
|
||||
local filtered = {}
|
||||
for i = 1, table.getn(spellIds) do
|
||||
local isQiraji = zButtonMounts_IsQirajiSpell(spellIds[i])
|
||||
if inAQ then
|
||||
if isQiraji then
|
||||
table.insert(filtered, spellIds[i])
|
||||
end
|
||||
else
|
||||
if not isQiraji then
|
||||
table.insert(filtered, spellIds[i])
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Fallback: if filtering removed everything, return original list
|
||||
if table.getn(filtered) == 0 then
|
||||
return spellIds
|
||||
end
|
||||
return filtered
|
||||
end
|
||||
|
||||
local function zButtonMounts_RefreshSpells()
|
||||
if not zButtonMounts then
|
||||
return 0
|
||||
@@ -207,6 +263,7 @@ local function zButtonMounts_RefreshSpells()
|
||||
saved["spellIds"] = spellIds
|
||||
saved["spells"] = spellNames
|
||||
zButtonMounts.spells = spellNames
|
||||
spellIds = zButtonMounts_FilterAQ(spellIds)
|
||||
zButtonMounts.found = zButtonMounts_ApplySpellIds(spellIds)
|
||||
zButtonMounts_ApplyRuntimeSettings()
|
||||
return zButtonMounts.found or 0
|
||||
@@ -227,9 +284,21 @@ function zButtonMounts_OnEvent()
|
||||
return
|
||||
end
|
||||
zButtonMounts_CreateButtons()
|
||||
zButtonMounts.afterclick = function(button)
|
||||
local saved = zButtonMounts_GetSaved()
|
||||
if saved["parent"]["random"] and zButtonMounts.found and zButtonMounts.found > 1 then
|
||||
local pick = math.random(1, zButtonMounts.found)
|
||||
local child = getglobal("zButtonMounts" .. pick)
|
||||
if child and child.id then
|
||||
zButtonMounts.id = child.id
|
||||
ZSpellButton_UpdateButton(zButtonMounts)
|
||||
end
|
||||
end
|
||||
end
|
||||
MTH_ZH_MountsAdjust = CreateFrame("Frame", "MTH_ZH_MountsAdjust")
|
||||
MTH_ZH_MountsAdjust:RegisterEvent("SPELLS_CHANGED")
|
||||
MTH_ZH_MountsAdjust:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
MTH_ZH_MountsAdjust:RegisterEvent("ZONE_CHANGED_NEW_AREA")
|
||||
MTH_ZH_MountsAdjust:SetScript("OnEvent", MTH_ZH_MountsAdjust_OnEvent)
|
||||
zButtonMounts_SetupSizeAndPosition()
|
||||
end
|
||||
@@ -249,7 +318,8 @@ end
|
||||
function zButtonMounts_SetupSizeAndPosition()
|
||||
local saved = zButtonMounts_GetSaved()
|
||||
if saved["enabled"] == false or saved["enabled"] == 0 then
|
||||
if MTH_ZH_MountsAdjust and MTH_ZH_MountsAdjust.SetScript then
|
||||
if MTH_ZH_MountsAdjust then
|
||||
MTH_ZH_MountsAdjust:UnregisterAllEvents()
|
||||
MTH_ZH_MountsAdjust:SetScript("OnEvent", nil)
|
||||
end
|
||||
if zButtonMounts and zButtonMounts.Hide then
|
||||
@@ -257,19 +327,24 @@ function zButtonMounts_SetupSizeAndPosition()
|
||||
end
|
||||
return
|
||||
end
|
||||
if MTH_ZH_MountsAdjust and MTH_ZH_MountsAdjust.SetScript then
|
||||
if MTH_ZH_MountsAdjust then
|
||||
MTH_ZH_MountsAdjust:RegisterEvent("SPELLS_CHANGED")
|
||||
MTH_ZH_MountsAdjust:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
MTH_ZH_MountsAdjust:RegisterEvent("ZONE_CHANGED_NEW_AREA")
|
||||
MTH_ZH_MountsAdjust:SetScript("OnEvent", MTH_ZH_MountsAdjust_OnEvent)
|
||||
end
|
||||
local displayCount = zButtonMounts.found or 0
|
||||
if displayCount < 0 then
|
||||
displayCount = 0
|
||||
end
|
||||
ZSpellButton_SetSize(zButtonMounts, saved["parent"]["size"])
|
||||
ZSpellButton_SetSize(zButtonMounts, saved["children"]["size"], 1)
|
||||
ZSpellButton_SetExpandDirection(zButtonMounts, saved["firstbutton"])
|
||||
ZSpellButton_ArrangeChildren(zButtonMounts, saved["rows"],
|
||||
displayCount, saved["horizontal"],
|
||||
saved["vertical"])
|
||||
if not (type(MTH_ZBar_ApplyToButton) == "function" and MTH_ZBar_ApplyToButton(zButtonMounts, displayCount)) then
|
||||
ZSpellButton_SetSize(zButtonMounts, saved["parent"]["size"])
|
||||
ZSpellButton_SetSize(zButtonMounts, saved["children"]["size"], 1)
|
||||
ZSpellButton_SetExpandDirection(zButtonMounts, saved["firstbutton"])
|
||||
ZSpellButton_ArrangeChildren(zButtonMounts, saved["rows"],
|
||||
displayCount, saved["horizontal"],
|
||||
saved["vertical"])
|
||||
end
|
||||
end
|
||||
|
||||
function zButtonMounts_Reset()
|
||||
|
||||
@@ -84,6 +84,9 @@ local function zButtonPet_EnsureConfig()
|
||||
if saved["parent"]["circle"] == nil then
|
||||
saved["parent"]["circle"] = 1
|
||||
end
|
||||
if saved["parent"]["smart"] == nil then
|
||||
saved["parent"]["smart"] = true
|
||||
end
|
||||
if saved["enabled"] == nil then
|
||||
saved["enabled"] = 1
|
||||
end
|
||||
@@ -235,7 +238,8 @@ end
|
||||
function zButtonPet_SetupSizeAndPosition()
|
||||
local saved = zButtonPet_GetSaved()
|
||||
if saved["enabled"] == false or saved["enabled"] == 0 then
|
||||
if MTH_ZH_PetAdjust and MTH_ZH_PetAdjust.SetScript then
|
||||
if MTH_ZH_PetAdjust then
|
||||
MTH_ZH_PetAdjust:UnregisterAllEvents()
|
||||
MTH_ZH_PetAdjust:SetScript("OnEvent", nil)
|
||||
end
|
||||
if zButtonPet and zButtonPet.Hide then
|
||||
@@ -243,19 +247,28 @@ function zButtonPet_SetupSizeAndPosition()
|
||||
end
|
||||
return
|
||||
end
|
||||
if MTH_ZH_PetAdjust and MTH_ZH_PetAdjust.SetScript then
|
||||
if MTH_ZH_PetAdjust then
|
||||
MTH_ZH_PetAdjust:RegisterEvent("UNIT_HEALTH")
|
||||
MTH_ZH_PetAdjust:RegisterEvent("UNIT_HAPPINESS")
|
||||
MTH_ZH_PetAdjust:RegisterEvent("UNIT_PET")
|
||||
MTH_ZH_PetAdjust:RegisterEvent("PET_BAR_UPDATE")
|
||||
MTH_ZH_PetAdjust:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
MTH_ZH_PetAdjust:RegisterEvent("SPELLS_CHANGED")
|
||||
MTH_ZH_PetAdjust:RegisterEvent("LEARNED_SPELL_IN_TAB")
|
||||
MTH_ZH_PetAdjust:SetScript("OnEvent", MTH_ZH_PetAdjust_OnEvent)
|
||||
end
|
||||
local displayCount = zButtonPet.found or ZHUNTER_PET_MAX
|
||||
if displayCount < 0 then
|
||||
displayCount = 0
|
||||
end
|
||||
ZSpellButton_SetSize(zButtonPet, saved["parent"]["size"])
|
||||
ZSpellButton_SetSize(zButtonPet, saved["children"]["size"], 1)
|
||||
ZSpellButton_SetExpandDirection(zButtonPet, saved["firstbutton"])
|
||||
ZSpellButton_ArrangeChildren(zButtonPet, saved["rows"],
|
||||
displayCount, saved["horizontal"],
|
||||
saved["vertical"])
|
||||
if not (type(MTH_ZBar_ApplyToButton) == "function" and MTH_ZBar_ApplyToButton(zButtonPet, displayCount)) then
|
||||
ZSpellButton_SetSize(zButtonPet, saved["parent"]["size"])
|
||||
ZSpellButton_SetSize(zButtonPet, saved["children"]["size"], 1)
|
||||
ZSpellButton_SetExpandDirection(zButtonPet, saved["firstbutton"])
|
||||
ZSpellButton_ArrangeChildren(zButtonPet, saved["rows"],
|
||||
displayCount, saved["horizontal"],
|
||||
saved["vertical"])
|
||||
end
|
||||
end
|
||||
|
||||
function zButtonPet_Reset()
|
||||
@@ -299,6 +312,8 @@ local function zButtonPet_GetCorePetInfo()
|
||||
return nil
|
||||
end
|
||||
|
||||
local zButtonPet_LastLoyalty = nil
|
||||
|
||||
local function zButtonPet_RefreshSavedPetState(saved, corePetInfo)
|
||||
local hasLivePet = false
|
||||
if type(UnitExists) == "function" then
|
||||
@@ -330,8 +345,34 @@ local function zButtonPet_RefreshSavedPetState(saved, corePetInfo)
|
||||
if happiness == nil and type(GetPetHappiness) == "function" then
|
||||
happiness = GetPetHappiness()
|
||||
end
|
||||
local prevHappiness = saved["pet"]["happiness"]
|
||||
saved["pet"]["happiness"] = happiness
|
||||
|
||||
-- Animations: flash the pet button when happiness or loyalty changes.
|
||||
if type(MTH_FX_Flash) == "function" and type(MTH_FX_IsEnabled) == "function" then
|
||||
if MTH_FX_IsEnabled("pet.happiness")
|
||||
and prevHappiness ~= nil and happiness ~= nil and happiness ~= prevHappiness then
|
||||
local target = getglobal("zButtonPet")
|
||||
if target then
|
||||
if happiness > prevHappiness then
|
||||
MTH_FX_Flash(target, 0.30, 1.00, 0.35, 0.5)
|
||||
else
|
||||
MTH_FX_Flash(target, 1.00, 0.30, 0.30, 0.5)
|
||||
end
|
||||
end
|
||||
end
|
||||
local loyalty = corePetInfo and tonumber(corePetInfo.loyaltyLevel) or nil
|
||||
if MTH_FX_IsEnabled("pet.loyaltyup") and loyalty ~= nil then
|
||||
if zButtonPet_LastLoyalty ~= nil and loyalty > zButtonPet_LastLoyalty then
|
||||
local target = getglobal("zButtonPet")
|
||||
if target then
|
||||
MTH_FX_Flash(target, 1.00, 0.82, 0.20, 0.6)
|
||||
end
|
||||
end
|
||||
zButtonPet_LastLoyalty = loyalty
|
||||
end
|
||||
end
|
||||
|
||||
if isDead then
|
||||
saved["pet"]["status"] = 1
|
||||
elseif currentHealth and currentHealthMax and currentHealthMax > 0 then
|
||||
@@ -494,7 +535,11 @@ local function zButtonPet_RefreshParentSpell()
|
||||
local id = zButtonPet.id
|
||||
name = nil
|
||||
|
||||
if hasCurrentPet == false and hasLivePet == false then
|
||||
if saved["parent"]["smart"] == false then
|
||||
if choiceId then
|
||||
id = choiceId
|
||||
end
|
||||
elseif hasCurrentPet == false and hasLivePet == false then
|
||||
name = ZHUNTER_PET_TAMING
|
||||
elseif not status then
|
||||
name = ZHUNTER_PET_CALL
|
||||
|
||||
@@ -648,10 +648,12 @@ function zButtonRanged_SetupSizeAndPosition()
|
||||
if displayCount < 0 then
|
||||
displayCount = 0
|
||||
end
|
||||
ZSpellButton_SetSize(zButtonRanged, saved["parent"]["size"])
|
||||
ZSpellButton_SetSize(zButtonRanged, saved["children"]["size"], 1)
|
||||
ZSpellButton_SetExpandDirection(zButtonRanged, saved["firstbutton"])
|
||||
ZSpellButton_ArrangeChildren(zButtonRanged, saved["rows"], displayCount, saved["horizontal"], saved["vertical"])
|
||||
if not (type(MTH_ZBar_ApplyToButton) == "function" and MTH_ZBar_ApplyToButton(zButtonRanged, displayCount)) then
|
||||
ZSpellButton_SetSize(zButtonRanged, saved["parent"]["size"])
|
||||
ZSpellButton_SetSize(zButtonRanged, saved["children"]["size"], 1)
|
||||
ZSpellButton_SetExpandDirection(zButtonRanged, saved["firstbutton"])
|
||||
ZSpellButton_ArrangeChildren(zButtonRanged, saved["rows"], displayCount, saved["horizontal"], saved["vertical"])
|
||||
end
|
||||
zButtonRanged_UpdateButton(zButtonRanged)
|
||||
end
|
||||
|
||||
|
||||
@@ -249,7 +249,8 @@ end
|
||||
function zButtonToys_SetupSizeAndPosition()
|
||||
local saved = zButtonToys_GetSaved()
|
||||
if saved["enabled"] == false or saved["enabled"] == 0 then
|
||||
if MTH_ZH_ToysAdjust and MTH_ZH_ToysAdjust.SetScript then
|
||||
if MTH_ZH_ToysAdjust then
|
||||
MTH_ZH_ToysAdjust:UnregisterAllEvents()
|
||||
MTH_ZH_ToysAdjust:SetScript("OnEvent", nil)
|
||||
end
|
||||
if zButtonToys and zButtonToys.Hide then
|
||||
@@ -257,19 +258,23 @@ function zButtonToys_SetupSizeAndPosition()
|
||||
end
|
||||
return
|
||||
end
|
||||
if MTH_ZH_ToysAdjust and MTH_ZH_ToysAdjust.SetScript then
|
||||
if MTH_ZH_ToysAdjust then
|
||||
MTH_ZH_ToysAdjust:RegisterEvent("SPELLS_CHANGED")
|
||||
MTH_ZH_ToysAdjust:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
MTH_ZH_ToysAdjust:SetScript("OnEvent", MTH_ZH_ToysAdjust_OnEvent)
|
||||
end
|
||||
local displayCount = zButtonToys.found or 0
|
||||
if displayCount < 0 then
|
||||
displayCount = 0
|
||||
end
|
||||
ZSpellButton_SetSize(zButtonToys, saved["parent"]["size"])
|
||||
ZSpellButton_SetSize(zButtonToys, saved["children"]["size"], 1)
|
||||
ZSpellButton_SetExpandDirection(zButtonToys, saved["firstbutton"])
|
||||
ZSpellButton_ArrangeChildren(zButtonToys, saved["rows"],
|
||||
displayCount, saved["horizontal"],
|
||||
saved["vertical"])
|
||||
if not (type(MTH_ZBar_ApplyToButton) == "function" and MTH_ZBar_ApplyToButton(zButtonToys, displayCount)) then
|
||||
ZSpellButton_SetSize(zButtonToys, saved["parent"]["size"])
|
||||
ZSpellButton_SetSize(zButtonToys, saved["children"]["size"], 1)
|
||||
ZSpellButton_SetExpandDirection(zButtonToys, saved["firstbutton"])
|
||||
ZSpellButton_ArrangeChildren(zButtonToys, saved["rows"],
|
||||
displayCount, saved["horizontal"],
|
||||
saved["vertical"])
|
||||
end
|
||||
end
|
||||
|
||||
function zButtonToys_Reset()
|
||||
|
||||
@@ -5,7 +5,7 @@ end
|
||||
local root = zButtonTrack_GetRoot()
|
||||
if not root["zButtonTrack"] then
|
||||
root["zButtonTrack"] = {}
|
||||
root["zButtonTrack"]["spells"] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
|
||||
root["zButtonTrack"]["spells"] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}
|
||||
root["zButtonTrack"]["rows"] = 1
|
||||
root["zButtonTrack"]["horizontal"] = nil
|
||||
root["zButtonTrack"]["vertical"] = nil
|
||||
@@ -32,7 +32,9 @@ ZHunterMod_Track_Spells = {
|
||||
ZHUNTER_TRACK_GIANTS,
|
||||
ZHUNTER_TRACK_MINERALS,
|
||||
ZHUNTER_TRACK_HERBS,
|
||||
ZHUNTER_TRACK_TREASURE
|
||||
ZHUNTER_TRACK_TREASURE,
|
||||
ZHUNTER_TRACK_TREES,
|
||||
ZHUNTER_TRACK_FISH
|
||||
}
|
||||
|
||||
local ZHUNTER_TRACK_MAX = table.getn(ZHunterMod_Track_Spells)
|
||||
@@ -92,6 +94,32 @@ local function zButtonTrack_FindSpellIdByTrackingName(trackingName)
|
||||
return nil
|
||||
end
|
||||
|
||||
local function zButtonTrack_ScanBuffsForTrackingSpell()
|
||||
if not zButtonTrack or not zButtonTrack.count then
|
||||
return nil, nil
|
||||
end
|
||||
local childTextures = {}
|
||||
for i = 1, zButtonTrack.count do
|
||||
local child = getglobal("zButtonTrack" .. i)
|
||||
if child and child.id then
|
||||
local tex = GetSpellTexture and GetSpellTexture(child.id, "spell") or nil
|
||||
if tex then
|
||||
childTextures[tex] = child.id
|
||||
end
|
||||
end
|
||||
end
|
||||
local buffIndex = 1
|
||||
local buff = UnitBuff("player", buffIndex)
|
||||
while buff do
|
||||
if childTextures[buff] then
|
||||
return childTextures[buff], buff
|
||||
end
|
||||
buffIndex = buffIndex + 1
|
||||
buff = UnitBuff("player", buffIndex)
|
||||
end
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
local function zButtonTrack_GetActiveTrackingSpellId()
|
||||
local activeName = nil
|
||||
local activeTexture = nil
|
||||
@@ -111,10 +139,26 @@ local function zButtonTrack_GetActiveTrackingSpellId()
|
||||
return byName, activeTexture
|
||||
end
|
||||
if activeTexture then
|
||||
return zButtonTrack_FindSpellIdByTrackingTexture(activeTexture), activeTexture
|
||||
local byTex = zButtonTrack_FindSpellIdByTrackingTexture(activeTexture)
|
||||
if byTex then
|
||||
return byTex, activeTexture
|
||||
end
|
||||
end
|
||||
local buff = GetTrackingTexture and GetTrackingTexture() or nil
|
||||
return zButtonTrack_FindSpellIdByTrackingTexture(buff), buff
|
||||
-- Fallback: scan player buffs for tracking spell icon textures
|
||||
local buffId, buffTex = zButtonTrack_ScanBuffsForTrackingSpell()
|
||||
if buffId then
|
||||
return buffId, buffTex
|
||||
end
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
local function zButtonTrack_SmartParentId(activeChildId)
|
||||
local child1 = getglobal("zButtonTrack1")
|
||||
local child2 = getglobal("zButtonTrack2")
|
||||
if not child1 or not child1.id then return nil end
|
||||
if not child2 or not child2.id then return child1.id end
|
||||
if activeChildId == child1.id then return child2.id end
|
||||
return child1.id
|
||||
end
|
||||
|
||||
local function zButtonTrack_AfterClick(button)
|
||||
@@ -122,9 +166,19 @@ local function zButtonTrack_AfterClick(button)
|
||||
return
|
||||
end
|
||||
if button and button.id then
|
||||
zButtonTrack.id = button.id
|
||||
ZSpellButton_UpdateButton(zButtonTrack)
|
||||
ZSpellButton_UpdateCooldown(zButtonTrack)
|
||||
local saved = zButtonTrack_GetSaved()
|
||||
local newId
|
||||
if saved["parent"]["smart"] ~= false then
|
||||
newId = zButtonTrack_SmartParentId(button.id)
|
||||
else
|
||||
local firstChild = getglobal("zButtonTrack1")
|
||||
newId = firstChild and firstChild.id
|
||||
end
|
||||
if newId and newId ~= zButtonTrack.id then
|
||||
zButtonTrack.id = newId
|
||||
ZSpellButton_UpdateButton(zButtonTrack)
|
||||
ZSpellButton_UpdateCooldown(zButtonTrack)
|
||||
end
|
||||
zButtonTrack_SyncActiveChild(button.id)
|
||||
if GetSpellTexture then
|
||||
zButtonTrack_LastTrackingTexture = GetSpellTexture(button.id, "spell") or zButtonTrack_LastTrackingTexture
|
||||
@@ -155,34 +209,36 @@ local function zButtonTrack_RefreshTrackingState()
|
||||
if not zButtonTrack or not zButtonTrack.count then
|
||||
return
|
||||
end
|
||||
local newId, trackingTexture = zButtonTrack_GetActiveTrackingSpellId()
|
||||
local buff = trackingTexture or (GetTrackingTexture and GetTrackingTexture() or nil)
|
||||
if not newId and not buff then
|
||||
if zButtonTrack.id then
|
||||
zButtonTrack_SyncActiveChild(zButtonTrack.id)
|
||||
else
|
||||
zButtonTrack_LastTrackingTexture = nil
|
||||
zButtonTrack_SyncActiveChild(nil)
|
||||
local saved = zButtonTrack_GetSaved()
|
||||
local firstChild = getglobal("zButtonTrack1")
|
||||
|
||||
-- Smart OFF: always show first child
|
||||
if saved["parent"]["smart"] == false then
|
||||
if firstChild and firstChild.id and zButtonTrack.id ~= firstChild.id then
|
||||
zButtonTrack.id = firstChild.id
|
||||
ZSpellButton_UpdateButton(zButtonTrack)
|
||||
ZSpellButton_UpdateCooldown(zButtonTrack)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- Smart ON: detect which tracking is active, show the OTHER one
|
||||
local activeId = zButtonTrack_GetActiveTrackingSpellId()
|
||||
local newId = zButtonTrack_SmartParentId(activeId)
|
||||
if not newId then
|
||||
newId = zButtonTrack.id or nil
|
||||
end
|
||||
if not newId then
|
||||
return
|
||||
end
|
||||
if zButtonTrack_NormalizeTexture(zButtonTrack_LastTrackingTexture) == zButtonTrack_NormalizeTexture(buff) and zButtonTrack.id == newId then
|
||||
zButtonTrack_SyncActiveChild(newId)
|
||||
return
|
||||
newId = firstChild and firstChild.id
|
||||
end
|
||||
if not newId then return end
|
||||
|
||||
-- Sync checked state on children
|
||||
zButtonTrack_SyncActiveChild(activeId)
|
||||
|
||||
if zButtonTrack.id == newId then return end
|
||||
|
||||
local isOwned = GameTooltip and GameTooltip.IsOwned and GameTooltip:IsOwned(zButtonTrack)
|
||||
zButtonTrack_LastTrackingTexture = buff
|
||||
zButtonTrack.id = newId
|
||||
ZSpellButton_UpdateButton(zButtonTrack)
|
||||
ZSpellButton_UpdateCooldown(zButtonTrack)
|
||||
zButtonTrack_SyncActiveChild(newId)
|
||||
if isOwned then
|
||||
ZSpellButtonParent_OnEnter(zButtonTrack)
|
||||
end
|
||||
@@ -220,6 +276,9 @@ local function zButtonTrack_EnsureConfig()
|
||||
if saved["parent"]["circle"] == nil then
|
||||
saved["parent"]["circle"] = 1
|
||||
end
|
||||
if saved["parent"]["smart"] == nil then
|
||||
saved["parent"]["smart"] = true
|
||||
end
|
||||
if not saved["children"] then
|
||||
saved["children"] = {}
|
||||
end
|
||||
@@ -294,7 +353,7 @@ function zButtonTrack_CreateButtons()
|
||||
for i=1, table.getn(ZHunterMod_Track_Spells) do
|
||||
if not tonumber(saved["spells"][i]) then
|
||||
info = ZHunterMod_Track_Spells
|
||||
saved["spells"] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
|
||||
saved["spells"] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}
|
||||
break
|
||||
end
|
||||
local spellIndex = saved["spells"][i]
|
||||
@@ -311,7 +370,8 @@ function zButtonTrack_SetupSizeAndPosition()
|
||||
zButtonTrack_EnsureConfig()
|
||||
local saved = zButtonTrack_GetSaved()
|
||||
if saved["enabled"] == false or saved["enabled"] == 0 then
|
||||
if MTH_ZH_TrackAdjust and MTH_ZH_TrackAdjust.SetScript then
|
||||
if MTH_ZH_TrackAdjust then
|
||||
MTH_ZH_TrackAdjust:UnregisterAllEvents()
|
||||
MTH_ZH_TrackAdjust:SetScript("OnEvent", nil)
|
||||
end
|
||||
if zButtonTrack and zButtonTrack.Hide then
|
||||
@@ -319,19 +379,27 @@ function zButtonTrack_SetupSizeAndPosition()
|
||||
end
|
||||
return
|
||||
end
|
||||
if MTH_ZH_TrackAdjust and MTH_ZH_TrackAdjust.SetScript then
|
||||
if MTH_ZH_TrackAdjust then
|
||||
MTH_ZH_TrackAdjust:RegisterEvent("MINIMAP_UPDATE_TRACKING")
|
||||
MTH_ZH_TrackAdjust:RegisterEvent("PLAYER_AURAS_CHANGED")
|
||||
MTH_ZH_TrackAdjust:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
MTH_ZH_TrackAdjust:RegisterEvent("SPELLS_CHANGED")
|
||||
MTH_ZH_TrackAdjust:RegisterEvent("CHARACTER_POINTS_CHANGED")
|
||||
MTH_ZH_TrackAdjust:RegisterEvent("LEARNED_SPELL_IN_TAB")
|
||||
MTH_ZH_TrackAdjust:SetScript("OnEvent", MTH_ZH_TrackAdjust_OnEvent)
|
||||
end
|
||||
local displayCount = zButtonTrack.found or ZHUNTER_TRACK_MAX
|
||||
if displayCount < 0 then
|
||||
displayCount = 0
|
||||
end
|
||||
ZSpellButton_SetSize(zButtonTrack, saved["parent"]["size"])
|
||||
ZSpellButton_SetSize(zButtonTrack, saved["children"]["size"], 1)
|
||||
ZSpellButton_SetExpandDirection(zButtonTrack, saved["firstbutton"])
|
||||
ZSpellButton_ArrangeChildren(zButtonTrack, saved["rows"],
|
||||
displayCount, saved["horizontal"],
|
||||
saved["vertical"])
|
||||
if not (type(MTH_ZBar_ApplyToButton) == "function" and MTH_ZBar_ApplyToButton(zButtonTrack, displayCount)) then
|
||||
ZSpellButton_SetSize(zButtonTrack, saved["parent"]["size"])
|
||||
ZSpellButton_SetSize(zButtonTrack, saved["children"]["size"], 1)
|
||||
ZSpellButton_SetExpandDirection(zButtonTrack, saved["firstbutton"])
|
||||
ZSpellButton_ArrangeChildren(zButtonTrack, saved["rows"],
|
||||
displayCount, saved["horizontal"],
|
||||
saved["vertical"])
|
||||
end
|
||||
zButtonTrack_RefreshTrackingState()
|
||||
end
|
||||
|
||||
@@ -339,7 +407,7 @@ function zButtonTrack_Reset()
|
||||
local currentRoot = zButtonTrack_GetRoot()
|
||||
currentRoot["zButtonTrack"] = {}
|
||||
local saved = zButtonTrack_GetSaved()
|
||||
saved["spells"] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
|
||||
saved["spells"] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}
|
||||
saved["rows"] = 1
|
||||
saved["horizontal"] = nil
|
||||
saved["vertical"] = nil
|
||||
@@ -353,7 +421,7 @@ function zButtonTrack_Reset()
|
||||
saved["children"] = {}
|
||||
saved["children"]["size"] = 36
|
||||
saved["children"]["hideonclick"] = 1
|
||||
saved["visible"] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
|
||||
saved["visible"] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
|
||||
zButtonTrack_EnsureConfig()
|
||||
end
|
||||
|
||||
@@ -365,7 +433,7 @@ function MTH_ZH_TrackAdjust_OnEvent()
|
||||
zButtonTrack_CreateButtons()
|
||||
zButtonTrack_SetupSizeAndPosition()
|
||||
end
|
||||
if event == "MINIMAP_UPDATE_TRACKING" or event == "PLAYER_ENTERING_WORLD"
|
||||
if event == "MINIMAP_UPDATE_TRACKING" or event == "PLAYER_AURAS_CHANGED" or event == "PLAYER_ENTERING_WORLD"
|
||||
or event == "SPELLS_CHANGED" or event == "CHARACTER_POINTS_CHANGED" or event == "LEARNED_SPELL_IN_TAB" then
|
||||
zButtonTrack_RefreshTrackingState()
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user