First version

This commit is contained in:
DuvelCorp
2026-02-24 23:00:59 +01:00
commit ee8328e903
125 changed files with 140652 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
# Ignore all hidden files and hidden directories
.*
**/.*
# Keep this ignore file tracked
!.gitignore
+121
View File
@@ -0,0 +1,121 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<!-- FeedOMatic Bindings -->
<Binding name="FEEDOMATIC" header="FEEDOMATIC">
FOM_Feed();
</Binding>
<!-- zhunter Aspect Bindings -->
<Binding name="ZAspect Parent" header="ZAspectHeader">
zButtonAspect_KeyBinding()
</Binding>
<Binding name="ZAspect 1" header="ZAspectHeader">
zButtonAspect_KeyBinding(1)
</Binding>
<Binding name="ZAspect 2" header="ZAspectHeader">
zButtonAspect_KeyBinding(2)
</Binding>
<Binding name="ZAspect 3" header="ZAspectHeader">
zButtonAspect_KeyBinding(3)
</Binding>
<Binding name="ZAspect 4" header="ZAspectHeader">
zButtonAspect_KeyBinding(4)
</Binding>
<Binding name="ZAspect 5" header="ZAspectHeader">
zButtonAspect_KeyBinding(5)
</Binding>
<Binding name="ZAspect 6" header="ZAspectHeader">
zButtonAspect_KeyBinding(6)
</Binding>
<!-- zhunter Track Bindings -->
<Binding name="ZTrack Parent" header="ZTrackHeader">
zButtonTrack_KeyBinding()
</Binding>
<Binding name="ZTrack 1" header="ZTrackHeader">
zButtonTrack_KeyBinding(1)
</Binding>
<Binding name="ZTrack 2" header="ZTrackHeader">
zButtonTrack_KeyBinding(2)
</Binding>
<Binding name="ZTrack 3" header="ZTrackHeader">
zButtonTrack_KeyBinding(3)
</Binding>
<Binding name="ZTrack 4" header="ZTrackHeader">
zButtonTrack_KeyBinding(4)
</Binding>
<Binding name="ZTrack 5" header="ZTrackHeader">
zButtonTrack_KeyBinding(5)
</Binding>
<Binding name="ZTrack 6" header="ZTrackHeader">
zButtonTrack_KeyBinding(6)
</Binding>
<!-- zhunter Trap Bindings -->
<Binding name="ZTrap Parent" header="ZTrapHeader">
zButtonTrap_KeyBinding()
</Binding>
<Binding name="ZTrap 1" header="ZTrapHeader">
zButtonTrap_KeyBinding(1)
</Binding>
<Binding name="ZTrap 2" header="ZTrapHeader">
zButtonTrap_KeyBinding(2)
</Binding>
<Binding name="ZTrap 3" header="ZTrapHeader">
zButtonTrap_KeyBinding(3)
</Binding>
<Binding name="ZTrap 4" header="ZTrapHeader">
zButtonTrap_KeyBinding(4)
</Binding>
<Binding name="ZTrap 5" header="ZTrapHeader">
zButtonTrap_KeyBinding(5)
</Binding>
<Binding name="ZTrap 6" header="ZTrapHeader">
zButtonTrap_KeyBinding(6)
</Binding>
<!-- zhunter Ammo Bindings -->
<Binding name="ZAmmo Parent" header="ZAmmoHeader">
zButtonAmmo_KeyBinding()
</Binding>
<Binding name="ZAmmo 1" header="ZAmmoHeader">
zButtonAmmo_KeyBinding(1)
</Binding>
<Binding name="ZAmmo 2" header="ZAmmoHeader">
zButtonAmmo_KeyBinding(2)
</Binding>
<Binding name="ZAmmo 3" header="ZAmmoHeader">
zButtonAmmo_KeyBinding(3)
</Binding>
<Binding name="ZAmmo 4" header="ZAmmoHeader">
zButtonAmmo_KeyBinding(4)
</Binding>
<Binding name="ZAmmo 5" header="ZAmmoHeader">
zButtonAmmo_KeyBinding(5)
</Binding>
<Binding name="ZAmmo 6" header="ZAmmoHeader">
zButtonAmmo_KeyBinding(6)
</Binding>
<!-- zhunter Pet Bindings -->
<Binding name="ZPet Parent" header="ZPetHeader">
zButtonPet_KeyBinding()
</Binding>
<Binding name="ZPet 1" header="ZPetHeader">
zButtonPet_KeyBinding(1)
</Binding>
<Binding name="ZPet 2" header="ZPetHeader">
zButtonPet_KeyBinding(2)
</Binding>
<Binding name="ZPet 3" header="ZPetHeader">
zButtonPet_KeyBinding(3)
</Binding>
<Binding name="ZPet 4" header="ZPetHeader">
zButtonPet_KeyBinding(4)
</Binding>
<Binding name="ZPet 5" header="ZPetHeader">
zButtonPet_KeyBinding(5)
</Binding>
<Binding name="ZPet 6" header="ZPetHeader">
zButtonPet_KeyBinding(6)
</Binding>
</Ui>
+10
View File
@@ -0,0 +1,10 @@
------------------------------------------------------
-- MetaHunt: Hunter's Toolkit Addon
-- A unified addon merging FeedOMatic, Tooltips, and zhunter
-- Modern modular architecture with legacy addon support
------------------------------------------------------
-- Initialize beast database after all files loaded
if MTH_DS_OnLoad then
MTH_DS_OnLoad()
end
+45
View File
@@ -0,0 +1,45 @@
## Interface: 11200
## Version: 1.0.3
## Title: MetaHunt - |cff00ff00Hunter
## Author: Metasploit
## 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
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
data\ds-beasts-extra.lua
data\ds-beasts-html-enrichment.lua
data\ds-vendors.lua
data\ds-vendors-extra.lua
data\ds-items.lua
data\ds-items-extra.lua
data\ds-items-atlas-sources.lua
data\ds-zones.lua
data\ds-zones-fallback.lua
data\ds-minimap-sizes.lua
data\ds-unit-coords.lua
data\ds-creature-coords.lua
data\ds-beasts-families.lua
data\ds-pet-spells.lua
data\ds-pet-spells-trainer.lua
data\ds-racial.lua
data\init.lua
## Load main addon entry point
MetaHunt.lua
## Load modules (each addon is now a self-contained module)
init\modules.xml
+52
View File
@@ -0,0 +1,52 @@
# MetaHunt
MetaHunt is a Turtle WoW Hunter toolkit.
It combines never seen pet-centric features and revives old broken hunter add-ons.
## Twow Data
Metahunt ships with larges up-to-date datastores for all data a hunter needs :
- All beasts, their location, 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 Ammo vendors
## 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.
# Tooltips
Get enhanced tooltips on beasts, ammo vendors, and your pet.
# zBouttons
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
- zTrack: All your trackings in one collapsible bar
- zAspect: All your trackings in one collapsible bar
- But also zMounts, zToys and zCompanions.
# Feed-O-Matic
Now compatible with Turtle, feed your pet with one key bind, it will always pickup the best food for your pet.
# Chronometer
The good old Chronometer addon, purged of non-necessary things and reconfigured for a pure hunter use.
# Autobuy
Define your own simple rules for auto buy ammo and pet food.
# The book of Hunters
- Browse the Metahunt datastores of NPCs 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
- Keeep an history of all your pets after you abbandon them
+147
View File
@@ -0,0 +1,147 @@
local function AntiDaze_Print(msg)
if MTH and MTH.Print then
MTH:Print(msg)
elseif DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.AddMessage then
DEFAULT_CHAT_FRAME:AddMessage(tostring(msg))
end
end
local function AntiDaze_GetSaved()
if type(MTH_CharSavedVariables) ~= "table" then
MTH_CharSavedVariables = {}
end
if type(MTH_CharSavedVariables.antiDaze) ~= "table" then
MTH_CharSavedVariables.antiDaze = {}
end
return MTH_CharSavedVariables.antiDaze
end
local function AntiDaze_IsEnabled()
local saved = AntiDaze_GetSaved()
return saved["enabled"] and true or false
end
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")
end
local function AntiDaze_UnregisterCombatEvents()
if not AntiDazeFrame then
return
end
AntiDazeFrame:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE")
AntiDazeFrame:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE")
AntiDazeFrame:UnregisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
AntiDazeFrame:UnregisterEvent("CHAT_MSG_SPELL_PARTY_DAMAGE")
end
function AntiDaze_SetEnabled(enabled, silent)
local saved = AntiDaze_GetSaved()
if enabled then
saved["enabled"] = 1
AntiDaze_RegisterCombatEvents()
if not silent then
AntiDaze_Print("AntiDaze enabled.")
end
else
saved["enabled"] = nil
AntiDaze_UnregisterCombatEvents()
if not silent then
AntiDaze_Print("AntiDaze disabled.")
end
end
end
function AntiDaze_GetEnabled()
return AntiDaze_IsEnabled()
end
local function AntiDaze_ResolvePlayerFromMessage(msg)
local afflictedPattern = _G["ZHUNTER_AFFLICTED_DAZED"] or "(.+) (.+) afflicted by Dazed"
local _, _, player = string.find(msg or "", afflictedPattern)
if player then
return player
end
local lowerMsg = string.lower(msg or "")
if string.find(lowerMsg, "dazed", 1, true) or string.find(lowerMsg, "daze", 1, true) then
local playerName = UnitName("player")
if string.find(msg or "", "You", 1, true) or (playerName and string.find(msg or "", playerName, 1, true)) then
return _G["ZHUNTER_YOU"] or "You"
end
for i = 1, GetNumPartyMembers() do
local partyName = UnitName("party" .. i)
if partyName and string.find(msg or "", partyName, 1, true) then
return partyName
end
end
end
return nil
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"
for i = 0, 32 do
if GetPlayerBuff(i, "HELPFUL") < 0 then
break
end
AntiDazeTooltip:SetOwner(UIParent, "ANCHOR_NONE")
AntiDazeTooltip:SetPlayerBuff(i, "HELPFUL")
local text = AntiDazeTooltipTextLeft1 and AntiDazeTooltipTextLeft1:GetText()
if text == packName or (isSelfDazed and text == cheetahName) then
CancelPlayerBuff(i)
return 1
end
end
return nil
end
local function AntiDaze_HandleSlash()
AntiDaze_SetEnabled(not AntiDaze_IsEnabled())
end
AntiDazeTooltip = CreateFrame("GameTooltip", "AntiDazeTooltip", nil, "GameTooltipTemplate")
AntiDazeFrame = CreateFrame("Frame", "AntiDazeFrame")
if AntiDazeFrame then
AntiDazeFrame:RegisterEvent("VARIABLES_LOADED")
AntiDazeFrame:SetScript("OnEvent", function()
if event == "VARIABLES_LOADED" then
if AntiDaze_IsEnabled() then
AntiDaze_RegisterCombatEvents()
else
AntiDaze_UnregisterCombatEvents()
end
return
end
local player = AntiDaze_ResolvePlayerFromMessage(arg1)
if player then
local you = _G["ZHUNTER_YOU"] or "You"
local group = {}
group[you] = 1
for i = 1, GetNumPartyMembers() do
group[UnitName("party" .. i)] = 1
end
if group[player] then
AntiDaze_CancelRelevantAspect(player == you)
end
end
end)
end
SLASH_ANTIDAZE1 = "/antidaze"
SlashCmdList["ANTIDAZE"] = AntiDaze_HandleSlash
SLASH_ZANTIDAZE1 = "/zantidaze"
SlashCmdList["ZANTIDAZE"] = AntiDaze_HandleSlash
+189
View File
@@ -0,0 +1,189 @@
local function AutoStrip_Print(msg)
if MTH and MTH.Print then
MTH:Print(msg)
elseif DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.AddMessage then
DEFAULT_CHAT_FRAME:AddMessage(tostring(msg))
end
end
function AutoStrip_GetSaved()
if type(MTH_CharSavedVariables) ~= "table" then
MTH_CharSavedVariables = {}
end
if type(MTH_CharSavedVariables.autoStrip) ~= "table" then
MTH_CharSavedVariables.autoStrip = {}
end
return MTH_CharSavedVariables.autoStrip
end
local AUTO_STRIP_ORDER = {16, 17, 18, 5, 7, 1, 3, 10, 8, 6, 9}
AutoStrip_On = nil
AutoStrip_WasInCombat = nil
AutoStrip_Frame = nil
function AutoStrip_SetDisplayIcon()
local icon = getglobal("AutoStripDisplayIcon") or getglobal("AutoStripDisplayIconTexture")
if icon then
icon:SetTexture("Interface\\Icons\\Ability_Creature_Cursed_02")
icon:Show()
end
end
function AutoStrip_GetEnabled()
return AutoStrip_On and true or false
end
function AutoStrip_SetAutoStripToggle(enabled, silent)
local saved = AutoStrip_GetSaved()
if enabled then
saved["autostrip"] = 1
AutoStrip_On = 1
if AutoStripDisplay and AutoStripDisplay:IsVisible() and AutoStripDisplayAutoCast then
AutoStripDisplayAutoCast:Show()
end
if not silent then
AutoStrip_Print("Auto-Strip enabled.")
end
else
saved["autostrip"] = nil
AutoStrip_On = nil
if AutoStripDisplayAutoCast then
AutoStripDisplayAutoCast:Hide()
end
if not silent then
AutoStrip_Print("Auto-Strip disabled.")
end
end
end
function AutoStrip_SetDisplayToggle(enabled, silent)
local saved = AutoStrip_GetSaved()
if enabled then
saved["display"] = 1
if AutoStripDisplay then
AutoStripDisplay:Show()
AutoStrip_SetDisplayIcon()
if AutoStrip_On and AutoStripDisplayAutoCast then
AutoStripDisplayAutoCast:Show()
end
end
if not silent then
AutoStrip_Print("AutoStrip button is now visible.")
end
else
saved["display"] = nil
if AutoStripDisplay then
AutoStripDisplay:Hide()
end
if not silent then
AutoStrip_Print("AutoStrip button is now hidden.")
end
end
end
function AutoStrip_UnequipAll(weaponsOnly)
AutoStrip_On = nil
if AutoStripDisplayAutoCast then
AutoStripDisplayAutoCast:Hide()
end
local hasEmptySlot = nil
for bag = 0, 4 do
for slot = 1, GetContainerNumSlots(bag) do
if not GetContainerItemLink(bag, slot) then
hasEmptySlot = 1
break
end
end
if hasEmptySlot then
break
end
end
if not hasEmptySlot then
AutoStrip_Print("You need at least one empty bag slot to auto-strip.")
return
end
local startIndex = 1
local finishIndex = weaponsOnly and 3 or table.getn(AUTO_STRIP_ORDER)
for bag = 0, 4 do
for slot = 1, GetContainerNumSlots(bag) do
if not GetContainerItemLink(bag, slot) then
for index = startIndex, finishIndex do
if GetInventoryItemLink("player", AUTO_STRIP_ORDER[index]) then
PickupInventoryItem(AUTO_STRIP_ORDER[index])
PickupContainerItem(bag, slot)
startIndex = index + 1
break
end
end
end
end
end
end
local function AutoStrip_OnEvent()
if event == "VARIABLES_LOADED" then
local saved = AutoStrip_GetSaved()
AutoStrip_WasInCombat = UnitAffectingCombat("player") and 1 or nil
AutoStrip_SetDisplayToggle(saved["display"] and true or false, 1)
AutoStrip_SetAutoStripToggle(saved["autostrip"] and true or false, 1)
return
end
if event == "PLAYER_ENTERING_WORLD" then
AutoStrip_WasInCombat = UnitAffectingCombat("player") and 1 or nil
return
end
if event == "PLAYER_REGEN_DISABLED" then
AutoStrip_WasInCombat = 1
return
end
if event == "PLAYER_REGEN_ENABLED" then
if AutoStrip_On and AutoStrip_WasInCombat then
AutoStrip_UnequipAll()
end
AutoStrip_WasInCombat = nil
end
end
local function AutoStrip_HandleSlash(msg)
local saved = AutoStrip_GetSaved()
if msg == "display" then
AutoStrip_SetDisplayToggle(not (saved["display"] and true or false))
elseif msg == "toggle" then
local enableAutoStrip = not AutoStrip_GetEnabled()
if enableAutoStrip and not saved["display"] then
AutoStrip_SetDisplayToggle(true, 1)
end
AutoStrip_SetAutoStripToggle(enableAutoStrip, 1)
if enableAutoStrip then
AutoStrip_Print("You will strip when you leave combat.")
else
AutoStrip_Print("You will no longer strip when you leave combat.")
end
else
AutoStrip_Print("Possible Commands: \"display\", \"toggle\"")
end
end
AutoStrip_Frame = CreateFrame("Frame", "AutoStripFrame")
if AutoStrip_Frame then
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:SetScript("OnEvent", AutoStrip_OnEvent)
end
if AutoStripDisplay then
AutoStrip_SetDisplayIcon()
end
SLASH_AUTOSTRIP1 = "/autostrip"
SlashCmdList["AUTOSTRIP"] = AutoStrip_HandleSlash
SLASH_ZSTRIP1 = "/zstrip"
SlashCmdList["ZSTRIP"] = AutoStrip_HandleSlash
+97
View File
@@ -0,0 +1,97 @@
<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">
<Button name="AutoStripDisplay" parent="UIParent" enableMouse="true" movable="true" inherits="ActionButtonTemplate" frameStrata="DIALOG" hidden="true">
<Size>
<AbsDimension x="22.5" y="22.5"/>
</Size>
<Anchors>
<Anchor point="CENTER">
<Offset>
<AbsDimension x="0" y="0"/>
</Offset>
</Anchor>
</Anchors>
<Layers>
<Layer level="OVERLAY">
<Texture name="$parentAutoCastable" file="Interface\Buttons\UI-AutoCastableOverlay">
<Size>
<AbsDimension x="43.5" y="43.5"/>
</Size>
<Anchors>
<Anchor point="CENTER">
<Offset>
<AbsDimension x="0" y="0"/>
</Offset>
</Anchor>
</Anchors>
</Texture>
</Layer>
</Layers>
<Frames>
<Model name="$parentAutoCast" file="Interface\Buttons\UI-AutoCastButton.mdx" scale="0.9" hidden="true" setAllPoints="true">
<Scripts>
<OnLoad>
this:SetSequence(0);
this:SetSequenceTime(0, 0);
</OnLoad>
</Scripts>
</Model>
</Frames>
<Scripts>
<OnLoad>
this:RegisterForDrag("LeftButton");
this:RegisterForClicks("LeftButtonUp", "RightButtonUp");
</OnLoad>
<OnClick>
if arg1 == "LeftButton" then
if not UnitAffectingCombat("player") then
AutoStrip_UnequipAll()
else
AutoStrip_UnequipAll(1)
end
elseif arg1 == "RightButton" then
AutoStrip_SetAutoStripToggle(not AutoStrip_GetEnabled())
end
</OnClick>
<OnEnter>
GameTooltip:SetOwner(this, "ANCHOR_TOPLEFT");
GameTooltip:SetText("AutoStrip", 1, 1, 1)
GameTooltip:AddLine("Left-Click to strip now.")
GameTooltip:AddLine("Right-Click to toggle auto-strip")
GameTooltip:AddLine("Alt-Drag to move")
GameTooltip:Show()
</OnEnter>
<OnLeave>
GameTooltip:Hide()
</OnLeave>
<OnMouseUp>
if ( this.isMoving ) then
this:StopMovingOrSizing()
this.isMoving = false
end
</OnMouseUp>
<OnDragStart>
if IsAltKeyDown() then
this:StartMoving()
this.isMoving = true
end
</OnDragStart>
<OnDragStop>
this:StopMovingOrSizing()
this.isMoving = false
</OnDragStop>
</Scripts>
<NormalTexture name="$parentNormalTexture" file="Interface\Buttons\UI-Quickslot2">
<Size>
<AbsDimension x="40.5" y="40.5"/>
</Size>
<Anchors>
<Anchor point="CENTER">
<Offset>
<AbsDimension x="0" y="-1"/>
</Offset>
</Anchor>
</Anchors>
</NormalTexture>
</Button>
</Ui>
+58
View File
@@ -0,0 +1,58 @@
------------------------------------------------------
-- MetaHunt Configuration System
------------------------------------------------------
-- Global config management for modules
MTH.config = {}
local function MTH_ConfigEnsureStore(module_name)
if type(module_name) ~= "string" or module_name == "" then
module_name = "core"
end
local root = _G and _G.MTH_SavedVariables or MTH_SavedVariables
if type(root) ~= "table" then
if MTH and MTH.InitSavedVariables then
MTH:InitSavedVariables()
end
root = _G and _G.MTH_SavedVariables or MTH_SavedVariables
end
if type(root) ~= "table" then
root = { modules = {} }
if _G then
_G.MTH_SavedVariables = root
end
MTH_SavedVariables = root
end
if type(root.modules) ~= "table" then
root.modules = {}
end
if type(root.modules[module_name]) ~= "table" then
local legacy = root[module_name]
if type(legacy) ~= "table" then
legacy = {}
end
root.modules[module_name] = legacy
end
root[module_name] = root.modules[module_name]
MTH_SavedVariables = root
return root.modules[module_name]
end
function MTH:GetConfig(module_name, key, default)
local store = MTH_ConfigEnsureStore(module_name)
local value = store[key]
if value == nil then
return default
end
return value
end
function MTH:SetConfig(module_name, key, value)
local store = MTH_ConfigEnsureStore(module_name)
store[key] = value
end
+152
View File
@@ -0,0 +1,152 @@
MTH_CONST = MTH_CONST or {}
MTH_CONST.version = MTH_CONST.version or "1.0.3"
MTH_CONST.WEAPON_TYPES = {
BOWS = "Bows",
CROSSBOWS = "Crossbows",
GUNS = "Guns",
}
MTH_CONST.AMMO = {
BULLETS = {
LIGHT = "Light Shot",
CRAFTLIGHT = "Crafted Light Shot",
FLASH = "Flash Pellet",
HEAVY = "Heavy Shot",
PEBBLE = "Smooth Pebble",
CRAFTHEAVY = "Crafted Heavy Shot",
SOLID = "Solid Shot",
CRAFTSOLID = "Crafted Solid Shot",
EXPLODING = "Exploding Shot",
MITHRILSLUG = "Hi-impact Mithril Slugs",
ACCURATE = "Accurate Slugs",
GYROSHOT = "Mithril Gyro-Shot",
ICETHREADED = "Ice Threaded Bullet",
THORIUM = "Thorium Shells",
ROCKSHARD = "Rockshard Pellets",
CANNONBALL = "Miniature Cannon Balls",
},
ARROWS = {
ROUGH = "Rough Arrow",
SHARP = "Sharp Arrow",
RAZOR = "Razor Arrow",
FEATHER = "Feathered Arrow",
PRECISION = "Precision Arrow",
JAGGED = "Jagged Arrow",
ICETHREADED = "Ice Threaded Arrow",
THORIUM = "Thorium Headed Arrow",
DOOMSHOT = "Doomshot",
},
JUNKSHOTS = {
SERPENT = "Serpent Sting",
SCORPID = "Scorpid Sting",
VIPER = "Viper Sting",
ARCANE = "Arcane Shot",
CONCUSSIVE = "Concussive Shot",
DISTRACTING = "Distracting Shot",
BAITED = "Baited Shot",
TRANQ = "Tranquilizing Shot",
},
}
MTH_WEAPON_BOWS = MTH_CONST.WEAPON_TYPES.BOWS
MTH_WEAPON_CROSSBOWS = MTH_CONST.WEAPON_TYPES.CROSSBOWS
MTH_WEAPON_GUNS = MTH_CONST.WEAPON_TYPES.GUNS
BULLETS_LIGHT = MTH_CONST.AMMO.BULLETS.LIGHT
BULLETS_CRAFTLIGHT = MTH_CONST.AMMO.BULLETS.CRAFTLIGHT
BULLETS_FLASH = MTH_CONST.AMMO.BULLETS.FLASH
BULLETS_HEAVY = MTH_CONST.AMMO.BULLETS.HEAVY
BULLETS_PEBBLE = MTH_CONST.AMMO.BULLETS.PEBBLE
BULLETS_CRAFTHEAVY = MTH_CONST.AMMO.BULLETS.CRAFTHEAVY
BULLETS_SOLID = MTH_CONST.AMMO.BULLETS.SOLID
BULLETS_CRAFTSOLID = MTH_CONST.AMMO.BULLETS.CRAFTSOLID
BULLETS_EXPLODING = MTH_CONST.AMMO.BULLETS.EXPLODING
BULLETS_MITHRILSLUG = MTH_CONST.AMMO.BULLETS.MITHRILSLUG
BULLETS_ACCURATE = MTH_CONST.AMMO.BULLETS.ACCURATE
BULLETS_GYROSHOT = MTH_CONST.AMMO.BULLETS.GYROSHOT
BULLETS_ICETHREADED = MTH_CONST.AMMO.BULLETS.ICETHREADED
BULLETS_THORIUM = MTH_CONST.AMMO.BULLETS.THORIUM
BULLETS_ROCKSHARD = MTH_CONST.AMMO.BULLETS.ROCKSHARD
BULLETS_CANNONBALL = MTH_CONST.AMMO.BULLETS.CANNONBALL
ARROWS_ROUGH = MTH_CONST.AMMO.ARROWS.ROUGH
ARROWS_SHARP = MTH_CONST.AMMO.ARROWS.SHARP
ARROWS_RAZOR = MTH_CONST.AMMO.ARROWS.RAZOR
ARROWS_FEATHER = MTH_CONST.AMMO.ARROWS.FEATHER
ARROWS_PRECISION = MTH_CONST.AMMO.ARROWS.PRECISION
ARROWS_JAGGED = MTH_CONST.AMMO.ARROWS.JAGGED
ARROWS_ICETHREADED = MTH_CONST.AMMO.ARROWS.ICETHREADED
ARROWS_THORIUM = MTH_CONST.AMMO.ARROWS.THORIUM
ARROWS_DOOMSHOT = MTH_CONST.AMMO.ARROWS.DOOMSHOT
JUNKSHOT_SERPENT = MTH_CONST.AMMO.JUNKSHOTS.SERPENT
JUNKSHOT_SCORPID = MTH_CONST.AMMO.JUNKSHOTS.SCORPID
JUNKSHOT_VIPER = MTH_CONST.AMMO.JUNKSHOTS.VIPER
JUNKSHOT_ARCANE = MTH_CONST.AMMO.JUNKSHOTS.ARCANE
JUNKSHOT_CONCUSSIVE = MTH_CONST.AMMO.JUNKSHOTS.CONCUSSIVE
JUNKSHOT_DISTRACTING = MTH_CONST.AMMO.JUNKSHOTS.DISTRACTING
MTH_AMMO_BULLETS = {
BULLETS_LIGHT,
BULLETS_CRAFTLIGHT,
BULLETS_FLASH,
BULLETS_HEAVY,
BULLETS_PEBBLE,
BULLETS_CRAFTHEAVY,
BULLETS_SOLID,
BULLETS_CRAFTSOLID,
BULLETS_EXPLODING,
BULLETS_MITHRILSLUG,
BULLETS_ACCURATE,
BULLETS_GYROSHOT,
BULLETS_ICETHREADED,
BULLETS_THORIUM,
BULLETS_ROCKSHARD,
BULLETS_CANNONBALL,
}
MTH_AMMO_ARROWS = {
ARROWS_ROUGH,
ARROWS_SHARP,
ARROWS_RAZOR,
ARROWS_FEATHER,
ARROWS_PRECISION,
ARROWS_JAGGED,
ARROWS_ICETHREADED,
ARROWS_THORIUM,
ARROWS_DOOMSHOT,
}
MTH_AMMO_JUNKSHOTS = {
JUNKSHOT_SERPENT,
JUNKSHOT_SCORPID,
JUNKSHOT_VIPER,
JUNKSHOT_ARCANE,
JUNKSHOT_CONCUSSIVE,
JUNKSHOT_DISTRACTING,
}
MTH_AMMO_BULLET_RANK = {}
for i = 1, table.getn(MTH_AMMO_BULLETS) do
local ammoName = MTH_AMMO_BULLETS[i]
if ammoName then
MTH_AMMO_BULLET_RANK[ammoName] = i
end
end
MTH_AMMO_ARROW_RANK = {}
for i = 1, table.getn(MTH_AMMO_ARROWS) do
local ammoName = MTH_AMMO_ARROWS[i]
if ammoName then
MTH_AMMO_ARROW_RANK[ammoName] = i
end
end
MTH_AMMO_JUNKSHOT_SET = {}
for i = 1, table.getn(MTH_AMMO_JUNKSHOTS) do
local spellName = MTH_AMMO_JUNKSHOTS[i]
if spellName then
MTH_AMMO_JUNKSHOT_SET[spellName] = 1
end
end
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+912
View File
@@ -0,0 +1,912 @@
if not MTH then
error("MetaHunt core framework missing: api/core-framework.lua must load before api/core-scan-beasttraining.lua")
end
local function MTH_PT_SafeLower(text)
if text == nil then return "" end
return string.lower(tostring(text))
end
local function MTH_PT_GetGlobal(name)
if type(getglobal) == "function" then
return getglobal(name)
end
if _G then
return _G[name]
end
return nil
end
local MTH_PT_ApplyScan
local MTH_PT_AbilityHasPositiveRanks
local MTH_PT_PromoteUnrankedKnownAliases
local MTH_PT_VERBOSE_SCAN_LOGS = false
local function MTH_PT_GetStore()
if not MTH_CharSavedVariables then MTH_CharSavedVariables = {} end
if not MTH_CharSavedVariables.trainScan and MTH_CharSavedVariables.petTraining then
MTH_CharSavedVariables.trainScan = MTH_CharSavedVariables.petTraining
end
if not MTH_CharSavedVariables.trainScan then
MTH_CharSavedVariables.trainScan = {
spellMap = {},
hunterKnownMap = {},
lastScan = 0,
lastPrompt = 0,
hasCompletedPetScan = nil,
}
end
if not MTH_CharSavedVariables.trainScan.spellMap then
MTH_CharSavedVariables.trainScan.spellMap = {}
end
if not MTH_CharSavedVariables.trainScan.hunterKnownMap then
MTH_CharSavedVariables.trainScan.hunterKnownMap = {}
end
if MTH_CharSavedVariables.trainScan.hasCompletedPetScan == nil then
if tonumber(MTH_CharSavedVariables.trainScan.lastScan) and tonumber(MTH_CharSavedVariables.trainScan.lastScan) > 0 then
MTH_CharSavedVariables.trainScan.hasCompletedPetScan = 1
elseif MTH_CharSavedVariables.trainScan.spellMap then
for _ in pairs(MTH_CharSavedVariables.trainScan.spellMap) do
MTH_CharSavedVariables.trainScan.hasCompletedPetScan = 1
break
end
end
end
MTH_CharSavedVariables.petTraining = MTH_CharSavedVariables.trainScan
MTH_PT_PromoteUnrankedKnownAliases(MTH_CharSavedVariables.trainScan)
return MTH_CharSavedVariables.trainScan
end
function MTH_GetPetTrainingSnapshot()
return MTH_PT_GetStore()
end
function MTH_GetKnownPetSpellMap()
local store = MTH_PT_GetStore()
if type(store) ~= "table" then
return nil
end
return store.spellMap
end
local function MTH_PT_ParseRank(name, subText)
local rankNumber = nil
local sub = tostring(subText or "")
if sub ~= "" then
local _, _, subRank = string.find(sub, "(%d+)")
rankNumber = tonumber(subRank)
end
if not rankNumber then
local nm = tostring(name or "")
local _, _, nameRank = string.find(nm, "(%d+)")
rankNumber = tonumber(nameRank)
end
if not rankNumber then
local full = tostring(name or "") .. " " .. tostring(subText or "")
local _, _, fullRank = string.find(full, "(%d+)")
rankNumber = tonumber(fullRank)
end
if rankNumber then
return "Rank " .. tostring(rankNumber), rankNumber
end
return "", nil
end
local function MTH_PT_NormalizeName(name)
local label = tostring(name or "")
label = string.gsub(label, "^%s+", "")
label = string.gsub(label, "%s+$", "")
return label
end
local function MTH_PT_BaseAbility(name)
local base = MTH_PT_NormalizeName(name)
base = string.gsub(base, "%s*%([^%)]*%d+[^%)]*%)", "")
base = string.gsub(base, "%s*%([Rr]ank[^%)]*%)", "")
base = string.gsub(base, "%s+[Rr]ank%s+%d+", "")
base = string.gsub(base, "%s+%d+$", "")
base = string.gsub(base, "%s+$", "")
return base
end
local function MTH_PT_MakeToken(name, subText)
local base = MTH_PT_BaseAbility(name)
local rankText, rankNumber = MTH_PT_ParseRank(name, subText)
if rankNumber and rankNumber > 0 and not MTH_PT_AbilityHasPositiveRanks(base) then
rankText = ""
rankNumber = nil
end
if rankText ~= "" then
return MTH_PT_SafeLower(base) .. "#" .. tostring(rankNumber), base, rankText, rankNumber
end
return MTH_PT_SafeLower(base), base, "", nil
end
local function MTH_PT_HasAnyLearnedSpells()
local store = MTH_PT_GetStore()
local spellMap = store.spellMap
if not spellMap then return false end
for _ in pairs(spellMap) do
return true
end
return false
end
local function MTH_PT_IsBeastTrainingContextActive()
local frameNames = { "BeastTrainingFrame", "PetTrainingFrame", "CraftFrame" }
for i = 1, table.getn(frameNames) do
local frameName = frameNames[i]
local frame = (type(getglobal) == "function" and getglobal(frameName)) or (_G and _G[frameName])
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
local function MTH_PT_GetBeastTrainingRowCount()
if not MTH_PT_IsBeastTrainingContextActive() then
return 0
end
local getNumCrafts = MTH_PT_GetGlobal("GetNumCrafts")
if type(getNumCrafts) ~= "function" then
return 0
end
return tonumber(getNumCrafts()) or 0
end
function MTH_PT_HasBeastTrainingRows()
return MTH_PT_GetBeastTrainingRowCount() > 0
end
local function MTH_PT_DebugLog(line)
if not MTH_PT_VERBOSE_SCAN_LOGS then return end
local text = "[TRAINSCAN] " .. tostring(line or "")
MTH:Print(text, "debug")
end
local function MTH_PT_MakeTokenFromAbilityRank(abilityName, rankNumber)
local ability = MTH_PT_NormalizeName(abilityName)
local key = MTH_PT_SafeLower(ability)
if key == "" then return "", ability, "", nil end
local rank = tonumber(rankNumber)
if rank and rank > 0 and not MTH_PT_AbilityHasPositiveRanks(ability) then
rank = nil
end
if rank and rank > 0 then
return key .. "#" .. tostring(rank), ability, "Rank " .. tostring(rank), rank
end
return key, ability, "", nil
end
local function MTH_PT_AbilityExistsInData(baseAbility)
local ability = MTH_PT_NormalizeName(baseAbility)
if ability == "" then return false end
if not MTH_DS_PetSpells or not MTH_DS_PetSpells.byAbility then
return false
end
if MTH_DS_PetSpells.byAbility[ability] then
return true
end
for abilityName in pairs(MTH_DS_PetSpells.byAbility) do
if MTH_PT_SafeLower(abilityName) == MTH_PT_SafeLower(ability) then
return true
end
end
return false
end
MTH_PT_AbilityHasPositiveRanks = function(baseAbility)
local ability = MTH_PT_NormalizeName(baseAbility)
if ability == "" then return false end
if not MTH_DS_PetSpells or not MTH_DS_PetSpells.byAbility then
return false
end
local bucket = MTH_DS_PetSpells.byAbility[ability]
if not bucket then
for abilityName, row in pairs(MTH_DS_PetSpells.byAbility) do
if MTH_PT_SafeLower(abilityName) == MTH_PT_SafeLower(ability) then
bucket = row
break
end
end
end
if not (bucket and bucket.spells) then
return false
end
for i = 1, table.getn(bucket.spells) do
local spell = bucket.spells[i]
if spell then
local rank = tonumber(spell.rankNumber)
if not rank and spell.rank then
local _, _, parsed = string.find(tostring(spell.rank), "(%d+)")
rank = tonumber(parsed)
end
if rank and rank > 0 then
return true
end
end
end
return false
end
MTH_PT_PromoteUnrankedKnownAliases = function(store)
if not store or not store.hunterKnownMap or not store.spellMap then
return
end
for token, isKnown in pairs(store.hunterKnownMap) do
if isKnown == true then
local _, _, baseToken = string.find(tostring(token), "^(.-)#%d+$")
if baseToken and baseToken ~= "" then
local baseRow = store.spellMap[baseToken]
if baseRow and ((tonumber(baseRow.rankNumber) or 0) <= 0) then
store.hunterKnownMap[baseToken] = true
if baseRow.isKnown ~= true then
baseRow.isKnown = true
end
end
end
end
end
end
local function MTH_PT_MarkHunterKnownToken(token)
if token == nil or token == "" then
return false
end
local store = MTH_PT_GetStore()
local markToken = token
local _, _, baseToken = string.find(tostring(token), "^(.-)#%d+$")
if baseToken and baseToken ~= "" then
local baseRow = store.spellMap and store.spellMap[baseToken]
if baseRow and ((tonumber(baseRow.rankNumber) or 0) <= 0) then
markToken = baseToken
end
end
local changed = false
if store.hunterKnownMap[markToken] ~= true then
store.hunterKnownMap[markToken] = true
changed = true
end
if markToken ~= token and store.hunterKnownMap[token] ~= true then
store.hunterKnownMap[token] = true
changed = true
end
if store.spellMap and store.spellMap[markToken] and store.spellMap[markToken].isKnown ~= true then
store.spellMap[markToken].isKnown = true
changed = true
end
if markToken ~= token and store.spellMap and store.spellMap[token] and store.spellMap[token].isKnown ~= true then
store.spellMap[token].isKnown = true
changed = true
end
if changed and MTH_PT_DebugLog then
MTH_PT_DebugLog("hunter-known token promoted: " .. tostring(token))
end
return changed
end
local function MTH_PT_IsHunterTokenKnown(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
local _, _, baseToken = string.find(tostring(token), "^(.-)#%d+$")
if baseToken and baseToken ~= "" then
if store.hunterKnownMap and store.hunterKnownMap[baseToken] == true then
return true
end
if store.spellMap and store.spellMap[baseToken] and store.spellMap[baseToken].isKnown == true then
return true
end
end
return false
end
local function MTH_PT_ProcessLearnSystemMessage(rawMessage)
local message = tostring(rawMessage or "")
if message == "" then return false end
message = string.gsub(message, "|c%x%x%x%x%x%x%x%x", "")
message = string.gsub(message, "|r", "")
message = string.gsub(message, "^%s+", "")
message = string.gsub(message, "%s+$", "")
local _, _, abilityName, rankText = string.find(message, "^[Yy]ou have learned a new spell:%s*(.-)%s*%([Rr]ank%s*(%d+)%)%.?$")
if not abilityName or not rankText then
_, _, 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
return false
end
local rankNumber = tonumber(rankText)
if not rankNumber then
return false
end
abilityName = MTH_PT_NormalizeName(abilityName)
if abilityName == "" then return false end
if not MTH_PT_AbilityExistsInData(abilityName) then
if MTH_PT_DebugLog then
MTH_PT_DebugLog("learn-msg ignored (not pet ability dataset): '" .. tostring(abilityName) .. "'")
end
return false
end
local token = MTH_PT_MakeTokenFromAbilityRank(abilityName, rankNumber)
if token == "" then return false end
local alreadyKnown = MTH_PT_IsHunterTokenKnown(token)
local changed = false
if not alreadyKnown then
changed = MTH_PT_MarkHunterKnownToken(token)
end
local petRecorded = false
if type(MTH_PETS_RecordCurrentPetLearnedAbility) == "function" then
petRecorded = MTH_PETS_RecordCurrentPetLearnedAbility(abilityName, rankNumber, "beasttraining:learn-msg") and true or false
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))
end
local label = tostring(abilityName)
if rankNumber and rankNumber > 0 then
label = label .. " (Rank " .. tostring(rankNumber) .. ")"
end
if MTH and MTH.Print then
if changed then
MTH:Print("Pet ability learned and recorded: " .. label .. ". It is now marked as known in MetaHunt.")
else
MTH:Print("Pet ability learned for current pet: " .. label .. ".")
end
end
if changed then
MTH_PT_ApplyScan("learn-msg")
end
if type(MTH_PSP_RequestScan) == "function" then
MTH_PSP_RequestScan("train-learn-msg")
end
elseif alreadyKnown and MTH_PT_DebugLog then
MTH_PT_DebugLog("learn-msg ignored (already known and no active pet row update): token='" .. tostring(token) .. "'")
end
return changed or petRecorded
end
local function MTH_PT_CollectKnownBeastTrainingMap()
if not MTH_PT_IsBeastTrainingContextActive() then
if MTH_PT_DebugLog then
MTH_PT_DebugLog("collect beast-training known skipped: explicit beast training context not active")
end
return {}
end
local getNumCrafts = MTH_PT_GetGlobal("GetNumCrafts")
local getCraftInfo = MTH_PT_GetGlobal("GetCraftInfo")
if type(getNumCrafts) ~= "function" or type(getCraftInfo) ~= "function" then
if MTH_PT_DebugLog then
MTH_PT_DebugLog("collect beast-training known unavailable: GetNumCrafts=" .. tostring(type(getNumCrafts)) .. ", GetCraftInfo=" .. tostring(type(getCraftInfo)))
end
return {}
end
local totalCrafts = tonumber(getNumCrafts()) or 0
local knownMap = {}
if totalCrafts <= 0 then
if MTH_PT_DebugLog then
MTH_PT_DebugLog("collect beast-training known empty: totalRows=0")
end
return knownMap
end
if MTH_PT_DebugLog then
MTH_PT_DebugLog("collect beast-training known start: totalRows=" .. tostring(totalCrafts))
end
for i = 1, totalCrafts do
local craftName, craftSubSpellName, craftType = getCraftInfo(i)
local typeText = tostring(craftType or "")
local token, base, parsedRankText, rankNumber = MTH_PT_MakeToken(craftName, craftSubSpellName)
if token ~= "" then
if MTH_PT_DebugLog then
MTH_PT_DebugLog("beast-training row=" .. tostring(i) .. " token='" .. tostring(token) .. "' name='" .. tostring(base or "") .. "' rank='" .. tostring(parsedRankText or "") .. "' rankNumber=" .. tostring(rankNumber) .. " type='" .. typeText .. "'")
end
knownMap[token] = {
name = MTH_PT_NormalizeName(base),
rank = parsedRankText,
rankNumber = rankNumber,
fullName = MTH_PT_NormalizeName(craftName),
subText = tostring(craftSubSpellName or ""),
isKnown = true,
scannedAt = time(),
rowType = typeText,
}
end
end
if MTH_PT_DebugLog then
local total = 0
for _ in pairs(knownMap) do total = total + 1 end
MTH_PT_DebugLog("collect beast-training known end: tokens=" .. tostring(total))
end
return knownMap
end
function MTH_PT_ApplyScan(triggerReason)
if MTH_PT_DebugLog then
MTH_PT_DebugLog("scan start: trigger=" .. tostring(triggerReason or ""))
end
local store = MTH_PT_GetStore()
local oldMap = store.spellMap
if not oldMap then oldMap = {} end
local hunterKnownMap = store.hunterKnownMap or {}
local knownMap = MTH_PT_CollectKnownBeastTrainingMap()
local knownCount = 0
for _ in pairs(knownMap) do knownCount = knownCount + 1 end
if knownCount <= 0 then
if MTH_PT_DebugLog then
MTH_PT_DebugLog("scan abort: beast-training known count=0")
end
return false, 0, 0, 0
end
for token in pairs(knownMap) do
hunterKnownMap[token] = true
end
for token, row in pairs(oldMap) do
if row and row.isKnown then
hunterKnownMap[token] = true
end
end
for token in pairs(knownMap) do
if hunterKnownMap[token] == nil then
hunterKnownMap[token] = false
end
end
store.hunterKnownMap = hunterKnownMap
local newMap = {}
if MTH_DS_PetSpells and MTH_DS_PetSpells.byAbility then
for abilityName, bucket in pairs(MTH_DS_PetSpells.byAbility) do
if bucket and bucket.spells then
for i = 1, table.getn(bucket.spells) do
local spell = bucket.spells[i]
if spell then
local rankNumber = tonumber(spell.rankNumber)
if not rankNumber and spell.rank then
local _, _, parsed = string.find(tostring(spell.rank), "(%d+)")
rankNumber = tonumber(parsed)
end
local token, baseName, rankText, parsedRankNumber = MTH_PT_MakeTokenFromAbilityRank(abilityName, rankNumber)
if token ~= "" then
local tpCost = tonumber(spell.trainingPointCost)
if not tpCost and spell.cost then
local _, _, parsedCost = string.find(tostring(spell.cost), "(%d+)")
tpCost = tonumber(parsedCost)
end
newMap[token] = {
name = baseName,
rank = rankText,
rankNumber = parsedRankNumber,
fullName = rankText ~= "" and (baseName .. " " .. rankText) or baseName,
subText = rankText,
serviceType = "datastore",
reqLevel = tonumber(spell.trainLevel),
isKnown = hunterKnownMap[token] and true or false,
isKnownPet = knownMap[token] and true or false,
scannedAt = time(),
trainingPointCost = tpCost,
learnMethod = spell.learnMethod,
icon = spell.icon,
}
end
end
end
end
end
end
for token, row in pairs(knownMap) do
if newMap[token] then
newMap[token].isKnownPet = true
newMap[token].scannedAt = time()
else
newMap[token] = {
name = row.name,
rank = row.rank,
rankNumber = row.rankNumber,
fullName = row.fullName,
subText = row.subText,
serviceType = "beast-training",
reqLevel = nil,
isKnown = hunterKnownMap[token] and true or false,
isKnownPet = true,
scannedAt = time(),
trainingPointCost = nil,
}
end
end
local discovered = 0
for _ in pairs(newMap) do discovered = discovered + 1 end
if discovered <= 0 then
return false, 0, 0, 0
end
local added = 0
local removed = 0
local addedDetails = {}
for token in pairs(newMap) do
if not oldMap[token] then
added = added + 1
local row = newMap[token]
local spellName = MTH_PT_NormalizeName((row and row.name) or "")
local rankText = tostring((row and row.rank) or "")
if spellName == "" then
spellName = MTH_PT_NormalizeName((row and row.fullName) or "")
end
if spellName ~= "" then
local label = spellName
if rankText ~= "" then
label = label .. " " .. rankText
end
table.insert(addedDetails, label)
end
end
end
for token in pairs(oldMap) do
if not newMap[token] then
removed = removed + 1
end
end
store.spellMap = newMap
store.lastScan = time()
store.hasCompletedPetScan = 1
MTH_CharSavedVariables.trainScan = store
MTH_CharSavedVariables.petTraining = store
local totalRanks = 0
local baseSet = {}
for _, row in pairs(newMap) do
totalRanks = totalRanks + 1
local base = MTH_PT_SafeLower(row and row.name)
if base ~= "" then baseSet[base] = true end
end
local totalBaselines = 0
for _ in pairs(baseSet) do totalBaselines = totalBaselines + 1 end
if triggerReason == "initial" then
MTH:Print("Initial train scan complete: " .. tostring(totalBaselines) .. " baselines / " .. tostring(totalRanks) .. " ranks recorded for this character.")
elseif added > 0 then
table.sort(addedDetails)
local detailsText = table.concat(addedDetails, ", ")
if detailsText ~= "" then
local msg = "New pet data recorded (" .. tostring(added) .. " new rank(s)): " .. detailsText .. ". Known totals for this character: " .. tostring(totalBaselines) .. " ability type(s), " .. tostring(totalRanks) .. " rank(s)."
MTH:Print(msg)
MTH:Print("INFO: " .. msg, "debug")
else
local msg = "New pet data recorded (" .. tostring(added) .. " new rank(s)). Known totals for this character: " .. tostring(totalBaselines) .. " ability type(s), " .. tostring(totalRanks) .. " rank(s)."
MTH:Print(msg)
MTH:Print("INFO: " .. msg, "debug")
end
end
return true, added, totalBaselines, totalRanks
end
local function MTH_PT_ShouldScanNow()
return MTH_PT_HasBeastTrainingRows()
end
MTH_PT_IsPetBookVisible = function()
local frameNames = { "BeastTrainingFrame", "PetTrainingFrame", "CraftFrame" }
for i = 1, table.getn(frameNames) do
local frameName = frameNames[i]
local frame = (type(getglobal) == "function" and getglobal(frameName)) or (_G and _G[frameName])
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
local function MTH_PT_RunDoubleScan(triggerBase)
local triggerText = tostring(triggerBase or "")
local feedbackOnOpen = (
triggerText == "petbook-open"
or triggerText == "craft-open"
or triggerText == "craft-update"
or triggerText == "beast-training-rows-open"
or triggerText == "petbook-visible-poll"
)
if not MTH_PT_ShouldScanNow() then
if MTH_PT_DebugLog then
MTH_PT_DebugLog("scan skipped: no beast-training rows, trigger=" .. tostring(triggerBase or ""))
end
if feedbackOnOpen and MTH and MTH.Print then
MTH:Print("Beast Training opened, but scan did not run yet (no scannable rows detected).")
end
return false
end
local t0 = time()
local ok, added, baselines, ranks = MTH_PT_ApplyScan(triggerText .. ":single")
local t1 = time()
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")
end
if feedbackOnOpen and MTH and MTH.Print then
if ok then
local addedCount = tonumber(added) or 0
local msg = "Beast Training scan complete: " .. tostring(baselines or 0) .. " baselines / " .. tostring(ranks or 0) .. " ranks."
if addedCount > 0 then
msg = msg .. " Added +" .. tostring(addedCount) .. " new rank(s)."
end
MTH:Print(msg)
else
MTH:Print("Beast Training scan attempted, but no data was recorded.")
end
end
return ok and true or false
end
local MTH_PetTrainingFrame = nil
local MTH_PT_PetBookWasVisible = false
local MTH_PT_PetBookPollElapsed = 0
local MTH_PT_TrainingRowsWerePresent = false
local MTH_PT_LastTrainingRowCount = 0
local MTH_PT_LastAutoDoubleScanAt = 0
local function MTH_PT_RunDoubleScanThrottled(triggerBase, minIntervalSeconds)
local now = tonumber(time()) or 0
local minInterval = tonumber(minIntervalSeconds) or 0
if minInterval > 0 and (now - (MTH_PT_LastAutoDoubleScanAt or 0)) < minInterval then
return false
end
MTH_PT_LastAutoDoubleScanAt = now
return MTH_PT_RunDoubleScan(triggerBase)
end
local function MTH_PT_OnEvent(_, evt, eventArg1)
evt = evt or event
eventArg1 = eventArg1 or arg1
if not evt then
return
end
if evt == "PLAYER_ENTERING_WORLD" then
if UnitClass then
local _, classToken = UnitClass("player")
if classToken ~= "HUNTER" then return end
end
if not MTH_PT_HasAnyLearnedSpells() then
local store = MTH_PT_GetStore()
local now = time()
if not store.lastPrompt or (now - store.lastPrompt) > 60 then
store.lastPrompt = now
MTH:Print("No Beast Training scan found for this character. Open Beast Training (right panel) to record pet spell ranks.")
end
end
return
end
if evt == "LEARNED_SPELL_IN_TAB" then
if MTH_PT_ShouldScanNow() then
MTH_PT_RunDoubleScan("petbook-visible:" .. tostring(evt))
end
return
end
if evt == "PET_TRAINING_SHOW" then
MTH_PT_RunDoubleScan("petbook-open")
return
end
if evt == "CRAFT_SHOW" then
MTH_PT_RunDoubleScanThrottled("craft-open", 1)
return
end
if evt == "CRAFT_UPDATE" then
if MTH_PT_ShouldScanNow() then
MTH_PT_RunDoubleScanThrottled("craft-update", 1)
end
return
end
if evt == "SPELLS_CHANGED" then
if MTH_PT_ShouldScanNow() then
MTH_PT_RunDoubleScan("petbook-visible:" .. tostring(evt))
end
return
end
if evt == "UNIT_PET" or evt == "PET_BAR_UPDATE" then
if evt == "UNIT_PET" then
local unit = eventArg1
if unit ~= "player" then return end
end
if MTH_PT_ShouldScanNow() then
MTH_PT_RunDoubleScan("petbook-visible:" .. tostring(evt))
end
return
end
if evt == "CHAT_MSG_SYSTEM" then
MTH_PT_ProcessLearnSystemMessage(eventArg1)
return
end
end
function MTH_PT_InitService()
if MTH and MTH.ApplyClassGate and MTH:ApplyClassGate("trainscan-init") then
return
end
if MTH_PetTrainingFrame then
return
end
MTH_PetTrainingFrame = CreateFrame("Frame", "MTHPetTrainingScanFrame")
MTH_PetTrainingFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
MTH_PetTrainingFrame:RegisterEvent("LEARNED_SPELL_IN_TAB")
MTH_PetTrainingFrame:RegisterEvent("PET_TRAINING_SHOW")
MTH_PetTrainingFrame:RegisterEvent("SPELLS_CHANGED")
MTH_PetTrainingFrame:RegisterEvent("UNIT_PET")
MTH_PetTrainingFrame:RegisterEvent("PET_BAR_UPDATE")
MTH_PetTrainingFrame:RegisterEvent("CHAT_MSG_SYSTEM")
MTH_PetTrainingFrame:RegisterEvent("CRAFT_SHOW")
MTH_PetTrainingFrame:RegisterEvent("CRAFT_UPDATE")
MTH_PetTrainingFrame:SetScript("OnEvent", MTH_PT_OnEvent)
MTH_PetTrainingFrame:SetScript("OnUpdate", function(_, elapsed)
elapsed = elapsed or arg1
MTH_PT_PetBookPollElapsed = (MTH_PT_PetBookPollElapsed or 0) + (elapsed or 0)
if MTH_PT_PetBookPollElapsed < 0.20 then
return
end
MTH_PT_PetBookPollElapsed = 0
local rowCount = MTH_PT_GetBeastTrainingRowCount()
local hasRows = rowCount > 0
if hasRows and not MTH_PT_TrainingRowsWerePresent then
MTH_PT_RunDoubleScanThrottled("beast-training-rows-open", 1)
end
if hasRows and rowCount ~= (MTH_PT_LastTrainingRowCount or 0) then
MTH_PT_RunDoubleScanThrottled("beast-training-rows-changed", 1)
end
if hasRows then
MTH_PT_RunDoubleScanThrottled("beast-training-rows-present", 8)
end
MTH_PT_TrainingRowsWerePresent = hasRows
MTH_PT_LastTrainingRowCount = rowCount
local visible = MTH_PT_IsPetBookVisible()
if visible and not MTH_PT_PetBookWasVisible then
MTH_PT_RunDoubleScanThrottled("petbook-visible-poll", 1)
end
MTH_PT_PetBookWasVisible = visible and true or false
end)
end
function MTH_PT_ShutdownService(_reason)
if not MTH_PetTrainingFrame then
return
end
MTH_PetTrainingFrame:UnregisterAllEvents()
MTH_PetTrainingFrame:SetScript("OnEvent", nil)
MTH_PetTrainingFrame:SetScript("OnUpdate", nil)
MTH_PetTrainingFrame = nil
MTH_PT_PetBookWasVisible = false
MTH_PT_PetBookPollElapsed = 0
MTH_PT_TrainingRowsWerePresent = false
MTH_PT_LastTrainingRowCount = 0
MTH_PT_LastAutoDoubleScanAt = 0
end
function MTH_CommandPetScan()
local function requestBeastTrainingOpen()
if MTH_PT_IsPetBookVisible and MTH_PT_IsPetBookVisible() then
return true
end
local bookTypePet = MTH_PT_GetGlobal("BOOKTYPE_PET")
local openSpellBook = MTH_PT_GetGlobal("OpenSpellBook")
if type(openSpellBook) == "function" and bookTypePet then
openSpellBook(bookTypePet)
end
if MTH_PT_IsPetBookVisible and MTH_PT_IsPetBookVisible() then
return true
end
local toggleSpellBook = MTH_PT_GetGlobal("ToggleSpellBook")
if type(toggleSpellBook) == "function" then
if bookTypePet then
toggleSpellBook(bookTypePet)
else
toggleSpellBook()
end
end
return MTH_PT_IsPetBookVisible and MTH_PT_IsPetBookVisible() or false
end
if UnitClass then
local _, classToken = UnitClass("player")
if classToken ~= "HUNTER" then
MTH:Print("Train scan is hunter-only.")
return
end
end
if not MTH_PT_ShouldScanNow() then
local opened = requestBeastTrainingOpen()
if not MTH_PT_ShouldScanNow() then
if opened then
MTH:Print("Beast Training is open, but no scannable rows were detected yet. Try once more after the list fully loads.")
else
MTH:Print("Could not detect Beast Training rows. Open Beast Training (right panel) and run /mth trainscan again.")
end
return
end
end
local ok, added, baselines, ranks = MTH_PT_ApplyScan("manual")
if not ok then
MTH:Print("Train scan failed. Keep Beast Training open and try /mth trainscan again.")
return
end
MTH:Print("Train scan saved for this character: " .. tostring(baselines) .. " baselines / " .. tostring(ranks) .. " ranks.")
if (tonumber(added) or 0) > 0 then
MTH:Print("Newly recorded ranks: +" .. tostring(added))
end
end
function MTH_CommandTrainScan()
return MTH_CommandPetScan()
end
MTH_TR_InitService = MTH_PT_InitService
MTH_TR_ShutdownService = MTH_PT_ShutdownService
+295
View File
@@ -0,0 +1,295 @@
if not MTH then
error("MetaHunt core framework missing: api/core-framework.lua must load before api/core-scan-spellbookpet.lua")
end
local MTH_PS_Frame = nil
local MTH_PS_LastScanAt = 0
local MTH_PS_VERBOSE_LOGS = false
local MTH_PS_ForceScanUntil = 0
local MTH_PS_ForceScanBudget = 0
local function MTH_PS_ClearForceWindow()
MTH_PS_ForceScanUntil = 0
MTH_PS_ForceScanBudget = 0
end
local function MTH_PS_ArmForceWindow(durationSeconds, maxScans)
local now = tonumber(time()) or 0
local duration = tonumber(durationSeconds) or 4
local budget = tonumber(maxScans) or 6
if duration < 1 then duration = 1 end
if budget < 1 then budget = 1 end
MTH_PS_ForceScanUntil = now + duration
MTH_PS_ForceScanBudget = budget
end
local function MTH_PS_IsFollowupEventTrigger(trigger)
local t = tostring(trigger or "")
return t == "event:UNIT_PET"
or t == "event:PET_BAR_UPDATE"
or t == "event:SPELLS_CHANGED"
or t == "event:LEARNED_SPELL_IN_TAB"
end
local function MTH_PS_IsPriorityTrigger(trigger)
local t = tostring(trigger or "")
return t == "unit-pet-current-change" or t == "refresh-current-change"
end
local function MTH_PS_DebugPrint(message, force)
return
end
local function MTH_PS_TraceTrigger(trigger, accepted, reason)
return
end
local function MTH_PS_SafeLower(text)
if text == nil then return "" end
return string.lower(tostring(text))
end
local function MTH_PS_GetGlobal(name)
if type(getglobal) == "function" then
return getglobal(name)
end
if _G then
return _G[name]
end
return nil
end
local function MTH_PS_GetStore()
if not MTH_CharSavedVariables then MTH_CharSavedVariables = {} end
if type(MTH_CharSavedVariables.petSpellScan) ~= "table" then
MTH_CharSavedVariables.petSpellScan = {
lastScan = 0,
lastTrigger = "",
lastCount = 0,
lastHash = "",
lastChangeAt = 0,
}
end
return MTH_CharSavedVariables.petSpellScan
end
local function MTH_PS_ParseRank(name, subText)
local rankNumber = nil
local sub = tostring(subText or "")
if sub ~= "" then
local _, _, subRank = string.find(sub, "(%d+)")
rankNumber = tonumber(subRank)
end
if not rankNumber then
local _, _, nameRank = string.find(tostring(name or ""), "(%d+)")
rankNumber = tonumber(nameRank)
end
if rankNumber and rankNumber > 0 then
return rankNumber
end
return nil
end
local function MTH_PS_MakeToken(name, rankNumber)
local base = MTH_PS_SafeLower(tostring(name or ""))
if base == "" then return "" end
local rank = tonumber(rankNumber)
if rank and rank > 0 then
return base .. "#" .. tostring(rank)
end
return base
end
local function MTH_PS_CollectPetSpellbookRows()
local getSpellName = MTH_PS_GetGlobal("GetSpellName")
local getSpellTexture = MTH_PS_GetGlobal("GetSpellTexture")
local isPassiveSpell = MTH_PS_GetGlobal("IsPassiveSpell")
local bookTypePet = MTH_PS_GetGlobal("BOOKTYPE_PET")
if type(getSpellName) ~= "function" or not bookTypePet then
MTH_PS_DebugPrint("Collect rows aborted: GetSpellName=" .. tostring(type(getSpellName)) .. " BOOKTYPE_PET=" .. tostring(bookTypePet), true)
return {}
end
local rows = {}
for slot = 1, 200 do
local name, subText = getSpellName(slot, bookTypePet)
if not name or name == "" then
if slot == 1 then
MTH_PS_DebugPrint("Slot 1 returned empty name (pet spellbook not ready/visible in this client context)", true)
end
break
end
local rankNumber = MTH_PS_ParseRank(name, subText)
local token = MTH_PS_MakeToken(name, rankNumber)
table.insert(rows, {
slot = slot,
name = tostring(name or ""),
rank = rankNumber,
icon = (type(getSpellTexture) == "function") and getSpellTexture(slot, bookTypePet) or nil,
isPassive = (type(isPassiveSpell) == "function") and (isPassiveSpell(slot, bookTypePet) and true or false) or false,
token = token,
})
end
MTH_PS_DebugPrint("Collected rows: " .. tostring(table.getn(rows)), true)
return rows
end
local function MTH_PS_HashRows(rows)
if type(rows) ~= "table" or table.getn(rows) == 0 then
return ""
end
local parts = {}
for i = 1, table.getn(rows) do
local row = rows[i]
parts[i] = tostring(row.token or "") .. ":" .. tostring(row.slot or "")
end
return table.concat(parts, "|")
end
function MTH_PS_ScanNow(trigger)
if UnitClass then
local _, classToken = UnitClass("player")
if classToken ~= "HUNTER" then
return false, 0, false
end
end
local rows = MTH_PS_CollectPetSpellbookRows()
local count = table.getn(rows)
local hash = MTH_PS_HashRows(rows)
local store = MTH_PS_GetStore()
local changed = (hash ~= tostring(store.lastHash or ""))
MTH_PS_DebugPrint("Scan trigger=" .. tostring(trigger or "auto") .. " count=" .. tostring(count) .. " changed=" .. tostring(changed and true or false), true)
store.lastScan = time()
store.lastTrigger = tostring(trigger or "auto")
store.lastCount = count
if changed then
store.lastHash = hash
store.lastChangeAt = store.lastScan
end
if type(MTH_PETS_RecordCurrentPetSpellbookSnapshot) == "function" then
local persistedCount, updated = MTH_PETS_RecordCurrentPetSpellbookSnapshot(rows, "petspellscan:" .. tostring(trigger or "auto"))
if updated == false then
MTH_PS_DebugPrint("Snapshot skipped; preserved existing count=" .. tostring(persistedCount or 0), true)
else
MTH_PS_DebugPrint("Snapshot persisted to current pet row", true)
end
else
MTH_PS_DebugPrint("Snapshot function missing: MTH_PETS_RecordCurrentPetSpellbookSnapshot", true)
end
MTH_PS_LastScanAt = store.lastScan
return true, count, changed
end
function MTH_PSP_RequestScan(trigger, minIntervalSeconds)
local triggerText = tostring(trigger or "request")
local now = tonumber(time()) or 0
local minInterval = tonumber(minIntervalSeconds) or 1
local bypassThrottle = false
local bypassReason = ""
if MTH_PS_IsPriorityTrigger(triggerText) then
bypassThrottle = true
bypassReason = "priority"
MTH_PS_ArmForceWindow(4, 6)
elseif MTH_PS_IsFollowupEventTrigger(triggerText)
and MTH_PS_ForceScanBudget > 0
and now <= (tonumber(MTH_PS_ForceScanUntil) or 0)
then
bypassThrottle = true
bypassReason = "followup"
MTH_PS_ForceScanBudget = MTH_PS_ForceScanBudget - 1
end
if (not bypassThrottle) and minInterval > 0 and (now - (MTH_PS_LastScanAt or 0)) < minInterval then
MTH_PS_TraceTrigger(triggerText, false, "throttled")
return false
end
if bypassThrottle then
MTH_PS_TraceTrigger(triggerText, true, bypassReason)
else
MTH_PS_TraceTrigger(triggerText, true, "queued")
end
local ok, count = MTH_PS_ScanNow(triggerText)
if ok and tonumber(count) and tonumber(count) > 0 then
MTH_PS_ClearForceWindow()
elseif MTH_PS_IsPriorityTrigger(triggerText) and type(UnitExists) == "function" and UnitExists("pet") then
MTH_PS_ArmForceWindow(4, 6)
end
if not ok then
MTH_PS_TraceTrigger(triggerText, false, "scan-failed")
end
return ok and true or false
end
local function MTH_PS_OnEvent(_, evt, eventArg1)
evt = evt or event
eventArg1 = eventArg1 or arg1
if not evt then return end
if evt == "UNIT_PET" and eventArg1 ~= "player" then
return
end
if evt == "PLAYER_ENTERING_WORLD" or evt == "UNIT_PET" or evt == "PET_BAR_UPDATE" or evt == "SPELLS_CHANGED" or evt == "LEARNED_SPELL_IN_TAB" then
MTH_PSP_RequestScan("event:" .. tostring(evt), 1)
end
end
function MTH_PS_InitService()
if MTH and MTH.ApplyClassGate and MTH:ApplyClassGate("petspellscan-init") then
return
end
if MTH_PS_Frame then
return
end
MTH_PS_Frame = CreateFrame("Frame", "MTHPetSpellScanFrame")
MTH_PS_Frame:RegisterEvent("PLAYER_ENTERING_WORLD")
MTH_PS_Frame:RegisterEvent("UNIT_PET")
MTH_PS_Frame:RegisterEvent("PET_BAR_UPDATE")
MTH_PS_Frame:RegisterEvent("SPELLS_CHANGED")
MTH_PS_Frame:RegisterEvent("LEARNED_SPELL_IN_TAB")
MTH_PS_Frame:SetScript("OnEvent", MTH_PS_OnEvent)
end
function MTH_PS_ShutdownService(_reason)
if not MTH_PS_Frame then
return
end
MTH_PS_Frame:UnregisterAllEvents()
MTH_PS_Frame:SetScript("OnEvent", nil)
MTH_PS_Frame = nil
MTH_PS_LastScanAt = 0
MTH_PS_ClearForceWindow()
end
function MTH_CommandPetSpellScan()
MTH_PS_VERBOSE_LOGS = true
local petName = UnitName and UnitName("pet") or nil
local hasPet = petName and petName ~= ""
local _, playerClass = UnitClass and UnitClass("player") or nil, nil
if UnitClass then
_, playerClass = UnitClass("player")
end
MTH_PS_DebugPrint("Manual command start: class=" .. tostring(playerClass) .. " pet=" .. tostring(petName or "<none>") .. " hasPet=" .. tostring(hasPet and true or false), true)
local bookTypePet = MTH_PS_GetGlobal("BOOKTYPE_PET")
local getSpellName = MTH_PS_GetGlobal("GetSpellName")
MTH_PS_DebugPrint("API check: BOOKTYPE_PET=" .. tostring(bookTypePet) .. " GetSpellNameType=" .. tostring(type(getSpellName)), true)
local ok, count, changed = MTH_PS_ScanNow("manual")
if not ok then
MTH:Print("Pet spellbook scan failed.")
MTH_PS_VERBOSE_LOGS = false
return
end
MTH:Print("Pet spellbook scan: " .. tostring(count or 0) .. " spell(s). changed=" .. tostring(changed and true or false))
local store = MTH_PS_GetStore()
MTH_PS_DebugPrint("Store status: lastScan=" .. tostring(store.lastScan or 0) .. " lastCount=" .. tostring(store.lastCount or 0) .. " lastTrigger=" .. tostring(store.lastTrigger or "") .. " lastChangeAt=" .. tostring(store.lastChangeAt or 0), true)
MTH_PS_VERBOSE_LOGS = false
end
File diff suppressed because it is too large Load Diff
+264
View File
@@ -0,0 +1,264 @@
if not MTH then
error("MetaHunt core framework missing: api/core-framework.lua must load before api/core.lua")
end
local function MTH_PT_GetGlobal(name)
if type(getglobal) == "function" then
return getglobal(name)
end
if _G then
return _G[name]
end
return nil
end
local MTH_ItemLinkCacheTooltip = nil
local function MTH_GetQualityHexColor(quality)
local q = tonumber(quality)
if type(GetItemQualityColor) == "function" and q then
local _, _, _, colorCode = GetItemQualityColor(q)
if colorCode and colorCode ~= "" then
local hex = string.gsub(tostring(colorCode), "|c", "")
hex = string.gsub(hex, "|r", "")
hex = string.gsub(hex, "^#", "")
if string.len(hex) == 8 then return hex end
if string.len(hex) == 6 then return "ff" .. hex end
end
end
return "ffffffff"
end
function MTH_PrimeItemCache(itemId)
local id = tonumber(itemId)
if not id or id <= 0 then return false end
if type(GetItemInfo) == "function" and GetItemInfo(id) then return true end
if not MTH_ItemLinkCacheTooltip then
MTH_ItemLinkCacheTooltip = CreateFrame("GameTooltip", "MTH_ItemLinkCacheTooltip", UIParent, "GameTooltipTemplate")
if not MTH_ItemLinkCacheTooltip then return false end
MTH_ItemLinkCacheTooltip:SetOwner(UIParent, "ANCHOR_NONE")
end
MTH_ItemLinkCacheTooltip:ClearLines()
MTH_ItemLinkCacheTooltip:SetHyperlink("item:" .. tostring(id) .. ":0:0:0")
return type(GetItemInfo) == "function" and (GetItemInfo(id) ~= nil) or false
end
function MTH_GetClickableItemLink(itemId, fallbackName, tryPrimeCache)
local id = tonumber(itemId)
if not id then return nil end
local itemName = nil
local itemLink = nil
local itemQuality = nil
if type(GetItemInfo) == "function" then
itemName, itemLink, itemQuality = GetItemInfo(id)
if (not itemName or itemName == "") and tryPrimeCache then
MTH_PrimeItemCache(id)
itemName, itemLink, itemQuality = GetItemInfo(id)
end
end
local displayName = tostring(itemName or fallbackName or ("Item " .. tostring(id)))
local coreLink = nil
if itemLink and itemLink ~= "" then
local _, _, parsedCore = string.find(tostring(itemLink), "|H(item:[^|]+)|h")
if parsedCore and parsedCore ~= "" then
coreLink = parsedCore
elseif string.find(tostring(itemLink), "^item:", 1, true) then
coreLink = tostring(itemLink)
end
end
if not coreLink then
coreLink = "item:" .. tostring(id) .. ":0:0:0"
end
local colorHex = MTH_GetQualityHexColor(itemQuality)
return "|c" .. tostring(colorHex) .. "|H" .. tostring(coreLink) .. "|h[" .. tostring(displayName) .. "]|h|r"
end
function MTH_InsertLinkToChat(link)
if type(link) ~= "string" or link == "" then return false end
local wimEditBox = MTH_PT_GetGlobal("WIM_EditBoxInFocus")
if wimEditBox and type(wimEditBox.Insert) == "function" then
wimEditBox:Insert(link)
return true
end
local chatEditBox = MTH_PT_GetGlobal("ChatFrameEditBox")
if not chatEditBox then return false end
if type(chatEditBox.IsVisible) == "function" and chatEditBox:IsVisible() and type(chatEditBox.Insert) == "function" then
chatEditBox:Insert(link)
return true
end
if type(chatEditBox.Show) == "function" then chatEditBox:Show() end
if type(chatEditBox.Insert) == "function" then
chatEditBox:Insert(link)
elseif type(chatEditBox.SetText) == "function" then
chatEditBox:SetText(link)
if type(chatEditBox.HighlightText) == "function" then
chatEditBox:HighlightText()
end
end
return true
end
-- Slash command handler (original, from backup)
MTH:RegisterSlashAliases("MTH")
local function MTH_CommandBook()
if type(MTH_OpenHunterBook) == "function" then
MTH_OpenHunterBook()
elseif type(MTH_ToggleHunterBook) == "function" then
MTH_ToggleHunterBook()
else
MTH:Print("Hunter Book is not available yet")
end
end
local function MTH_CommandOptions()
if MTH_OpenOptions then
MTH_OpenOptions("General")
else
MTH:Print("Options window is not available yet")
end
end
local function MTH_CommandPetSpellScanFallback()
local getSpellName = (type(getglobal) == "function" and getglobal("GetSpellName")) or (_G and _G["GetSpellName"])
local getSpellTexture = (type(getglobal) == "function" and getglobal("GetSpellTexture")) or (_G and _G["GetSpellTexture"])
local isPassiveSpell = (type(getglobal) == "function" and getglobal("IsPassiveSpell")) or (_G and _G["IsPassiveSpell"])
local bookTypePet = (type(getglobal) == "function" and getglobal("BOOKTYPE_PET")) or (_G and _G["BOOKTYPE_PET"])
local petName = UnitName and UnitName("pet") or nil
local pets = (type(MTH_PETS_GetRootStore) == "function") and MTH_PETS_GetRootStore() or nil
local currentPetId = (type(pets) == "table" and type(pets.currentPet) == "table") and pets.currentPet.id or nil
if type(getSpellName) ~= "function" or not bookTypePet then
return false, 0
end
local rows = {}
for slot = 1, 200 do
local name, subText = getSpellName(slot, bookTypePet)
if not name or name == "" then
break
end
local rankNumber = nil
if tostring(subText or "") ~= "" then
local _, _, r = string.find(tostring(subText), "(%d+)")
rankNumber = tonumber(r)
end
if not rankNumber then
local _, _, r2 = string.find(tostring(name), "(%d+)")
rankNumber = tonumber(r2)
end
local token = string.lower(tostring(name or ""))
if rankNumber and rankNumber > 0 then
token = token .. "#" .. tostring(rankNumber)
end
table.insert(rows, {
slot = slot,
name = tostring(name or ""),
rank = rankNumber,
icon = (type(getSpellTexture) == "function") and getSpellTexture(slot, bookTypePet) or nil,
isPassive = (type(isPassiveSpell) == "function") and (isPassiveSpell(slot, bookTypePet) and true or false) or false,
token = token,
})
end
local count = table.getn(rows)
if count > 0 then
local preview = {}
local maxPreview = math.min(6, count)
for i = 1, maxPreview do
local row = rows[i]
if type(row) == "table" then
local line = tostring(row.name or "?")
local rank = tonumber(row.rank)
if rank and rank > 0 then
line = line .. " R" .. tostring(rank)
end
table.insert(preview, line)
end
end
end
if type(MTH_PETS_RecordCurrentPetSpellbookSnapshot) == "function" then
local persisted, updated = MTH_PETS_RecordCurrentPetSpellbookSnapshot(rows, "petspellscan:core-fallback")
return true, tonumber(persisted) or count
else
return false, count
end
end
if type(MTH_PS_ScanNow) ~= "function" then
function MTH_PS_ScanNow(trigger)
local ok, count = MTH_CommandPetSpellScanFallback()
return ok and true or false, tonumber(count) or 0, false
end
end
if type(MTH_PSP_RequestScan) ~= "function" then
local mthPsFallbackLastScan = 0
function MTH_PSP_RequestScan(trigger, minIntervalSeconds)
local now = tonumber(time()) or 0
local minInterval = tonumber(minIntervalSeconds) or 1
local triggerText = tostring(trigger or "request")
if minInterval > 0 and (now - mthPsFallbackLastScan) < minInterval then
return false
end
local ok = MTH_PS_ScanNow(triggerText)
if ok then
mthPsFallbackLastScan = now
end
return ok and true or false
end
end
if type(MTH_CommandPetSpellScan) ~= "function" then
function MTH_CommandPetSpellScan()
local ok, count = MTH_PS_ScanNow("manual-core-fallback")
MTH:Print("Pet spellbook scan: " .. tostring(count or 0) .. " spell(s). ok=" .. tostring(ok and true or false))
end
end
function SlashCmdList.MTH(msg, editbox)
if MTH and MTH.ApplyClassGate and MTH:ApplyClassGate("slash") then
return
end
msg = msg or ""
msg = string.gsub(tostring(msg), "^%s+", "")
msg = string.gsub(msg, "%s+$", "")
local lowerMsg = string.lower(msg)
if msg == "" then
MTH:Print("Available: /mth options, /mth book")
elseif lowerMsg == "options" then
MTH_CommandOptions()
elseif lowerMsg == "book" or lowerMsg == "hunterbook" then
MTH_CommandBook()
else
MTH:Print("Unknown command: " .. tostring(msg))
MTH:Print("Available: /mth options, /mth book")
end
end
-- Initialize on load
MTH:InitSavedVariables()
if type(MTH_ST_InitService) == "function" then
MTH_ST_InitService()
end
if type(MTH_TR_InitService) == "function" then
MTH_TR_InitService()
elseif type(MTH_PT_InitService) == "function" then
MTH_PT_InitService()
end
if type(MTH_PS_InitService) == "function" then
MTH_PS_InitService()
end
+374
View File
@@ -0,0 +1,374 @@
------------------------------------------------------
-- MetaHunt: Debug Frame
-- Displays errors and debug messages in a scrollable frame
------------------------------------------------------
MTH_DebugFrame = {
frame = nil,
scrollFrame = nil,
scrollText = nil,
errors = {},
maxErrors = 500,
isVisible = false,
initialized = false,
captureCount = 0,
infoCount = 0
}
function MTH_DebugFrame:Initialize()
if self.initialized then
return
end
self.initialized = true
-- Create main frame
self.frame = CreateFrame("Frame", "MTH_DebugFrameWindow", UIParent)
self.frame:SetWidth(800)
self.frame:SetHeight(400)
self.frame:SetPoint("CENTER", UIParent, "CENTER")
self.frame:SetFrameStrata("DIALOG")
self.frame:SetBackdrop({
bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
tile = true,
tileSize = 32,
edgeSize = 32,
insets = { left = 11, right = 12, top = 12, bottom = 11 }
})
self.frame:SetBackdropColor(0, 0, 0, 0.8)
self.frame:SetMovable(true)
self.frame:EnableMouse(true)
self.frame:RegisterForDrag("LeftButton")
self.frame:SetScript("OnDragStart", function() self.frame:StartMoving() end)
self.frame:SetScript("OnDragStop", function() self.frame:StopMovingOrSizing() end)
if UISpecialFrames then
local already = false
for i = 1, table.getn(UISpecialFrames) do
if UISpecialFrames[i] == "MTH_DebugFrameWindow" then
already = true
break
end
end
if not already then
table.insert(UISpecialFrames, "MTH_DebugFrameWindow")
end
end
-- Title
local title = self.frame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
title:SetPoint("TOPLEFT", self.frame, "TOPLEFT", 20, -15)
title:SetText("MetaHunt Debug - Errors & Messages")
-- Close button
local closeButton = CreateFrame("Button", nil, self.frame, "UIPanelButtonTemplate")
closeButton:SetPoint("TOPRIGHT", self.frame, "TOPRIGHT", -8, -8)
closeButton:SetWidth(22)
closeButton:SetHeight(22)
closeButton:SetText("X")
closeButton:SetScript("OnClick", function() MTH_DebugFrame:Hide() end)
-- Clear button
local clearButton = CreateFrame("Button", nil, self.frame, "GameMenuButtonTemplate")
clearButton:SetPoint("BOTTOMLEFT", self.frame, "BOTTOMLEFT", 10, 10)
clearButton:SetWidth(100)
clearButton:SetHeight(25)
clearButton:SetText("Clear")
clearButton:SetScript("OnClick", function() MTH_DebugFrame:Clear() end)
-- Select-all button
local copyButton = CreateFrame("Button", nil, self.frame, "GameMenuButtonTemplate")
copyButton:SetPoint("LEFT", clearButton, "RIGHT", 10, 0)
copyButton:SetWidth(100)
copyButton:SetHeight(25)
copyButton:SetText("Select All")
copyButton:SetScript("OnClick", function() MTH_DebugFrame:SelectAllText() end)
-- Scroll frame + text content with proper vertical scrollbar
self.scrollFrame = CreateFrame("ScrollFrame", "MTH_DebugScrollFrame", self.frame, "UIPanelScrollFrameTemplate")
self.scrollFrame:SetPoint("TOPLEFT", self.frame, "TOPLEFT", 15, -50)
self.scrollFrame:SetPoint("BOTTOMRIGHT", self.frame, "BOTTOMRIGHT", -35, 45)
self.scrollText = CreateFrame("EditBox", "MTH_DebugScrollText", self.scrollFrame)
self.scrollText:SetMultiLine(true)
self.scrollText:SetMaxLetters(999999)
self.scrollText:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
self.scrollText:SetTextColor(0.8, 0.8, 0.8, 1)
self.scrollText:SetWidth(730)
self.scrollText:SetHeight(1)
self.scrollText:EnableMouse(true)
self.scrollText:SetScript("OnCursorChanged", function()
if not MTH_DebugFrame.scrollFrame then
return
end
local width = MTH_DebugFrame.scrollFrame:GetWidth()
if width and width > 20 then
MTH_DebugFrame.scrollText:SetWidth(width - 20)
end
end)
self.scrollText:SetScript("OnEscapePressed", function() self.scrollText:ClearFocus() end)
self.scrollText:SetAutoFocus(false)
self.scrollText:EnableMouseWheel(true)
self.scrollText:SetScript("OnMouseWheel", function()
if not MTH_DebugFrame.scrollFrame then
return
end
local curr = MTH_DebugFrame.scrollFrame:GetVerticalScroll()
local step = 24
if arg1 and arg1 > 0 then
MTH_DebugFrame.scrollFrame:SetVerticalScroll(curr - step)
else
MTH_DebugFrame.scrollFrame:SetVerticalScroll(curr + step)
end
end)
self.scrollText:SetScript("OnTextChanged", nil)
self.scrollFrame:SetScrollChild(self.scrollText)
-- Hide by default
self.frame:Hide()
self.isVisible = false
end
function MTH_DebugFrame:AddError(message)
if not message then return end
self.captureCount = self.captureCount + 1
if string.len(message) > 1000 then
message = string.sub(message, 1, 1000) .. "..."
end
table.insert(self.errors, 1, {
time = date("%H:%M:%S"),
message = message,
level = "error"
})
-- Limit stored errors
if table.getn(self.errors) > self.maxErrors then
table.remove(self.errors, self.maxErrors + 1)
end
self:UpdateDisplay()
end
function MTH_DebugFrame:AddInfo(message)
if not message then return end
self.infoCount = self.infoCount + 1
if string.len(message) > 1000 then
message = string.sub(message, 1, 1000) .. "..."
end
table.insert(self.errors, 1, {
time = date("%H:%M:%S"),
message = message,
level = "info"
})
if table.getn(self.errors) > self.maxErrors then
table.remove(self.errors, self.maxErrors + 1)
end
self:UpdateDisplay()
end
function MTH_DebugFrame:UpdateDisplay()
if not self.scrollText or not self.initialized then
return
end
local text = "=== Errors Captured: " .. self.captureCount .. " ===\n"
text = text .. "=== Info Captured: " .. (self.infoCount or 0) .. " ===\n"
text = text .. "=== Total in queue: " .. table.getn(self.errors) .. " ===\n\n"
for i = 1, table.getn(self.errors) do
local err = self.errors[i]
text = text .. "[" .. err.time .. "] " .. err.message .. "\n"
end
local _, lineCount = string.gsub(text, "\n", "\n")
local lineHeight = 13
local contentHeight = (lineCount + 1) * lineHeight + 8
if contentHeight < 1 then
contentHeight = 1
end
self.scrollText:SetHeight(contentHeight)
self.scrollText:SetText(text)
if self.scrollFrame then
self.scrollFrame:SetVerticalScroll(0)
end
end
function MTH_DebugFrame:Toggle()
if self.isVisible then
self.frame:Hide()
self.isVisible = false
else
if not self.initialized then
self:Initialize()
end
self.frame:Show()
self.isVisible = true
self:UpdateDisplay()
end
end
function MTH_DebugFrame:Show()
if not self.initialized then
self:Initialize()
end
self.frame:Show()
self.isVisible = true
self:UpdateDisplay()
end
function MTH_DebugFrame:Hide()
if self.frame then
self.frame:Hide()
end
self.isVisible = false
end
function MTH_DebugFrame:Clear()
self.errors = {}
self.captureCount = 0
self.infoCount = 0
self:UpdateDisplay()
if self.initialized and self.isVisible then
if MTH and MTH.Print then
MTH:Print("Debug frame cleared")
end
end
end
function MTH_DebugFrame:SelectAllText()
if not self.scrollText or not self.initialized then
return
end
-- Select all text
self.scrollText:HighlightText(0, -1)
self.scrollText:SetFocus()
if MTH and MTH.Print then
MTH:Print("All text selected - press Ctrl+C to copy")
end
end
------------------------------------------------------
-- Error Capture Hooks
------------------------------------------------------
local function MTH_DF_AddMessageCapture(msg)
if msg then
local msgStr = tostring(msg)
if string.find(msgStr, "%.lua:%d+:") then
MTH_DebugFrame:AddError(msgStr)
end
end
end
local function MTH_DF_IsGlobalCaptureEnabled()
if MTH and MTH.GetConfig then
return MTH:GetConfig("debug", "globalErrorCapture", false) and true or false
end
return false
end
local function MTH_DF_SetGlobalCaptureEnabled(enabled)
if MTH and MTH.SetConfig then
MTH:SetConfig("debug", "globalErrorCapture", enabled and true or false)
end
end
local function MTH_DF_InstallHooks()
if MTH_DebugFrame.hooksInstalled then
return
end
if not MTH_DF_IsGlobalCaptureEnabled() then
return
end
if DEFAULT_CHAT_FRAME and type(DEFAULT_CHAT_FRAME.AddMessage) == "function" then
MTH_DebugFrame.originalAddMessage = DEFAULT_CHAT_FRAME.AddMessage
MTH_DebugFrame.addMessageHook = function(self, msg, arg1, arg2, arg3, arg4, arg5)
MTH_DF_AddMessageCapture(msg)
if MTH_DebugFrame.originalAddMessage then
return MTH_DebugFrame.originalAddMessage(self, msg, arg1, arg2, arg3, arg4, arg5)
end
end
DEFAULT_CHAT_FRAME.AddMessage = MTH_DebugFrame.addMessageHook
end
if _G and type(_G["ScriptErrors"]) == "function" then
MTH_DebugFrame.originalScriptErrors = _G["ScriptErrors"]
MTH_DebugFrame.scriptErrorsHook = function(msg)
if msg then
MTH_DebugFrame:AddError(tostring(msg))
end
if MTH_DebugFrame.originalScriptErrors then
return MTH_DebugFrame.originalScriptErrors(msg)
end
end
_G["ScriptErrors"] = MTH_DebugFrame.scriptErrorsHook
end
MTH_DebugFrame.hooksInstalled = true
end
local function MTH_DF_UninstallHooks()
if not MTH_DebugFrame.hooksInstalled then
return
end
if DEFAULT_CHAT_FRAME
and MTH_DebugFrame.addMessageHook
and DEFAULT_CHAT_FRAME.AddMessage == MTH_DebugFrame.addMessageHook
and MTH_DebugFrame.originalAddMessage
then
DEFAULT_CHAT_FRAME.AddMessage = MTH_DebugFrame.originalAddMessage
end
if _G
and MTH_DebugFrame.scriptErrorsHook
and _G["ScriptErrors"] == MTH_DebugFrame.scriptErrorsHook
and MTH_DebugFrame.originalScriptErrors
then
_G["ScriptErrors"] = MTH_DebugFrame.originalScriptErrors
end
MTH_DebugFrame.hooksInstalled = false
end
function MTH_DebugFrame:IsGlobalCaptureEnabled()
return MTH_DF_IsGlobalCaptureEnabled()
end
function MTH_DebugFrame:SetGlobalCaptureEnabled(enabled)
local want = enabled and true or false
MTH_DF_SetGlobalCaptureEnabled(want)
if want then
MTH_DF_InstallHooks()
else
MTH_DF_UninstallHooks()
end
end
MTH_DebugFrame.InstallHooks = MTH_DF_InstallHooks
MTH_DebugFrame.UninstallHooks = MTH_DF_UninstallHooks
-- Event listener for addon load
local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("ADDON_LOADED")
eventFrame:SetScript("OnEvent", function(self, event, addonName)
if event == "ADDON_LOADED" and addonName == "MetaHunt" then
if MTH and MTH.GetConfig then
MTH_DF_SetGlobalCaptureEnabled(MTH:GetConfig("debug", "globalErrorCapture", false))
end
MTH_DF_InstallHooks()
MTH_DebugFrame:Initialize()
if MTH and MTH.Print then
local mode = MTH_DF_IsGlobalCaptureEnabled() and "legacy-global" or "safe-local"
MTH:Print("=== MetaHunt Debug Frame Initialized (" .. mode .. ") ===", "debug")
end
end
end)
+390
View File
@@ -0,0 +1,390 @@
if type(MTH_HUNTERBOOK_TABS) ~= "table" then MTH_HUNTERBOOK_TABS = {} end
MTH_HUNTERBOOK_TABS.pets = {
headerLabel = "Beasts",
columnLabels = { "ID", "Lvl", "Family", "Name", "Abilities", "Zone", "R", "E", "U" },
columnLayout = {
{ x = 8, width = 28, align = "LEFT" },
{ x = 38, width = 30, align = "LEFT" },
{ x = 68, width = 74, align = "LEFT" },
{ x = 142, width = 130, align = "LEFT" },
{ x = 272, width = 180, align = "LEFT" },
{ x = 452, width = 68, align = "LEFT" },
{ x = 520, width = 10, align = "CENTER" },
{ x = 532, width = 10, align = "CENTER" },
{ x = 544, width = 10, align = "CENTER" },
},
}
function MTH_BOOK_GetBeastAbilitiesSummary(beast)
if not beast or not beast.abilities or beast.abilities == "" or beast.abilities == "None" then
return "-"
end
return tostring(beast.abilities)
end
function MTH_BOOK_GetBeastZoneSummary(beast)
if not beast or not beast.coords or table.getn(beast.coords) == 0 then
return "-"
end
local seen = {}
local zoneNames = {}
for i = 1, table.getn(beast.coords) do
local c = beast.coords[i]
if c and c[3] and not seen[c[3]] then
seen[c[3]] = true
table.insert(zoneNames, MTH_BOOK_GetZoneName(c[3]))
end
end
if table.getn(zoneNames) == 0 then return "-" end
if table.getn(zoneNames) == 1 then return zoneNames[1] end
return zoneNames[1] .. " +" .. tostring(table.getn(zoneNames) - 1)
end
function MTH_BOOK_SplitAbilities(text)
local out = {}
if not text or text == "" or text == "None" then return out end
local startPos = 1
local textLen = string.len(text)
while startPos <= textLen do
local commaPos = string.find(text, ",", startPos, true)
local token
if commaPos then
token = string.sub(text, startPos, commaPos - 1)
startPos = commaPos + 1
else
token = string.sub(text, startPos)
startPos = textLen + 1
end
token = string.gsub(token, "^%s+", "")
token = string.gsub(token, "%s+$", "")
if token ~= "" then
table.insert(out, token)
end
end
return out
end
function MTH_BOOK_HasUsableAbilities(beast)
if not beast then return false end
local abilities = MTH_BOOK_SplitAbilities(beast.abilities)
if table.getn(abilities) == 0 then return false end
for i = 1, table.getn(abilities) do
local token = MTH_BOOK_SafeLower(abilities[i])
if token ~= "" and token ~= "none" and token ~= "unknown" then
return true
end
end
return false
end
function MTH_BOOK_HasKnownLevel(beast)
if not beast then return false end
if MTH_BOOK_IsUnknownText(beast.lvl) then return false end
return MTH_BOOK_ParseLevel(beast.lvl) ~= nil
end
function MTH_BOOK_HasKnownZone(beast)
if not beast or not beast.coords or table.getn(beast.coords) == 0 then
return false
end
for i = 1, table.getn(beast.coords) do
local c = beast.coords[i]
if c and c[3] then
local zoneName = MTH_BOOK_SafeLower(MTH_BOOK_GetZoneName(c[3]))
if zoneName ~= "" and zoneName ~= "unknown" and string.sub(zoneName, 1, 5) ~= "zone " then
return true
end
end
end
return false
end
function MTH_BOOK_ParseAbilityToken(token)
local text = tostring(token or "")
text = string.gsub(text, "^%s+", "")
text = string.gsub(text, "%s+$", "")
if text == "" then return "", "", nil end
local _, _, ability, rank = string.find(text, "^(.-)%s*(%d+)$")
if ability and ability ~= "" then
ability = string.gsub(ability, "^%s+", "")
ability = string.gsub(ability, "%s+$", "")
if ability ~= "" then
return MTH_BOOK_SafeLower(ability), ability, tonumber(rank)
end
end
return MTH_BOOK_SafeLower(text), text, nil
end
function MTH_BOOK_BeastHasAbilityRank(beast, abilityValue, rankValue)
if not beast then return false end
local wantedAbility = MTH_BOOK_SafeLower(abilityValue)
if wantedAbility == "" or wantedAbility == "all" then return true end
local wantedRank = tonumber(rankValue)
local abilities = MTH_BOOK_SplitAbilities(beast.abilities)
for i = 1, table.getn(abilities) do
local abilityLower, _, rank = MTH_BOOK_ParseAbilityToken(abilities[i])
if abilityLower == wantedAbility then
if not wantedRank then
return true
end
if rank and rank == wantedRank then
return true
end
end
end
return false
end
function MTH_BOOK_ParseBeastTraits(beast)
local traits = {
rare = beast and beast.rare and true or false,
elite = beast and (beast.elite == true or beast.isElite == true) or false,
unique = beast and (beast.unique == true or beast.isUnique == true) or false,
}
if not beast then return traits end
local rank = MTH_BOOK_SafeLower(beast.rank)
if rank ~= "" then
if string.find(rank, "rare", 1, true) then traits.rare = true end
if string.find(rank, "elite", 1, true) then traits.elite = true end
if string.find(rank, "unique", 1, true) then traits.unique = true end
end
local cls = MTH_BOOK_SafeLower(beast.classification)
if cls ~= "" then
if string.find(cls, "rare", 1, true) then traits.rare = true end
if string.find(cls, "elite", 1, true) then traits.elite = true end
if string.find(cls, "unique", 1, true) then traits.unique = true end
end
local name = MTH_BOOK_SafeLower(beast.name)
if name ~= "" then
local scanPos = 1
while true do
local startPos, endPos = string.find(name, "%b()", scanPos)
if not startPos then break end
local token = string.sub(name, startPos, endPos)
scanPos = endPos + 1
local inner = string.sub(token, 2, -2)
inner = string.gsub(inner, "^%s+", "")
inner = string.gsub(inner, "%s+$", "")
local normalized = string.gsub(inner, "[%s%-_/]", "")
if inner == "rare" or normalized == "r" then traits.rare = true end
if inner == "elite" or normalized == "e" then traits.elite = true end
if inner == "unique" or normalized == "u" then traits.unique = true end
if string.find(inner, "rare", 1, true) then traits.rare = true end
if string.find(inner, "elite", 1, true) then traits.elite = true end
if string.find(inner, "unique", 1, true) then traits.unique = true end
if normalized ~= "" and string.find(normalized, "[^reu]", 1) == nil then
if string.find(normalized, "r", 1, true) then traits.rare = true end
if string.find(normalized, "e", 1, true) then traits.elite = true end
if string.find(normalized, "u", 1, true) then traits.unique = true end
end
end
end
return traits
end
function MTH_BOOK_BeastMatches(beastId, beast)
if not beast then return false end
local forcedBeastId = MTH_BOOK_STATE and tonumber(MTH_BOOK_STATE.forcedBeastId) or nil
if forcedBeastId and tonumber(beastId) ~= forcedBeastId then
return false
end
local beastLevel = MTH_BOOK_ParseLevel(beast.lvl)
local traits = MTH_BOOK_ParseBeastTraits(beast)
local familyLower = MTH_BOOK_SafeLower(beast.family or "")
if MTH_BOOK_STATE.minLevel and (not beastLevel or beastLevel < MTH_BOOK_STATE.minLevel) then return false end
if MTH_BOOK_STATE.maxLevel and (not beastLevel or beastLevel > MTH_BOOK_STATE.maxLevel) then return false end
if MTH_BOOK_STATE.flag1 and not traits.rare then return false end
if MTH_BOOK_STATE.flag2 and not traits.elite then return false end
if MTH_BOOK_STATE.flag3 and not traits.unique then return false end
if not MTH_BOOK_BeastHasAbilityRank(beast, MTH_BOOK_STATE.petAbility, MTH_BOOK_STATE.petRank) then return false end
if MTH_BOOK_STATE.petHideNoAbilities and not MTH_BOOK_HasUsableAbilities(beast) then return false end
if MTH_BOOK_STATE.petHideUnknown then
if familyLower == "unknown" then return false end
if not MTH_BOOK_HasKnownLevel(beast) then return false end
if not MTH_BOOK_HasKnownZone(beast) then return false end
end
if MTH_BOOK_STATE.quick ~= "all" then
if familyLower ~= MTH_BOOK_STATE.quick then
return false
end
end
if MTH_BOOK_STATE.petInZoneOnly then
local currentZoneId = MTH_BOOK_GetCurrentZoneId()
if currentZoneId and not MTH_BOOK_BeastHasZoneId(beast, currentZoneId) then
return false
end
end
if MTH_BOOK_STATE.search ~= "" then
local name = MTH_BOOK_SafeLower(beast.name)
local family = MTH_BOOK_SafeLower(beast.family)
local idText = tostring(beastId)
if string.find(name, MTH_BOOK_STATE.search, 1, true) == nil
and string.find(family, MTH_BOOK_STATE.search, 1, true) == nil
and string.find(idText, MTH_BOOK_STATE.search, 1, true) == nil
then
return false
end
end
return true
end
function MTH_BOOK_BeastSort(a, b)
local beasts = MTH_DS_Beasts
local ba = beasts and beasts[a]
local bb = beasts and beasts[b]
if not ba or not bb then return a < b end
local la = MTH_BOOK_ParseLevel(ba.lvl) or 0
local lb = MTH_BOOK_ParseLevel(bb.lvl) or 0
if la ~= lb then return la < lb end
local na = ba.name or ""
local nb = bb.name or ""
if na ~= nb then return na < nb end
return a < b
end
local function MTH_BOOKTAB_BeastLoreTrim(value)
local text = tostring(value or "")
text = string.gsub(text, "^%s+", "")
text = string.gsub(text, "%s+$", "")
return text
end
local function MTH_BOOKTAB_BeastLoreFindAbilityBundle(abilityName)
if not (MTH_DS_PetSpells and type(MTH_DS_PetSpells.byAbility) == "table") then
return nil
end
local direct = MTH_DS_PetSpells.byAbility[abilityName]
if type(direct) == "table" then
return direct
end
local wanted = MTH_BOOK_SafeLower(abilityName)
for key, bundle in pairs(MTH_DS_PetSpells.byAbility) do
if MTH_BOOK_SafeLower(key) == wanted then
return bundle
end
end
return nil
end
function MTH_BOOKTAB_BuildBeastAbilityEntries(beast)
local entries = {}
if type(beast) ~= "table" then
return entries
end
local tokens = MTH_BOOK_SplitAbilities(beast.abilities)
local seen = {}
for i = 1, table.getn(tokens) do
local rawToken = MTH_BOOKTAB_BeastLoreTrim(tokens[i])
if rawToken ~= "" then
local tokenKey = MTH_BOOK_SafeLower(rawToken)
if tokenKey ~= "" and not seen[tokenKey] then
seen[tokenKey] = true
local _, abilityLabel, requestedRank = MTH_BOOK_ParseAbilityToken(rawToken)
local bundle = MTH_BOOKTAB_BeastLoreFindAbilityBundle(abilityLabel)
local canonical = abilityLabel
if type(bundle) == "table" and type(bundle.ability) == "string" and bundle.ability ~= "" then
canonical = bundle.ability
end
local spellRows = (type(bundle) == "table" and type(bundle.spells) == "table") and bundle.spells or {}
local preferredSpell = nil
local firstBeastSpell = nil
local rankMin = nil
local rankMax = nil
local rankMap = {}
for s = 1, table.getn(spellRows) do
local spell = spellRows[s]
if type(spell) == "table" and MTH_BOOK_SafeLower(spell.learnMethod or "beast") ~= "trainer" then
if not firstBeastSpell then
firstBeastSpell = spell
end
local rank = tonumber(spell.rankNumber)
if rank and rank > 0 then
rankMap[rank] = true
if not rankMin or rank < rankMin then rankMin = rank end
if not rankMax or rank > rankMax then rankMax = rank end
if requestedRank and requestedRank == rank then
preferredSpell = spell
end
end
end
end
local rankCount = 0
for _ in pairs(rankMap) do
rankCount = rankCount + 1
end
if not preferredSpell then
preferredSpell = firstBeastSpell
end
local displayName = canonical
if requestedRank then
displayName = canonical .. " " .. tostring(requestedRank)
end
table.insert(entries, {
name = displayName,
tooltipName = canonical,
requestedRank = requestedRank,
icon = preferredSpell and preferredSpell.icon or nil,
description = preferredSpell and preferredSpell.description or "",
rankMin = rankMin,
rankMax = rankMax,
rankCount = rankCount,
})
end
end
end
return entries
end
function MTH_BOOKTAB_ShowBeastAbilityTooltip(anchor, abilityEntry)
if not anchor or type(abilityEntry) ~= "table" then return end
local tooltip = MTH_BOOK_GetSpellTooltip and MTH_BOOK_GetSpellTooltip() or GameTooltip
if not tooltip then return end
tooltip:SetOwner(anchor, "ANCHOR_RIGHT")
tooltip:ClearLines()
if MTH_BOOK_SetSpellTooltipIcon then
MTH_BOOK_SetSpellTooltipIcon(tooltip, abilityEntry.icon)
end
local title = tostring(abilityEntry.tooltipName or abilityEntry.name or "Unknown")
if abilityEntry.requestedRank then
title = title .. " " .. tostring(abilityEntry.requestedRank)
end
tooltip:AddLine(title, 1.00, 0.82, 0.00)
if abilityEntry.description and abilityEntry.description ~= "" then
tooltip:AddLine(" ")
tooltip:AddLine(tostring(abilityEntry.description), 0.90, 0.90, 0.90, true)
end
tooltip:Show()
end
+398
View File
@@ -0,0 +1,398 @@
if type(MTH_HUNTERBOOK_TABS) ~= "table" then MTH_HUNTERBOOK_TABS = {} end
MTH_HUNTERBOOK_TABS.families = {
headerLabel = "Families",
columnLabels = { "Family", "Named", "Coords", "Abilities", "Diet" },
columnLayout = {
{ x = 10, width = 106, align = "LEFT" },
{ x = 122, width = 52, align = "LEFT" },
{ x = 176, width = 52, align = "LEFT" },
{ x = 232, width = 292, align = "LEFT" },
{ x = 460, width = 184, align = "LEFT" },
},
}
local function MTH_BOOKTAB_FamiliesTrace(message)
return
end
local function MTH_BOOKTAB_FamiliesSafeLower(value)
return string.lower(tostring(value or ""))
end
local function MTH_BOOKTAB_FamiliesTrim(value)
local text = tostring(value or "")
text = string.gsub(text, "^%s+", "")
text = string.gsub(text, "%s+$", "")
return text
end
local function MTH_BOOKTAB_FindAbilityBundle(abilityName)
if not (MTH_DS_PetSpells and type(MTH_DS_PetSpells.byAbility) == "table") then
return nil
end
local direct = MTH_DS_PetSpells.byAbility[abilityName]
if type(direct) == "table" then
return direct
end
local wanted = MTH_BOOKTAB_FamiliesSafeLower(abilityName)
for key, bundle in pairs(MTH_DS_PetSpells.byAbility) do
if MTH_BOOKTAB_FamiliesSafeLower(key) == wanted then
return bundle
end
end
return nil
end
function MTH_BOOKTAB_BuildFamiliesRows()
local results = {}
local families = MTH_DS_Families
if type(families) ~= "table" then
return results
end
local abilityCounts = {}
local canonicalMap = {}
for _, familyRow in pairs(families) do
if type(familyRow) == "table" and type(familyRow.abilities) == "table" then
local seenInFamily = {}
for i = 1, table.getn(familyRow.abilities) do
local rawName = MTH_BOOKTAB_FamiliesTrim(familyRow.abilities[i])
if rawName ~= "" then
local bundle = MTH_BOOKTAB_FindAbilityBundle(rawName)
local canonical = rawName
if type(bundle) == "table" and type(bundle.ability) == "string" and bundle.ability ~= "" then
canonical = bundle.ability
end
local token = MTH_BOOKTAB_FamiliesSafeLower(canonical)
if token ~= "" and not seenInFamily[token] then
seenInFamily[token] = true
abilityCounts[token] = (abilityCounts[token] or 0) + 1
canonicalMap[token] = canonical
end
end
end
end
end
for familyName, familyRow in pairs(families) do
if type(familyRow) == "table" then
local abilities = {}
local seen = {}
if type(familyRow.abilities) == "table" then
for i = 1, table.getn(familyRow.abilities) do
local rawName = MTH_BOOKTAB_FamiliesTrim(familyRow.abilities[i])
if rawName ~= "" then
local bundle = MTH_BOOKTAB_FindAbilityBundle(rawName)
local canonical = rawName
if type(bundle) == "table" and type(bundle.ability) == "string" and bundle.ability ~= "" then
canonical = bundle.ability
end
local token = MTH_BOOKTAB_FamiliesSafeLower(canonical)
if token ~= "" and not seen[token] then
seen[token] = true
local spellRows = (type(bundle) == "table" and type(bundle.spells) == "table") and bundle.spells or {}
local firstBeastSpell = nil
local rankMin = nil
local rankMax = nil
local rankMap = {}
for s = 1, table.getn(spellRows) do
local spell = spellRows[s]
if type(spell) == "table" and MTH_BOOKTAB_FamiliesSafeLower(spell.learnMethod or "beast") ~= "trainer" then
if not firstBeastSpell then
firstBeastSpell = spell
end
local rank = tonumber(spell.rankNumber)
if rank and rank > 0 then
rankMap[rank] = true
if not rankMin or rank < rankMin then rankMin = rank end
if not rankMax or rank > rankMax then rankMax = rank end
end
end
end
local rankCount = 0
for _ in pairs(rankMap) do
rankCount = rankCount + 1
end
table.insert(abilities, {
name = canonicalMap[token] or canonical,
token = token,
isUnique = (abilityCounts[token] or 0) == 1,
icon = firstBeastSpell and firstBeastSpell.icon or nil,
description = firstBeastSpell and firstBeastSpell.description or "",
rankMin = rankMin,
rankMax = rankMax,
rankCount = rankCount,
})
end
end
end
end
table.sort(abilities, function(a, b)
if a.isUnique ~= b.isUnique then
return a.isUnique and true or false
end
return MTH_BOOKTAB_FamiliesSafeLower(a.name) < MTH_BOOKTAB_FamiliesSafeLower(b.name)
end)
local dietItems = {}
if type(familyRow.food) == "table" then
for i = 1, table.getn(familyRow.food) do
local food = MTH_BOOKTAB_FamiliesTrim(familyRow.food[i])
if food ~= "" then
table.insert(dietItems, string.upper(string.sub(food, 1, 1)) .. string.sub(food, 2))
end
end
table.sort(dietItems, function(a, b)
return MTH_BOOKTAB_FamiliesSafeLower(a) < MTH_BOOKTAB_FamiliesSafeLower(b)
end)
end
table.insert(results, {
family = tostring(familyName),
named = tonumber(familyRow.named) or 0,
coords = tonumber(familyRow.coords) or 0,
abilities = abilities,
dietText = table.concat(dietItems, ", "),
})
end
end
table.sort(results, function(a, b)
return MTH_BOOKTAB_FamiliesSafeLower(a.family) < MTH_BOOKTAB_FamiliesSafeLower(b.family)
end)
return results
end
function MTH_BOOKTAB_ShowFamilyAbilityTooltip(anchor, abilityEntry)
if not anchor or type(abilityEntry) ~= "table" then return end
local tooltip = MTH_BOOK_GetSpellTooltip and MTH_BOOK_GetSpellTooltip() or GameTooltip
if not tooltip then return end
tooltip:SetOwner(anchor, "ANCHOR_RIGHT")
tooltip:ClearLines()
if MTH_BOOK_SetSpellTooltipIcon then
MTH_BOOK_SetSpellTooltipIcon(tooltip, abilityEntry.icon)
end
tooltip:AddLine(tostring(abilityEntry.name or "Unknown"), 1.00, 0.82, 0.00)
tooltip:AddLine(" ")
tooltip:AddLine("Baseline", 1.00, 0.82, 0.00)
if abilityEntry.rankCount and abilityEntry.rankCount > 0 then
if abilityEntry.rankMin and abilityEntry.rankMax then
if abilityEntry.rankMin == abilityEntry.rankMax then
tooltip:AddDoubleLine("Ranks", tostring(abilityEntry.rankMin), 0.85, 0.85, 0.85, 1, 1, 1)
else
tooltip:AddDoubleLine("Ranks", tostring(abilityEntry.rankMin) .. "-" .. tostring(abilityEntry.rankMax), 0.85, 0.85, 0.85, 1, 1, 1)
end
end
else
tooltip:AddDoubleLine("Ranks", "Unranked", 0.85, 0.85, 0.85, 1, 1, 1)
end
tooltip:AddDoubleLine("Family-Unique", abilityEntry.isUnique and "Yes" or "No", 0.85, 0.85, 0.85, 1, 1, 1)
if abilityEntry.description and abilityEntry.description ~= "" then
tooltip:AddLine(" ")
tooltip:AddLine(tostring(abilityEntry.description), 0.90, 0.90, 0.90, true)
end
tooltip:Show()
end
function MTH_BOOKTAB_EnsureFamiliesUI()
if MTH_BOOK_STATE.familiesUI then return end
local listParent = getglobal("MTH_BOOK_ListBackdrop")
if not listParent then
MTH_BOOKTAB_FamiliesTrace("EnsureFamiliesUI failed: missing MTH_BOOK_ListBackdrop")
return
end
local ui = {}
ui.frame = CreateFrame("Frame", nil, listParent)
ui.frame:SetPoint("TOPLEFT", listParent, "TOPLEFT", 6, -6)
ui.frame:SetPoint("BOTTOMRIGHT", listParent, "BOTTOMRIGHT", -6, 6)
ui.frame:Hide()
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: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: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: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", 460, -2)
ui.headerDiet:SetTextColor(1.00, 0.82, 0.00)
ui.headerDiet:SetText("Diet")
ui.rows = {}
for i = 1, 28 do
local row = CreateFrame("Frame", nil, ui.frame)
row:SetHeight(18)
if i == 1 then
row:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 0, -18)
row:SetPoint("TOPRIGHT", ui.frame, "TOPRIGHT", 0, -18)
else
row:SetPoint("TOPLEFT", ui.rows[i - 1], "BOTTOMLEFT", 0, -1)
row:SetPoint("TOPRIGHT", ui.rows[i - 1], "BOTTOMRIGHT", 0, -1)
end
row.family = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.family:SetPoint("LEFT", row, "LEFT", 4, 0)
row.family:SetWidth(106)
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:SetJustifyH("LEFT")
row.coords = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.coords:SetPoint("LEFT", row, "LEFT", 176, 0)
row.coords:SetWidth(52)
row.coords:SetJustifyH("LEFT")
row.diet = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.diet:SetPoint("LEFT", row, "LEFT", 460, 0)
row.diet:SetWidth(184)
row.diet:SetJustifyH("LEFT")
row.abilityButtons = {}
for b = 1, 16 do
local button = CreateFrame("Button", nil, row)
button:SetHeight(14)
button.icon = button:CreateTexture(nil, "ARTWORK")
button.icon:SetPoint("LEFT", button, "LEFT", 0, 0)
button.icon:SetWidth(12)
button.icon:SetHeight(12)
button.text = button:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
button.text:SetPoint("LEFT", button.icon, "RIGHT", 2, 0)
button.text:SetJustifyH("LEFT")
button.text:SetTextColor(0.90, 0.90, 0.90)
button.entry = nil
button:SetScript("OnEnter", function()
if this and this.entry then
MTH_BOOKTAB_ShowFamilyAbilityTooltip(this, this.entry)
end
end)
button:SetScript("OnLeave", function()
if type(MTH_BOOK_HideSpellTooltip) == "function" then
MTH_BOOK_HideSpellTooltip()
end
end)
button:Hide()
row.abilityButtons[b] = button
end
row.divider = row:CreateTexture(nil, "BORDER")
row.divider:SetTexture("Interface\\Buttons\\WHITE8X8")
row.divider:SetVertexColor(0.28, 0.28, 0.28, 0.45)
row.divider:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT", 0, 0)
row.divider:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT", 0, 0)
row.divider:SetHeight(1)
ui.rows[i] = row
end
MTH_BOOK_STATE.familiesUI = ui
MTH_BOOKTAB_FamiliesTrace("EnsureFamiliesUI created rows=" .. tostring(table.getn(ui.rows or {})))
end
function MTH_BOOKTAB_SetFamiliesUIVisible(visible)
local ui = MTH_BOOK_STATE.familiesUI
if visible then
if not ui then
MTH_BOOKTAB_EnsureFamiliesUI()
ui = MTH_BOOK_STATE.familiesUI
end
if not ui then return end
else
if not ui then return end
end
local listParent = getglobal("MTH_BOOK_ListBackdrop")
local detailParent = getglobal("MTH_BOOK_DetailBackdrop")
local detailText = getglobal("MTH_BOOK_DetailBackdropDetailText")
local openMapButton = getglobal("MTH_BOOK_OpenMapButton")
if visible then
if listParent then listParent:Show() end
if detailParent then detailParent:Hide() end
if detailText then detailText:Hide() end
if openMapButton then openMapButton:Hide() end
ui.frame:Show()
else
ui.frame:Hide()
if listParent then listParent:Show() end
if detailParent then detailParent:Show() end
if detailText then detailText:Show() end
end
end
function MTH_BOOKTAB_RenderFamiliesList()
local ui = MTH_BOOK_STATE.familiesUI
if not ui then return end
local rows = MTH_BOOK_STATE.results or {}
MTH_BOOKTAB_FamiliesTrace("RenderFamiliesList rows=" .. tostring(table.getn(rows)))
for i = 1, table.getn(ui.rows) do
local rowFrame = ui.rows[i]
local row = rows[i]
if row then
rowFrame:Show()
rowFrame.family:SetText(tostring(row.family or "-"))
rowFrame.named:SetText(tostring(row.named or 0))
rowFrame.coords:SetText(tostring(row.coords or 0))
rowFrame.diet:SetText(tostring(row.dietText or "-"))
local offsetX = 232
for b = 1, table.getn(rowFrame.abilityButtons) do
local button = rowFrame.abilityButtons[b]
local ability = row.abilities and row.abilities[b] or nil
if ability then
local nameText = tostring(ability.name or "")
if b < table.getn(row.abilities) then
nameText = nameText .. ","
end
button.entry = ability
button.text:SetText(nameText)
local textW = button.text:GetStringWidth() or 40
if textW < 24 then textW = 24 end
button:SetWidth(14 + textW)
button:ClearAllPoints()
button:SetPoint("LEFT", rowFrame, "LEFT", offsetX, 0)
local iconPath = ability.icon and MTH_BOOK_ResolveIconPath and MTH_BOOK_ResolveIconPath(ability.icon) or nil
if iconPath then
button.icon:SetTexture(iconPath)
else
button.icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
end
button:Show()
offsetX = offsetX + button:GetWidth() + 6
else
button.entry = nil
button:Hide()
end
end
else
rowFrame:Hide()
end
end
end
+189
View File
@@ -0,0 +1,189 @@
if type(MTH_HUNTERBOOK_TABS) ~= "table" then MTH_HUNTERBOOK_TABS = {} end
MTH_HUNTERBOOK_TABS.npcs = {
headerLabel = "NPC Finder",
columnLabels = { "ID", "Name", "React", "Function", "Zone" },
columnLayout = {
{ x = 8, width = 52, align = "LEFT" },
{ x = 60, width = 140, align = "LEFT" },
{ x = 200, width = 40, align = "LEFT" },
{ x = 242, width = 160, align = "LEFT" },
{ x = 404, width = 148, align = "LEFT" },
},
}
function MTH_BOOKTAB_GetNPCReactBucket(fac)
local f = tostring(fac or "")
if f == "A" then return "A" end
if f == "H" then return "H" end
return "AH"
end
MTH_BOOK_GetNPCReactBucket = MTH_BOOKTAB_GetNPCReactBucket
function MTH_BOOKTAB_GetNPCFunctionLabel(key)
local map = {
vendor = "Ammo Vendor",
repair = "Repair",
stablemaster = "Stable Master",
huntertrainer = "Hunter Trainer",
pettrainer = "Pet Trainer",
}
return map[key] or (string.upper(string.sub(key, 1, 1)) .. string.sub(key, 2))
end
MTH_BOOK_GetNPCFunctionLabel = MTH_BOOKTAB_GetNPCFunctionLabel
function MTH_BOOKTAB_GetNPCFunctions(vendor)
local out = {}
local seen = {}
local meta = vendor and vendor.meta
if meta then
for key, value in pairs(meta) do
if type(key) == "string" and value and tostring(value) ~= "" then
local normalizedKey = MTH_BOOK_SafeLower(key)
if normalizedKey ~= "" and not seen[normalizedKey] then
seen[normalizedKey] = true
table.insert(out, normalizedKey)
end
end
end
end
table.sort(out, function(a, b)
return MTH_BOOK_SafeLower(MTH_BOOKTAB_GetNPCFunctionLabel(a)) < MTH_BOOK_SafeLower(MTH_BOOKTAB_GetNPCFunctionLabel(b))
end)
return out
end
MTH_BOOK_GetNPCFunctions = MTH_BOOKTAB_GetNPCFunctions
function MTH_BOOKTAB_GetNPCFunctionSummary(vendor)
local keys = MTH_BOOKTAB_GetNPCFunctions(vendor)
if table.getn(keys) == 0 then return "-", {} end
local labels = {}
for i = 1, table.getn(keys) do
table.insert(labels, MTH_BOOKTAB_GetNPCFunctionLabel(keys[i]))
end
return table.concat(labels, ", "), keys
end
MTH_BOOK_GetNPCFunctionSummary = MTH_BOOKTAB_GetNPCFunctionSummary
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
local parentName = MTH_BOOK_GetZoneName(row.parent)
if parentName and string.sub(parentName, 1, 5) ~= "Zone " then
return parentName, zoneName
end
end
return zoneName, "-"
end
function MTH_BOOKTAB_GetNPCZoneSummary(vendor)
if not vendor or not vendor.coords or table.getn(vendor.coords) == 0 then
return "-", "-", {}
end
local zones = {}
local zoneSet = {}
local firstZone = "-"
local firstSubzone = "-"
for i = 1, table.getn(vendor.coords) do
local c = vendor.coords[i]
if c and c[3] then
local zoneName, subzoneName = MTH_BOOKTAB_GetZoneAndSubzone(c[3])
if firstZone == "-" then
firstZone = zoneName
firstSubzone = subzoneName
end
if zoneName ~= "-" and not zoneSet[zoneName] then
zoneSet[zoneName] = true
table.insert(zones, zoneName)
end
end
end
return firstZone, firstSubzone, zones
end
MTH_BOOK_GetNPCZoneSummary = MTH_BOOKTAB_GetNPCZoneSummary
function MTH_BOOK_NPCHasZoneId(vendor, zoneId)
local zid = tonumber(zoneId)
if not zid or not vendor or not vendor.coords then
return false
end
for i = 1, table.getn(vendor.coords) do
local c = vendor.coords[i]
if c and tonumber(c[3]) == zid then
return true
end
end
return false
end
function MTH_BOOK_NPCMatches(npcId, vendor)
if not vendor then return false end
local react = MTH_BOOK_GetNPCReactBucket(vendor.fac)
if react == "A" and not MTH_BOOK_STATE.flag1 then return false end
if react == "H" and not MTH_BOOK_STATE.flag2 then return false end
if react == "AH" and not MTH_BOOK_STATE.flag3 then return false end
if MTH_BOOK_STATE.npcHideNoZone then
local firstZone = MTH_BOOK_GetNPCZoneSummary(vendor)
local zoneText = MTH_BOOK_SafeLower(firstZone)
if firstZone == "-" or zoneText == "" or zoneText == "unknown" or string.sub(zoneText, 1, 5) == "zone " then
return false
end
end
if MTH_BOOK_STATE.npcFunction and MTH_BOOK_STATE.npcFunction ~= "all" then
local hasFunction = false
local functions = MTH_BOOK_GetNPCFunctions(vendor)
for i = 1, table.getn(functions) do
if functions[i] == MTH_BOOK_STATE.npcFunction then
hasFunction = true
break
end
end
if not hasFunction then return false end
end
if MTH_BOOK_STATE.npcZone and MTH_BOOK_STATE.npcZone ~= "all" then
local _, _, zones = MTH_BOOK_GetNPCZoneSummary(vendor)
local hasZone = false
for i = 1, table.getn(zones) do
if zones[i] == MTH_BOOK_STATE.npcZone then
hasZone = true
break
end
end
if not hasZone then return false end
end
if MTH_BOOK_STATE.npcInZoneOnly then
local currentZoneId = MTH_BOOK_GetCurrentZoneId()
if currentZoneId and not MTH_BOOK_NPCHasZoneId(vendor, currentZoneId) then
return false
end
end
if MTH_BOOK_STATE.search ~= "" then
local functionSummary = MTH_BOOK_GetNPCFunctionSummary(vendor)
local firstZone, firstSubzone = MTH_BOOK_GetNPCZoneSummary(vendor)
local hay = MTH_BOOK_SafeLower(tostring(vendor.name or "") .. " " .. tostring(functionSummary or "") .. " " .. tostring(firstZone or "") .. " " .. tostring(firstSubzone or "") .. " " .. tostring(npcId))
if string.find(hay, MTH_BOOK_STATE.search, 1, true) == nil then
return false
end
end
return true
end
function MTH_BOOK_NPCSort(a, b)
local vendors = MTH_DS_Vendors
local va = vendors and vendors[a]
local vb = vendors and vendors[b]
if not va or not vb then return a < b end
local na = MTH_BOOK_SafeLower(va.name)
local nb = MTH_BOOK_SafeLower(vb.name)
if na ~= nb then return na < nb end
return a < b
end
+903
View File
@@ -0,0 +1,903 @@
if type(MTH_HUNTERBOOK_TABS) ~= "table" then MTH_HUNTERBOOK_TABS = {} end
MTH_HUNTERBOOK_TABS.petabilities = {
headerLabel = "Pet Abilities",
columnLabels = { "Ability Name", "Ranks", "Families" },
columnLayout = {
{ x = 8, width = 160, align = "LEFT" },
{ x = 170, width = 52, align = "LEFT" },
{ x = 224, width = 328, align = "LEFT" },
},
}
MTH_HUNTERBOOK_TABS.abilities = {
headerLabel = "Pet Abilities",
columnLabels = { "Ability", "Lvl", "Family", "Beast", "Rare" },
columnLayout = {
{ x = 8, width = 112, align = "LEFT" },
{ x = 120, width = 40, align = "LEFT" },
{ x = 160, width = 78, align = "LEFT" },
{ x = 238, width = 142, align = "LEFT" },
{ x = 380, width = 38, align = "LEFT" },
},
}
function MTH_BOOK_AbilitySort(a, b)
if a.ability ~= b.ability then return a.ability < b.ability end
if a.level ~= b.level then return a.level < b.level end
if a.name ~= b.name then return a.name < b.name end
return a.beastId < b.beastId
end
function MTH_BOOK_PetAbilitySort(a, b)
local aa = MTH_BOOK_SafeLower(a and a.ability)
local ab = MTH_BOOK_SafeLower(b and b.ability)
if aa ~= ab then return aa < ab end
local ra = tonumber(a and a.rankCount) or 0
local rb = tonumber(b and b.rankCount) or 0
if ra ~= rb then return ra > rb end
local fa = tonumber(a and a.familyCount) or 0
local fb = tonumber(b and b.familyCount) or 0
if fa ~= fb then return fa > fb end
return tostring(a and a.familyList or "") < tostring(b and b.familyList or "")
end
function MTH_BOOK_GetSpellRowsForAbility(abilityName)
if not abilityName or abilityName == "" then return 0 end
if not (MTH_DS_PetSpells and MTH_DS_PetSpells.byAbility) then return 0 end
local row = MTH_DS_PetSpells.byAbility[abilityName]
if not row or not row.spells then return 0 end
return table.getn(row.spells)
end
function MTH_BOOK_GetSpellCoverageForAbility(abilityName)
local coverage = {
ability = abilityName,
icon = nil,
spellRows = 0,
hasBeastLearned = false,
hasTrainerLearned = false,
rankCount = 0,
minRank = nil,
maxRank = nil,
minTrainLevel = nil,
maxTrainLevel = nil,
}
if not abilityName or abilityName == "" then return coverage end
if not (MTH_DS_PetSpells and MTH_DS_PetSpells.byAbility) then return coverage end
local row = MTH_DS_PetSpells.byAbility[abilityName]
if not row or not row.spells then return coverage end
local rankMap = {}
local hasPositiveRank = false
for i = 1, table.getn(row.spells) do
local spell = row.spells[i]
if spell then
local rankNumber = tonumber(spell.rankNumber)
if rankNumber and rankNumber > 0 then
hasPositiveRank = true
break
end
end
end
coverage.spellRows = 0
for i = 1, table.getn(row.spells) do
local spell = row.spells[i]
if spell then
local rankNumber = tonumber(spell.rankNumber)
if hasPositiveRank and ((not rankNumber) or rankNumber <= 0) then
-- Ignore legacy/unranked rows when proper ranked variants exist.
else
coverage.spellRows = coverage.spellRows + 1
local learnMethod = MTH_BOOK_SafeLower(spell.learnMethod)
if learnMethod == "trainer" then
coverage.hasTrainerLearned = true
else
coverage.hasBeastLearned = true
end
if not coverage.icon and spell.icon and spell.icon ~= "" then
coverage.icon = spell.icon
end
if rankNumber and rankNumber > 0 then
rankMap[rankNumber] = true
if not coverage.minRank or rankNumber < coverage.minRank then coverage.minRank = rankNumber end
if not coverage.maxRank or rankNumber > coverage.maxRank then coverage.maxRank = rankNumber end
end
local trainLevel = tonumber(spell.trainLevel)
if trainLevel then
if not coverage.minTrainLevel or trainLevel < coverage.minTrainLevel then coverage.minTrainLevel = trainLevel end
if not coverage.maxTrainLevel or trainLevel > coverage.maxTrainLevel then coverage.maxTrainLevel = trainLevel end
end
end
end
end
for _ in pairs(rankMap) do
coverage.rankCount = coverage.rankCount + 1
end
if coverage.rankCount == 0 and coverage.spellRows > 0 then
coverage.rankCount = 1
end
return coverage
end
function MTH_BOOK_GetKnownSpellMap()
if not MTH_CharSavedVariables then return nil end
if not MTH_CharSavedVariables.petTraining then return nil end
local petTraining = MTH_CharSavedVariables.petTraining
if petTraining.hunterKnownMap and petTraining.spellMap then
for token, isKnown in pairs(petTraining.hunterKnownMap) do
if isKnown == true then
local _, _, baseToken = string.find(tostring(token), "^(.-)#%d+$")
if baseToken and baseToken ~= "" then
local baseRow = petTraining.spellMap[baseToken]
if baseRow and ((tonumber(baseRow.rankNumber) or 0) <= 0) then
baseRow.isKnown = true
end
end
end
end
end
return MTH_CharSavedVariables.petTraining.spellMap
end
function MTH_BOOK_GetKnownRankCountForAbility(abilityName)
local spellMap = MTH_BOOK_GetKnownSpellMap()
if not spellMap or not abilityName or abilityName == "" then return 0 end
local abilityLower = MTH_BOOK_SafeLower(abilityName)
if abilityLower == "" then return 0 end
local knownRanks = {}
for _, row in pairs(spellMap) do
if row and row.name and (row.isKnown ~= false) then
if MTH_BOOK_SafeLower(row.name) == abilityLower then
local rankNumber = tonumber(row.rankNumber) or 0
knownRanks[rankNumber] = true
end
end
end
local count = 0
for _ in pairs(knownRanks) do
count = count + 1
end
return count
end
function MTH_BOOK_IsKnownRankForAbility(abilityName, rankNumber)
local spellMap = MTH_BOOK_GetKnownSpellMap()
if not spellMap then return false end
if not abilityName or abilityName == "" then return false end
local rankValue = tonumber(rankNumber)
local abilityLower = MTH_BOOK_SafeLower(abilityName)
for _, row in pairs(spellMap) do
if row and row.name and (row.isKnown ~= false) then
if MTH_BOOK_SafeLower(row.name) == abilityLower then
local rowRank = tonumber(row.rankNumber)
if rankValue and rankValue > 0 then
if rowRank == rankValue then
return true
end
else
return true
end
end
end
end
return false
end
function MTH_BOOK_BuildPetAbilitiesRows(ignoreSearch)
local spellRows = {}
if MTH_DS_PetSpells and MTH_DS_PetSpells.byAbility then
for abilityName in pairs(MTH_DS_PetSpells.byAbility) do
local ability = tostring(abilityName or "")
if ability ~= "" then
local key = MTH_BOOK_SafeLower(ability)
if key ~= "" then
spellRows[key] = {
ability = ability,
familiesMap = {},
}
end
end
end
end
local function ensureRow(abilityName)
local ability = tostring(abilityName or "")
if ability == "" then return nil end
local key = MTH_BOOK_SafeLower(ability)
if key == "" then return nil end
return spellRows[key], key
end
if MTH_DS_Families then
for familyName, row in pairs(MTH_DS_Families) do
if familyName and familyName ~= "" and row and row.abilities then
for i = 1, table.getn(row.abilities) do
local ability = row.abilities[i]
local spellRow = ensureRow(ability)
if spellRow then
spellRow.familiesMap[familyName] = true
end
end
end
end
end
if MTH_DS_Beasts then
for _, beast in pairs(MTH_DS_Beasts) do
if beast and beast.family and beast.family ~= "" then
local tokens = MTH_BOOK_SplitAbilities(beast.abilities)
for i = 1, table.getn(tokens) do
local _, abilityName = MTH_BOOK_ParseAbilityToken(tokens[i])
local spellRow = ensureRow(abilityName)
if spellRow then
spellRow.familiesMap[beast.family] = true
end
end
end
end
end
local results = {}
for _, row in pairs(spellRows) do
local coverage = MTH_BOOK_GetSpellCoverageForAbility(row.ability)
local families = {}
for familyName in pairs(row.familiesMap or {}) do
table.insert(families, familyName)
end
table.sort(families, function(a, b)
return MTH_BOOK_SafeLower(a) < MTH_BOOK_SafeLower(b)
end)
local rankCount = tonumber(coverage.rankCount) or 0
local knownRankCount = MTH_BOOK_GetKnownRankCountForAbility(row.ability)
local minRank = coverage.minRank
local maxRank = coverage.maxRank
local minTrainLevel = coverage.minTrainLevel
local maxTrainLevel = coverage.maxTrainLevel
if MTH_BOOK_STATE.petOnlyMyLevel then
knownRankCount, rankCount, minRank, maxRank, minTrainLevel, maxTrainLevel = MTH_BOOK_GetScopedRankSummary(row.ability)
end
if knownRankCount > rankCount then knownRankCount = rankCount end
local rankText = tostring(knownRankCount) .. "/" .. tostring(rankCount)
local sourceFilter = tostring(MTH_BOOK_STATE.petLearnSource or "all")
local sourceMatch = true
if sourceFilter == "beast" then
sourceMatch = coverage.hasBeastLearned and true or false
elseif sourceFilter == "trainer" then
sourceMatch = coverage.hasTrainerLearned and true or false
end
local familyList = table.concat(families, ", ")
local searchable = MTH_BOOK_SafeLower(tostring(row.ability or "") .. " " .. familyList)
if sourceMatch and rankCount > 0 and (ignoreSearch or MTH_BOOK_STATE.search == "" or string.find(searchable, MTH_BOOK_STATE.search, 1, true) ~= nil) then
table.insert(results, {
ability = row.ability,
icon = coverage.icon,
knownRankCount = knownRankCount,
rankCount = rankCount,
rankText = rankText,
families = families,
familyCount = table.getn(families),
familyList = familyList,
spellRows = coverage.spellRows or 0,
minRank = minRank,
maxRank = maxRank,
minTrainLevel = minTrainLevel,
maxTrainLevel = maxTrainLevel,
hasBeastLearned = coverage.hasBeastLearned,
hasTrainerLearned = coverage.hasTrainerLearned,
})
end
end
table.sort(results, MTH_BOOK_PetAbilitySort)
return results
end
function MTH_BOOK_BuildPetAbilityRankRows(entry)
local rows = {}
if not entry or not entry.ability then return rows end
if not (MTH_DS_PetSpells and MTH_DS_PetSpells.byAbility) then return rows end
local spellBucket = MTH_DS_PetSpells.byAbility[entry.ability]
if not spellBucket or not spellBucket.spells then return rows end
local hasPositiveRank = false
for i = 1, table.getn(spellBucket.spells) do
local spell = spellBucket.spells[i]
if spell then
local rankNumber = tonumber(spell.rankNumber)
if rankNumber and rankNumber > 0 then
hasPositiveRank = true
break
end
end
end
for i = 1, table.getn(spellBucket.spells) do
local spell = spellBucket.spells[i]
if spell then
local rankNumber = tonumber(spell.rankNumber)
if not rankNumber and spell.rank then
local _, _, parsed = string.find(tostring(spell.rank), "(%d+)")
rankNumber = tonumber(parsed)
end
if hasPositiveRank and ((not rankNumber) or rankNumber <= 0) then
-- Ignore legacy/unranked rows when proper ranked variants exist.
elseif MTH_BOOK_IsSpellInLevelScope(tonumber(spell.trainLevel)) then
table.insert(rows, {
id = spell.id,
ability = entry.ability,
icon = spell.icon or spell.iconName or spell.texture or spell.iconTexture or entry.icon,
rankNumber = rankNumber,
isKnown = MTH_BOOK_IsKnownRankForAbility(entry.ability, rankNumber),
trainLevel = tonumber(spell.trainLevel),
description = spell.description,
cost = spell.cost,
castTime = spell.castTime,
range = spell.range,
school = spell.school,
effects = spell.effects,
sourceUrl = spell.sourceUrl,
})
end
end
end
table.sort(rows, function(a, b)
local ra = a.rankNumber or 0
local rb = b.rankNumber or 0
if ra ~= rb then return ra < rb end
local la = a.trainLevel or 0
local lb = b.trainLevel or 0
if la ~= lb then return la < lb end
return (tonumber(a.id) or 0) < (tonumber(b.id) or 0)
end)
return rows
end
function MTH_BOOK_GetRankLabel(rankNumber, includePrefix)
local rankValue = tonumber(rankNumber)
if rankValue and rankValue > 0 then
if includePrefix then
return "Rank " .. tostring(rankValue)
end
return tostring(rankValue)
end
if includePrefix then
return "Unranked"
end
return "None"
end
function MTH_BOOKTAB_EnsurePetAbilitiesUI(listParent, detailParent)
if MTH_BOOK_STATE.petUI then return end
if not listParent or not detailParent then return end
local ui = {}
ui.leftPane = CreateFrame("Frame", nil, listParent)
ui.leftPane:SetPoint("TOPLEFT", listParent, "TOPLEFT", 4, -4)
ui.leftPane:SetWidth(162)
ui.leftPane:SetHeight(346)
ui.rightPane = CreateFrame("Frame", nil, listParent)
ui.rightPane:SetPoint("TOPLEFT", ui.leftPane, "TOPRIGHT", 8, 0)
ui.rightPane:SetWidth(390)
ui.rightPane:SetHeight(346)
ui.vSplit = listParent:CreateTexture(nil, "BORDER")
ui.vSplit:SetTexture("Interface\\Buttons\\WHITE8X8")
ui.vSplit:SetVertexColor(0.30, 0.30, 0.30, 0.60)
ui.vSplit:SetPoint("TOPLEFT", ui.leftPane, "TOPRIGHT", 4, -2)
ui.vSplit:SetPoint("BOTTOMLEFT", ui.leftPane, "BOTTOMRIGHT", 4, 2)
ui.vSplit:SetWidth(1)
ui.leftHeaderAbility = ui.leftPane:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.leftHeaderAbility:SetPoint("TOPLEFT", ui.leftPane, "TOPLEFT", 24, -6)
ui.leftHeaderAbility:SetWidth(102)
ui.leftHeaderAbility:SetJustifyH("LEFT")
ui.leftHeaderAbility:SetTextColor(1.00, 0.82, 0.00)
ui.leftHeaderAbility:SetText("Ability")
ui.leftHeaderRank = ui.leftPane:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.leftHeaderRank:SetPoint("TOPLEFT", ui.leftPane, "TOPLEFT", 126, -6)
ui.leftHeaderRank:SetWidth(28)
ui.leftHeaderRank:SetJustifyH("LEFT")
ui.leftHeaderRank:SetTextColor(1.00, 0.82, 0.00)
ui.leftHeaderRank:SetText("K/T")
ui.rightHeaderRank = ui.rightPane:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.rightHeaderRank:SetPoint("TOPLEFT", ui.rightPane, "TOPLEFT", 2, -6)
ui.rightHeaderRank:SetWidth(36)
ui.rightHeaderRank:SetJustifyH("LEFT")
ui.rightHeaderRank:SetTextColor(1.00, 0.82, 0.00)
ui.rightHeaderRank:SetText("Rank")
ui.rightHeaderLevel = ui.rightPane:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.rightHeaderKnown = ui.rightPane:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.rightHeaderKnown:SetPoint("TOPLEFT", ui.rightPane, "TOPLEFT", 34, -6)
ui.rightHeaderKnown:SetWidth(22)
ui.rightHeaderKnown:SetJustifyH("LEFT")
ui.rightHeaderKnown:SetTextColor(1.00, 0.82, 0.00)
ui.rightHeaderKnown:SetText("K?")
ui.rightHeaderLevel:SetPoint("TOPLEFT", ui.rightPane, "TOPLEFT", 58, -6)
ui.rightHeaderLevel:SetWidth(40)
ui.rightHeaderLevel:SetJustifyH("LEFT")
ui.rightHeaderLevel:SetTextColor(1.00, 0.82, 0.00)
ui.rightHeaderLevel:SetText("Lvl")
ui.rightHeaderDesc = ui.rightPane:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.rightHeaderDesc:SetPoint("TOPLEFT", ui.rightPane, "TOPLEFT", 98, -6)
ui.rightHeaderDesc:SetWidth(286)
ui.rightHeaderDesc:SetJustifyH("LEFT")
ui.rightHeaderDesc:SetTextColor(1.00, 0.82, 0.00)
ui.rightHeaderDesc:SetText("Description")
ui.leftRowsVisible = 20
ui.leftScroll = CreateFrame("Slider", "MTH_BOOK_PetLeftScroll", ui.leftPane, "UIPanelScrollBarTemplate")
ui.leftScroll:SetPoint("TOPRIGHT", ui.leftPane, "TOPRIGHT", -2, -22)
ui.leftScroll:SetPoint("BOTTOMRIGHT", ui.leftPane, "BOTTOMRIGHT", -2, 20)
ui.leftScroll:SetWidth(16)
if not getglobal("MTH_BOOK_PetLeftScrollScrollFrame") then
local leftScrollShim = CreateFrame("ScrollFrame", "MTH_BOOK_PetLeftScrollScrollFrame", ui.leftPane)
leftScrollShim:SetPoint("TOPLEFT", ui.leftPane, "TOPLEFT", 0, 0)
leftScrollShim:SetWidth(1)
leftScrollShim:SetHeight(1)
leftScrollShim:Hide()
end
ui.leftScroll:SetScript("OnValueChanged", function()
local val = this:GetValue() or 0
val = math.floor(val + 0.5)
if val ~= MTH_BOOK_STATE.petLeftOffset then
MTH_BOOK_STATE.petLeftOffset = val
MTH_BOOK_UpdatePetAbilitiesLists()
end
end)
ui.leftScroll:SetMinMaxValues(0, 0)
ui.leftScroll:SetValueStep(1)
ui.leftScroll:SetValue(0)
ui.leftPane:EnableMouseWheel(true)
ui.leftPane:SetScript("OnMouseWheel", function()
local delta = arg1 or 0
if delta == 0 then return end
local current = tonumber(MTH_BOOK_STATE.petLeftOffset or 0) or 0
if delta < 0 then
current = current + 1
else
current = current - 1
end
if current < 0 then current = 0 end
local maxOffset = 0
if MTH_BOOK_STATE.results then
maxOffset = math.max(0, table.getn(MTH_BOOK_STATE.results) - table.getn(ui.leftButtons or {}))
end
if current > maxOffset then current = maxOffset end
MTH_BOOK_STATE.petLeftOffset = current
if ui.leftScroll then
ui.leftScroll:SetValue(current)
end
MTH_BOOK_UpdatePetAbilitiesLists()
end)
ui.leftButtons = {}
local leftY = -24
for i = 1, ui.leftRowsVisible do
local btn = CreateFrame("Button", nil, ui.leftPane)
btn:SetPoint("TOPLEFT", ui.leftPane, "TOPLEFT", 0, leftY)
btn:SetWidth(144)
btn:SetHeight(19)
btn.entry = nil
btn.selected = btn:CreateTexture(nil, "BACKGROUND")
btn.selected:SetAllPoints(btn)
btn.selected:SetTexture("Interface\\Buttons\\WHITE8X8")
btn.selected:SetVertexColor(0.18, 0.35, 0.55, 0.35)
btn.selected:Hide()
local hl = btn:CreateTexture(nil, "HIGHLIGHT")
hl:SetAllPoints(btn)
hl:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
hl:SetBlendMode("ADD")
hl:SetAlpha(0.30)
btn.ability = btn:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
btn.ability:SetPoint("LEFT", btn, "LEFT", 24, 0)
btn.ability:SetWidth(100)
btn.ability:SetJustifyH("LEFT")
btn.rank = btn:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
btn.rank:SetPoint("LEFT", btn, "LEFT", 126, 0)
btn.rank:SetWidth(26)
btn.rank:SetJustifyH("LEFT")
btn.icon = btn:CreateTexture(nil, "ARTWORK")
btn.icon:SetPoint("LEFT", btn, "LEFT", 2, 0)
btn.icon:SetWidth(16)
btn.icon:SetHeight(16)
btn:SetScript("OnClick", function()
if not this.entry then return end
MTH_BOOK_STATE.selectedEntry = this.entry
MTH_BOOK_STATE.selectedPetRankEntry = nil
MTH_BOOK_STATE.petRankRows = MTH_BOOK_BuildPetAbilityRankRows(this.entry)
MTH_BOOK_UpdateResults()
end)
btn:SetScript("OnEnter", function()
if this and this.entry then
MTH_BOOK_ShowBaselineTooltip(this, this.entry)
end
end)
btn:SetScript("OnLeave", function()
MTH_BOOK_HideSpellTooltip()
end)
ui.leftButtons[i] = btn
leftY = leftY - 20
end
local footerParent = listParent:GetParent() or listParent
ui.recapText = footerParent:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.recapText:SetPoint("TOPLEFT", footerParent, "TOPLEFT", 20, -530)
ui.recapText:SetWidth(190)
ui.recapText:SetJustifyH("LEFT")
ui.recapText:SetTextColor(1.00, 0.82, 0.00)
ui.recapText:SetText("Abilities 0 | Known 0/0")
ui.progressBackdrop = CreateFrame("Frame", nil, footerParent)
ui.progressBackdrop:SetPoint("LEFT", ui.recapText, "RIGHT", 8, 0)
ui.progressBackdrop:SetWidth(320)
ui.progressBackdrop:SetHeight(14)
ui.progressBackdrop:SetFrameLevel(footerParent:GetFrameLevel() + 3)
ui.progressBackdrop:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true,
tileSize = 8,
edgeSize = 8,
insets = { left = 2, right = 2, top = 2, bottom = 2 },
})
ui.progressBackdrop:SetBackdropColor(0.08, 0.08, 0.08, 0.95)
ui.progressBackdrop:SetBackdropBorderColor(0.24, 0.24, 0.24, 0.90)
ui.progressBar = CreateFrame("StatusBar", nil, ui.progressBackdrop)
ui.progressBar:SetPoint("TOPLEFT", ui.progressBackdrop, "TOPLEFT", 2, -2)
ui.progressBar:SetPoint("BOTTOMRIGHT", ui.progressBackdrop, "BOTTOMRIGHT", -2, 2)
ui.progressBar:SetStatusBarTexture("Interface\\Buttons\\WHITE8X8")
ui.progressBar:SetStatusBarColor(0.18, 0.75, 0.22, 0.9)
ui.progressBar:SetMinMaxValues(0, 1)
ui.progressBar:SetValue(0)
ui.progressText = ui.progressBackdrop:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
ui.progressText:SetPoint("CENTER", ui.progressBackdrop, "CENTER", 0, 0)
ui.progressText:SetTextColor(1.00, 1.00, 1.00)
ui.progressText:SetText("0%")
ui.rightButtons = {}
local rightRowsVisible = 10
local rightRowHeight = 36
local rightRowStep = 37
local rightY = -24
for i = 1, rightRowsVisible do
local btn = CreateFrame("Button", nil, ui.rightPane)
btn:SetPoint("TOPLEFT", ui.rightPane, "TOPLEFT", 0, rightY)
btn:SetWidth(388)
btn:SetHeight(rightRowHeight)
btn.entry = nil
btn.selected = btn:CreateTexture(nil, "BACKGROUND")
btn.selected:SetAllPoints(btn)
btn.selected:SetTexture("Interface\\Buttons\\WHITE8X8")
btn.selected:SetVertexColor(0.18, 0.35, 0.55, 0.35)
btn.selected:Hide()
local hl = btn:CreateTexture(nil, "HIGHLIGHT")
hl:SetAllPoints(btn)
hl:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
hl:SetBlendMode("ADD")
hl:SetAlpha(0.28)
btn.rank = btn:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
btn.rank:SetPoint("LEFT", btn, "LEFT", 2, 1)
btn.rank:SetWidth(30)
btn.rank:SetJustifyH("LEFT")
btn.knownIcon = btn:CreateTexture(nil, "ARTWORK")
btn.knownIcon:SetPoint("LEFT", btn, "LEFT", 38, 1)
btn.knownIcon:SetWidth(12)
btn.knownIcon:SetHeight(12)
btn.knownIcon:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
btn.knownIcon:SetVertexColor(0.18, 0.90, 0.18, 1.00)
btn.knownIcon:Hide()
btn.level = btn:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
btn.level:SetPoint("LEFT", btn, "LEFT", 58, 1)
btn.level:SetWidth(40)
btn.level:SetJustifyH("LEFT")
btn.desc = btn:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
btn.desc:SetPoint("LEFT", btn, "LEFT", 98, 1)
btn.desc:SetWidth(286)
btn.desc:SetJustifyH("LEFT")
btn:SetScript("OnClick", function()
if not this.entry then return end
MTH_BOOK_STATE.selectedPetRankEntry = this.entry
MTH_BOOK_UpdateDetail()
MTH_BOOK_UpdateResults()
end)
btn:SetScript("OnEnter", function()
if this and this.entry then
MTH_BOOK_ShowRankTooltip(this, this.entry)
end
end)
btn:SetScript("OnLeave", function()
MTH_BOOK_HideSpellTooltip()
end)
ui.rightButtons[i] = btn
rightY = rightY - rightRowStep
end
ui.detailTitleTop = detailParent:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.detailTitleTop:SetPoint("TOPLEFT", detailParent, "TOPLEFT", 8, -8)
ui.detailTitleTop:SetWidth(120)
ui.detailTitleTop:SetJustifyH("LEFT")
ui.detailTitleTop:SetTextColor(1.00, 0.82, 0.00)
ui.detailTitleTop:SetText("Spell baseline")
ui.detailTop = CreateFrame("EditBox", nil, detailParent)
ui.detailTop:SetMultiLine(true)
ui.detailTop:SetAutoFocus(false)
ui.detailTop:SetPoint("TOPLEFT", ui.detailTitleTop, "BOTTOMLEFT", 0, -4)
ui.detailTop:SetWidth(120)
ui.detailTop:SetHeight(132)
ui.detailTopIcon = detailParent:CreateTexture(nil, "ARTWORK")
ui.detailTopIcon:SetPoint("TOPRIGHT", ui.detailTop, "TOPRIGHT", -4, -4)
ui.detailTopIcon:SetWidth(16)
ui.detailTopIcon:SetHeight(16)
ui.detailTopIcon:Hide()
ui.detailTitleBottom = detailParent:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.detailTitleBottom:SetPoint("TOPLEFT", ui.detailTop, "BOTTOMLEFT", 0, -8)
ui.detailTitleBottom:SetWidth(120)
ui.detailTitleBottom:SetJustifyH("LEFT")
ui.detailTitleBottom:SetTextColor(1.00, 0.82, 0.00)
ui.detailTitleBottom:SetText("Current Rank")
ui.detailBottom = CreateFrame("EditBox", nil, detailParent)
ui.detailBottom:SetMultiLine(true)
ui.detailBottom:SetAutoFocus(false)
ui.detailBottom:SetPoint("TOPLEFT", ui.detailTitleBottom, "BOTTOMLEFT", 0, -4)
ui.detailBottom:SetWidth(120)
ui.detailBottom:SetHeight(116)
MTH_BOOK_ConfigureReadOnlyEditBox(ui.detailTop)
MTH_BOOK_ConfigureReadOnlyEditBox(ui.detailBottom)
if ui.detailTop.SetTextColor then ui.detailTop:SetTextColor(1.00, 1.00, 1.00) end
if ui.detailBottom.SetTextColor then ui.detailBottom:SetTextColor(1.00, 1.00, 1.00) end
MTH_BOOK_STATE.petUI = ui
end
function MTH_BOOKTAB_SetPetAbilitiesUIVisible(visible)
local ui = MTH_BOOK_STATE.petUI
if not ui then return end
local detail = getglobal("MTH_BOOK_DetailBackdropDetailText")
if detail then
if visible then detail:Hide() else detail:Show() end
end
local widgets = {
ui.leftPane,
ui.leftScroll,
ui.rightPane,
ui.vSplit,
ui.leftHeaderAbility,
ui.leftHeaderRank,
ui.rightHeaderRank,
ui.rightHeaderKnown,
ui.rightHeaderLevel,
ui.rightHeaderDesc,
ui.recapText,
ui.progressBackdrop,
ui.detailTop,
ui.detailBottom,
ui.detailTitleTop,
ui.detailTitleBottom,
}
for i = 1, table.getn(widgets) do
local widget = widgets[i]
if widget and widget.Show and widget.Hide then
if visible then widget:Show() else widget:Hide() end
end
end
if ui.detailTopIcon and not visible then
ui.detailTopIcon:Hide()
end
if not visible then
MTH_BOOK_HideSpellTooltip()
end
end
function MTH_BOOKTAB_UpdatePetAbilitiesLists()
local ui = MTH_BOOK_STATE.petUI
if not ui then return end
local resultCount = table.getn(MTH_BOOK_STATE.results)
local visibleRows = table.getn(ui.leftButtons)
local maxOffset = math.max(0, resultCount - visibleRows)
if MTH_BOOK_STATE.petLeftOffset < 0 then MTH_BOOK_STATE.petLeftOffset = 0 end
if MTH_BOOK_STATE.petLeftOffset > maxOffset then MTH_BOOK_STATE.petLeftOffset = maxOffset end
if ui.leftScroll then
ui.leftScroll:SetMinMaxValues(0, maxOffset)
ui.leftScroll:SetValueStep(1)
ui.leftScroll:SetValue(MTH_BOOK_STATE.petLeftOffset)
local upButton = getglobal("MTH_BOOK_PetLeftScrollScrollUpButton")
local downButton = getglobal("MTH_BOOK_PetLeftScrollScrollDownButton")
if upButton then
if MTH_BOOK_STATE.petLeftOffset > 0 then
upButton:EnableMouse(true)
upButton:SetAlpha(1)
else
upButton:EnableMouse(false)
upButton:SetAlpha(0.35)
end
end
if downButton then
if MTH_BOOK_STATE.petLeftOffset < maxOffset then
downButton:EnableMouse(true)
downButton:SetAlpha(1)
else
downButton:EnableMouse(false)
downButton:SetAlpha(0.35)
end
end
if maxOffset > 0 then
ui.leftScroll:SetAlpha(1)
ui.leftScroll:Show()
else
ui.leftScroll:SetAlpha(1)
ui.leftScroll:Hide()
end
end
local startIndex = MTH_BOOK_STATE.petLeftOffset + 1
for i = 1, visibleRows do
local btn = ui.leftButtons[i]
if btn then
local rowIndex = startIndex + i - 1
local entry = MTH_BOOK_STATE.results[rowIndex]
btn.entry = entry
if entry then
if btn.icon then
if entry.icon and entry.icon ~= "" then
if string.find(entry.icon, "\\", 1, true) then
btn.icon:SetTexture(entry.icon)
else
btn.icon:SetTexture("Interface\\Icons\\" .. tostring(entry.icon))
end
btn.icon:Show()
else
btn.icon:Hide()
end
end
btn.ability:SetText(tostring(entry.ability or "?"))
btn.rank:SetText(tostring(entry.rankText or ""))
btn.ability:SetTextColor(0.92, 0.92, 0.92)
btn.rank:SetTextColor(0.92, 0.92, 0.92)
if btn.icon then btn.icon:SetVertexColor(1, 1, 1, 1) end
if MTH_BOOK_STATE.selectedEntry and MTH_BOOK_STATE.selectedEntry.ability == entry.ability then
btn.selected:Show()
else
btn.selected:Hide()
end
btn:Show()
else
if btn.icon then btn.icon:Hide() end
btn.ability:SetText("")
btn.rank:SetText("")
btn.selected:Hide()
btn:Hide()
end
end
end
local totalKnown = 0
local totalRanks = 0
for i = 1, resultCount do
local entry = MTH_BOOK_STATE.results[i]
if entry then
totalKnown = totalKnown + (tonumber(entry.knownRankCount) or 0)
totalRanks = totalRanks + (tonumber(entry.rankCount) or 0)
end
end
if ui.recapText then
ui.recapText:SetText("Abilities " .. tostring(resultCount) .. " | Known " .. tostring(totalKnown) .. "/" .. tostring(totalRanks))
end
if ui.progressBar then
ui.progressBar:SetMinMaxValues(0, math.max(1, totalRanks))
ui.progressBar:SetValue(totalKnown)
end
if ui.progressText then
local pct = 0
if totalRanks > 0 then
pct = math.floor((totalKnown / totalRanks) * 100 + 0.5)
end
ui.progressText:SetText(tostring(pct) .. "%")
end
if MTH_BOOK_STATE.selectedEntry then
local keep = false
for i = 1, table.getn(MTH_BOOK_STATE.results) do
local row = MTH_BOOK_STATE.results[i]
if row and row.ability == MTH_BOOK_STATE.selectedEntry.ability then
MTH_BOOK_STATE.selectedEntry = row
keep = true
break
end
end
if not keep then
MTH_BOOK_STATE.selectedEntry = nil
MTH_BOOK_STATE.selectedPetRankEntry = nil
end
end
MTH_BOOK_STATE.petRankRows = MTH_BOOK_BuildPetAbilityRankRows(MTH_BOOK_STATE.selectedEntry)
for i = 1, table.getn(ui.rightButtons) do
local btn = ui.rightButtons[i]
if btn then
local entry = MTH_BOOK_STATE.petRankRows[i]
btn.entry = entry
if entry then
btn.rank:SetText(MTH_BOOK_GetRankLabel(entry.rankNumber, false))
if btn.knownIcon then
if entry.isKnown then
btn.knownIcon:Show()
else
btn.knownIcon:Hide()
end
end
btn.level:SetText(entry.trainLevel and tostring(entry.trainLevel) or "-")
btn.desc:SetText(tostring(entry.description or "-"))
if MTH_BOOK_STATE.selectedPetRankEntry and MTH_BOOK_STATE.selectedPetRankEntry.id == entry.id then
btn.selected:Show()
else
btn.selected:Hide()
end
btn:Show()
else
btn.rank:SetText("")
if btn.knownIcon then btn.knownIcon:Hide() end
btn.level:SetText("")
btn.desc:SetText("")
btn.selected:Hide()
btn:Hide()
end
end
end
end
+29
View File
@@ -0,0 +1,29 @@
if type(MTH_HUNTERBOOK_TABS) ~= "table" then MTH_HUNTERBOOK_TABS = {} end
MTH_HUNTERBOOK_TABS.pethistory = {
headerLabel = "Pet History",
columnLabels = { "Pet ID", "Name", "Family", "Lvl", "Lost Date/Time", "Lost Cause" },
columnLayout = {
{ x = 8, width = 66, align = "LEFT" },
{ x = 76, width = 104, align = "LEFT" },
{ x = 182, width = 84, align = "LEFT" },
{ x = 268, width = 30, align = "LEFT" },
{ x = 300, width = 128, align = "LEFT" },
{ x = 430, width = 78, align = "LEFT" },
},
}
function MTH_BOOK_PetHistorySort(a, b)
local ra = MTH_BOOK_GetPetStoreRow(a)
local rb = MTH_BOOK_GetPetStoreRow(b)
if not ra and not rb then return tostring(a) < tostring(b) end
if not ra then return false end
if not rb then return true end
local ta = tonumber(ra.abandonedAt) or 0
local tb = tonumber(rb.abandonedAt) or 0
if ta ~= tb then return ta > tb end
local nameA = MTH_BOOK_SafeLower(ra.name)
local nameB = MTH_BOOK_SafeLower(rb.name)
if nameA ~= nameB then return nameA < nameB end
return tostring(a) < tostring(b)
end
File diff suppressed because it is too large Load Diff
+73
View File
@@ -0,0 +1,73 @@
if type(MTH_HUNTERBOOK_TABS) ~= "table" then MTH_HUNTERBOOK_TABS = {} end
MTH_HUNTERBOOK_TABS.items = {
headerLabel = "Ranged Weapons",
columnLabels = { "ID", "Req", "Lvl", "Type", "Name", "DPS", "Speed", "Source" },
columnLayout = {
{ x = 8, width = 36, align = "LEFT" },
{ x = 44, width = 30, align = "LEFT" },
{ x = 74, width = 30, align = "LEFT" },
{ x = 104, width = 62, align = "LEFT" },
{ x = 166, width = 188, align = "LEFT" },
{ x = 356, width = 44, align = "LEFT" },
{ x = 402, width = 40, align = "LEFT" },
{ x = 444, width = 108, align = "LEFT" },
},
}
function MTH_BOOK_NormalizeItemSubtype(value)
local subtype = MTH_BOOK_SafeLower(value)
if subtype == "arrows" then return "arrow" end
if subtype == "bullets" then return "bullet" end
if subtype == "bows" then return "bow" end
if subtype == "rifle" or subtype == "rifles" then return "gun" end
if subtype == "cross bow" or subtype == "cross-bow" or subtype == "crossbows" or subtype == "xbow" or subtype == "xbows" then return "crossbow" end
return subtype
end
function MTH_BOOK_IsRangedWeaponSubtype(subtype)
return subtype == "bow" or subtype == "gun" or subtype == "crossbow"
end
function MTH_BOOK_ItemMatches(itemId, item)
if not item then return false end
if item.notingame then return false end
local itemSubtype = MTH_BOOK_NormalizeItemSubtype(item.subtype)
if not MTH_BOOK_IsRangedWeaponSubtype(itemSubtype) then return false end
local selectedSubtype = MTH_BOOK_NormalizeItemSubtype(MTH_BOOK_STATE.itemSubtype)
if selectedSubtype ~= "all" and itemSubtype ~= selectedSubtype then return false end
if MTH_BOOK_STATE.itemOnlyMyLevel then
local playerLevel = MTH_BOOK_GetPlayerLevelValue()
local reqLevel = tonumber(item.reqlevel)
if playerLevel and reqLevel and reqLevel > playerLevel then
return false
end
end
if MTH_BOOK_STATE.flag1 and not MTH_BOOK_HasEntries(item.vendors) then return false end
if MTH_BOOK_STATE.flag2 and not MTH_BOOK_HasEntries(item.drops) then return false end
if MTH_BOOK_STATE.flag3 and not MTH_BOOK_HasEntries(item.objects) then return false end
if MTH_BOOK_STATE.search ~= "" then
local name = MTH_BOOK_SafeLower(item.name)
local idText = tostring(itemId)
if string.find(name, MTH_BOOK_STATE.search, 1, true) == nil and string.find(idText, MTH_BOOK_STATE.search, 1, true) == nil then
return false
end
end
return true
end
function MTH_BOOK_ItemSort(a, b)
local items = MTH_BOOK_GetItemsTable()
local ia = items and items[a]
local ib = items and items[b]
if not ia or not ib then return a < b end
local la = ia.level or 0
local lb = ib.level or 0
if la ~= lb then return la < lb end
local na = ia.name or ""
local nb = ib.name or ""
if na ~= nb then return na < nb end
return a < b
end
+4946
View File
File diff suppressed because it is too large Load Diff
+333
View File
@@ -0,0 +1,333 @@
<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="MTH_BOOK_Template" toplevel="true" frameStrata="DIALOG" movable="true" enableMouse="true" hidden="true" parent="UIParent" virtual="true">
<Size>
<AbsDimension x="760" y="560"/>
</Size>
<Anchors>
<Anchor point="CENTER"/>
</Anchors>
<Backdrop bgFile="Interface\Buttons\WHITE8X8" edgeFile="Interface\DialogFrame\UI-DialogBox-Border" tile="true">
<BackgroundInsets>
<AbsInset left="11" right="12" top="12" bottom="11"/>
</BackgroundInsets>
<TileSize><AbsValue val="32"/></TileSize>
<EdgeSize><AbsValue val="32"/></EdgeSize>
</Backdrop>
<Layers>
<Layer level="ARTWORK">
<FontString name="$parentHeaderText" inherits="GameFontNormal" text="The Book of Hunters">
<Anchors>
<Anchor point="TOP">
<Offset><AbsDimension x="0" y="-18"/></Offset>
</Anchor>
</Anchors>
</FontString>
<FontString name="$parentStatsText" inherits="GameFontNormalSmall" text="Beasts: 0 | Filtered: 0 | Page 1/1">
<Anchors>
<Anchor point="TOPLEFT">
<Offset><AbsDimension x="20" y="-530"/></Offset>
</Anchor>
</Anchors>
</FontString>
<FontString name="$parentSearchLabel" inherits="GameFontNormalSmall" text="Search">
<Anchors>
<Anchor point="TOPLEFT">
<Offset><AbsDimension x="18" y="-82"/></Offset>
</Anchor>
</Anchors>
</FontString>
<FontString name="$parentMinLabel" inherits="GameFontNormalSmall" text="Min Lvl">
<Anchors>
<Anchor point="TOPLEFT">
<Offset><AbsDimension x="266" y="-82"/></Offset>
</Anchor>
</Anchors>
</FontString>
<FontString name="$parentMaxLabel" inherits="GameFontNormalSmall" text="Max Lvl">
<Anchors>
<Anchor point="TOPLEFT">
<Offset><AbsDimension x="374" y="-82"/></Offset>
</Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<Frame name="$parentListBackdrop">
<Size><AbsDimension x="564" y="354"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="16" y="-166"/></Offset></Anchor>
</Anchors>
<Backdrop bgFile="Interface\Buttons\WHITE8X8" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
<BackgroundInsets><AbsInset left="3" right="3" top="3" bottom="3"/></BackgroundInsets>
<TileSize><AbsValue val="16"/></TileSize>
<EdgeSize><AbsValue val="16"/></EdgeSize>
</Backdrop>
</Frame>
<Frame name="$parentDetailBackdrop">
<Size><AbsDimension x="136" y="324"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="608" y="-166"/></Offset></Anchor>
</Anchors>
<Backdrop bgFile="Interface\Buttons\WHITE8X8" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
<BackgroundInsets><AbsInset left="3" right="3" top="3" bottom="3"/></BackgroundInsets>
<TileSize><AbsValue val="16"/></TileSize>
<EdgeSize><AbsValue val="16"/></EdgeSize>
</Backdrop>
<Frames>
<EditBox name="$parentDetailText" autoFocus="false" multiLine="true" text="Select an entry to view details.">
<Size><AbsDimension x="120" y="306"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="8" y="-8"/></Offset></Anchor>
</Anchors>
</EditBox>
</Frames>
</Frame>
<Button name="$parentOpenMapButton" inherits="UIPanelButtonTemplate" text="Open Map" hidden="true">
<Size><AbsDimension x="136" y="22"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="608" y="-496"/></Offset></Anchor>
</Anchors>
</Button>
<Button name="$parentCloseButton" inherits="UIPanelButtonTemplate" text="X">
<Size><AbsDimension x="22" y="22"/></Size>
<Anchors>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-8" y="-8"/></Offset></Anchor>
</Anchors>
</Button>
<Frame name="$parentTopAreaBackdrop">
<Size><AbsDimension x="728" y="472"/></Size>
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentTabBar" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-2" y="-2"/></Offset></Anchor>
</Anchors>
<Backdrop bgFile="Interface\Buttons\WHITE8X8" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
<BackgroundInsets><AbsInset left="3" right="3" top="3" bottom="3"/></BackgroundInsets>
<TileSize><AbsValue val="16"/></TileSize>
<EdgeSize><AbsValue val="16"/></EdgeSize>
</Backdrop>
</Frame>
<Frame name="$parentTabBar">
<Size><AbsDimension x="620" y="24"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="18" y="-44"/></Offset></Anchor>
</Anchors>
</Frame>
<Button name="$parentSectionStable" inherits="TabButtonTemplate" text="Stable">
<Size><AbsDimension x="86" y="24"/></Size>
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentTabBar"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
</Anchors>
</Button>
<Button name="$parentSectionPetAbilities" inherits="TabButtonTemplate" text="Pet Abilities">
<Size><AbsDimension x="108" y="24"/></Size>
<Anchors>
<Anchor point="LEFT" relativeTo="$parentSectionStable" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
</Anchors>
</Button>
<Button name="$parentSectionPets" inherits="TabButtonTemplate" text="Beast Lore">
<Size><AbsDimension x="98" y="24"/></Size>
<Anchors>
<Anchor point="LEFT" relativeTo="$parentSectionPetAbilities" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
</Anchors>
</Button>
<Button name="$parentSectionFamilies" inherits="TabButtonTemplate" text="Families">
<Size><AbsDimension x="92" y="24"/></Size>
<Anchors>
<Anchor point="LEFT" relativeTo="$parentSectionPets" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
</Anchors>
</Button>
<Button name="$parentSectionNPCs" inherits="TabButtonTemplate" text="NPC Finder">
<Size><AbsDimension x="110" y="24"/></Size>
<Anchors>
<Anchor point="LEFT" relativeTo="$parentSectionFamilies" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
</Anchors>
</Button>
<Button name="$parentSectionItems" inherits="TabButtonTemplate" text="Weapons">
<Size><AbsDimension x="104" y="24"/></Size>
<Anchors>
<Anchor point="LEFT" relativeTo="$parentSectionNPCs" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
</Anchors>
</Button>
<Button name="$parentSectionPetHistory" inherits="TabButtonTemplate" text="Pet History">
<Size><AbsDimension x="104" y="24"/></Size>
<Anchors>
<Anchor point="LEFT" relativeTo="$parentSectionItems" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
</Anchors>
</Button>
<EditBox name="$parentSearch" inherits="InputBoxTemplate">
<Size><AbsDimension x="180" y="20"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="54" y="-80"/></Offset></Anchor>
</Anchors>
</EditBox>
<EditBox name="$parentMinLevel" inherits="InputBoxTemplate">
<Size><AbsDimension x="45" y="20"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="302" y="-80"/></Offset></Anchor>
</Anchors>
</EditBox>
<EditBox name="$parentMaxLevel" inherits="InputBoxTemplate">
<Size><AbsDimension x="45" y="20"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="414" y="-80"/></Offset></Anchor>
</Anchors>
</EditBox>
<Button name="$parentApplyButton" inherits="UIPanelButtonTemplate" text="Apply">
<Size><AbsDimension x="60" y="20"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="478" y="-80"/></Offset></Anchor>
</Anchors>
</Button>
<Frame name="$parentPetLearnSourceDropdown" inherits="UIDropDownMenuTemplate" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="12" y="-80"/></Offset></Anchor>
</Anchors>
</Frame>
<Frame name="$parentItemSubtypeDropdown" inherits="UIDropDownMenuTemplate" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="12" y="-108"/></Offset></Anchor>
</Anchors>
</Frame>
<CheckButton name="$parentPetOnlyMyLevel" inherits="UICheckButtonTemplate" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="228" 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>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="614" y="-80"/></Offset></Anchor>
</Anchors>
</Button>
<Button name="$parentResetButton" inherits="UIPanelButtonTemplate" text="Reset">
<Size><AbsDimension x="60" y="20"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="546" y="-80"/></Offset></Anchor>
</Anchors>
</Button>
<Button name="$parentFilter1" inherits="UIPanelButtonTemplate" text="All"><Size><AbsDimension x="70" y="20"/></Size><Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="18" y="-114"/></Offset></Anchor></Anchors></Button>
<Button name="$parentFilter2" inherits="UIPanelButtonTemplate" text=""><Size><AbsDimension x="70" y="20"/></Size><Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="92" y="-114"/></Offset></Anchor></Anchors></Button>
<Button name="$parentFilter3" inherits="UIPanelButtonTemplate" text=""><Size><AbsDimension x="70" y="20"/></Size><Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="166" y="-114"/></Offset></Anchor></Anchors></Button>
<Button name="$parentFilter4" inherits="UIPanelButtonTemplate" text=""><Size><AbsDimension x="70" y="20"/></Size><Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="240" y="-114"/></Offset></Anchor></Anchors></Button>
<Button name="$parentFilter5" inherits="UIPanelButtonTemplate" text=""><Size><AbsDimension x="70" y="20"/></Size><Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="314" y="-114"/></Offset></Anchor></Anchors></Button>
<Button name="$parentFilter6" inherits="UIPanelButtonTemplate" text=""><Size><AbsDimension x="70" y="20"/></Size><Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="388" y="-114"/></Offset></Anchor></Anchors></Button>
<Frame name="$parentFamilyDropdown" inherits="UIDropDownMenuTemplate" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="10" y="-108"/></Offset></Anchor>
</Anchors>
</Frame>
<Frame name="$parentAbilityDropdown" inherits="UIDropDownMenuTemplate" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="180" y="-108"/></Offset></Anchor>
</Anchors>
</Frame>
<Frame name="$parentRankDropdown" inherits="UIDropDownMenuTemplate" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="350" y="-108"/></Offset></Anchor>
</Anchors>
</Frame>
<CheckButton name="$parentPetInZoneOnly" inherits="UICheckButtonTemplate" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="488" y="-112"/></Offset></Anchor>
</Anchors>
</CheckButton>
<Button name="$parentShowAllOnMapButton" inherits="UIPanelButtonTemplate" text="Show all on map" hidden="true">
<Size><AbsDimension x="122" y="20"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="602" y="-110"/></Offset></Anchor>
</Anchors>
</Button>
<Frame name="$parentNPCFunctionDropdown" inherits="UIDropDownMenuTemplate" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="242" y="-80"/></Offset></Anchor>
</Anchors>
</Frame>
<CheckButton name="$parentNPCInZoneOnly" inherits="UICheckButtonTemplate" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="404" y="-84"/></Offset></Anchor>
</Anchors>
</CheckButton>
<CheckButton name="$parentRequireVendor" inherits="UICheckButtonTemplate">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="18" y="-140"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="$parentRequireDrop" inherits="UICheckButtonTemplate">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="86" y="-140"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="$parentRequireObject" inherits="UICheckButtonTemplate">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="154" y="-140"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="$parentHideNoAbilities" inherits="UICheckButtonTemplate">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="232" y="-140"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="$parentHideUnknown" inherits="UICheckButtonTemplate">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="372" y="-140"/></Offset></Anchor></Anchors>
</CheckButton>
<Frame name="$parentListSliderBackdrop">
<Size><AbsDimension x="22" y="322"/></Size>
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentListBackdrop" relativePoint="TOPRIGHT"><Offset><AbsDimension x="2" y="-20"/></Offset></Anchor>
</Anchors>
<Backdrop bgFile="Interface\Buttons\WHITE8X8" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
<BackgroundInsets><AbsInset left="2" right="2" top="2" bottom="2"/></BackgroundInsets>
<TileSize><AbsValue val="16"/></TileSize>
<EdgeSize><AbsValue val="16"/></EdgeSize>
</Backdrop>
</Frame>
<Slider name="$parentListSlider" inherits="UISliderTemplate" orientation="VERTICAL" minValue="1" maxValue="1" defaultValue="1" valueStep="1">
<Size><AbsDimension x="20" y="322"/></Size>
<Anchors>
<Anchor point="CENTER" relativeTo="$parentListSliderBackdrop"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
</Anchors>
</Slider>
<Button name="$parentPrevButton" inherits="UIPanelButtonTemplate" text="Prev">
<Size><AbsDimension x="80" y="22"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="280" y="-524"/></Offset></Anchor></Anchors>
</Button>
<Button name="$parentNextButton" inherits="UIPanelButtonTemplate" text="Next">
<Size><AbsDimension x="80" y="22"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="368" y="-524"/></Offset></Anchor></Anchors>
</Button>
</Frames>
<Scripts>
<OnLoad>
this:RegisterForDrag("LeftButton")
</OnLoad>
<OnDragStart>
this:StartMoving();
</OnDragStart>
<OnDragStop>
this:StopMovingOrSizing();
</OnDragStop>
</Scripts>
</Frame>
</Ui>
+180
View File
@@ -0,0 +1,180 @@
------------------------------------------------------
-- MetaHunt Localization System
------------------------------------------------------
MTH.localization = {}
MTH.currentLocale = GetLocale()
-- Binding Headers (must be defined early for keybindings)
BINDING_HEADER_FEEDOMATIC = "[MetaHunt] Feed-O-Matic"
BINDING_HEADER_ZAspectHeader = "[MetaHunt] ZAspect Buttons"
BINDING_HEADER_ZTrackHeader = "[MetaHunt] ZTrack Buttons"
BINDING_HEADER_ZTrapHeader = "[MetaHunt] ZTrap Buttons"
BINDING_HEADER_ZPetHeader = "[MetaHunt] ZPet Buttons"
BINDING_HEADER_ZAmmoHeader = "[MetaHunt] ZAmmo Buttons"
-- Localization table structure
local L = {}
-- English (default)
if MTH.currentLocale == "enUS" or MTH.currentLocale == "enGB" then
-- FeedOMatic
L.FEEDOMATIC_NAME = "Feed-O-Matic"
L.FEEDOMATIC_DESC = "Helps a Hunter keep his pets well fed"
L.TOOLTIPS_OPTION_FOOD = "Add tooltips on food"
L.TOOLTIPS_OPTION_FOOD_HELP = "Show your current pet's food preference directly in item tooltips."
L.TOOLTIPS_OPTION_OWNPET = "Activate on my pet"
L.TOOLTIPS_OPTION_OWNPET_HELP = "Show your own pet status details in tooltip when hovering your pet context."
L.TOOLTIPS_FOOD_QUALITY_UNKNOWN = "%s can eat this, but hasn't tried it yet."
L.TOOLTIPS_FOOD_QUALITY_UNDER = "%s doesn't like this anymore."
L.TOOLTIPS_FOOD_QUALITY_MIGHT = "%s might eat this."
L.TOOLTIPS_FOOD_QUALITY_WILL = "%s will eat this."
L.TOOLTIPS_FOOD_QUALITY_LIKE = "%s likes to eat this."
L.TOOLTIPS_FOOD_QUALITY_LOVE = "%s loves to eat this."
L.TOOLTIPS_FOOD_MARKER_UNKNOWN = "can eat this, but hasn't tried it yet."
L.TOOLTIPS_FOOD_MARKER_UNDER = "doesn't like this anymore."
L.TOOLTIPS_FOOD_MARKER_MIGHT = "might eat this."
L.TOOLTIPS_FOOD_MARKER_WILL = "will eat this."
L.TOOLTIPS_FOOD_MARKER_LIKE = "likes to eat this."
L.TOOLTIPS_FOOD_MARKER_LOVE = "loves to eat this."
-- zhunter
L.ZHUNTER_NAME = "zhunter"
L.ZHUNTER_DESC = "Various features to make a hunter's life easier"
-- German / Deutsch
elseif MTH.currentLocale == "deDE" then
L.FEEDOMATIC_NAME = "Futter-O-Mat"
L.ZHUNTER_NAME = "zhunter"
L.TOOLTIPS_OPTION_FOOD = "Add tooltips on food"
L.TOOLTIPS_OPTION_FOOD_HELP = "Show your current pet's food preference directly in item tooltips."
L.TOOLTIPS_OPTION_OWNPET = "Activate on my pet"
L.TOOLTIPS_OPTION_OWNPET_HELP = "Show your own pet status details in tooltip when hovering your pet context."
L.TOOLTIPS_FOOD_QUALITY_UNKNOWN = "%s can eat this, but hasn't tried it yet."
L.TOOLTIPS_FOOD_QUALITY_UNDER = "%s doesn't like this anymore."
L.TOOLTIPS_FOOD_QUALITY_MIGHT = "%s might eat this."
L.TOOLTIPS_FOOD_QUALITY_WILL = "%s will eat this."
L.TOOLTIPS_FOOD_QUALITY_LIKE = "%s likes to eat this."
L.TOOLTIPS_FOOD_QUALITY_LOVE = "%s loves to eat this."
L.TOOLTIPS_FOOD_MARKER_UNKNOWN = "can eat this, but hasn't tried it yet."
L.TOOLTIPS_FOOD_MARKER_UNDER = "doesn't like this anymore."
L.TOOLTIPS_FOOD_MARKER_MIGHT = "might eat this."
L.TOOLTIPS_FOOD_MARKER_WILL = "will eat this."
L.TOOLTIPS_FOOD_MARKER_LIKE = "likes to eat this."
L.TOOLTIPS_FOOD_MARKER_LOVE = "loves to eat this."
-- French / Français
elseif MTH.currentLocale == "frFR" then
L.FEEDOMATIC_NAME = "Nourricier"
L.ZHUNTER_NAME = "zhunter"
L.TOOLTIPS_OPTION_FOOD = "Add tooltips on food"
L.TOOLTIPS_OPTION_FOOD_HELP = "Show your current pet's food preference directly in item tooltips."
L.TOOLTIPS_OPTION_OWNPET = "Activate on my pet"
L.TOOLTIPS_OPTION_OWNPET_HELP = "Show your own pet status details in tooltip when hovering your pet context."
L.TOOLTIPS_FOOD_QUALITY_UNKNOWN = "%s can eat this, but hasn't tried it yet."
L.TOOLTIPS_FOOD_QUALITY_UNDER = "%s doesn't like this anymore."
L.TOOLTIPS_FOOD_QUALITY_MIGHT = "%s might eat this."
L.TOOLTIPS_FOOD_QUALITY_WILL = "%s will eat this."
L.TOOLTIPS_FOOD_QUALITY_LIKE = "%s likes to eat this."
L.TOOLTIPS_FOOD_QUALITY_LOVE = "%s loves to eat this."
L.TOOLTIPS_FOOD_MARKER_UNKNOWN = "can eat this, but hasn't tried it yet."
L.TOOLTIPS_FOOD_MARKER_UNDER = "doesn't like this anymore."
L.TOOLTIPS_FOOD_MARKER_MIGHT = "might eat this."
L.TOOLTIPS_FOOD_MARKER_WILL = "will eat this."
L.TOOLTIPS_FOOD_MARKER_LIKE = "likes to eat this."
L.TOOLTIPS_FOOD_MARKER_LOVE = "loves to eat this."
end
MTH.localization[MTH.currentLocale] = L
-- Get localized string
function MTH:GetLocalization(key, default)
if L[key] then
return L[key]
end
return default or key
end
-- Module localization helper
function MTH:RegisterModuleLocalization(module_name, localizationTable)
if not self.localization.modules then
self.localization.modules = {}
end
self.localization.modules[module_name] = localizationTable
end
function MTH:GetModuleLocalization(module_name, key, default)
if self.localization.modules and self.localization.modules[module_name] then
local L = self.localization.modules[module_name]
if L[key] then
return L[key]
end
end
return default or key
end
local MTH_SPELL_LOCALE = {
enUS = {
["Banish"] = "Banish",
["Bestial Wrath"] = "Bestial Wrath",
["Concussive Shot"] = "Concussive Shot",
["Counterattack"] = "Counterattack",
["Critical Mass"] = "Critical Mass",
["Clever Traps"] = "Clever Traps",
["Deterrence"] = "Deterrence",
["Explosive Trap"] = "Explosive Trap",
["Explosive Trap Effect"] = "Explosive Trap Effect",
["Feed Pet"] = "Feed Pet",
["Flare"] = "Flare",
["Freezing Trap"] = "Freezing Trap",
["Freezing Trap Effect"] = "Freezing Trap Effect",
["Frost Trap"] = "Frost Trap",
["Frost Trap Aura"] = "Frost Trap Aura",
["Hunter's Mark"] = "Hunter's Mark",
["Holy Strength"] = "Holy Strength",
["Immolation Trap"] = "Immolation Trap",
["Immolation Trap Effect"] = "Immolation Trap Effect",
["Improved Concussive Shot"] = "Improved Concussive Shot",
["Improved Wing Clip"] = "Improved Wing Clip",
["Perception"] = "Perception",
["Piercing Shot"] = "Piercing Shot",
["Piercing Shots"] = "Piercing Shots",
["Quick Shots"] = "Quick Shots",
["Rapid Fire"] = "Rapid Fire",
["Scare Beast"] = "Scare Beast",
["Scatter Shot"] = "Scatter Shot",
["Scorpid Poison"] = "Scorpid Poison",
["Scorpid Sting"] = "Scorpid Sting",
["Serpent Sting"] = "Serpent Sting",
["Stoneform"] = "Stoneform",
["Unstable Power"] = "Unstable Power",
["Ephemeral Power"] = "Ephemeral Power",
["Viper Sting"] = "Viper Sting",
["War Stomp"] = "War Stomp",
["Will of the Forsaken"] = "Will of the Forsaken",
["Wing Clip"] = "Wing Clip",
["Wyvern Sting"] = "Wyvern Sting",
["Blood Fury"] = "Blood Fury",
["Berserking"] = "Berserking",
},
}
MTH.Const = MTH.Const or {}
MTH.Const.SpellLocaleMap = MTH_SPELL_LOCALE
function MTH:LocalizeSpell(spellToken)
local key = tostring(spellToken or "")
if key == "" then
return key
end
local locale = tostring(self.currentLocale or GetLocale() or "enUS")
local localeMap = MTH_SPELL_LOCALE[locale] or MTH_SPELL_LOCALE.enUS
if localeMap and localeMap[key] then
return localeMap[key]
end
return key
end
+1262
View File
File diff suppressed because it is too large Load Diff
+171
View File
@@ -0,0 +1,171 @@
------------------------------------------------------
-- MetaHunt Minimap Button
------------------------------------------------------
if not MTH_MinimapButton then
MTH_MinimapButton = {}
end
local function MTH_MB_GetStore()
if not MTH_CharSavedVariables then
MTH_CharSavedVariables = {}
end
if type(MTH_CharSavedVariables.minimapButton) ~= "table" then
MTH_CharSavedVariables.minimapButton = {}
end
local store = MTH_CharSavedVariables.minimapButton
if store.angle == nil then
store.angle = 220
end
return store
end
local function MTH_MB_GetRadius()
return 80
end
local function MTH_MB_UpdatePosition()
if not MTH_MinimapButton.frame then
return
end
local store = MTH_MB_GetStore()
local angle = tonumber(store.angle) or 220
local rad = math.rad(angle)
local radius = MTH_MB_GetRadius()
local x = math.cos(rad) * radius
local y = math.sin(rad) * radius
MTH_MinimapButton.frame:ClearAllPoints()
MTH_MinimapButton.frame:SetPoint("CENTER", Minimap, "CENTER", x, y)
end
local function MTH_MB_UpdateDragPosition()
if not MTH_MinimapButton.frame then
return
end
local mx, my = Minimap:GetCenter()
local px, py = GetCursorPosition()
local scale = Minimap:GetEffectiveScale()
if scale and scale ~= 0 then
px = px / scale
py = py / scale
end
local dx = px - mx
local dy = py - my
local angle = math.deg(math.atan2(dy, dx))
local store = MTH_MB_GetStore()
store.angle = angle
MTH_MB_UpdatePosition()
end
local function MTH_MB_SetHunterIcon(texture)
if not texture then
return
end
texture:SetTexture("Interface\\GLUES\\CHARACTERCREATE\\UI-CHARACTERCREATE-CLASSES")
if CLASS_ICON_TCOORDS and CLASS_ICON_TCOORDS["HUNTER"] then
local coords = CLASS_ICON_TCOORDS["HUNTER"]
texture:SetTexCoord(coords[1], coords[2], coords[3], coords[4])
else
texture:SetTexCoord(0, 0.25, 0.25, 0.5)
end
end
function MTH_MinimapButton:Initialize()
if self.frame then
MTH_MB_UpdatePosition()
self.frame:Show()
return
end
local button = CreateFrame("Button", "MTH_MinimapButtonFrame", Minimap)
if not button then
return
end
button:SetWidth(31)
button:SetHeight(31)
button:SetFrameStrata("MEDIUM")
button:RegisterForClicks("LeftButtonUp", "RightButtonUp")
button:RegisterForDrag("LeftButton")
local border = button:CreateTexture(nil, "OVERLAY")
border:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder")
border:SetWidth(54)
border:SetHeight(54)
border:SetPoint("TOPLEFT", button, "TOPLEFT", 0, 0)
local icon = button:CreateTexture(nil, "BACKGROUND")
icon:SetWidth(20)
icon:SetHeight(20)
icon:SetPoint("CENTER", button, "CENTER", 0, 0)
MTH_MB_SetHunterIcon(icon)
local highlight = button:CreateTexture(nil, "HIGHLIGHT")
highlight:SetTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight")
highlight:SetBlendMode("ADD")
highlight:SetWidth(31)
highlight:SetHeight(31)
highlight:SetPoint("CENTER", button, "CENTER", 0, 0)
button:SetScript("OnEnter", function(self)
self = self or this
if not self then return end
GameTooltip:SetOwner(self, "ANCHOR_LEFT")
GameTooltip:SetText("MetaHunt", 1, 0.82, 0)
GameTooltip:AddLine("Left-Click: Open Hunter Book", 1, 1, 1)
GameTooltip:AddLine("Right-Click: Open MetaHunt Options", 1, 1, 1)
GameTooltip:AddLine("Drag: Move button", 0.8, 0.8, 0.8)
GameTooltip:Show()
end)
button:SetScript("OnLeave", function()
GameTooltip:Hide()
end)
button:SetScript("OnDragStart", function(self)
self = self or this
if not self then return end
self.dragging = 1
self:SetScript("OnUpdate", function()
MTH_MB_UpdateDragPosition()
end)
end)
button:SetScript("OnDragStop", function(self)
self = self or this
if not self then return end
self.dragging = nil
self:SetScript("OnUpdate", nil)
MTH_MB_UpdateDragPosition()
end)
button:SetScript("OnClick", function(_, mouseButton)
mouseButton = mouseButton or arg1
if mouseButton == "RightButton" then
if type(MTH_OpenOptions) == "function" then
MTH_OpenOptions("General")
end
return
end
if type(MTH_OpenHunterBook) == "function" then
MTH_OpenHunterBook()
elseif type(MTH_ToggleHunterBook) == "function" then
MTH_ToggleHunterBook()
end
end)
self.frame = button
self.icon = icon
MTH_MB_UpdatePosition()
end
local loader = CreateFrame("Frame", "MTH_MinimapButtonLoader")
loader:RegisterEvent("ADDON_LOADED")
loader:SetScript("OnEvent", function(_, eventName, addonName)
eventName = eventName or event
addonName = addonName or arg1
if eventName == "ADDON_LOADED" and addonName == "MetaHunt" then
MTH_MinimapButton:Initialize()
end
end)
+845
View File
@@ -0,0 +1,845 @@
local MTH_AB_READY = MTH_OptionsRequire and MTH_OptionsRequire("options-autobuy", {
"MTH_GetFrame",
"MTH_ClearContainer",
"MTH_CreateCheckbox",
})
local MTH_AB_LAYOUT = {
LEFT_SECTION_X = 0,
RIGHT_SECTION_X = 290,
ROW_CHECK_X = 0,
ROW_CHECK_LABEL_X = 27,
ROW_DROPDOWN_X = 33,
ROW_DROPDOWN_WIDTH = 100,
ROW_QTY_GAP = 6,
ROW_SUFFIX_GAP = 6,
ROW_STEP = 40,
SECTION_TITLE_Y = -110,
SECTION_ROWS_Y0 = -138,
}
local MTH_AB_STATE = {
container = nil,
built = false,
syncing = false,
controls = {
moduleEnabled = nil,
projectilesEnabled = nil,
petFoodEnabled = nil,
petFoodQty = nil,
petFoodQtyLabel = nil,
petFoodQtySuffix = nil,
petFoodScopeCurrent = nil,
petFoodScopeAll = nil,
petFoodTitle = nil,
petFoodPetLines = {},
title = nil,
body = nil,
sectionArrowTitle = nil,
sectionBulletTitle = nil,
rows = {
arrow = {},
bullet = {},
},
},
}
local function MTH_AB_SetCheckedNoClick(check, checked)
if not check then return end
MTH_AB_STATE.syncing = true
check:SetChecked(checked and true or false)
MTH_AB_STATE.syncing = false
end
local function MTH_AB_GetEngine()
return MTH_AutoBuyEngine
end
local function MTH_AB_GetStore()
local engine = MTH_AB_GetEngine()
if engine and engine.EnsureDefaults then
return engine:EnsureDefaults()
end
MTH_AutoBuy_Saved = MTH_AutoBuy_Saved or {}
return MTH_AutoBuy_Saved
end
local function MTH_AB_NormalizeRule(rule)
if type(rule) ~= "table" then
rule = {}
end
if rule.enabled == nil then
rule.enabled = false
end
rule.enabled = rule.enabled and true or false
rule.itemId = rule.itemId and (tonumber(rule.itemId) or rule.itemId) or nil
local stacks = tonumber(rule.stacks)
if not stacks then
stacks = rule.enabled and 1 or 0
end
stacks = math.floor(stacks)
if stacks < 0 then
stacks = 0
end
if rule.enabled and stacks < 1 then
stacks = 1
end
rule.stacks = stacks
return rule
end
local function MTH_AB_EnsureConfig()
local store = MTH_AB_GetStore()
if store.enabled == nil then store.enabled = true end
if type(store.projectiles) ~= "table" then store.projectiles = {} end
if type(store.petFood) ~= "table" then store.petFood = {} end
if store.projectiles.enabled == nil then store.projectiles.enabled = true end
if store.petFood.enabled == nil then store.petFood.enabled = false end
if store.petFood.stacks == nil then store.petFood.stacks = 1 end
if store.petFood.scope == nil then store.petFood.scope = "current" end
store.petFood.stacks = math.floor(tonumber(store.petFood.stacks) or 1)
if store.petFood.stacks < 1 then store.petFood.stacks = 1 end
if store.petFood.scope ~= "current" and store.petFood.scope ~= "all" then
store.petFood.scope = "current"
end
if type(store.projectiles.arrows) ~= "table" then store.projectiles.arrows = {} end
if type(store.projectiles.bullets) ~= "table" then store.projectiles.bullets = {} end
if type(store.projectiles.arrows.rules) ~= "table" then store.projectiles.arrows.rules = {} end
if type(store.projectiles.bullets.rules) ~= "table" then store.projectiles.bullets.rules = {} end
for i = 1, 3 do
store.projectiles.arrows.rules[i] = MTH_AB_NormalizeRule(store.projectiles.arrows.rules[i])
store.projectiles.bullets.rules[i] = MTH_AB_NormalizeRule(store.projectiles.bullets.rules[i])
end
return store
end
local function MTH_AB_SetPetFoodStacks(store, value)
if type(store) ~= "table" or type(store.petFood) ~= "table" then
return
end
local stacks = math.floor(tonumber(value) or 1)
if stacks < 1 then stacks = 1 end
store.petFood.stacks = stacks
end
local function MTH_AB_TitleCase(text)
local source = tostring(text or "")
if source == "" then return "" end
return string.upper(string.sub(source, 1, 1)) .. string.lower(string.sub(source, 2))
end
local function MTH_AB_NormalizeFamilyToken(text)
local value = string.lower(tostring(text or ""))
value = string.gsub(value, "%b()", " ")
value = string.gsub(value, "[^%a]", "")
return value
end
local function MTH_AB_SingularizeFamilyToken(token)
local value = tostring(token or "")
if string.len(value) <= 3 then
return value
end
if string.sub(value, -3) == "ves" and string.len(value) > 4 then
return string.sub(value, 1, -4) .. "f"
end
if string.sub(value, -3) == "ies" and string.len(value) > 4 then
return string.sub(value, 1, -4) .. "y"
end
if string.sub(value, -3) == "xes" and string.len(value) > 4 then
return string.sub(value, 1, -3)
end
if string.sub(value, -4) == "ches" and string.len(value) > 5 then
return string.sub(value, 1, -3)
end
if string.sub(value, -4) == "shes" and string.len(value) > 5 then
return string.sub(value, 1, -3)
end
if string.sub(value, -3) == "zes" and string.len(value) > 4 then
return string.sub(value, 1, -3)
end
if string.sub(value, -3) == "ses" then
return string.sub(value, 1, -3)
end
if string.sub(value, -1) == "s" then
return string.sub(value, 1, -2)
end
return value
end
local function MTH_AB_FindFamilyDietRow(familyName)
if type(MTH_DS_Families) ~= "table" then
return nil
end
local needle = MTH_AB_NormalizeFamilyToken(familyName)
if needle == "" then
return nil
end
local needleSingular = MTH_AB_SingularizeFamilyToken(needle)
for key, row in pairs(MTH_DS_Families) do
local candidate = MTH_AB_NormalizeFamilyToken(key)
if candidate ~= "" then
if candidate == needle or candidate == needleSingular then
return row
end
local candidateSingular = MTH_AB_SingularizeFamilyToken(candidate)
if candidateSingular == needle or candidateSingular == needleSingular then
return row
end
end
end
return nil
end
local function MTH_AB_GetPetDietText(familyName)
local family = tostring(familyName or "")
if family == "" then
return "unknown diet"
end
local row = MTH_AB_FindFamilyDietRow(family)
if type(row) ~= "table" or type(row.food) ~= "table" then
return "unknown diet"
end
local diets = {}
for i = 1, table.getn(row.food) do
local diet = tostring(row.food[i] or "")
if diet ~= "" then
table.insert(diets, MTH_AB_TitleCase(diet))
end
end
if table.getn(diets) == 0 then
return "unknown diet"
end
return table.concat(diets, ", ")
end
local function MTH_AB_GetPetFoodEntries()
if type(MTH_PETS_RefreshCurrentPet) == "function" then
pcall(MTH_PETS_RefreshCurrentPet)
end
local entries = {}
local defaultIcon = "Interface\\Icons\\INV_Misc_QuestionMark"
local function buildEntryFromRow(row)
if type(row) ~= "table" then
return nil
end
local name = tostring(row.name or "")
if name == "" then
return nil
end
local family = tostring(row.family or "")
local icon = tostring((type(row.stableInfo) == "table" and row.stableInfo.icon) or row.icon or "")
if icon == "" and type(row.stableRaw) == "table" then
icon = tostring(row.stableRaw[1] or "")
end
if icon == "" then icon = defaultIcon end
return {
icon = icon,
name = name,
family = family,
diets = MTH_AB_GetPetDietText(family),
}
end
local function setSlot(slot, data)
entries[slot + 1] = data
end
for slot = 0, 4 do
setSlot(slot, {
icon = defaultIcon,
name = "",
diets = "",
placeholder = (slot == 0) and "NO CURRENT PET !" or "Free slot",
})
end
local petsRoot = type(MTH_PETS_GetRootStore) == "function" and MTH_PETS_GetRootStore() or nil
local petStore = petsRoot and petsRoot.petStore or nil
local activeById = petStore and petStore.activeById or nil
local stableSlotIndex = petStore and petStore.stableSlotIndex or nil
local currentInfo = type(MTH_GetCurrentPetInfo) == "function" and MTH_GetCurrentPetInfo() or nil
if currentInfo and currentInfo.liveExists then
local currentName = tostring(currentInfo.name or "")
if currentName ~= "" then
local currentFamily = tostring(currentInfo.family or "")
local currentIcon = defaultIcon
local currentId = currentInfo.id
if currentId and type(activeById) == "table" and type(activeById[currentId]) == "table" then
local currentRow = activeById[currentId]
local iconFromRow = tostring((type(currentRow.stableInfo) == "table" and currentRow.stableInfo.icon) or currentRow.icon or "")
if iconFromRow == "" and type(currentRow.stableRaw) == "table" then
iconFromRow = tostring(currentRow.stableRaw[1] or "")
end
if iconFromRow ~= "" then
currentIcon = iconFromRow
end
end
setSlot(0, {
icon = currentIcon,
name = currentName,
family = currentFamily,
diets = MTH_AB_GetPetDietText(currentFamily),
})
end
end
for slot = 1, 4 do
local petId = nil
if type(stableSlotIndex) == "table" then
petId = stableSlotIndex[slot]
end
local row = (petId and type(activeById) == "table") and activeById[petId] or nil
if type(row) ~= "table" and type(activeById) == "table" then
for _, candidate in pairs(activeById) do
if type(candidate) == "table" and tonumber(candidate.stableSlot) == slot then
row = candidate
break
end
end
end
local entry = buildEntryFromRow(row)
if entry then
setSlot(slot, entry)
end
end
return entries
end
local function MTH_AB_RefreshPetFoodListUI()
local lines = MTH_AB_STATE.controls.petFoodPetLines or {}
if table.getn(lines) == 0 then
return
end
local entries = MTH_AB_GetPetFoodEntries()
for i = 1, table.getn(lines) do
local line = lines[i]
local entry = entries[i]
if line and line.text and line.icon then
if entry then
line.icon:Show()
line.icon:SetTexture(tostring(entry.icon or "Interface\\Icons\\INV_Misc_QuestionMark"))
line.text:Show()
if tostring(entry.name or "") ~= "" then
line.text:SetText(tostring(entry.name) .. " likes " .. tostring(entry.diets or "unknown diet"))
else
line.text:SetText(tostring(entry.placeholder or ""))
end
else
line.icon:Hide()
line.text:SetText("")
end
end
end
end
local function MTH_AB_GetBuyableItems(subtype)
if type(MTH_AutoBuy_GetProjectileItems) == "function" then
local result = MTH_AutoBuy_GetProjectileItems(subtype)
if type(result) == "table" then
return result
end
end
local wanted = string.lower(tostring(subtype or ""))
local result = {}
local source = MTH_DS_AmmoItems or {}
for itemId, row in pairs(source) do
if type(row) == "table" and string.lower(tostring(row.subtype or "")) == wanted and type(row.vendors) == "table" then
for _ in pairs(row.vendors) do
table.insert(result, {
itemId = tonumber(itemId) or itemId,
name = tostring(row.name or ("Item " .. tostring(itemId))),
level = tonumber(row.level) or 0,
reqlevel = tonumber(row.reqlevel) or 0,
})
break
end
end
end
table.sort(result, function(a, b)
if (a.level or 0) ~= (b.level or 0) then
return (a.level or 0) < (b.level or 0)
end
if tostring(a.name or "") ~= tostring(b.name or "") then
return tostring(a.name or "") < tostring(b.name or "")
end
return tonumber(a.itemId or 0) < tonumber(b.itemId or 0)
end)
return result
end
local function MTH_AB_GetItemLabel(item)
if type(item) ~= "table" then return "Unknown" end
return tostring(item.name or ("Item " .. tostring(item.itemId or "")))
end
local function MTH_AB_DropdownSetText(frame, text)
if UIDropDownMenu_SetText then
UIDropDownMenu_SetText(tostring(text or ""), frame)
return
end
if not frame then return end
local textRegion = getglobal(frame:GetName() .. "Text")
if textRegion and textRegion.SetText then
textRegion:SetText(tostring(text or ""))
end
end
local function MTH_AB_FindItemById(list, itemId)
itemId = tonumber(itemId)
if not itemId or type(list) ~= "table" then return nil end
for i = 1, table.getn(list) do
if tonumber(list[i].itemId) == itemId then
return list[i]
end
end
return nil
end
local function MTH_AB_GetRule(store, subtype, index)
local bucket = subtype == "arrow" and store.projectiles.arrows or store.projectiles.bullets
if type(bucket.rules[index]) ~= "table" then
bucket.rules[index] = MTH_AB_NormalizeRule(nil)
end
bucket.rules[index] = MTH_AB_NormalizeRule(bucket.rules[index])
return bucket.rules[index]
end
local function MTH_AB_SetRuleEnabled(store, subtype, index, enabled)
local rule = MTH_AB_GetRule(store, subtype, index)
rule.enabled = enabled and true or false
if not rule.enabled then
rule.itemId = nil
rule.stacks = 0
elseif rule.stacks < 1 then
rule.stacks = 1
end
end
local function MTH_AB_SetRuleItem(store, subtype, index, itemId)
local rule = MTH_AB_GetRule(store, subtype, index)
rule.itemId = itemId and (tonumber(itemId) or itemId) or nil
end
local function MTH_AB_SetRuleStacks(store, subtype, index, value)
local rule = MTH_AB_GetRule(store, subtype, index)
local stacks = math.floor(tonumber(value) or 0)
if stacks < 0 then stacks = 0 end
if rule.enabled and stacks < 1 then stacks = 1 end
rule.stacks = stacks
end
local function MTH_AB_RefreshRowUI(row, store, moduleEnabled, projectilesEnabled)
local rule = MTH_AB_GetRule(store, row.subtype, row.index)
local rowEnabled = moduleEnabled and projectilesEnabled and rule.enabled
local rowInteractive = moduleEnabled and projectilesEnabled
local muted = rowInteractive and 1 or 0.5
MTH_AB_SetCheckedNoClick(row.check, rule.enabled)
if rowInteractive then
if row.check.Enable then row.check:Enable() end
else
if row.check.Disable then row.check:Disable() end
end
if UIDropDownMenu_EnableDropDown and UIDropDownMenu_DisableDropDown then
if rowEnabled then
UIDropDownMenu_EnableDropDown(row.dropdown)
else
UIDropDownMenu_DisableDropDown(row.dropdown)
end
end
local selectedItem = MTH_AB_FindItemById(row.items, rule.itemId)
if selectedItem then
MTH_AB_DropdownSetText(row.dropdown, MTH_AB_GetItemLabel(selectedItem))
else
MTH_AB_DropdownSetText(row.dropdown, "Select item...")
end
if row.qty then
if rowEnabled then
if row.qty.Enable then row.qty:Enable() end
if row.qty.EnableMouse then row.qty:EnableMouse(true) end
if row.qty.SetTextColor then row.qty:SetTextColor(1, 1, 1) end
else
if row.qty.Disable then row.qty:Disable() end
if row.qty.EnableMouse then row.qty:EnableMouse(false) end
if row.qty.ClearFocus then row.qty:ClearFocus() end
if row.qty.SetTextColor then row.qty:SetTextColor(0.6, 0.6, 0.6) end
end
row.qty:SetText(tostring(rule.stacks or 0))
end
if row.label and row.label.SetTextColor then
row.label:SetTextColor(muted, muted, muted)
end
if row.suffix and row.suffix.SetTextColor then
row.suffix:SetTextColor(muted, muted, muted)
end
end
local function MTH_AB_RefreshOptionsUI()
if not MTH_AB_STATE.built then return end
local store = MTH_AB_EnsureConfig()
local moduleEnabled = true
if MTH and MTH.IsModuleEnabled then
moduleEnabled = MTH:IsModuleEnabled("autobuy", true) and true or false
end
MTH_AB_SetCheckedNoClick(MTH_AB_STATE.controls.moduleEnabled, moduleEnabled)
MTH_AB_SetCheckedNoClick(MTH_AB_STATE.controls.projectilesEnabled, store.projectiles.enabled and true or false)
if MTH_AB_STATE.controls.projectilesEnabled then
local proj = MTH_AB_STATE.controls.projectilesEnabled
if moduleEnabled then
if proj.Enable then
proj:Enable()
end
else
if proj.Disable then
proj:Disable()
end
end
end
if MTH_AB_STATE.controls.petFoodEnabled then
local pf = MTH_AB_STATE.controls.petFoodEnabled
MTH_AB_SetCheckedNoClick(pf, store.petFood.enabled and true or false)
if moduleEnabled then
if pf.Enable then pf:Enable() end
else
if pf.Disable then pf:Disable() end
end
end
local petFoodInteractive = moduleEnabled and store.petFood.enabled
if MTH_AB_STATE.controls.petFoodQty then
local qty = MTH_AB_STATE.controls.petFoodQty
qty:SetText(tostring(store.petFood.stacks or 1))
if petFoodInteractive then
if qty.Enable then qty:Enable() end
if qty.EnableMouse then qty:EnableMouse(true) end
if qty.SetTextColor then qty:SetTextColor(1, 1, 1) end
else
if qty.Disable then qty:Disable() end
if qty.EnableMouse then qty:EnableMouse(false) end
if qty.ClearFocus then qty:ClearFocus() end
if qty.SetTextColor then qty:SetTextColor(0.6, 0.6, 0.6) end
end
end
if MTH_AB_STATE.controls.petFoodScopeCurrent then
MTH_AB_SetCheckedNoClick(MTH_AB_STATE.controls.petFoodScopeCurrent, store.petFood.scope == "current")
if petFoodInteractive then
if MTH_AB_STATE.controls.petFoodScopeCurrent.Enable then MTH_AB_STATE.controls.petFoodScopeCurrent:Enable() end
else
if MTH_AB_STATE.controls.petFoodScopeCurrent.Disable then MTH_AB_STATE.controls.petFoodScopeCurrent:Disable() end
end
end
if MTH_AB_STATE.controls.petFoodScopeAll then
MTH_AB_SetCheckedNoClick(MTH_AB_STATE.controls.petFoodScopeAll, store.petFood.scope == "all")
if petFoodInteractive then
if MTH_AB_STATE.controls.petFoodScopeAll.Enable then MTH_AB_STATE.controls.petFoodScopeAll:Enable() end
else
if MTH_AB_STATE.controls.petFoodScopeAll.Disable then MTH_AB_STATE.controls.petFoodScopeAll:Disable() end
end
end
MTH_AB_RefreshPetFoodListUI()
for i = 1, 3 do
MTH_AB_RefreshRowUI(MTH_AB_STATE.controls.rows.arrow[i], store, moduleEnabled, store.projectiles.enabled)
MTH_AB_RefreshRowUI(MTH_AB_STATE.controls.rows.bullet[i], store, moduleEnabled, store.projectiles.enabled)
end
end
local function MTH_AB_CreateRow(container, subtype, index, xBase, yOffset)
local row = {
subtype = subtype,
index = index,
items = MTH_AB_GetBuyableItems(subtype),
}
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.label:SetText("On?")
row.dropdown = CreateFrame("Frame", nameBase .. "Drop", container, "UIDropDownMenuTemplate")
row.dropdown:ClearAllPoints()
row.dropdown:SetPoint("TOPLEFT", container, "TOPLEFT", xBase + MTH_AB_LAYOUT.ROW_DROPDOWN_X, yOffset + 3)
if UIDropDownMenu_SetWidth then
UIDropDownMenu_SetWidth(MTH_AB_LAYOUT.ROW_DROPDOWN_WIDTH, row.dropdown)
end
if UIDropDownMenu_JustifyText then
UIDropDownMenu_JustifyText("LEFT", row.dropdown)
end
if UIDropDownMenu_Initialize then
UIDropDownMenu_Initialize(row.dropdown, function()
local infoFactory = UIDropDownMenu_CreateInfo
local info = type(infoFactory) == "function" and infoFactory() or {}
info.text = "Select item..."
info.func = function()
local store = MTH_AB_EnsureConfig()
MTH_AB_SetRuleItem(store, subtype, index, nil)
MTH_AB_RefreshOptionsUI()
end
if UIDropDownMenu_AddButton then UIDropDownMenu_AddButton(info) end
for j = 1, table.getn(row.items) do
local item = row.items[j]
local label = MTH_AB_GetItemLabel(item)
local itemId = item.itemId
local entry = type(infoFactory) == "function" and infoFactory() or {}
entry.text = label
entry.func = function()
local store = MTH_AB_EnsureConfig()
MTH_AB_SetRuleItem(store, subtype, index, itemId)
MTH_AB_RefreshOptionsUI()
end
if UIDropDownMenu_AddButton then UIDropDownMenu_AddButton(entry) end
end
end)
end
row.qty = CreateFrame("EditBox", nameBase .. "Qty", container, "InputBoxTemplate")
local dropButton = getglobal(nameBase .. "DropButton")
if dropButton then
row.qty:SetPoint("LEFT", dropButton, "RIGHT", MTH_AB_LAYOUT.ROW_QTY_GAP, -1)
else
row.qty:SetPoint("LEFT", row.dropdown, "RIGHT", MTH_AB_LAYOUT.ROW_QTY_GAP + 6, -3)
end
row.qty:SetWidth(40)
row.qty:SetHeight(20)
row.qty:SetNumeric(true)
row.qty:SetAutoFocus(false)
row.suffix = container:CreateFontString(nameBase .. "Suffix", "ARTWORK", "GameFontNormalSmall")
row.suffix:SetPoint("LEFT", row.qty, "RIGHT", MTH_AB_LAYOUT.ROW_SUFFIX_GAP, 0)
row.suffix:SetText("Stacks")
if row.check then
row.check:SetScript("OnClick", function(self)
self = self or this
if not self or MTH_AB_STATE.syncing then return end
local store = MTH_AB_EnsureConfig()
MTH_AB_SetRuleEnabled(store, subtype, index, self:GetChecked() and true or false)
MTH_AB_RefreshOptionsUI()
end)
end
row.qty:SetScript("OnEnterPressed", function(self)
self = self or this
if not self then return end
local store = MTH_AB_EnsureConfig()
MTH_AB_SetRuleStacks(store, subtype, index, self:GetText())
MTH_AB_RefreshOptionsUI()
self:ClearFocus()
end)
row.qty:SetScript("OnEditFocusLost", function(self)
self = self or this
if not self then return end
local store = MTH_AB_EnsureConfig()
MTH_AB_SetRuleStacks(store, subtype, index, self:GetText())
MTH_AB_RefreshOptionsUI()
end)
return row
end
local function MTH_AB_BuildUI(container)
if MTH_AB_STATE.built and MTH_AB_STATE.container == container then
return
end
MTH_ClearContainer(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
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(self)
self = self or this
if not self or MTH_AB_STATE.syncing then return end
if MTH and MTH.SetModuleEnabled then
local ok, err = MTH:SetModuleEnabled("autobuy", self:GetChecked() and true or false)
if not ok and MTH and MTH.Print then
MTH:Print("Failed to change Auto Buy state: " .. tostring(err), "error")
end
end
MTH_AB_RefreshOptionsUI()
end)
end
controls.projectilesEnabled = MTH_CreateCheckbox(container, "MetaHuntOptionsAutoBuyProjectilesEnabled", "Enable for Projectiles", -74)
if controls.projectilesEnabled then
controls.projectilesEnabled:SetScript("OnClick", function(self)
self = self or this
if not self or MTH_AB_STATE.syncing then return end
local store = MTH_AB_EnsureConfig()
store.projectiles.enabled = self:GetChecked() and true or false
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
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)
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")
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(self)
self = self or this
if not self or MTH_AB_STATE.syncing then return end
local store = MTH_AB_EnsureConfig()
store.petFood.enabled = self:GetChecked() and true or false
MTH_AB_RefreshOptionsUI()
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")
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(self)
self = self or this
if not self then return end
local store = MTH_AB_EnsureConfig()
MTH_AB_SetPetFoodStacks(store, self:GetText())
MTH_AB_RefreshOptionsUI()
self:ClearFocus()
end)
controls.petFoodQty:SetScript("OnEditFocusLost", function(self)
self = self or this
if not self then return end
local store = MTH_AB_EnsureConfig()
MTH_AB_SetPetFoodStacks(store, self:GetText())
MTH_AB_RefreshOptionsUI()
end)
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(self)
self = self or this
if not self or MTH_AB_STATE.syncing then return end
local store = MTH_AB_EnsureConfig()
store.petFood.scope = "current"
MTH_AB_RefreshOptionsUI()
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(self)
self = self or this
if not self or MTH_AB_STATE.syncing then return end
local store = MTH_AB_EnsureConfig()
store.petFood.scope = "all"
MTH_AB_RefreshOptionsUI()
end)
end
controls.petFoodPetLines = {}
for i = 1, 5 do
local rowY = -434 - ((i - 1) * 22)
local icon = container:CreateTexture("MetaHuntOptionsAutoBuyPetFoodPetIcon" .. tostring(i), "ARTWORK")
icon:SetWidth(16)
icon:SetHeight(16)
icon:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_AB_LAYOUT.LEFT_SECTION_X + 28, rowY)
icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
local text = container:CreateFontString("MetaHuntOptionsAutoBuyPetFoodPetText" .. tostring(i), "ARTWORK", "GameFontNormalSmall")
text:SetPoint("LEFT", icon, "RIGHT", 6, 0)
text:SetJustifyH("LEFT")
text:SetTextColor(1, 1, 1)
text:SetText("")
controls.petFoodPetLines[i] = { icon = icon, text = text }
end
end
function MTH_SetupAutoBuyOptions()
if not MTH_AB_READY then return end
local container = MTH_GetFrame("MetaHuntOptionsAutoBuy")
if not container then return end
if CloseDropDownMenus then
CloseDropDownMenus()
end
MTH_AB_BuildUI(container)
MTH_AB_RefreshOptionsUI()
end
+597
View File
@@ -0,0 +1,597 @@
local function MTH_CHRON_GetGlobal(name)
if type(getglobal) == "function" then
return getglobal(name)
end
if _G then
return _G[name]
end
return nil
end
local function MTH_CHRON_IsReady()
local required = {
"MTH_GetFrame",
"MTH_ClearContainer",
"MTH_CreateCheckbox",
"MTH_CreateSlider",
"MTH_CreateActionButton",
}
for i = 1, table.getn(required) do
if MTH_CHRON_GetGlobal(required[i]) == nil then
return false
end
end
return true
end
local MTH_CHRON_STATE = {
controls = {},
}
local MTH_CHRON_DEFAULTS = {
growup = false,
reverse = false,
fadeonkill = true,
fadeonfade = true,
barposition = {},
ghost = 0,
selfbars = true,
barwidth = 220,
barheight = 16,
spacing = 0,
barscale = 1,
iconposition = "LEFT",
textsize = 10,
textcolor = "white",
bgcolor = "teal",
barcolor = "gray",
bordercolor = "black",
bordertex = "None",
bgalpha = 0.5,
text = "$t",
onlyself = false,
disabledSpells = {
COMMON = {
["Ephemeral Power"] = {},
["Mind Quickening"] = {},
["Unstable Power"] = {},
},
RACIAL = {},
HUNTER = {},
},
}
local function MTH_CHRON_SeedDefaultCommonDisabled(commonBucket)
if type(commonBucket) ~= "table" then
return
end
local names = {
"Ephemeral Power",
"Mind Quickening",
"Unstable Power",
}
for i = 1, table.getn(names) do
local baseName = names[i]
if commonBucket[baseName] == nil then
commonBucket[baseName] = {}
end
if MTH and MTH.LocalizeSpell then
local localized = MTH:LocalizeSpell(baseName)
if localized and localized ~= "" and commonBucket[localized] == nil then
commonBucket[localized] = {}
end
end
end
end
local function MTH_CHRON_CopyTable(source)
local target = {}
for key, value in pairs(source) do
if type(value) == "table" then
target[key] = MTH_CHRON_CopyTable(value)
else
target[key] = value
end
end
return target
end
local function MTH_CHRON_GetProfile()
if not (MTH and MTH.GetModuleSavedVariables) then
return MTH_CHRON_CopyTable(MTH_CHRON_DEFAULTS)
end
local store = MTH:GetModuleSavedVariables("chronometer")
if type(store.profile) ~= "table" then
store.profile = {}
end
local profile = store.profile
for key, value in pairs(MTH_CHRON_DEFAULTS) do
if profile[key] == nil then
if type(value) == "table" then
profile[key] = MTH_CHRON_CopyTable(value)
else
profile[key] = value
end
end
end
if type(profile.disabledSpells) ~= "table" then
profile.disabledSpells = {}
end
if type(profile.disabledSpells.COMMON) ~= "table" then
profile.disabledSpells.COMMON = {}
end
MTH_CHRON_SeedDefaultCommonDisabled(profile.disabledSpells.COMMON)
if type(profile.disabledSpells.RACIAL) ~= "table" then
profile.disabledSpells.RACIAL = {}
end
local _, class = UnitClass("player")
class = class or "HUNTER"
if type(profile.disabledSpells[class]) ~= "table" then
profile.disabledSpells[class] = {}
end
return profile
end
local function MTH_CHRON_GetTimerDefinitions()
local engine = MTH_ChronometerHunter
if engine and engine.timers and (engine.timers[engine.SPELL] or engine.timers[engine.EVENT]) then
return engine.timers, engine.SPELL, engine.EVENT
end
if not engine then
return nil, 1, 2
end
local fake = {
SPELL = 1,
EVENT = 2,
groups = {},
timers = {},
}
function fake:AddGroup(id, forall, color)
if color then
self.groups[id] = { fa = forall, cr = color }
else
self.groups[id] = { fa = forall }
end
end
function fake:AddTimer(kind, name, duration, targeted, isgain, selforselect, extra)
if type(self.timers[kind]) ~= "table" then
self.timers[kind] = {}
end
if type(extra) ~= "table" then
extra = {}
end
self.timers[kind][name] = {
d = duration,
k = { t = targeted, g = isgain, s = selforselect },
x = extra,
}
end
if type(engine.dataSetup) == "table" then
for _, setupFunc in pairs(engine.dataSetup) do
if type(setupFunc) == "function" then
setupFunc(fake)
end
end
end
return fake.timers, fake.SPELL, fake.EVENT
end
local function MTH_CHRON_SortKeys(list)
table.sort(list, function(a, b)
return tostring(a) < tostring(b)
end)
end
local function MTH_CHRON_BuildTimerLists()
local timers, spellKind, eventKind = MTH_CHRON_GetTimerDefinitions()
local classSpells, classEvents, racial = {}, {}, {}
if type(timers) ~= "table" then
return classSpells, classEvents, racial
end
local function classify(timerRecord)
if timerRecord and timerRecord.x and timerRecord.x.cl == "COMMON" then
return "COMMON"
elseif timerRecord and timerRecord.x and timerRecord.x.cl == "RACIAL" then
return "RACIAL"
end
local _, class = UnitClass("player")
return class or "HUNTER"
end
for timerName, timerRecord in pairs(timers[spellKind] or {}) do
local bucket = classify(timerRecord)
if bucket == "RACIAL" then
table.insert(racial, timerName)
elseif bucket ~= "COMMON" then
table.insert(classSpells, timerName)
end
end
for timerName, timerRecord in pairs(timers[eventKind] or {}) do
local bucket = classify(timerRecord)
if bucket == "RACIAL" then
table.insert(racial, timerName)
elseif bucket ~= "COMMON" then
table.insert(classEvents, timerName)
end
end
MTH_CHRON_SortKeys(classSpells)
MTH_CHRON_SortKeys(classEvents)
MTH_CHRON_SortKeys(racial)
return classSpells, classEvents, racial
end
local function MTH_CHRON_ApplyLiveProfile(profile)
if not profile then
return
end
local engine = MTH_ChronometerHunter
if not (engine and engine._mth_enabled) then
return
end
engine.profile = profile
if engine.SetCandyBarGroupGrowth then
engine:SetCandyBarGroupGrowth("MTHChronometer", profile.growup and true or false)
end
if engine.SetCandyBarGroupVerticalSpacing then
engine:SetCandyBarGroupVerticalSpacing("MTHChronometer", profile.spacing or 0)
end
if type(engine.bars) == "table" then
for i = 1, 20 do
local bar = engine.bars[i]
if bar and bar.id then
if profile.barscale and engine.SetCandyBarScale then
engine:SetCandyBarScale(bar.id, profile.barscale)
end
if profile.barwidth and engine.SetCandyBarWidth then
engine:SetCandyBarWidth(bar.id, profile.barwidth)
end
if profile.barheight and engine.SetCandyBarHeight then
engine:SetCandyBarHeight(bar.id, profile.barheight)
end
if profile.iconposition and engine.SetCandyBarIconPosition then
engine:SetCandyBarIconPosition(bar.id, profile.iconposition)
end
if profile.textsize and engine.SetCandyBarFontSize then
engine:SetCandyBarFontSize(bar.id, profile.textsize)
end
if profile.text and engine.SetCandyBarText then
local target = bar.target or "none"
local text = target == "none" and (bar.name or "") or profile.text
text = string.gsub(tostring(text or ""), "$t", tostring(target))
text = string.gsub(tostring(text or ""), "$s", tostring(bar.name or ""))
engine:SetCandyBarText(bar.id, text)
end
if engine.SetCandyBarReversed then
engine:SetCandyBarReversed(bar.id, profile.reverse and true or false)
end
end
end
end
end
local function MTH_CHRON_MakeTitle(parent, name, text, x, y)
local label = parent:CreateFontString(name, "ARTWORK", "GameFontHighlight")
label:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
label:SetText(text)
return label
end
local function MTH_CHRON_MakeSmall(parent, name, text, x, y)
local label = parent:CreateFontString(name, "ARTWORK", "GameFontNormalSmall")
label:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
label:SetText(text)
return label
end
local function MTH_CHRON_MakeInput(parent, name, x, y, width, height, value, onAccept)
local edit = CreateFrame("EditBox", name, parent, "InputBoxTemplate")
if not edit then
return nil
end
edit:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
edit:SetWidth(width or 200)
edit:SetHeight(height or 20)
edit:SetAutoFocus(false)
edit:SetText(tostring(value or ""))
edit:SetScript("OnEnterPressed", function(self)
self = self or this
if not self then return end
if type(onAccept) == "function" then
onAccept(self:GetText() or "")
end
self:ClearFocus()
end)
return edit
end
local function MTH_CHRON_NormalizeSection(tabKey)
if not tabKey and _G then
tabKey = _G.MTH_CHRON_OPTIONS_SECTION
end
if tabKey == "ChronometerBar" then
return "bar"
elseif tabKey == "ChronometerClassSpells" then
return "classspells"
elseif tabKey == "ChronometerClassEvents" then
return "classevents"
elseif tabKey == "ChronometerRacial" then
return "racial"
end
return "general"
end
local function MTH_CHRON_RenderTimerSection(container, profile, title, items, bucket)
local timerX = 20
local timerY = -84
MTH_CHRON_MakeTitle(container, "MetaHuntOptionsChronometerTimersHeader", title, timerX, timerY)
timerY = timerY - 24
if table.getn(items) == 0 then
MTH_CHRON_MakeSmall(container, "MetaHuntOptionsChronometerSectionEmpty", "(none)", timerX + 8, timerY)
return
end
for i = 1, table.getn(items) do
if timerY < -560 then
MTH_CHRON_MakeSmall(container, "MetaHuntOptionsChronometerOverflow", "...more", timerX + 8, timerY)
break
end
local timerName = items[i]
local controlName = "MetaHuntOptionsChronometerTimer" .. tostring(bucket) .. tostring(i)
local check = MTH_CreateCheckbox(container, controlName, tostring(timerName), timerY, timerX)
if check then
check:SetChecked(profile.disabledSpells[bucket][timerName] == nil and true or false)
check:SetScript("OnClick", function(self)
self = self or this
if not self then return end
if self:GetChecked() then
profile.disabledSpells[bucket][timerName] = nil
else
profile.disabledSpells[bucket][timerName] = {}
end
end)
end
timerY = timerY - 20
end
end
function MTH_RefreshChronometerOptions(tabKey)
if not MTH_CHRON_IsReady() then return end
MTH_SetupChronometerOptions(tabKey)
end
function MTH_SetupChronometerOptions(tabKey)
if not MTH_CHRON_IsReady() then
if MTH and MTH.Print then
MTH:Print("Chronometer options dependencies missing", "error")
end
return
end
local section = MTH_CHRON_NormalizeSection(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 moduleCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerEnabled", "Enable Chronometer module", -54)
if moduleCheck then
local module = MTH and MTH:GetModule("chronometer")
moduleCheck:SetChecked(module and module.enabled and true or false)
moduleCheck:SetScript("OnClick", function(self)
self = self or this
if not self then return end
local enabled = self:GetChecked() and true or false
if MTH and MTH.SetModuleEnabled then
MTH:SetModuleEnabled("chronometer", enabled)
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)
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(self)
self = self or this
if not self then return end
profile.fadeonkill = self:GetChecked() and true or false
end)
end
local fadeCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerFadeOnFade", "Fade on aura fade", -179)
if fadeCheck then
fadeCheck:SetChecked(profile.fadeonfade and true or false)
fadeCheck:SetScript("OnClick", function(self)
self = self or this
if not self then return end
profile.fadeonfade = self:GetChecked() and true or false
end)
end
local selfCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerSelfBars", "Show self bars", -204)
if selfCheck then
selfCheck:SetChecked(profile.selfbars and true or false)
selfCheck:SetScript("OnClick", function(self)
self = self or this
if not self then return end
profile.selfbars = self:GetChecked() and true or false
end)
end
local onlySelfCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerOnlySelf", "Only self target", -229)
if onlySelfCheck then
onlySelfCheck:SetChecked(profile.onlyself and true or false)
onlySelfCheck:SetScript("OnClick", function(self)
self = self or this
if not self then return end
profile.onlyself = self:GetChecked() and true or false
end)
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 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)
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)
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)
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)
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)
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)
profile.textsize = tonumber(value) or 10
MTH_CHRON_ApplyLiveProfile(profile)
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(self)
self = self or this
if not self then return end
profile.growup = self:GetChecked() and true or false
MTH_CHRON_ApplyLiveProfile(profile)
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(self)
self = self or this
if not self then return end
profile.reverse = self:GetChecked() and true or false
MTH_CHRON_ApplyLiveProfile(profile)
end)
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()
profile.iconposition = "LEFT"
iconLeft:SetChecked(true)
iconRight:SetChecked(nil)
MTH_CHRON_ApplyLiveProfile(profile)
end)
iconRight:SetScript("OnClick", function()
profile.iconposition = "RIGHT"
iconRight:SetChecked(true)
iconLeft:SetChecked(nil)
MTH_CHRON_ApplyLiveProfile(profile)
end)
end
else
local classSpells, classEvents, racial = MTH_CHRON_BuildTimerLists()
if section == "classspells" then
MTH_CHRON_RenderTimerSection(container, profile, "Hunter Spells", classSpells, class)
elseif section == "classevents" then
MTH_CHRON_RenderTimerSection(container, profile, "Hunter Events", classEvents, class)
elseif section == "racial" then
MTH_CHRON_RenderTimerSection(container, profile, "Racial", racial, "RACIAL")
end
end
end
if _G then
_G.MTH_SetupChronometerOptions = MTH_SetupChronometerOptions
_G.MTH_RefreshChronometerOptions = MTH_RefreshChronometerOptions
end
+193
View File
@@ -0,0 +1,193 @@
local MTH_CREDITS_READY = MTH_OptionsRequire and MTH_OptionsRequire("options-credits", {
"MTH_GetFrame",
"MTH_ClearContainer",
})
local MTH_CREDITS_FONT_DELTA = 2
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()
if path and size then
fs:SetFont(path, size + MTH_CREDITS_FONT_DELTA, flags)
end
end
local function MTH_CreditsInsertLink(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("Link inserted in chat: " .. link)
end
return
end
if MTH and MTH.Print then
MTH:Print("Link: " .. link)
end
end
local function MTH_CreditsClearFrame(frame)
if not frame then return end
local children = { frame:GetChildren() }
for i = 1, table.getn(children) do
if children[i] and children[i].Hide then
children[i]:Hide()
end
end
local regions = { frame:GetRegions() }
for i = 1, table.getn(regions) do
if regions[i] and regions[i].Hide then
regions[i]:Hide()
end
end
end
local function MTH_CreditsCreateLink(parent, label, url, anchorTo, y)
local button = CreateFrame("Button", nil, parent)
if not button then return nil end
button:SetPoint("TOPLEFT", anchorTo, "BOTTOMLEFT", 0, y or -4)
button:SetHeight(18)
button:SetWidth(500)
local text = button:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
text:SetPoint("LEFT", button, "LEFT", 0, 0)
text:SetJustifyH("LEFT")
text:SetText(label)
text:SetTextColor(0.40, 0.75, 1.00)
MTH_CreditsBumpFont(text)
button:SetScript("OnClick", function()
MTH_CreditsInsertLink(url)
end)
button:SetScript("OnEnter", function()
text:SetTextColor(0.60, 0.90, 1.00)
end)
button:SetScript("OnLeave", function()
text:SetTextColor(0.40, 0.75, 1.00)
end)
return button
end
local function MTH_CreditsCreateText(parent, anchorTo, text, font, y, r, g, b)
local fs = parent:CreateFontString(nil, "ARTWORK", font or "GameFontNormalSmall")
if anchorTo == parent then
fs:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, y or 0)
else
fs:SetPoint("TOPLEFT", anchorTo, "BOTTOMLEFT", 0, y or -8)
end
fs:SetWidth(500)
fs:SetJustifyH("LEFT")
fs:SetJustifyV("TOP")
fs:SetTextColor(r or 0.93, g or 0.93, b or 0.93)
fs:SetText(text)
MTH_CreditsBumpFont(fs)
return fs
end
function MTH_SetupCreditsOptions()
if not MTH_CREDITS_READY then return end
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("Credits")
title:SetTextColor(1.00, 0.82, 0.00)
local scrollFrame = MTH_GetFrame("MetaHuntOptionsCreditsScroll")
if not scrollFrame then
scrollFrame = CreateFrame("ScrollFrame", "MetaHuntOptionsCreditsScroll", container, "UIPanelScrollFrameTemplate")
end
scrollFrame:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -10)
scrollFrame:SetPoint("BOTTOMRIGHT", container, "BOTTOMRIGHT", -30, 10)
scrollFrame:Show()
local content = MTH_GetFrame("MetaHuntOptionsCreditsContent")
if not content then
content = CreateFrame("Frame", "MetaHuntOptionsCreditsContent", scrollFrame)
end
content:SetWidth(520)
content:SetHeight(2600)
MTH_CreditsClearFrame(content)
scrollFrame:SetScrollChild(content)
local cursor = MTH_CreditsCreateText(content, content, "About the code", "GameFontNormal", -2, 1.00, 0.82, 0.00)
cursor = MTH_CreditsCreateText(content, cursor,
"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:",
"GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateText(content, cursor,
"- zBars, Antidaze and Autostrip were core functionalities of Vanilla zHunterMod addon. And I kept the \"z\" naming of bars/buttons to always remember it. On top of this I have created myself zAmmo, zCompanions, zMount, zToys.\nAlso the SmartAmmo feature idea is coming from Zhuntermod: there was a file in it with that functionality, but was like a work-in-progress, totally unfunctional and even \"dangerous\" in its current state, and was not active in the addon. Since it was a fucking great idea, I recoded it mostly from scratch and made it work reliably and safely.",
"GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateText(content, cursor,
"- 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.",
"GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateLink(content,
"https://www.wowinterface.com/downloads/info4160-FizzwidgetFeed-O-Matic.html",
"https://www.wowinterface.com/downloads/info4160-FizzwidgetFeed-O-Matic.html",
cursor,
-4)
cursor = MTH_CreditsCreateText(content, cursor,
"- 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.",
"GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateLink(content,
"https://legacy-wow.com/vanilla-addons/fizzwidget-hunter-helper/",
"https://legacy-wow.com/vanilla-addons/fizzwidget-hunter-helper/",
cursor,
-4)
cursor = MTH_CreditsCreateText(content, cursor,
"- 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.",
"GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateLink(content,
"https://github.com/wigan91/Chronometer-TWoW",
"https://github.com/wigan91/Chronometer-TWoW",
cursor,
-4)
cursor = MTH_CreditsCreateText(content, cursor,
"- The Map/Marker system is the one of pfQuest from Master Shagu. Unfortunately he is no more reachable for some months and I could not have some talk with him about this. I mostly let it untouched, I just made slight modifications so it can integrate better within Metahunt, and doesn't conflict with pfQuest.",
"GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateText(content, cursor,
"- The system allowing to fetch itemlinks in game (In the Weapon tab of the Hunter Book notably) is the one of Atlas Twow.",
"GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateText(content, cursor, "About data sources", "GameFontNormal", -14, 1.00, 0.82, 0.00)
cursor = MTH_CreditsCreateText(content, cursor,
"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:",
"GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateText(content, cursor,
"- pfQuest for Beasts and Ammo vendors NPCs and their spawn points (and beasts respawn times if any). Thank you again Shagu.",
"GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateText(content, cursor,
"- 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,
"- 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,
"With that being said, go back hunting fellas!",
"GameFontNormalSmall", -10, 1.00, 0.82, 0.00)
cursor = MTH_CreditsCreateText(content, cursor,
"\n"
.. "Metasploit <Atlantis>, of Nordaanar.",
"GameFontNormalSmall", -10)
end
+373
View File
@@ -0,0 +1,373 @@
local MTH_FOM_READY = MTH_OptionsRequire and MTH_OptionsRequire("options-feedomatic", {
"MTH_GetFrame",
"MTH_ClearContainer",
"MTH_CreateCheckbox",
})
local MTH_FOM_STATE = {
enableCheck = nil,
keepOpenEdit = nil,
bindStatus = nil,
bindButton = nil,
bindClearButton = nil,
bindingCapture = false,
ctrl = {},
}
local function MTH_EnsureFOMConfig()
if FOM_Config then
return
end
if FOM_Config_Default then
FOM_Config = {}
for key, value in pairs(FOM_Config_Default) do
FOM_Config[key] = value
end
return
end
FOM_Config = {
Enabled = false,
Alert = "emote",
Level = "content",
KeepOpenSlots = 8,
AvoidUsefulFood = true,
AvoidQuestFood = true,
AvoidBonusFood = true,
Fallback = false,
SaveForCookingLevel = 1,
PreferHigherQuality = true,
AudioWarning = true,
TextWarning = true,
IconWarning = true,
}
end
local function MTH_FOM_SetAlert(mode)
MTH_EnsureFOMConfig()
if mode == "none" then
FOM_Config.Alert = nil
else
FOM_Config.Alert = mode
end
end
local function MTH_FOM_SetLevel(level)
MTH_EnsureFOMConfig()
if level == "off" then
FOM_Config.Level = nil
else
FOM_Config.Level = level
end
end
local function MTH_FOM_SetCookingLevel(level)
MTH_EnsureFOMConfig()
FOM_Config.SaveForCookingLevel = level
end
local function MTH_FOM_GetBindingDisplayText()
if not GetBindingKey then
return "Unavailable"
end
local key1, key2 = GetBindingKey("FEEDOMATIC")
if not key1 and not key2 then
return "Unbound"
end
if key1 and key2 then
return key1 .. " / " .. key2
end
return key1 or key2 or "Unbound"
end
local function MTH_FOM_UpdateBindingStatus()
local bindStatus = MTH_FOM_STATE.bindStatus
if not bindStatus or not bindStatus.SetText then
return
end
if MTH_FOM_STATE.bindingCapture then
bindStatus:SetText("Feed key: press a key... (ESC to clear)")
else
bindStatus:SetText("Feed key: " .. MTH_FOM_GetBindingDisplayText())
end
end
local function MTH_FOM_StopBindingCapture()
MTH_FOM_STATE.bindingCapture = false
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("Set Key")
end
MTH_FOM_UpdateBindingStatus()
end
local function MTH_FOM_StartBindingCapture()
MTH_FOM_STATE.bindingCapture = true
if MTH_FOM_STATE.bindButton then
MTH_FOM_STATE.bindButton:SetText("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)
if not SetBinding or not SaveBindings or not GetBindingKey then
if MTH and MTH.Print then
MTH:Print("Keybinding APIs unavailable.")
end
return
end
if InCombatLockdown and InCombatLockdown() then
if MTH and MTH.Print then
MTH:Print("Cannot change keybindings during combat.")
end
MTH_FOM_StopBindingCapture()
return
end
local old1, old2 = GetBindingKey("FEEDOMATIC")
if old1 then SetBinding(old1) end
if old2 then SetBinding(old2) end
if key and key ~= "" and key ~= "ESCAPE" then
SetBinding(key, "FEEDOMATIC")
end
local bindingSet = 1
if GetCurrentBindingSet then
bindingSet = GetCurrentBindingSet() or 1
end
SaveBindings(bindingSet)
MTH_FOM_StopBindingCapture()
end
function MTH_RefreshFeedOMaticOptions()
if not MTH_FOM_READY then return end
MTH_EnsureFOMConfig()
if MTH_FOM_STATE.enableCheck then
local module = MTH and MTH:GetModule("feedomatic")
MTH_FOM_STATE.enableCheck:SetChecked(module and module.enabled and true or false)
end
if MTH_FOM_STATE.keepOpenEdit then
MTH_FOM_STATE.keepOpenEdit:SetText(tostring(FOM_Config.KeepOpenSlots or 0))
end
MTH_FOM_UpdateBindingStatus()
if MTH_FOM_STATE.ctrl.AvoidQuestFood then MTH_FOM_STATE.ctrl.AvoidQuestFood:SetChecked(FOM_Config.AvoidQuestFood and 1 or nil) end
if MTH_FOM_STATE.ctrl.AvoidBonusFood then MTH_FOM_STATE.ctrl.AvoidBonusFood:SetChecked(FOM_Config.AvoidBonusFood and 1 or nil) end
if MTH_FOM_STATE.ctrl.PreferHigherQuality then MTH_FOM_STATE.ctrl.PreferHigherQuality:SetChecked(FOM_Config.PreferHigherQuality and 1 or nil) end
if MTH_FOM_STATE.ctrl.Fallback then MTH_FOM_STATE.ctrl.Fallback:SetChecked(FOM_Config.Fallback and 1 or nil) end
if MTH_FOM_STATE.ctrl.AudioWarning then MTH_FOM_STATE.ctrl.AudioWarning:SetChecked(FOM_Config.AudioWarning and true or false) end
if MTH_FOM_STATE.ctrl.AudioWarningBell then MTH_FOM_STATE.ctrl.AudioWarningBell:SetChecked(FOM_Config.AudioWarning == "bell" and 1 or nil) end
if MTH_FOM_STATE.ctrl.TextWarning then MTH_FOM_STATE.ctrl.TextWarning:SetChecked(FOM_Config.TextWarning and 1 or nil) end
if MTH_FOM_STATE.ctrl.IconWarning then MTH_FOM_STATE.ctrl.IconWarning:SetChecked(FOM_Config.IconWarning and 1 or nil) end
if MTH_FOM_STATE.ctrl.AlertEmote then MTH_FOM_STATE.ctrl.AlertEmote:SetChecked(FOM_Config.Alert == "emote" and 1 or nil) end
if MTH_FOM_STATE.ctrl.AlertChat then MTH_FOM_STATE.ctrl.AlertChat:SetChecked(FOM_Config.Alert == "chat" and 1 or nil) end
if MTH_FOM_STATE.ctrl.AlertNone then MTH_FOM_STATE.ctrl.AlertNone:SetChecked((not FOM_Config.Alert) and 1 or nil) end
if MTH_FOM_STATE.ctrl.LevelContent then MTH_FOM_STATE.ctrl.LevelContent:SetChecked(FOM_Config.Level == "content" and 1 or nil) end
if MTH_FOM_STATE.ctrl.LevelUnhappy then MTH_FOM_STATE.ctrl.LevelUnhappy:SetChecked(FOM_Config.Level == "unhappy" and 1 or nil) end
if MTH_FOM_STATE.ctrl.LevelOff then MTH_FOM_STATE.ctrl.LevelOff:SetChecked((not FOM_Config.Level) and 1 or nil) end
if MTH_FOM_STATE.ctrl.SaveCookOrange then MTH_FOM_STATE.ctrl.SaveCookOrange:SetChecked(FOM_Config.SaveForCookingLevel == 3 and 1 or nil) end
if MTH_FOM_STATE.ctrl.SaveCookYellow then MTH_FOM_STATE.ctrl.SaveCookYellow:SetChecked(FOM_Config.SaveForCookingLevel == 2 and 1 or nil) end
if MTH_FOM_STATE.ctrl.SaveCookGreen then MTH_FOM_STATE.ctrl.SaveCookGreen:SetChecked(FOM_Config.SaveForCookingLevel == 1 and 1 or nil) end
if MTH_FOM_STATE.ctrl.SaveCookAll then MTH_FOM_STATE.ctrl.SaveCookAll:SetChecked(FOM_Config.SaveForCookingLevel == 0 and 1 or nil) end
if MTH_FOM_STATE.ctrl.SaveCookNone then MTH_FOM_STATE.ctrl.SaveCookNone:SetChecked(FOM_Config.SaveForCookingLevel and FOM_Config.SaveForCookingLevel >= 4 and 1 or nil) end
end
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("Feed-O-Matic")
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("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", "Enable FeedOMatic module", -32 + yAdjust)
if moduleCheck then
moduleCheck:SetScript("OnClick", function(self)
self = self or this
if not self then return end
local enabled = self:GetChecked() == 1
if MTH and MTH.SetModuleEnabled then
local ok, err = MTH:SetModuleEnabled("feedomatic", enabled)
if not ok and MTH and MTH.Print then
MTH:Print("Failed to change FeedOMatic state: " .. tostring(err))
end
end
MTH_RefreshFeedOMaticOptions()
end)
MTH_FOM_STATE.enableCheck = moduleCheck
end
local leftHeader = container:CreateFontString("MetaHuntOptionsFeedOMaticGeneralHeader", "ARTWORK", "GameFontHighlight")
leftHeader:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -62 + yAdjust)
leftHeader:SetText("General")
local bindStatus = container:CreateFontString("MetaHuntOptionsFOMBindStatus", "ARTWORK", "GameFontNormalSmall")
bindStatus:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -80 + yAdjust)
bindStatus:SetText("Feed key: " .. MTH_FOM_GetBindingDisplayText())
MTH_FOM_STATE.bindStatus = bindStatus
local bindButton = CreateFrame("Button", "MetaHuntOptionsFOMBindButton", container, "UIPanelButtonTemplate")
bindButton:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -100 + yAdjust)
bindButton:SetWidth(90)
bindButton:SetHeight(22)
bindButton:SetText("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(_, key)
if not MTH_FOM_STATE.bindingCapture then
return
end
key = key or arg1
if not key or key == "" then
return
end
if key == "ESCAPE" then
MTH_FOM_SaveBinding(nil)
return
end
if key == "UNKNOWN" or key == "PRINTSCREEN" then
return
end
MTH_FOM_SaveBinding(MTH_FOM_BuildBindingChord(key))
end)
MTH_FOM_STATE.bindButton = bindButton
local bindClearButton = CreateFrame("Button", "MetaHuntOptionsFOMBindClearButton", container, "UIPanelButtonTemplate")
bindClearButton:SetPoint("LEFT", bindButton, "RIGHT", 8, 0)
bindClearButton:SetWidth(70)
bindClearButton:SetHeight(22)
bindClearButton:SetText("Clear")
bindClearButton:SetScript("OnClick", function()
MTH_FOM_SaveBinding(nil)
end)
MTH_FOM_STATE.bindClearButton = bindClearButton
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("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 = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMAvoidQuest", FOM_OptionsButtonText and FOM_OptionsButtonText["AvoidQuestFood"] or "Avoid quest foods", -170 + yAdjust)
MTH_FOM_STATE.ctrl.AvoidBonusFood = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMAvoidBonus", FOM_OptionsButtonText and FOM_OptionsButtonText["AvoidBonusFood"] or "Avoid bonus foods", -195 + yAdjust)
MTH_FOM_STATE.ctrl.PreferHigherQuality = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMPreferQuality", FOM_OptionsButtonText and FOM_OptionsButtonText["PreferHigherQuality"] or "Prefer higher quality food", -220 + yAdjust)
MTH_FOM_STATE.ctrl.Fallback = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMFallback", FOM_OptionsButtonText and FOM_OptionsButtonText["Fallback"] or "Fallback to avoided foods", -245 + yAdjust)
if MTH_FOM_STATE.ctrl.AvoidQuestFood then MTH_FOM_STATE.ctrl.AvoidQuestFood:SetScript("OnClick", function(self) self = self or this if not self then return end FOM_Config.AvoidQuestFood = self:GetChecked() == 1 end) end
if MTH_FOM_STATE.ctrl.AvoidBonusFood then MTH_FOM_STATE.ctrl.AvoidBonusFood:SetScript("OnClick", function(self) self = self or this if not self then return end FOM_Config.AvoidBonusFood = self:GetChecked() == 1 end) end
if MTH_FOM_STATE.ctrl.PreferHigherQuality then MTH_FOM_STATE.ctrl.PreferHigherQuality:SetScript("OnClick", function(self) self = self or this if not self then return end FOM_Config.PreferHigherQuality = self:GetChecked() == 1 end) end
if MTH_FOM_STATE.ctrl.Fallback then MTH_FOM_STATE.ctrl.Fallback:SetScript("OnClick", function(self) self = self or this if not self then return end FOM_Config.Fallback = self:GetChecked() == 1 end) end
local keepOpenLabel = container:CreateFontString("MetaHuntOptionsFOMKeepOpenLabel", "ARTWORK", "GameFontNormalSmall")
keepOpenLabel:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -302 + yAdjust)
keepOpenLabel:SetText("Keep open bag slots:")
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(self)
self = self or this
if not self then return end
local value = tonumber(self:GetText()) or 0
FOM_Config.KeepOpenSlots = value
end)
keepOpen:SetScript("OnEnterPressed", function(self) self = self or this if self then self:ClearFocus() end end)
keepOpen:SetScript("OnEscapePressed", function(self) self = self or this if self then self:ClearFocus() end end)
MTH_FOM_STATE.keepOpenEdit = keepOpen
local notifyHeader = container:CreateFontString("MetaHuntOptionsFOMNotifyHeader", "ARTWORK", "GameFontHighlight")
notifyHeader:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -336 + yAdjust)
notifyHeader:SetText("Notify when feeding")
MTH_FOM_STATE.ctrl.AlertEmote = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMAlertEmote", FOM_OptionsButtonText and FOM_OptionsButtonText["AlertEmote"] or "Via emote", -360 + yAdjust)
MTH_FOM_STATE.ctrl.AlertChat = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMAlertChat", FOM_OptionsButtonText and FOM_OptionsButtonText["AlertChat"] or "In chat", -385 + yAdjust)
MTH_FOM_STATE.ctrl.AlertNone = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMAlertNone", FOM_OptionsButtonText and FOM_OptionsButtonText["AlertNone"] or "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("Warn when pet needs feeding")
MTH_FOM_STATE.ctrl.LevelContent = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMLevelContent", FOM_OptionsButtonText and FOM_OptionsButtonText["LevelContent"] or "When content", -300 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.LevelUnhappy = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMLevelUnhappy", FOM_OptionsButtonText and FOM_OptionsButtonText["LevelUnhappy"] or "When unhappy", -325 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.LevelOff = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMLevelOff", FOM_OptionsButtonText and FOM_OptionsButtonText["LevelOff"] or "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 "Play sound", -378 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.AudioWarningBell = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMAudioWarningBell", FOM_OptionsButtonText and FOM_OptionsButtonText["AudioWarningBell"] or "Use bell sound", -403 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.TextWarning = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMTextWarning", FOM_OptionsButtonText and FOM_OptionsButtonText["TextWarning"] or "Show text", -428 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.IconWarning = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMIconWarning", FOM_OptionsButtonText and FOM_OptionsButtonText["IconWarning"] or "Flash icon", -453 + yAdjust, rightColumnX)
if MTH_FOM_STATE.ctrl.AudioWarning then MTH_FOM_STATE.ctrl.AudioWarning:SetScript("OnClick", function(self) self = self or this if not self then return end if self:GetChecked() == 1 then if FOM_Config.AudioWarning ~= "bell" then FOM_Config.AudioWarning = true end else FOM_Config.AudioWarning = nil end MTH_RefreshFeedOMaticOptions() end) end
if MTH_FOM_STATE.ctrl.AudioWarningBell then MTH_FOM_STATE.ctrl.AudioWarningBell:SetScript("OnClick", function(self) self = self or this if not self then return end if self:GetChecked() == 1 then FOM_Config.AudioWarning = "bell" else FOM_Config.AudioWarning = true end MTH_RefreshFeedOMaticOptions() end) end
if MTH_FOM_STATE.ctrl.TextWarning then MTH_FOM_STATE.ctrl.TextWarning:SetScript("OnClick", function(self) self = self or this if not self then return end FOM_Config.TextWarning = self:GetChecked() == 1 end) end
if MTH_FOM_STATE.ctrl.IconWarning then MTH_FOM_STATE.ctrl.IconWarning:SetScript("OnClick", function(self) self = self or this if not self then return end FOM_Config.IconWarning = self:GetChecked() == 1 end) end
local cookHeader = container:CreateFontString("MetaHuntOptionsFOMCookingHeader", "ARTWORK", "GameFontHighlight")
cookHeader:SetPoint("TOPLEFT", container, "TOPLEFT", rightColumnX, -62 + yAdjust)
cookHeader:SetText("Avoid foods used in cooking")
MTH_FOM_STATE.ctrl.SaveCookOrange = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMSaveCookOrange", FOM_OptionsButtonText and FOM_OptionsButtonText["SaveForCook_Orange"] or "Only difficult recipes", -86 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.SaveCookYellow = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMSaveCookYellow", FOM_OptionsButtonText and FOM_OptionsButtonText["SaveForCook_Yellow"] or "Medium or better", -111 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.SaveCookGreen = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMSaveCookGreen", FOM_OptionsButtonText and FOM_OptionsButtonText["SaveForCook_Green"] or "Easy or better", -136 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.SaveCookAll = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMSaveCookAll", FOM_OptionsButtonText and FOM_OptionsButtonText["SaveForCook_All"] or "All foods", -161 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.SaveCookNone = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMSaveCookNone", FOM_OptionsButtonText and FOM_OptionsButtonText["SaveForCook_None"] or "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_RefreshFeedOMaticOptions()
end
+220
View File
@@ -0,0 +1,220 @@
MetaHuntOptions = nil
local MTH_OPTIONS_SETUP = {} -- Track which frames have been set up
MTH_OPTIONS_CONST = MTH_OPTIONS_CONST or {}
MTH_OPTIONS_CONST.NAV_DEFAULT_WIDTH = MTH_OPTIONS_CONST.NAV_DEFAULT_WIDTH or 120
MTH_OPTIONS_CONST.WINDOW_PADDING = MTH_OPTIONS_CONST.WINDOW_PADDING or 12
MTH_OPTIONS_CONST.CONTENT_TOP_OFFSET = MTH_OPTIONS_CONST.CONTENT_TOP_OFFSET or -72
MTH_OPTIONS_CONST.CONTENT_HEIGHT = MTH_OPTIONS_CONST.CONTENT_HEIGHT or 540
MTH_OPTIONS_CONST.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
local MTH_OPTIONS_SHELL_READY = MTH_OptionsRequire and MTH_OptionsRequire("options-shell", {
"MTH_GetFrame",
"MTH_BuildOptionsTree",
})
local function MTH_RegisterEscClose(frameName)
if not (frameName and UISpecialFrames) then
return
end
for i = 1, table.getn(UISpecialFrames) do
if UISpecialFrames[i] == frameName then
return
end
end
table.insert(UISpecialFrames, frameName)
end
local function MTH_EnsureOptionsFrame()
if MetaHuntOptions then
return MetaHuntOptions
end
MetaHuntOptions = CreateFrame("Frame", "MetaHuntOptions", UIParent, "MetaHuntOptionsTemplate")
if not MetaHuntOptions then
if MTH and MTH.Print then
MTH:Print("[MTH OPTIONS] Failed to create MetaHuntOptions", "error")
end
return nil
end
MTH_RegisterEscClose("MetaHuntOptions")
local nav = MTH_GetFrame("MetaHuntOptionsNav")
if nav and nav.SetBackdropColor then
nav:SetBackdropColor(0.08, 0.08, 0.08, 0.95)
nav:SetBackdropBorderColor(0.25, 0.25, 0.25, 1)
end
local nav = MTH_GetFrame("MetaHuntOptionsNav")
local navWidth = nav and nav:GetWidth() or MTH_OPTIONS_CONST.NAV_DEFAULT_WIDTH
local contentLeft = MTH_OPTIONS_CONST.WINDOW_PADDING + navWidth + MTH_OPTIONS_CONST.WINDOW_PADDING
local contentWidth = (MetaHuntOptions:GetWidth() or 720) - contentLeft - MTH_OPTIONS_CONST.WINDOW_PADDING
for i = 1, table.getn(MTH_OPTIONS_TABS) do
local frame = MTH_GetFrame(MTH_OPTIONS_TABS[i].frame)
if frame then
frame:ClearAllPoints()
frame:SetPoint("TOPLEFT", MetaHuntOptions, "TOPLEFT", contentLeft, MTH_OPTIONS_CONST.CONTENT_TOP_OFFSET)
frame:SetWidth(contentWidth)
frame:SetHeight(MTH_OPTIONS_CONST.CONTENT_HEIGHT)
frame:SetFrameLevel(MetaHuntOptions:GetFrameLevel() + 1)
frame:EnableMouse(true)
frame:Hide()
end
end
local closeBtn = MTH_GetFrame("MetaHuntOptionsCloseButton")
if closeBtn then
closeBtn:ClearAllPoints()
closeBtn:SetPoint("TOPRIGHT", MetaHuntOptions, "TOPRIGHT", MTH_OPTIONS_CONST.CLOSE_BTN_OFFSET_X, MTH_OPTIONS_CONST.CLOSE_BTN_OFFSET_Y)
closeBtn:SetWidth(MTH_OPTIONS_CONST.CLOSE_BTN_SIZE)
closeBtn:SetHeight(MTH_OPTIONS_CONST.CLOSE_BTN_SIZE)
closeBtn:SetText("X")
closeBtn:SetScript("OnClick", function() MetaHuntOptions:Hide() end)
end
MTH_BuildOptionsTree()
MTH_SelectOptionsTab("General")
return MetaHuntOptions
end
function MTH_SelectOptionsTab(tabKey)
if not MTH_OPTIONS_SHELL_READY then return end
local optionsFrame = MTH_EnsureOptionsFrame()
if not optionsFrame then
return
end
MTH_OPTIONS_STATE = MTH_OPTIONS_STATE or {}
MTH_OPTIONS_STATE.activeTab = tabKey
MTH_OPTIONS_ACTIVE_TAB = tabKey
for i = 1, table.getn(MTH_OPTIONS_TABS) do
local frame = MTH_GetFrame(MTH_OPTIONS_TABS[i].frame)
if frame then
frame:Hide()
end
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
frame:Show()
end
end
end
MTH_BuildOptionsTree()
if tabKey == "General" then
if MTH_SetupGeneralOptions then
MTH_SetupGeneralOptions()
MTH_OPTIONS_SETUP["General"] = true
end
elseif tabKey == "Pet" then
if not MTH_OPTIONS_SETUP["Pet"] then
MTH_SetupPetOptions()
MTH_OPTIONS_SETUP["Pet"] = true
end
elseif tabKey == "Track" then
if not MTH_OPTIONS_SETUP["Track"] then
MTH_SetupTrackOptions()
MTH_OPTIONS_SETUP["Track"] = true
end
elseif tabKey == "Aspect" then
if not MTH_OPTIONS_SETUP["Aspect"] then
MTH_SetupAspectOptions()
MTH_OPTIONS_SETUP["Aspect"] = true
end
elseif tabKey == "Trap" then
if not MTH_OPTIONS_SETUP["Trap"] then
MTH_SetupTrapOptions()
MTH_OPTIONS_SETUP["Trap"] = true
end
elseif tabKey == "Ammo" then
if not MTH_OPTIONS_SETUP["Ammo"] then
MTH_SetupAmmoOptions()
MTH_OPTIONS_SETUP["Ammo"] = true
else
MTH_SetupAmmoOptions()
end
elseif tabKey == "Mounts" then
if not MTH_OPTIONS_SETUP["Mounts"] then
MTH_SetupMountsOptions()
MTH_OPTIONS_SETUP["Mounts"] = true
end
elseif tabKey == "Companions" then
if not MTH_OPTIONS_SETUP["Companions"] then
MTH_SetupCompanionsOptions()
MTH_OPTIONS_SETUP["Companions"] = true
end
elseif tabKey == "Toys" then
if not MTH_OPTIONS_SETUP["Toys"] then
MTH_SetupToysOptions()
MTH_OPTIONS_SETUP["Toys"] = true
end
elseif tabKey == "SmartAmmo" then
if not MTH_OPTIONS_SETUP["SmartAmmo"] then
MTH_SetupSmartAmmoOptions()
MTH_OPTIONS_SETUP["SmartAmmo"] = true
end
elseif tabKey == "FeedOMatic" then
if not MTH_OPTIONS_SETUP["FeedOMatic"] then
MTH_SetupFeedOMaticOptions()
MTH_OPTIONS_SETUP["FeedOMatic"] = true
end
MTH_RefreshFeedOMaticOptions()
elseif tabKey == "AutoBuy" then
if not MTH_OPTIONS_SETUP["AutoBuy"] then
MTH_SetupAutoBuyOptions()
MTH_OPTIONS_SETUP["AutoBuy"] = true
else
MTH_SetupAutoBuyOptions()
end
elseif tabKey == "Credits" then
local setupCredits = _G and _G["MTH_SetupCreditsOptions"]
if type(setupCredits) == "function" then
setupCredits()
end
MTH_OPTIONS_SETUP["Credits"] = true
elseif string.sub(tabKey or "", 1, 11) == "Chronometer" then
if not MTH_OPTIONS_SETUP[tabKey] then
if _G then
_G.MTH_CHRON_OPTIONS_SECTION = tabKey
end
if type(MTH_SetupChronometerOptions) == "function" then
MTH_SetupChronometerOptions()
MTH_OPTIONS_SETUP[tabKey] = true
elseif MTH and MTH.Print then
MTH:Print("Chronometer options module not loaded", "error")
end
else
if _G then
_G.MTH_CHRON_OPTIONS_SECTION = tabKey
end
if type(MTH_RefreshChronometerOptions) == "function" then
MTH_RefreshChronometerOptions()
elseif type(MTH_SetupChronometerOptions) == "function" then
MTH_SetupChronometerOptions()
end
end
end
end
function MTH_ResetAndSelectOptionsTab(tabKey)
if not MTH_OPTIONS_SHELL_READY then return end
if not tabKey then
return
end
MTH_OPTIONS_SETUP[tabKey] = nil
MTH_SelectOptionsTab(tabKey)
end
function MTH_OpenOptions(tabKey)
if not MTH_OPTIONS_SHELL_READY then return end
local optionsFrame = MTH_EnsureOptionsFrame()
if not optionsFrame then
return
end
optionsFrame:Show()
MTH_SelectOptionsTab(tabKey or "General")
end
+184
View File
@@ -0,0 +1,184 @@
MTH_OPTIONS_TREE_STATE = MTH_OPTIONS_TREE_STATE or { zButtons = true, chronometer = true }
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
MTH_OPTIONS_STATE.activeTab = MTH_OPTIONS_ACTIVE_TAB
end
MTH_OPTIONS_ACTIVE_TAB = MTH_OPTIONS_STATE.activeTab
MTH_OPTIONS_CONST = MTH_OPTIONS_CONST or {
NAV_DEFAULT_WIDTH = 120,
NAV_INNER_PADDING = 16,
TREE_START_Y = -10,
TREE_BUTTON_HEIGHT = 20,
TREE_ROW_STEP = 22,
TREE_INDENT_BASE = 8,
TREE_CHILD_INDENT = 14,
HIGHLIGHT_ALPHA = 0.25,
SELECTED_BG_R = 0.35,
SELECTED_BG_G = 0.25,
SELECTED_BG_B = 0.10,
SELECTED_BG_A = 0.55,
SELECTED_TEXT_R = 1.00,
SELECTED_TEXT_G = 0.82,
SELECTED_TEXT_B = 0.00,
GROUP_TEXT_R = 1.00,
GROUP_TEXT_G = 0.82,
GROUP_TEXT_B = 0.00,
}
MTH_OPTIONS_TABS = MTH_OPTIONS_TABS or {
{ key = "General", label = "General", frame = "MetaHuntOptionsGeneral" },
{ key = "Pet", label = "ZPet", frame = "MetaHuntOptionsPet" },
{ key = "Track", label = "ZTrack", frame = "MetaHuntOptionsTrack" },
{ key = "Aspect", label = "ZAspect", frame = "MetaHuntOptionsAspect" },
{ key = "Trap", label = "ZTrap", frame = "MetaHuntOptionsTrap" },
{ key = "Ammo", label = "ZAmmo", frame = "MetaHuntOptionsAmmo" },
{ key = "Mounts", label = "ZMounts", frame = "MetaHuntOptionsMounts" },
{ key = "Companions", label = "ZCompanions", frame = "MetaHuntOptionsCompanions" },
{ key = "Toys", label = "ZToys", frame = "MetaHuntOptionsToys" },
{ key = "SmartAmmo", label = "Smart Ammo", frame = "MetaHuntOptionsSmartAmmo" },
{ key = "FeedOMatic", label = "FeedOMatic", frame = "MetaHuntOptionsFeedOMatic" },
{ key = "AutoBuy", label = "Auto Buy", frame = "MetaHuntOptionsAutoBuy" },
{ key = "ChronometerGeneral", label = "General", frame = "MetaHuntOptionsChronometer" },
{ key = "ChronometerBar", label = "Bar", frame = "MetaHuntOptionsChronometer" },
{ key = "ChronometerClassSpells", label = "Hunter Spells", frame = "MetaHuntOptionsChronometer" },
{ key = "ChronometerClassEvents", label = "Hunter Events", frame = "MetaHuntOptionsChronometer" },
{ key = "ChronometerRacial", label = "Racial", frame = "MetaHuntOptionsChronometer" },
{ key = "Credits", label = "Credits", frame = "MetaHuntOptionsCredits" },
}
MTH_OPTIONS_TREE = MTH_OPTIONS_TREE or {
{ label = "General", key = "General" },
{ label = "Auto Buy", key = "AutoBuy" },
{ label = "Feed-O-Matic", key = "FeedOMatic" },
{
label = "zButtons",
node = "zButtons",
children = {
{ label = "zAmmo", key = "Ammo" },
{ label = "zPet", key = "Pet" },
{ label = "zAspect", key = "Aspect" },
{ label = "zTrack", key = "Track" },
{ label = "zTrap", key = "Trap" },
{ label = "zMounts", key = "Mounts" },
{ label = "zCompanions", key = "Companions" },
{ label = "zToys", key = "Toys" },
},
},
{
label = "Chronometer",
node = "chronometer",
children = {
{ label = "General", key = "ChronometerGeneral" },
{ label = "Bar", key = "ChronometerBar" },
{ label = "Hunter Spells", key = "ChronometerClassSpells" },
{ label = "Hunter Events", key = "ChronometerClassEvents" },
{ label = "Racial", key = "ChronometerRacial" },
},
},
{ label = "Credits", key = "Credits" },
}
function MTH_BuildOptionsTree()
local nav = MTH_GetFrame and MTH_GetFrame("MetaHuntOptionsNav") or nil
if not nav then
return
end
local navInnerWidth = (nav:GetWidth() or MTH_OPTIONS_CONST.NAV_DEFAULT_WIDTH) - MTH_OPTIONS_CONST.NAV_INNER_PADDING
local yOffset = MTH_OPTIONS_CONST.TREE_START_Y
local buttonIndex = 1
local function acquireButton()
local button = MTH_OPTIONS_TREE_BUTTONS[buttonIndex]
if not button then
button = CreateFrame("Button", "MetaHuntOptionsNavItem"..buttonIndex, nav)
button:SetHeight(MTH_OPTIONS_CONST.TREE_BUTTON_HEIGHT)
button:SetNormalTexture("")
button:SetPushedTexture("")
button:SetHighlightTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
button.highlight = button:GetHighlightTexture()
if button.highlight then
button.highlight:SetBlendMode("ADD")
button.highlight:SetVertexColor(1, 1, 1, MTH_OPTIONS_CONST.HIGHLIGHT_ALPHA)
end
button.selectedTexture = button:CreateTexture(nil, "BACKGROUND")
button.selectedTexture:SetTexture("Interface\\Buttons\\WHITE8X8")
button.selectedTexture:SetVertexColor(MTH_OPTIONS_CONST.SELECTED_BG_R, MTH_OPTIONS_CONST.SELECTED_BG_G, MTH_OPTIONS_CONST.SELECTED_BG_B, MTH_OPTIONS_CONST.SELECTED_BG_A)
button.selectedTexture:SetPoint("TOPLEFT", button, "TOPLEFT", 0, -1)
button.selectedTexture:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", 0, 1)
button.selectedTexture:Hide()
button.expander = button:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
button.expander:SetPoint("LEFT", button, "LEFT", 4, 0)
button.label = button:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
button.label:SetJustifyH("LEFT")
button.label:SetPoint("LEFT", button, "LEFT", 18, 0)
button.label:SetPoint("RIGHT", button, "RIGHT", -4, 0)
MTH_OPTIONS_TREE_BUTTONS[buttonIndex] = button
end
button:Show()
buttonIndex = buttonIndex + 1
return button
end
local function placeLeaf(node, indent)
local button = acquireButton()
local xOffset = MTH_OPTIONS_CONST.TREE_INDENT_BASE + (indent or 0)
local width = navInnerWidth - (indent or 0)
button:ClearAllPoints()
button:SetPoint("TOPLEFT", nav, "TOPLEFT", xOffset, yOffset)
button:SetWidth(width)
button.expander:SetText("")
button.label:SetPoint("LEFT", button, "LEFT", 4, 0)
button.label:SetText(node.label)
button:SetScript("OnClick", function() MTH_SelectOptionsTab(node.key) end)
if MTH_OPTIONS_STATE.activeTab == node.key then
button.selectedTexture:Show()
button.label:SetTextColor(MTH_OPTIONS_CONST.SELECTED_TEXT_R, MTH_OPTIONS_CONST.SELECTED_TEXT_G, MTH_OPTIONS_CONST.SELECTED_TEXT_B)
else
button.selectedTexture:Hide()
button.label:SetTextColor(1, 1, 1)
end
button.expander:SetTextColor(1, 1, 1)
yOffset = yOffset - MTH_OPTIONS_CONST.TREE_ROW_STEP
end
for i = 1, table.getn(MTH_OPTIONS_TREE) do
local node = MTH_OPTIONS_TREE[i]
if node.children then
local expanded = MTH_OPTIONS_TREE_STATE[node.node] and true or false
local button = acquireButton()
button:ClearAllPoints()
button:SetPoint("TOPLEFT", nav, "TOPLEFT", MTH_OPTIONS_CONST.TREE_INDENT_BASE, yOffset)
button:SetWidth(navInnerWidth)
button.expander:SetText(expanded and "-" or "+")
button.expander:SetTextColor(MTH_OPTIONS_CONST.GROUP_TEXT_R, MTH_OPTIONS_CONST.GROUP_TEXT_G, MTH_OPTIONS_CONST.GROUP_TEXT_B)
button.label:SetPoint("LEFT", button, "LEFT", 18, 0)
button.label:SetText(node.label)
button.label:SetTextColor(MTH_OPTIONS_CONST.GROUP_TEXT_R, MTH_OPTIONS_CONST.GROUP_TEXT_G, MTH_OPTIONS_CONST.GROUP_TEXT_B)
button.selectedTexture:Hide()
button:SetScript("OnClick", function()
MTH_OPTIONS_TREE_STATE[node.node] = not MTH_OPTIONS_TREE_STATE[node.node]
MTH_BuildOptionsTree()
end)
yOffset = yOffset - MTH_OPTIONS_CONST.TREE_ROW_STEP
if expanded then
for j = 1, table.getn(node.children) do
placeLeaf(node.children[j], MTH_OPTIONS_CONST.TREE_CHILD_INDENT)
end
end
else
placeLeaf(node, 0)
end
end
for i = buttonIndex, table.getn(MTH_OPTIONS_TREE_BUTTONS) do
if MTH_OPTIONS_TREE_BUTTONS[i] then
MTH_OPTIONS_TREE_BUTTONS[i]:Hide()
end
end
end
File diff suppressed because it is too large Load Diff
+125
View File
@@ -0,0 +1,125 @@
-- MetaHunt unified options window
function MTH_GetFrame(name)
return getglobal(name)
end
function MTH_OptionsRequire(moduleName, requiredGlobals)
local missing = {}
for i = 1, table.getn(requiredGlobals) do
local key = requiredGlobals[i]
if _G[key] == nil then
table.insert(missing, key)
end
end
if table.getn(missing) == 0 then
return true
end
local message = "[MTH OPTIONS] " .. tostring(moduleName) .. " missing dependencies: " .. table.concat(missing, ", ")
if MTH and MTH.Print then
MTH:Print(message, "error")
elseif type(MTH_Log) == "function" then
MTH_Log(message, "error")
elseif DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.AddMessage then
DEFAULT_CHAT_FRAME:AddMessage(message)
end
return false
end
function MTH_CreateActionButton(parent, name, text, x, y, width, height, onClick)
local button = CreateFrame("Button", name, parent, "UIPanelButtonTemplate")
if not button then return nil end
button:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
button:SetWidth(width or 120)
button:SetHeight(height or 24)
button:SetText(text)
if onClick then
button:SetScript("OnClick", onClick)
end
return button
end
-- Helper: Create a slider control
function MTH_CreateSlider(parent, name, label, minVal, maxVal, step, yOffset)
local slider = CreateFrame("Slider", name, parent, "OptionsSliderTemplate")
if not slider then return nil end
slider:SetPoint("TOPLEFT", parent, "TOPLEFT", 15, yOffset)
slider:SetWidth(280)
slider:SetMinMaxValues(minVal, maxVal)
slider:SetValueStep(step)
slider:SetValue(minVal)
-- Labels
local text = slider:CreateFontString(name.."Text", "ARTWORK", "GameFontNormalSmall")
text:SetPoint("BOTTOM", slider, "TOP", 0, 2)
text:SetText(label)
local value = slider:CreateFontString(name.."Value", "ARTWORK", "GameFontHighlight")
value:SetPoint("TOP", slider, "BOTTOM", 0, 2)
value:SetText(tostring(minVal))
-- Hide default low/high labels from OptionsSliderTemplate
local lowLabel = getglobal(name.."Low")
local highLabel = getglobal(name.."High")
if lowLabel then lowLabel:Hide() end
if highLabel then highLabel:Hide() end
slider:SetScript("OnValueChanged", function(self, newValue)
local activeSlider = self or this or slider
local current = tonumber(newValue)
if not current and activeSlider and activeSlider.GetValue then
current = tonumber(activeSlider:GetValue())
end
if not current then
current = minVal
end
value:SetText(tostring(math.floor(current + 0.5)))
if slider.onChange then slider.onChange(current, activeSlider) end
end)
return slider
end
-- Helper: Create a checkbox control
MTH_CreateCheckbox = function(parent, name, label, yOffset, xOffset)
local check = CreateFrame("CheckButton", name, parent, "UICheckButtonTemplate")
if not check then return nil end
check:SetPoint("TOPLEFT", parent, "TOPLEFT", xOffset or 15, yOffset)
local text = check:CreateFontString(name.."Text", "ARTWORK", "GameFontNormalSmall")
text:SetPoint("LEFT", check, "RIGHT", 5, 0)
text:SetText(label)
return check
end
-- Helper: Clear all children from a container
MTH_ClearContainer = function(container)
if not container then return end
-- Hide all child frames
local children = { container:GetChildren() }
for i = 1, table.getn(children) do
local child = children[i]
if child then
child:Hide()
child:ClearAllPoints()
end
end
-- Hide all font strings and textures
local regions = { container:GetRegions() }
for i = 1, table.getn(regions) do
local region = regions[i]
if region and region ~= container then
region:Hide()
if region.ClearAllPoints then
region:ClearAllPoints()
end
end
end
end
+117
View File
@@ -0,0 +1,117 @@
<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="MetaHuntOptionsTemplate" toplevel="true" frameStrata="DIALOG" movable="true" enableMouse="true" hidden="true" parent="UIParent" virtual="true">
<Size>
<AbsDimension x="720" y="650"/>
</Size>
<Anchors>
<Anchor point="CENTER"/>
</Anchors>
<Backdrop bgFile="Interface\DialogFrame\UI-DialogBox-Background" edgeFile="Interface\DialogFrame\UI-DialogBox-Border" tile="true">
<BackgroundInsets>
<AbsInset left="11" right="12" top="12" bottom="11"/>
</BackgroundInsets>
<TileSize>
<AbsValue val="32"/>
</TileSize>
<EdgeSize>
<AbsValue val="32"/>
</EdgeSize>
</Backdrop>
<Layers>
<Layer level="ARTWORK">
<Texture name="$parentHeader" file="Interface\DialogFrame\UI-DialogBox-Header">
<Size>
<AbsDimension x="256" y="64"/>
</Size>
<Anchors>
<Anchor point="TOP">
<Offset>
<AbsDimension x="0" y="12"/>
</Offset>
</Anchor>
</Anchors>
</Texture>
<FontString name="$parentHeaderText" inherits="GameFontNormal" text="MetaHunt Options">
<Anchors>
<Anchor point="TOP" relativeTo="$parentHeader">
<Offset>
<AbsDimension x="0" y="-14"/>
</Offset>
</Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<Frame name="$parentNav" enableMouse="true">
<Size>
<AbsDimension x="120" y="540"/>
</Size>
<Anchors>
<Anchor point="TOPLEFT">
<Offset>
<AbsDimension x="12" y="-72"/>
</Offset>
</Anchor>
</Anchors>
<Backdrop bgFile="Interface\Buttons\WHITE8X8" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
<BackgroundInsets>
<AbsInset left="3" right="3" top="3" bottom="3"/>
</BackgroundInsets>
<TileSize><AbsValue val="16"/></TileSize>
<EdgeSize><AbsValue val="16"/></EdgeSize>
</Backdrop>
</Frame>
<!-- Content Areas -->
<Frame name="$parentGeneral" hidden="true" enableMouse="true"/>
<Frame name="$parentPet" hidden="true" enableMouse="true"/>
<Frame name="$parentTrack" hidden="true" enableMouse="true"/>
<Frame name="$parentAspect" hidden="true" enableMouse="true"/>
<Frame name="$parentTrap" hidden="true" enableMouse="true"/>
<Frame name="$parentAmmo" hidden="true" enableMouse="true"/>
<Frame name="$parentMounts" hidden="true" enableMouse="true"/>
<Frame name="$parentCompanions" hidden="true" enableMouse="true"/>
<Frame name="$parentToys" hidden="true" enableMouse="true"/>
<Frame name="$parentSmartAmmo" hidden="true" enableMouse="true"/>
<Frame name="$parentFeedOMatic" hidden="true" enableMouse="true"/>
<Frame name="$parentAutoBuy" hidden="true" enableMouse="true"/>
<Frame name="$parentChronometer" hidden="true" enableMouse="true"/>
<Frame name="$parentCredits" hidden="true" enableMouse="true"/>
<!-- Top-right close button -->
<Button name="$parentCloseButton" inherits="UIPanelButtonTemplate" text="X">
<Size>
<AbsDimension x="22" y="22"/>
</Size>
<Anchors>
<Anchor point="TOPRIGHT">
<Offset>
<AbsDimension x="-8" y="-8"/>
</Offset>
</Anchor>
</Anchors>
</Button>
</Frames>
<Scripts>
<OnLoad>
this:RegisterForDrag("LeftButton")
</OnLoad>
<OnMouseUp>
if ( this.isMoving ) then
this:StopMovingOrSizing();
this.isMoving = false;
end
</OnMouseUp>
<OnDragStart>
this:StartMoving();
this.isMoving = true;
</OnDragStart>
<OnDragStop>
this:StopMovingOrSizing();
this.isMoving = false;
</OnDragStop>
</Scripts>
</Frame>
</Ui>
+125
View File
@@ -0,0 +1,125 @@
MTH_UI = MTH_UI or {}
MTH_UI.version = "0.1.0"
MTH_UI.pages = MTH_UI.pages or {}
MTH_UI.pageOrder = MTH_UI.pageOrder or {}
MTH_UI.callbacks = MTH_UI.callbacks or {}
local function MTH_UI_SafeCall(handler, a1, a2, a3)
if type(handler) ~= "function" then
return false, "handler is not a function"
end
local ok, resultOrError = pcall(handler, a1, a2, a3)
if not ok then
if MTH and MTH.Print then
MTH:Print("[MTH_UI] " .. tostring(resultOrError), "error")
end
return false, resultOrError
end
return true, resultOrError
end
local function MTH_UI_CopyTable(value)
if type(value) ~= "table" then return value end
local copy = {}
for key, nestedValue in pairs(value) do
copy[key] = MTH_UI_CopyTable(nestedValue)
end
return copy
end
local function MTH_UI_ApplyDefaults(target, defaults)
if type(target) ~= "table" or type(defaults) ~= "table" then return target end
for key, defaultValue in pairs(defaults) do
if target[key] == nil then
target[key] = MTH_UI_CopyTable(defaultValue)
elseif type(target[key]) == "table" and type(defaultValue) == "table" then
MTH_UI_ApplyDefaults(target[key], defaultValue)
end
end
return target
end
function MTH_UI.CloneTable(value)
return MTH_UI_CopyTable(value)
end
function MTH_UI.MergeDefaults(target, defaults)
return MTH_UI_ApplyDefaults(target, defaults)
end
function MTH_UI.ResolveValue(value, context)
if type(value) == "function" then
local ok, resolved = MTH_UI_SafeCall(value, context)
if ok then return resolved end
return nil
end
return value
end
function MTH_UI.RegisterCallback(eventName, callbackKey, handler)
if type(eventName) ~= "string" or eventName == "" then return false end
if type(callbackKey) ~= "string" or callbackKey == "" then return false end
if type(handler) ~= "function" then return false end
if not MTH_UI.callbacks[eventName] then
MTH_UI.callbacks[eventName] = {}
end
MTH_UI.callbacks[eventName][callbackKey] = handler
return true
end
function MTH_UI.UnregisterCallback(eventName, callbackKey)
if not MTH_UI.callbacks[eventName] then return false end
if not MTH_UI.callbacks[eventName][callbackKey] then return false end
MTH_UI.callbacks[eventName][callbackKey] = nil
return true
end
function MTH_UI.FireCallback(eventName, payload)
local listeners = MTH_UI.callbacks[eventName]
if not listeners then return end
for _, handler in pairs(listeners) do
MTH_UI_SafeCall(handler, payload)
end
end
function MTH_UI.RegisterPage(pageId, pageDefinition)
if type(pageId) ~= "string" or pageId == "" then return false, "invalid page id" end
if type(pageDefinition) ~= "table" then return false, "page definition must be a table" end
local rootType = pageDefinition.type or "group"
if rootType ~= "group" then
return false, "root page type must be group"
end
local existing = MTH_UI.pages[pageId]
MTH_UI.pages[pageId] = MTH_UI_CopyTable(pageDefinition)
MTH_UI.pages[pageId].id = pageId
if not existing then
table.insert(MTH_UI.pageOrder, pageId)
end
MTH_UI.FireCallback("PageRegistered", pageId)
return true
end
function MTH_UI.GetPage(pageId)
return MTH_UI.pages[pageId]
end
function MTH_UI.ListPages()
local pageList = {}
for _, pageId in ipairs(MTH_UI.pageOrder) do
if MTH_UI.pages[pageId] then
table.insert(pageList, pageId)
end
end
return pageList
end
function MTH_UI.NotifyPageChanged(pageId)
MTH_UI.FireCallback("PageChanged", pageId)
end
+190
View File
@@ -0,0 +1,190 @@
MTH_UIRenderer = MTH_UIRenderer or {}
MTH_UIRenderer.activeWidgets = MTH_UIRenderer.activeWidgets or {}
local function MTH_UIR_Clear(frame)
if not frame then return end
local widgets = frame._mthWidgets
if not widgets then return end
for _, widget in ipairs(widgets) do
if widget and widget.Hide then
widget:Hide()
end
end
frame._mthWidgets = {}
end
local function MTH_UIR_AddWidget(frame, widget)
if not frame._mthWidgets then
frame._mthWidgets = {}
end
table.insert(frame._mthWidgets, widget)
end
local function MTH_UIR_MakeLabel(frame, text, x, y, width, template)
local label = frame:CreateFontString(nil, "ARTWORK", template or "GameFontNormalSmall")
label:SetPoint("TOPLEFT", frame, "TOPLEFT", x, y)
label:SetWidth(width)
label:SetJustifyH("LEFT")
label:SetText(text or "")
MTH_UIR_AddWidget(frame, label)
return label
end
local function MTH_UIR_SetCheckboxLabel(checkButton, text)
local textRegion = nil
local checkName = checkButton and checkButton.GetName and checkButton:GetName()
if type(checkName) == "string" and checkName ~= "" then
textRegion = getglobal(checkName .. "Text")
end
if not textRegion and checkButton and checkButton.Text then
textRegion = checkButton.Text
end
if textRegion then
textRegion:SetText(text or "")
end
end
local function MTH_UIR_CreateControl(frame, key, option, context, x, y, width)
local optionType = option.type or "description"
local optionName = MTH_UI.ResolveValue(option.name, context)
if optionType == "description" then
local label = MTH_UIR_MakeLabel(frame, optionName or "", x, y, width, "GameFontHighlightSmall")
if label then label:SetTextColor(0.90, 0.90, 0.90) end
return 18
end
if optionType == "header" then
local label = MTH_UIR_MakeLabel(frame, optionName or "", x, y, width, "GameFontNormal")
if label then label:SetTextColor(1.00, 0.82, 0.00) end
return 24
end
if optionType == "toggle" then
local checkButton = CreateFrame("CheckButton", nil, frame, "UICheckButtonTemplate")
checkButton:SetPoint("TOPLEFT", frame, "TOPLEFT", x, y + 2)
checkButton:SetChecked(MTH_UI.ResolveValue(option.get, context) and 1 or nil)
MTH_UIR_SetCheckboxLabel(checkButton, optionName or "")
checkButton:SetScript("OnClick", function(self)
self = self or this
if not self then return end
if option.set then
option.set(context, self:GetChecked() == 1)
end
end)
if MTH_UI.ResolveValue(option.disabled, context) then
checkButton:Disable()
end
MTH_UIR_AddWidget(frame, checkButton)
return 24
end
if optionType == "execute" then
local button = CreateFrame("Button", nil, frame)
button:SetPoint("TOPLEFT", frame, "TOPLEFT", x, y)
button:SetHeight(20)
button:SetWidth(math.min(180, width))
button:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true,
tileSize = 16,
edgeSize = 16,
insets = { left = 3, right = 3, top = 3, bottom = 3 },
})
button:SetBackdropColor(0.1, 0.1, 0.1, 0.5)
button:SetBackdropBorderColor(0.4, 0.4, 0.4)
local text = button:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
text:SetPoint("CENTER", button, "CENTER", 0, 0)
text:SetText(optionName or "Run")
text:SetTextColor(1.00, 0.82, 0.00)
button:SetScript("OnEnter", function(self) self = self or this if self then self:SetBackdropBorderColor(0.6, 0.6, 0.6) end end)
button:SetScript("OnLeave", function(self) self = self or this if self then self:SetBackdropBorderColor(0.4, 0.4, 0.4) end end)
button:SetScript("OnClick", function()
if option.func then
option.func(context)
end
end)
if MTH_UI.ResolveValue(option.disabled, context) then
button:Disable()
end
MTH_UIR_AddWidget(frame, button)
return 24
end
if optionType == "select" then
local label = MTH_UIR_MakeLabel(frame, optionName or "Select", x, y, width, "GameFontNormalSmall")
if label then label:SetTextColor(1.00, 0.82, 0.00) end
local button = CreateFrame("Button", nil, frame)
button:SetPoint("TOPLEFT", frame, "TOPLEFT", x, y - 16)
button:SetHeight(20)
button:SetWidth(math.min(220, width))
button:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true,
tileSize = 16,
edgeSize = 16,
insets = { left = 3, right = 3, top = 3, bottom = 3 },
})
button:SetBackdropColor(0.1, 0.1, 0.1, 0.5)
button:SetBackdropBorderColor(0.4, 0.4, 0.4)
local currentValue = MTH_UI.ResolveValue(option.get, context)
local text = button:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
text:SetPoint("CENTER", button, "CENTER", 0, 0)
text:SetText(tostring(currentValue or "Choose"))
text:SetTextColor(0.90, 0.90, 0.90)
button:SetScript("OnEnter", function(self) self = self or this if self then self:SetBackdropBorderColor(0.6, 0.6, 0.6) end end)
button:SetScript("OnLeave", function(self) self = self or this if self then self:SetBackdropBorderColor(0.4, 0.4, 0.4) end end)
button:SetScript("OnClick", function()
if option.onClick then
option.onClick(context, option)
end
end)
if MTH_UI.ResolveValue(option.disabled, context) then
button:Disable()
end
MTH_UIR_AddWidget(frame, button)
return 42
end
MTH_UIR_MakeLabel(frame, optionName or ("Unsupported type: " .. tostring(optionType)), x, y, width, "GameFontDisableSmall")
return 18
end
function MTH_UIRenderer.RenderPage(frame, pageDefinition, context)
if not frame or not pageDefinition then return false, "invalid render target" end
MTH_UIR_Clear(frame)
local args = pageDefinition.args or {}
local orderedKeys = {}
for key in pairs(args) do
table.insert(orderedKeys, key)
end
table.sort(orderedKeys, function(leftKey, rightKey)
local leftOrder = (args[leftKey] and args[leftKey].order) or 9999
local rightOrder = (args[rightKey] and args[rightKey].order) or 9999
if leftOrder ~= rightOrder then return leftOrder < rightOrder end
return tostring(leftKey) < tostring(rightKey)
end)
local cursorY = -8
for _, key in ipairs(orderedKeys) do
local option = args[key]
if option then
local hidden = MTH_UI.ResolveValue(option.hidden, context)
if not hidden then
local consumedHeight = MTH_UIR_CreateControl(frame, key, option, context, 8, cursorY, frame:GetWidth() - 16)
cursorY = cursorY - consumedHeight
end
end
end
return true
end
File diff suppressed because it is too large Load Diff
+250
View File
@@ -0,0 +1,250 @@
-- MetaHunt Beast Families Database
-- Food map sourced from Turtle WoW Hunter Pets wiki
-- Total: 19 families
if not MTH_DS then MTH_DS = {} end
MTH_DS_Families = {
["Bats"] = {
["named"] = 20,
["coords"] = 18,
["food"] = {
"fruit",
"fungus",
},
["abilities"] = {
"Cower",
"Dive",
"Screech",
},
},
["Bears"] = {
["named"] = 45,
["coords"] = 45,
["food"] = {
"meat",
"fruit",
"fish",
"fungus",
"cheese",
"bread",
},
["abilities"] = {
"Claw",
"Roar of Fortitude",
},
},
["Boars"] = {
["named"] = 45,
["coords"] = 87,
["food"] = {
"meat",
"fruit",
"fish",
"fungus",
"cheese",
"bread",
},
["abilities"] = {
"Charge",
"Dash",
},
},
["Carrion Birds"] = {
["named"] = 36,
["coords"] = 101,
["food"] = {
"meat",
},
["abilities"] = {
"Dive",
"Screech",
},
},
["Cats"] = {
["named"] = 85,
["coords"] = 88,
["food"] = {
"meat",
"fish",
},
["abilities"] = {
"Claw",
"Cower",
"Dash",
"Prowl",
},
},
["Crabs"] = {
["named"] = 32,
["coords"] = 74,
["food"] = {
"fruit",
"fish",
"fungus",
"bread",
},
["abilities"] = {
"Bubble Barrier",
"Claw",
},
},
["Crocolisks"] = {
["named"] = 27,
["coords"] = 71,
["food"] = {
"meat",
"fish",
},
["abilities"] = {
"BIte",
"Bite",
"Claw",
"Death Roll",
},
},
["Foxes"] = {
["named"] = 9,
["coords"] = 51,
["food"] = {
"meat",
"fruit",
},
["abilities"] = {
"Bite",
"Grace",
},
},
["Gorillas"] = {
["named"] = 24,
["coords"] = 117,
["food"] = {
"fruit",
"fungus",
},
["abilities"] = {
"Thunderstomp",
},
},
["Hyenas"] = {
["named"] = 26,
["coords"] = 36,
["food"] = {
"meat",
"fruit",
},
["abilities"] = {
"Bite",
"Dash",
"Packleader",
},
},
["Owls"] = {
["named"] = 16,
["coords"] = 59,
["food"] = {
"meat",
},
["abilities"] = {
"Claw",
"Dive",
"Screech",
},
},
["Raptors"] = {
["named"] = 51,
["coords"] = 115,
["food"] = {
"meat",
},
["abilities"] = {
"Savage Rend",
},
},
["Scorpids"] = {
["named"] = 38,
["coords"] = 36,
["food"] = {
"meat",
},
["abilities"] = {
"Claw",
"Scorpid Poison",
},
},
["Serpents (Cobra)"] = {
["named"] = 10,
["coords"] = 13,
["food"] = {
"meat",
"fish",
},
["abilities"] = {
"Bite",
"Poison Spit",
},
},
["Spiders"] = {
["named"] = 91,
["coords"] = 213,
["food"] = {
"meat",
},
["abilities"] = {
"Bite",
"Web",
},
},
["Tallstriders"] = {
["named"] = 15,
["coords"] = 112,
["food"] = {
"fruit",
"fungus",
"bread",
},
["abilities"] = {
"Bite",
"Cower",
"Strider Presence",
},
},
["Turtles"] = {
["named"] = 25,
["coords"] = 42,
["food"] = {
"fruit",
"fish",
"fungus",
},
["abilities"] = {
"Bite",
"Shell Shield",
},
},
["Wind Serpents"] = {
["named"] = 27,
["coords"] = 42,
["food"] = {
"fish",
"cheese",
"bread",
},
["abilities"] = {
"Dive",
"Lightning Breath",
},
},
["Wolves"] = {
["named"] = 81,
["coords"] = 99,
["food"] = {
"meat",
},
["abilities"] = {
"Bite",
"Dash",
"Furious Howl",
},
},
}
File diff suppressed because it is too large Load Diff
+8840
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+150
View File
@@ -0,0 +1,150 @@
-- MetaHunt Atlas Source Enrichment
-- Adds Atlas-derived source category hints to existing ranged/ammo item entries
if not MTH_DS then MTH_DS = {} end
if not MTH_DS_AmmoItems then MTH_DS_AmmoItems = {} end
local MTH_DS_AtlasSources = {
[10567] = {["boss"] = true, },
[10624] = {["boss"] = true, },
[11628] = {["boss"] = true, },
[11629] = {["boss"] = true, },
[11630] = {["boss"] = true, },
[12651] = {["boss"] = true, },
[12653] = {["boss"] = true, },
[12654] = {["boss"] = true, },
[13019] = {["worlddrop"] = true, },
[13020] = {["worlddrop"] = true, },
[13021] = {["worlddrop"] = true, },
[13022] = {["worlddrop"] = true, },
[13023] = {["worlddrop"] = true, },
[13037] = {["worlddrop"] = true, },
[13038] = {["worlddrop"] = true, },
[13039] = {["worlddrop"] = true, },
[13040] = {["worlddrop"] = true, },
[13136] = {["worlddrop"] = true, },
[13137] = {["worlddrop"] = true, },
[13138] = {["worlddrop"] = true, },
[13139] = {["worlddrop"] = true, },
[13146] = {["worlddrop"] = true, },
[13248] = {["boss"] = true, },
[13377] = {["boss"] = true, },
[13380] = {["boss"] = true, ["crafted"] = true, },
[15294] = {["crafted"] = true, },
[15295] = {["crafted"] = true, },
[15296] = {["crafted"] = true, },
[16992] = {["crafted"] = true, },
[17042] = {["quest"] = true, },
[17069] = {["boss"] = true, },
[17072] = {["boss"] = true, },
[17717] = {["boss"] = true, },
[17753] = {["quest"] = true, },
[18282] = {["worlddrop"] = true, },
[18323] = {["boss"] = true, },
[18388] = {["boss"] = true, },
[18460] = {["boss"] = true, },
[18482] = {["boss"] = true, },
[18680] = {["boss"] = true, },
[18729] = {["boss"] = true, },
[18738] = {["boss"] = true, },
[18755] = {["boss"] = true, },
[18833] = {["pvp"] = true, },
[18835] = {["pvp"] = true, },
[18836] = {["pvp"] = true, },
[18837] = {["pvp"] = true, },
[18855] = {["pvp"] = true, },
[18860] = {["pvp"] = true, },
[19082] = {["crafted"] = true, },
[19107] = {["crafted"] = true, ["quest"] = true, },
[19316] = {["vendor"] = true, },
[19317] = {["vendor"] = true, },
[19350] = {["boss"] = true, },
[19361] = {["boss"] = true, },
[19368] = {["boss"] = true, },
[19558] = {["vendor"] = true, },
[19559] = {["vendor"] = true, },
[19560] = {["vendor"] = true, },
[19561] = {["vendor"] = true, },
[19562] = {["vendor"] = true, },
[19563] = {["vendor"] = true, },
[19564] = {["vendor"] = true, },
[19565] = {["vendor"] = true, },
[19853] = {["boss"] = true, },
[19993] = {["boss"] = true, },
[20038] = {["boss"] = true, },
[20437] = {["vendor"] = true, },
[20438] = {["vendor"] = true, },
[20599] = {["world"] = true, },
[20663] = {["event"] = true, },
[20722] = {["worlddrop"] = true, },
[2098] = {["worlddrop"] = true, },
[2099] = {["worlddrop"] = true, },
[2100] = {["worlddrop"] = true, },
[21459] = {["boss"] = true, },
[21478] = {["boss"] = true, },
[21616] = {["boss"] = true, },
[21800] = {["boss"] = true, },
[22318] = {["boss"] = true, },
[22347] = {["quest"] = true, },
[22656] = {["vendor"] = true, },
[22810] = {["boss"] = true, },
[22811] = {["boss"] = true, },
[22812] = {["boss"] = true, },
[23557] = {["boss"] = true, },
[2824] = {["worlddrop"] = true, },
[2825] = {["worlddrop"] = true, },
[3021] = {["worlddrop"] = true, },
[3078] = {["boss"] = true, },
[3493] = {["crafted"] = true, },
[3778] = {["crafted"] = true, },
[3780] = {["crafted"] = true, },
[41023] = {["crafted"] = true, },
[41725] = {["boss"] = true, },
[51780] = {["world"] = true, },
[55096] = {["boss"] = true, },
[55346] = {["boss"] = true, },
[55379] = {["boss"] = true, },
[5748] = {["vendor"] = true, },
[58083] = {["quest"] = true, },
[58193] = {["boss"] = true, },
[60440] = {["world"] = true, },
[60506] = {["boss"] = true, },
[60545] = {["boss"] = true, },
[60624] = {["quest"] = true, },
[60882] = {["vendor"] = true, },
[61011] = {["vendor"] = true, },
[61068] = {["quest"] = true, },
[61248] = {["boss"] = true, },
[61307] = {["boss"] = true, },
[61383] = {["quest"] = true, },
[61525] = {["boss"] = true, },
[61569] = {["boss"] = true, },
[61629] = {["quest"] = true, },
[6469] = {["boss"] = true, },
[6696] = {["boss"] = true, },
[7729] = {["boss"] = true, },
[80503] = {["vendor"] = true, },
[80546] = {["vendor"] = true, },
[80603] = {["vendor"] = true, },
[80646] = {["vendor"] = true, },
[80745] = {["boss"] = true, },
[83225] = {["boss"] = true, },
[83257] = {["world"] = true, },
[83452] = {["boss"] = true, },
[9399] = {["boss"] = true, },
[9400] = {["boss"] = true, },
[9412] = {["boss"] = true, },
[9422] = {["boss"] = true, },
[9426] = {["boss"] = true, },
[9456] = {["boss"] = true, },
[9487] = {["boss"] = true, },
}
for itemId, sourceMap in pairs(MTH_DS_AtlasSources) do
local item = MTH_DS_AmmoItems[itemId]
if item then
if not item["atlas_sources"] then item["atlas_sources"] = {} end
for sourceType, enabled in pairs(sourceMap) do
if enabled then item["atlas_sources"][sourceType] = true end
end
end
end
File diff suppressed because it is too large Load Diff
+1133
View File
File diff suppressed because it is too large Load Diff
+121
View File
@@ -0,0 +1,121 @@
if not MTH_DS then MTH_DS = {} end
MTH_DS_MinimapSizes = {
[0] = { 0, 0 },
[1] = { 4925, 3283.34 },
[3] = { 2487.5, 1658.34 },
[4] = { 3350, 2233.3 },
[8] = { 2293.75, 1529.17 },
[10] = { 2700, 1800.03 },
[11] = { 4135.42, 2756.25 },
[12] = { 3470.84, 2314.62 },
[14] = { 5287.5, 3525 },
[15] = { 5250, 3500 },
[16] = { 5070.84, 3381.25 },
[17] = { 10133.34, 6756.25 },
[25] = { 711.56, 468.68 },
[28] = { 4300, 2866.67 },
[33] = { 6381.25, 4254.1 },
[36] = { 2800, 1866.67 },
[38] = { 2758.33, 1839.58 },
[40] = { 3500, 2333.3 },
[41] = { 2500, 1666.63 },
[44] = { 2170.84, 1447.9 },
[45] = { 3600, 2400 },
[46] = { 2929.16, 1952.08 },
[47] = { 3850, 2566.67 },
[51] = { 2231.25, 1487.5 },
[85] = { 4518.75, 3012.5 },
[130] = { 4200, 2800 },
[139] = { 3870.83, 2581.25 },
[141] = { 5091.66, 3393.7 },
[148] = { 6550, 4366.66 },
[209] = { 381, 254 },
[215] = { 5137.5, 3425 },
[267] = { 3200, 2133.33 },
[331] = { 5766.67, 3843.75 },
[357] = { 6950, 4633.33 },
[361] = { 5750, 3833.33 },
[400] = { 4400, 2933.33 },
[405] = { 4495.83, 2997.91 },
[406] = { 6018, 4010 },
[408] = { 3092.1, 2047 },
[409] = { 2901.45, 1915.9 },
[440] = { 6900, 4600 },
[490] = { 3700, 2466.66 },
[491] = { 736.45, 490.96 },
[493] = { 2308.33, 1539.59 },
[616] = { 3206, 2142 },
[618] = { 7100, 4733.33 },
[717] = { 378.15, 252.1 },
[718] = { 572.78, 381.85 },
[719] = { 1221.87, 806.42 },
[721] = { 769.67, 513.11 },
[722] = { 881, 589 },
[796] = { 203.66, 135.04 },
[876] = { 828, 541 },
[1176] = { 1383.33, 922.91 },
[1337] = { 893.67, 595.78 },
[1377] = { 3483.33, 2322.92 },
[1477] = { 695.03, 463.35 },
[1497] = { 959.37, 640.11 },
[1519] = { 1737.5, 1158.34 },
[1537] = { 790.63, 527.61 },
[1581] = { 449.89, 300 },
[1583] = { 886.84, 591.23 },
[1584] = { 1985, 1268 },
[1637] = { 1402.61, 935.42 },
[1638] = { 1043.75, 695.83 },
[1657] = { 1058.33, 705.71 },
[1941] = { 1348.24, 888.09 },
[1977] = { 2120.83, 1414.6 },
[2017] = { 1185.35, 789.86 },
[2040] = { 1468, 976 },
[2057] = { 320.05, 213.37 },
[2100] = { 2112.09, 1410.89 },
[2159] = { 483.11, 322.08 },
[2257] = { 312, 208 },
[2366] = { 1085.86, 726.61 },
[2437] = { 738.86, 492.57 },
[2557] = { 1324, 869 },
[2597] = { 4237.5, 2825 },
[2677] = { 499.43, 332.95 },
[2717] = { 1495, 983 },
[3277] = { 1145.84, 764.58 },
[3358] = { 1756.25, 1170.83 },
[3428] = { 977.56, 651.7 },
[3429] = { 2512.5, 1675 },
[3456] = { 1991.69, 1318.42 },
[3457] = { 823, 549 },
[3478] = { 4139.02, 2946 },
[4012] = { 3159, 2108 },
[5023] = { 988.63, 1393.17 },
[5024] = { 1608, 1075 },
[5053] = { 1007.7, 671.8 },
[5077] = { 2643.21, 1751.16 },
[5086] = { 546.75, 391.9 },
[5087] = { 354.5, 234.74 },
[5097] = { 1273.1, 853.72 },
[5103] = { 752.12, 510.33 },
[5121] = { 3227, 2187 },
[5130] = { 1432, 977 },
[5135] = { 320.19, 213.46 },
[5136] = { 619.98, 413.32 },
[5138] = { 656.59, 434.97 },
[5144] = { 309, 208 },
[5147] = { 2777.54, 1851.69 },
[5148] = { 652.1, 439.67 },
[5153] = { 612.69, 408.46 },
[5163] = { 703.3, 468.87 },
[5179] = { 3666, 2442 },
[5204] = { 1271.99, 845.48 },
[5208] = { 1250.18, 837.44 },
[5225] = { 3082, 2061 },
[5536] = { 2472, 1665 },
[5557] = { 2800, 1868 },
[5561] = { 3098, 2068 },
[5581] = { 3241, 2157 },
[5601] = { 1634, 1073 },
[5602] = { 5387, 3584 },
[5628] = { 1980, 1350 },
}
+236
View File
@@ -0,0 +1,236 @@
-- MetaHunt Pet Spells Trainer Overlay
-- Adds trainer-learned pet abilities and normalizes learn method metadata.
if not MTH_DS_PetSpells then
return
end
local function MTH_DSPS_EnsureArray(tbl)
if type(tbl) ~= "table" then
return {}
end
return tbl
end
MTH_DS_PetSpells.abilitiesDiscovered = MTH_DSPS_EnsureArray(MTH_DS_PetSpells.abilitiesDiscovered)
MTH_DS_PetSpells.allSpells = MTH_DSPS_EnsureArray(MTH_DS_PetSpells.allSpells)
MTH_DS_PetSpells.byAbility = MTH_DSPS_EnsureArray(MTH_DS_PetSpells.byAbility)
for _, row in ipairs(MTH_DS_PetSpells.allSpells) do
if type(row) == "table" and not row.learnMethod then
row.learnMethod = "beast"
end
end
for _, bundle in pairs(MTH_DS_PetSpells.byAbility) do
if type(bundle) == "table" and type(bundle.spells) == "table" then
for _, row in ipairs(bundle.spells) do
if type(row) == "table" and not row.learnMethod then
row.learnMethod = "beast"
end
end
end
end
local growlTrainerRows = {
{
["ability"] = "Growl",
["castTime"] = "Instant",
["categoryCooldown"] = "5 seconds",
["cooldown"] = "n/a",
["cost"] = "10 mana",
["description"] = "Taunt the target, increasing the likelihood the creature will focus attacks on you.",
["effects"] = {
"(63) Threat Value: 50",
},
["icon"] = "Ability_Physical_Taunt",
["id"] = 2649,
["learnMethod"] = "trainer",
["name"] = "Growl",
["range"] = "5 yards (Combat Range)",
["rank"] = "Rank 1",
["rankNumber"] = 1,
["school"] = "Physical",
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=2649",
["trainLevel"] = 1,
},
{
["ability"] = "Growl",
["castTime"] = "Instant",
["categoryCooldown"] = "5 seconds",
["cooldown"] = "n/a",
["cost"] = "10 mana",
["description"] = "Taunt the target, increasing the likelihood the creature will focus attacks on you.",
["effects"] = {
"(63) Threat Value: 65",
},
["icon"] = "Ability_Physical_Taunt",
["id"] = 14916,
["learnMethod"] = "trainer",
["name"] = "Growl",
["range"] = "5 yards (Combat Range)",
["rank"] = "Rank 2",
["rankNumber"] = 2,
["school"] = "Physical",
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=14916",
["trainLevel"] = 10,
},
{
["ability"] = "Growl",
["castTime"] = "Instant",
["categoryCooldown"] = "5 seconds",
["cooldown"] = "n/a",
["cost"] = "10 mana",
["description"] = "Taunt the target, increasing the likelihood the creature will focus attacks on you.",
["effects"] = {
"(63) Threat Value: 110",
},
["icon"] = "Ability_Physical_Taunt",
["id"] = 14917,
["learnMethod"] = "trainer",
["name"] = "Growl",
["range"] = "5 yards (Combat Range)",
["rank"] = "Rank 3",
["rankNumber"] = 3,
["school"] = "Physical",
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=14917",
["trainLevel"] = 20,
},
{
["ability"] = "Growl",
["castTime"] = "Instant",
["categoryCooldown"] = "5 seconds",
["cooldown"] = "n/a",
["cost"] = "10 mana",
["description"] = "Taunt the target, increasing the likelihood the creature will focus attacks on you.",
["effects"] = {
"(63) Threat Value: 170",
},
["icon"] = "Ability_Physical_Taunt",
["id"] = 14918,
["learnMethod"] = "trainer",
["name"] = "Growl",
["range"] = "5 yards (Combat Range)",
["rank"] = "Rank 4",
["rankNumber"] = 4,
["school"] = "Physical",
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=14918",
["trainLevel"] = 30,
},
{
["ability"] = "Growl",
["castTime"] = "Instant",
["categoryCooldown"] = "5 seconds",
["cooldown"] = "n/a",
["cost"] = "10 mana",
["description"] = "Taunt the target, increasing the likelihood the creature will focus attacks on you.",
["effects"] = {
"(63) Threat Value: 240",
},
["icon"] = "Ability_Physical_Taunt",
["id"] = 14919,
["learnMethod"] = "trainer",
["name"] = "Growl",
["range"] = "5 yards (Combat Range)",
["rank"] = "Rank 5",
["rankNumber"] = 5,
["school"] = "Physical",
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=14919",
["trainLevel"] = 40,
},
{
["ability"] = "Growl",
["castTime"] = "Instant",
["categoryCooldown"] = "5 seconds",
["cooldown"] = "n/a",
["cost"] = "10 mana",
["description"] = "Taunt the target, increasing the likelihood the creature will focus attacks on you.",
["effects"] = {
"(63) Threat Value: 320",
},
["icon"] = "Ability_Physical_Taunt",
["id"] = 14920,
["learnMethod"] = "trainer",
["name"] = "Growl",
["range"] = "5 yards (Combat Range)",
["rank"] = "Rank 6",
["rankNumber"] = 6,
["school"] = "Physical",
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=14920",
["trainLevel"] = 50,
},
{
["ability"] = "Growl",
["castTime"] = "Instant",
["categoryCooldown"] = "5 seconds",
["cooldown"] = "n/a",
["cost"] = "10 mana",
["description"] = "Taunt the target, increasing the likelihood the creature will focus attacks on you.",
["effects"] = {
"(63) Threat Value: 415",
},
["icon"] = "Ability_Physical_Taunt",
["id"] = 14921,
["learnMethod"] = "trainer",
["name"] = "Growl",
["range"] = "5 yards (Combat Range)",
["rank"] = "Rank 7",
["rankNumber"] = 7,
["school"] = "Physical",
["sourceUrl"] = "https://database.turtlecraft.gg/?spell=14921",
["trainLevel"] = 60,
},
}
local function MTH_DSPS_Contains(array, value)
for _, item in ipairs(array) do
if item == value then
return true
end
end
return false
end
if not MTH_DSPS_Contains(MTH_DS_PetSpells.abilitiesDiscovered, "Growl") then
table.insert(MTH_DS_PetSpells.abilitiesDiscovered, "Growl")
table.sort(MTH_DS_PetSpells.abilitiesDiscovered)
end
local allById = {}
for i, row in ipairs(MTH_DS_PetSpells.allSpells) do
if type(row) == "table" and row.id then
allById[row.id] = i
end
end
for _, row in ipairs(growlTrainerRows) do
local idx = allById[row.id]
if idx then
MTH_DS_PetSpells.allSpells[idx] = row
else
table.insert(MTH_DS_PetSpells.allSpells, row)
end
end
table.sort(MTH_DS_PetSpells.allSpells, function(a, b)
local aa = (a and a.ability) or ""
local bb = (b and b.ability) or ""
if aa ~= bb then
return aa < bb
end
local ar = (a and a.rankNumber) or 0
local br = (b and b.rankNumber) or 0
if ar ~= br then
return ar < br
end
return ((a and a.id) or 0) < ((b and b.id) or 0)
end)
MTH_DS_PetSpells.byAbility["Growl"] = {
["ability"] = "Growl",
["searchUrl"] = "https://database.turtlecraft.gg/?search=Growl",
["spellCount"] = table.getn(growlTrainerRows),
["spells"] = growlTrainerRows,
}
MTH_DS_PetSpells.totalSpellRows = table.getn(MTH_DS_PetSpells.allSpells)
File diff suppressed because it is too large Load Diff
+70
View File
@@ -0,0 +1,70 @@
if not MTH_DS_Racial then
MTH_DS_Racial = {}
end
MTH_DS_Racial = {
version = "1.0",
generatedAtUTC = "2026-02-20T00:00:00Z",
source = {
site = "https://database.turtlecraft.gg",
runRoot = ".TurtleDBparsing/racial-audit/runs/20260220-164554Z",
selection = "user-curated-approved",
},
stats = {
raceCount = 9,
spellCount = 41,
},
byRace = {
["Tauren"] = { 20549, 20550, 20552, 20551 },
["Orc"] = { 20572, 20573, 20574, 20575, 20576, 21563 },
["Troll"] = { 20554, 26296, 26297, 20555, 20557, 20558, 26290 },
["Undead"] = { 7744, 20577, 20579, 5227, 52522 },
["Night Elf"] = { 20580, 21009, 20582, 20585, 20583 },
["Gnome"] = { 20589, 20591, 20592, 20593, 7340 },
["Dwarf"] = { 2481, 20596, 20595, 20594 },
["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" },
},
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2229
View File
File diff suppressed because it is too large Load Diff
+75
View File
@@ -0,0 +1,75 @@
-- Auto-generated fallback zone names for beast/vendor coordinate zone IDs
MTH_DS_ZoneNamesFallback = {
[1] = "Dun Morogh",
[3] = "Badlands",
[4] = "Blasted Lands",
[8] = "Swamp of Sorrows",
[10] = "Duskwood",
[11] = "Wetlands",
[12] = "Elwynn Forest",
[14] = "Durotar",
[15] = "Dustwallow Marsh",
[16] = "Azshara",
[17] = "The Barrens",
[28] = "Western Plaguelands",
[33] = "Stranglethorn Vale",
[36] = "Alterac Mountains",
[38] = "Loch Modan",
[40] = "Westfall",
[41] = "Deadwind Pass",
[44] = "Redridge Mountains",
[45] = "Arathi Highlands",
[46] = "Burning Steppes",
[47] = "The Hinterlands",
[51] = "Searing Gorge",
[85] = "Tirisfal Glades",
[130] = "Silverpine Forest",
[139] = "Eastern Plaguelands",
[141] = "Teldrassil",
[148] = "Darkshore",
[209] = "Shadowfang Keep",
[215] = "Mulgore",
[267] = "Hillsbrad Foothills",
[331] = "Ashenvale",
[357] = "Feralas",
[361] = "Felwood",
[400] = "Thousand Needles",
[405] = "Desolace",
[406] = "Stonetalon Mountains",
[408] = "Gillijim's Isle",
[409] = "Lapidis Isle",
[440] = "Tanaris",
[490] = "Un'Goro Crater",
[491] = "Razorfen Kraul",
[493] = "Moonglade",
[616] = "Hyjal",
[618] = "Winterspring",
[718] = "Wailing Caverns",
[719] = "Blackfathom Deeps",
[721] = "Gnomeregan",
[1176] = "Zul'Farrak",
[1337] = "Uldaman",
[1377] = "Silithus",
[1477] = "The Temple of Atal'Hakkar",
[1519] = "Stormwind City",
[1537] = "Ironforge",
[1583] = "Blackrock Spire",
[1941] = "Caverns of Time",
[1977] = "Zul'Gurub",
[2040] = "Alah'Thalas",
[2100] = "Maraudon",
[2597] = "Alterac Valley",
[3456] = "Naxxramas",
[3457] = "Tower of Karazhan",
[4012] = "Scarlet Enclave",
[5077] = "Crescent Grove",
[5121] = "Tel'Abim",
[5135] = "Scarlet Monastery Library",
[5179] = "Gilneas",
[5225] = "Thalassian Highlands",
[5536] = "Blackstone Island",
[5561] = "Balor",
[5581] = "Northwind",
[5601] = "Dragonmaw Retreat",
[5602] = "Grim Reaches",
}
+12054
View File
File diff suppressed because it is too large Load Diff
+598
View File
@@ -0,0 +1,598 @@
if not MTH_DS then
MTH_DS = {}
end
MTH_DS.version = "1.0"
MTH_DS.loaded = false
local function MTH_DS_MarkNotInGameRangedWeapons()
if not MTH_DS_AmmoItems then return end
for _, item in pairs(MTH_DS_AmmoItems) do
if type(item) == "table" then
local name = string.lower(tostring(item.name or ""))
if string.find(name, "monster - ", 1, true) == 1 or string.find(name, "90 green", 1, true) == 1 then
local subtype = string.lower(tostring(item.subtype or ""))
if subtype == "bow" or subtype == "bows"
or subtype == "gun" or subtype == "rifle" or subtype == "rifles"
or subtype == "crossbow" or subtype == "crossbows" or subtype == "cross bow" or subtype == "cross-bow"
then
item.notingame = true
end
end
end
end
end
MTH_DS_MarkNotInGameRangedWeapons()
local function MTH_DS_EnsurePetSpellsTrainerGrowl()
if not MTH_DS_PetSpells then return end
if type(MTH_DS_PetSpells.allSpells) ~= "table" then MTH_DS_PetSpells.allSpells = {} end
if type(MTH_DS_PetSpells.byAbility) ~= "table" then MTH_DS_PetSpells.byAbility = {} end
if type(MTH_DS_PetSpells.abilitiesDiscovered) ~= "table" then MTH_DS_PetSpells.abilitiesDiscovered = {} end
for _, row in ipairs(MTH_DS_PetSpells.allSpells) do
if type(row) == "table" and not row.learnMethod then
row.learnMethod = "beast"
end
end
local hasGrowlAbility = false
local hasGreatStaminaAbility = false
local hasNaturalArmorAbility = false
local hasFireResistanceAbility = false
local hasFrostResistanceAbility = false
local hasArcaneResistanceAbility = false
local hasNatureResistanceAbility = false
local hasShadowResistanceAbility = false
for i = 1, table.getn(MTH_DS_PetSpells.abilitiesDiscovered) do
if MTH_DS_PetSpells.abilitiesDiscovered[i] == "Growl" then
hasGrowlAbility = true
elseif MTH_DS_PetSpells.abilitiesDiscovered[i] == "Great Stamina" then
hasGreatStaminaAbility = true
elseif MTH_DS_PetSpells.abilitiesDiscovered[i] == "Natural Armor" then
hasNaturalArmorAbility = true
elseif MTH_DS_PetSpells.abilitiesDiscovered[i] == "Fire Resistance" then
hasFireResistanceAbility = true
elseif MTH_DS_PetSpells.abilitiesDiscovered[i] == "Frost Resistance" then
hasFrostResistanceAbility = true
elseif MTH_DS_PetSpells.abilitiesDiscovered[i] == "Arcane Resistance" then
hasArcaneResistanceAbility = true
elseif MTH_DS_PetSpells.abilitiesDiscovered[i] == "Nature Resistance" then
hasNatureResistanceAbility = true
elseif MTH_DS_PetSpells.abilitiesDiscovered[i] == "Shadow Resistance" then
hasShadowResistanceAbility = true
end
if hasGrowlAbility and hasGreatStaminaAbility and hasNaturalArmorAbility and hasFireResistanceAbility and hasFrostResistanceAbility and hasArcaneResistanceAbility and hasNatureResistanceAbility and hasShadowResistanceAbility then break end
end
if not hasGrowlAbility then
table.insert(MTH_DS_PetSpells.abilitiesDiscovered, "Growl")
end
if not hasGreatStaminaAbility then
table.insert(MTH_DS_PetSpells.abilitiesDiscovered, "Great Stamina")
end
if not hasNaturalArmorAbility then
table.insert(MTH_DS_PetSpells.abilitiesDiscovered, "Natural Armor")
end
if not hasFireResistanceAbility then
table.insert(MTH_DS_PetSpells.abilitiesDiscovered, "Fire Resistance")
end
if not hasFrostResistanceAbility then
table.insert(MTH_DS_PetSpells.abilitiesDiscovered, "Frost Resistance")
end
if not hasArcaneResistanceAbility then
table.insert(MTH_DS_PetSpells.abilitiesDiscovered, "Arcane Resistance")
end
if not hasNatureResistanceAbility then
table.insert(MTH_DS_PetSpells.abilitiesDiscovered, "Nature Resistance")
end
if not hasShadowResistanceAbility then
table.insert(MTH_DS_PetSpells.abilitiesDiscovered, "Shadow Resistance")
end
table.sort(MTH_DS_PetSpells.abilitiesDiscovered)
local growlRows = {
{ id = 2649, rank = "Rank 1", rankNumber = 1, trainLevel = 1, effect = "(63) Threat Value: 50" },
{ id = 14916, rank = "Rank 2", rankNumber = 2, trainLevel = 10, effect = "(63) Threat Value: 65" },
{ id = 14917, rank = "Rank 3", rankNumber = 3, trainLevel = 20, effect = "(63) Threat Value: 110" },
{ id = 14918, rank = "Rank 4", rankNumber = 4, trainLevel = 30, effect = "(63) Threat Value: 170" },
{ id = 14919, rank = "Rank 5", rankNumber = 5, trainLevel = 40, effect = "(63) Threat Value: 240" },
{ id = 14920, rank = "Rank 6", rankNumber = 6, trainLevel = 50, effect = "(63) Threat Value: 320" },
{ id = 14921, rank = "Rank 7", rankNumber = 7, trainLevel = 60, effect = "(63) Threat Value: 415" },
}
local allById = {}
for i, row in ipairs(MTH_DS_PetSpells.allSpells) do
if type(row) == "table" and row.id then
allById[row.id] = i
end
end
local growlSpells = {}
for i = 1, table.getn(growlRows) do
local src = growlRows[i]
local row = {
ability = "Growl",
castTime = "Instant",
categoryCooldown = "5 seconds",
cooldown = "n/a",
cost = "10 mana",
description = "Taunt the target, increasing the likelihood the creature will focus attacks on you.",
effects = { src.effect },
icon = "Ability_Physical_Taunt",
id = src.id,
learnMethod = "trainer",
name = "Growl",
range = "5 yards (Combat Range)",
rank = src.rank,
rankNumber = src.rankNumber,
school = "Physical",
sourceUrl = "https://database.turtlecraft.gg/?spell=" .. tostring(src.id),
trainLevel = src.trainLevel,
}
table.insert(growlSpells, row)
local idx = allById[src.id]
if idx then
MTH_DS_PetSpells.allSpells[idx] = row
else
table.insert(MTH_DS_PetSpells.allSpells, row)
end
end
local staminaRows = {
{ id = 4187, rank = "Rank 1", rankNumber = 1, trainLevel = 10, description = "Stamina increased by 3.", effect = "(6) Apply Aura #29: Mod Stat (2) Value: 3" },
{ id = 4188, rank = "Rank 2", rankNumber = 2, trainLevel = 12, description = "Stamina increased by 5.", effect = "(6) Apply Aura #29: Mod Stat (2) Value: 5" },
{ id = 4189, rank = "Rank 3", rankNumber = 3, trainLevel = 18, description = "Stamina increased by 7.", effect = "(6) Apply Aura #29: Mod Stat (2) Value: 7" },
{ id = 4190, rank = "Rank 4", rankNumber = 4, trainLevel = 24, description = "Stamina increased by 10.", effect = "(6) Apply Aura #29: Mod Stat (2) Value: 10" },
{ id = 4191, rank = "Rank 5", rankNumber = 5, trainLevel = 30, description = "Stamina increased by 13.", effect = "(6) Apply Aura #29: Mod Stat (2) Value: 13" },
{ id = 4192, rank = "Rank 6", rankNumber = 6, trainLevel = 36, description = "Stamina increased by 17.", effect = "(6) Apply Aura #29: Mod Stat (2) Value: 17" },
{ id = 4193, rank = "Rank 7", rankNumber = 7, trainLevel = 42, description = "Stamina increased by 21.", effect = "(6) Apply Aura #29: Mod Stat (2) Value: 21" },
{ id = 4194, rank = "Rank 8", rankNumber = 8, trainLevel = 48, description = "Stamina increased by 26.", effect = "(6) Apply Aura #29: Mod Stat (2) Value: 26" },
{ id = 5048, rank = "Rank 9", rankNumber = 9, trainLevel = 54, description = "Stamina increased by 32.", effect = "(36) Learn Spell Great Stamina" },
{ id = 5049, rank = "Rank 10", rankNumber = 10, trainLevel = 60, description = "Stamina increased by 40.", effect = "(36) Learn Spell Great Stamina" },
}
for i, row in ipairs(MTH_DS_PetSpells.allSpells) do
if type(row) == "table" and row.id then
allById[row.id] = i
end
end
local staminaSpells = {}
for i = 1, table.getn(staminaRows) do
local src = staminaRows[i]
local row = {
ability = "Great Stamina",
castTime = "Passive",
categoryCooldown = "n/a",
cooldown = "n/a",
cost = "None",
description = src.description,
effects = { src.effect },
icon = "Spell_Nature_UnyeildingStamina",
id = src.id,
learnMethod = "trainer",
name = "Great Stamina",
range = "0 yards (Self Only)",
rank = src.rank,
rankNumber = src.rankNumber,
school = "Physical",
sourceUrl = "https://database.turtlecraft.gg/?spell=" .. tostring(src.id),
trainLevel = src.trainLevel,
}
table.insert(staminaSpells, row)
local idx = allById[src.id]
if idx then
MTH_DS_PetSpells.allSpells[idx] = row
else
table.insert(MTH_DS_PetSpells.allSpells, row)
end
end
for i, row in ipairs(MTH_DS_PetSpells.allSpells) do
if type(row) == "table" and row.id then
allById[row.id] = i
end
end
local armorRows = {
{ id = 24545, rank = "Rank 1", rankNumber = 1, trainLevel = 10, description = "Armor increased by 50.", effect = "(6) Apply Aura #22: Mod Resistance (1) Value: 50" },
{ id = 24549, rank = "Rank 2", rankNumber = 2, trainLevel = 12, description = "Armor increased by 100.", effect = "(6) Apply Aura #22: Mod Resistance (1) Value: 100" },
{ id = 24550, rank = "Rank 3", rankNumber = 3, trainLevel = 18, description = "Armor increased by 160.", effect = "(6) Apply Aura #22: Mod Resistance (1) Value: 160" },
{ id = 24551, rank = "Rank 4", rankNumber = 4, trainLevel = 24, description = "Armor increased by 240.", effect = "(6) Apply Aura #22: Mod Resistance (1) Value: 240" },
{ id = 24552, rank = "Rank 5", rankNumber = 5, trainLevel = 30, description = "Armor increased by 330.", effect = "(6) Apply Aura #22: Mod Resistance (1) Value: 330" },
{ id = 24553, rank = "Rank 6", rankNumber = 6, trainLevel = 36, description = "Armor increased by 430.", effect = "(6) Apply Aura #22: Mod Resistance (1) Value: 430" },
{ id = 24554, rank = "Rank 7", rankNumber = 7, trainLevel = 42, description = "Armor increased by 550.", effect = "(6) Apply Aura #22: Mod Resistance (1) Value: 550" },
{ id = 24555, rank = "Rank 8", rankNumber = 8, trainLevel = 48, description = "Armor increased by 675.", effect = "(6) Apply Aura #22: Mod Resistance (1) Value: 675" },
{ id = 24629, rank = "Rank 9", rankNumber = 9, trainLevel = 54, description = "Armor increased by 810.", effect = "(6) Apply Aura #22: Mod Resistance (1) Value: 810" },
{ id = 24630, rank = "Rank 10", rankNumber = 10, trainLevel = 60, description = "Armor increased by 1000.", effect = "(6) Apply Aura #22: Mod Resistance (1) Value: 1000" },
}
local armorSpells = {}
for i = 1, table.getn(armorRows) do
local src = armorRows[i]
local row = {
ability = "Natural Armor",
castTime = "Passive",
categoryCooldown = "n/a",
cooldown = "n/a",
cost = "None",
description = src.description,
effects = { src.effect },
icon = "Spell_Nature_SpiritArmor",
id = src.id,
learnMethod = "trainer",
name = "Natural Armor",
range = "0 yards (Self Only)",
rank = src.rank,
rankNumber = src.rankNumber,
school = "Physical",
sourceUrl = "https://database.turtlecraft.gg/?spell=" .. tostring(src.id),
trainLevel = src.trainLevel,
}
table.insert(armorSpells, row)
local idx = allById[src.id]
if idx then
MTH_DS_PetSpells.allSpells[idx] = row
else
table.insert(MTH_DS_PetSpells.allSpells, row)
end
end
for i, row in ipairs(MTH_DS_PetSpells.allSpells) do
if type(row) == "table" and row.id then
allById[row.id] = i
end
end
local fireResRows = {
{ id = 23992, rank = "Rank 1", rankNumber = 1, trainLevel = 20, description = "Increases Fire resistance by 30.", effect = "(6) Apply Aura #22: Mod Resistance (4) Value: 30" },
{ id = 24439, rank = "Rank 2", rankNumber = 2, trainLevel = 30, description = "Increases Fire resistance by 60.", effect = "(6) Apply Aura #22: Mod Resistance (4) Value: 60" },
{ id = 24444, rank = "Rank 3", rankNumber = 3, trainLevel = 40, description = "Increases Fire resistance by 90.", effect = "(6) Apply Aura #22: Mod Resistance (4) Value: 90" },
{ id = 24445, rank = "Rank 4", rankNumber = 4, trainLevel = 50, description = "Increases Fire resistance by 120.", effect = "(6) Apply Aura #22: Mod Resistance (4) Value: 120" },
}
local fireResSpells = {}
for i = 1, table.getn(fireResRows) do
local src = fireResRows[i]
local row = {
ability = "Fire Resistance",
castTime = "Passive",
categoryCooldown = "n/a",
cooldown = "n/a",
cost = "None",
description = src.description,
effects = { src.effect },
icon = "Spell_Fire_FireArmor",
id = src.id,
learnMethod = "trainer",
name = "Fire Resistance",
range = "0 yards (Self Only)",
rank = src.rank,
rankNumber = src.rankNumber,
school = "Physical",
sourceUrl = "https://database.turtlecraft.gg/?spell=" .. tostring(src.id),
trainLevel = src.trainLevel,
}
table.insert(fireResSpells, row)
local idx = allById[src.id]
if idx then
MTH_DS_PetSpells.allSpells[idx] = row
else
table.insert(MTH_DS_PetSpells.allSpells, row)
end
end
for i, row in ipairs(MTH_DS_PetSpells.allSpells) do
if type(row) == "table" and row.id then
allById[row.id] = i
end
end
local frostResRows = {
{ id = 24446, rank = "Rank 1", rankNumber = 1, trainLevel = 20, description = "Increases Frost resistance by 30.", effect = "(6) Apply Aura #22: Mod Resistance (16) Value: 30" },
{ id = 24447, rank = "Rank 2", rankNumber = 2, trainLevel = 30, description = "Increases Frost resistance by 60.", effect = "(6) Apply Aura #22: Mod Resistance (16) Value: 60" },
{ id = 24448, rank = "Rank 3", rankNumber = 3, trainLevel = 40, description = "Increases Frost resistance by 90.", effect = "(6) Apply Aura #22: Mod Resistance (16) Value: 90" },
{ id = 24449, rank = "Rank 4", rankNumber = 4, trainLevel = 50, description = "Increases Frost resistance by 120.", effect = "(6) Apply Aura #22: Mod Resistance (16) Value: 120" },
}
local frostResSpells = {}
for i = 1, table.getn(frostResRows) do
local src = frostResRows[i]
local row = {
ability = "Frost Resistance",
castTime = "Passive",
categoryCooldown = "n/a",
cooldown = "n/a",
cost = "None",
description = src.description,
effects = { src.effect },
icon = "Spell_Frost_FrostWard",
id = src.id,
learnMethod = "trainer",
name = "Frost Resistance",
range = "0 yards (Self Only)",
rank = src.rank,
rankNumber = src.rankNumber,
school = "Physical",
sourceUrl = "https://database.turtlecraft.gg/?spell=" .. tostring(src.id),
trainLevel = src.trainLevel,
}
table.insert(frostResSpells, row)
local idx = allById[src.id]
if idx then
MTH_DS_PetSpells.allSpells[idx] = row
else
table.insert(MTH_DS_PetSpells.allSpells, row)
end
end
for i, row in ipairs(MTH_DS_PetSpells.allSpells) do
if type(row) == "table" and row.id then
allById[row.id] = i
end
end
local arcaneResRows = {
{ id = 24493, rank = "Rank 1", rankNumber = 1, trainLevel = 20, description = "Increases Arcane resistance by 30.", effect = "(6) Apply Aura #22: Mod Resistance (64) Value: 30" },
{ id = 24497, rank = "Rank 2", rankNumber = 2, trainLevel = 30, description = "Increases Arcane resistance by 60.", effect = "(6) Apply Aura #22: Mod Resistance (64) Value: 60" },
{ id = 24500, rank = "Rank 3", rankNumber = 3, trainLevel = 40, description = "Increases Arcane resistance by 90.", effect = "(6) Apply Aura #22: Mod Resistance (64) Value: 90" },
{ id = 24501, rank = "Rank 4", rankNumber = 4, trainLevel = 50, description = "Increases Arcane resistance by 120.", effect = "(6) Apply Aura #22: Mod Resistance (64) Value: 120" },
}
local arcaneResSpells = {}
for i = 1, table.getn(arcaneResRows) do
local src = arcaneResRows[i]
local row = {
ability = "Arcane Resistance",
castTime = "Passive",
categoryCooldown = "n/a",
cooldown = "n/a",
cost = "None",
description = src.description,
effects = { src.effect },
icon = "Spell_Nature_StarFall",
id = src.id,
learnMethod = "trainer",
name = "Arcane Resistance",
range = "0 yards (Self Only)",
rank = src.rank,
rankNumber = src.rankNumber,
school = "Physical",
sourceUrl = "https://database.turtlecraft.gg/?spell=" .. tostring(src.id),
trainLevel = src.trainLevel,
}
table.insert(arcaneResSpells, row)
local idx = allById[src.id]
if idx then
MTH_DS_PetSpells.allSpells[idx] = row
else
table.insert(MTH_DS_PetSpells.allSpells, row)
end
end
for i, row in ipairs(MTH_DS_PetSpells.allSpells) do
if type(row) == "table" and row.id then
allById[row.id] = i
end
end
local natureResRows = {
{ id = 24492, rank = "Rank 1", rankNumber = 1, trainLevel = 20, description = "Increases Nature resistance by 30.", effect = "(6) Apply Aura #22: Mod Resistance (8) Value: 30" },
{ id = 24502, rank = "Rank 2", rankNumber = 2, trainLevel = 30, description = "Increases Nature resistance by 60.", effect = "(6) Apply Aura #22: Mod Resistance (8) Value: 60" },
{ id = 24503, rank = "Rank 3", rankNumber = 3, trainLevel = 40, description = "Increases Nature resistance by 90.", effect = "(6) Apply Aura #22: Mod Resistance (8) Value: 90" },
{ id = 24504, rank = "Rank 4", rankNumber = 4, trainLevel = 50, description = "Increases Nature resistance by 120.", effect = "(6) Apply Aura #22: Mod Resistance (8) Value: 120" },
}
local natureResSpells = {}
for i = 1, table.getn(natureResRows) do
local src = natureResRows[i]
local row = {
ability = "Nature Resistance",
castTime = "Passive",
categoryCooldown = "n/a",
cooldown = "n/a",
cost = "None",
description = src.description,
effects = { src.effect },
icon = "Spell_Nature_ResistNature",
id = src.id,
learnMethod = "trainer",
name = "Nature Resistance",
range = "0 yards (Self Only)",
rank = src.rank,
rankNumber = src.rankNumber,
school = "Physical",
sourceUrl = "https://database.turtlecraft.gg/?spell=" .. tostring(src.id),
trainLevel = src.trainLevel,
}
table.insert(natureResSpells, row)
local idx = allById[src.id]
if idx then
MTH_DS_PetSpells.allSpells[idx] = row
else
table.insert(MTH_DS_PetSpells.allSpells, row)
end
end
for i, row in ipairs(MTH_DS_PetSpells.allSpells) do
if type(row) == "table" and row.id then
allById[row.id] = i
end
end
local shadowResRows = {
{ id = 24488, rank = "Rank 1", rankNumber = 1, trainLevel = 20, description = "Increases Shadow resistance by 30.", effect = "(6) Apply Aura #22: Mod Resistance (32) Value: 30" },
{ id = 24505, rank = "Rank 2", rankNumber = 2, trainLevel = 30, description = "Increases Shadow resistance by 60.", effect = "(6) Apply Aura #22: Mod Resistance (32) Value: 60" },
{ id = 24506, rank = "Rank 3", rankNumber = 3, trainLevel = 40, description = "Increases Shadow resistance by 90.", effect = "(6) Apply Aura #22: Mod Resistance (32) Value: 90" },
{ id = 24507, rank = "Rank 4", rankNumber = 4, trainLevel = 50, description = "Increases Shadow resistance by 120.", effect = "(6) Apply Aura #22: Mod Resistance (32) Value: 120" },
}
local shadowResSpells = {}
for i = 1, table.getn(shadowResRows) do
local src = shadowResRows[i]
local row = {
ability = "Shadow Resistance",
castTime = "Passive",
categoryCooldown = "n/a",
cooldown = "n/a",
cost = "None",
description = src.description,
effects = { src.effect },
icon = "Spell_Shadow_AntiShadow",
id = src.id,
learnMethod = "trainer",
name = "Shadow Resistance",
range = "0 yards (Self Only)",
rank = src.rank,
rankNumber = src.rankNumber,
school = "Physical",
sourceUrl = "https://database.turtlecraft.gg/?spell=" .. tostring(src.id),
trainLevel = src.trainLevel,
}
table.insert(shadowResSpells, row)
local idx = allById[src.id]
if idx then
MTH_DS_PetSpells.allSpells[idx] = row
else
table.insert(MTH_DS_PetSpells.allSpells, row)
end
end
table.sort(MTH_DS_PetSpells.allSpells, function(a, b)
local aa = (a and a.ability) or ""
local bb = (b and b.ability) or ""
if aa ~= bb then return aa < bb end
local ar = (a and a.rankNumber) or 0
local br = (b and b.rankNumber) or 0
if ar ~= br then return ar < br end
return ((a and a.id) or 0) < ((b and b.id) or 0)
end)
MTH_DS_PetSpells.byAbility["Growl"] = {
ability = "Growl",
searchUrl = "https://database.turtlecraft.gg/?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",
spellCount = table.getn(staminaSpells),
spells = staminaSpells,
}
MTH_DS_PetSpells.byAbility["Natural Armor"] = {
ability = "Natural Armor",
searchUrl = "https://database.turtlecraft.gg/?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",
spellCount = table.getn(fireResSpells),
spells = fireResSpells,
}
MTH_DS_PetSpells.byAbility["Frost Resistance"] = {
ability = "Frost Resistance",
searchUrl = "https://database.turtlecraft.gg/?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",
spellCount = table.getn(arcaneResSpells),
spells = arcaneResSpells,
}
MTH_DS_PetSpells.byAbility["Nature Resistance"] = {
ability = "Nature Resistance",
searchUrl = "https://database.turtlecraft.gg/?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",
spellCount = table.getn(shadowResSpells),
spells = shadowResSpells,
}
MTH_DS_PetSpells.totalSpellRows = table.getn(MTH_DS_PetSpells.allSpells)
end
function MTH_DS_OnLoad()
if MTH_DS.loaded then
return
end
-- Database files loaded via TOC
-- Check if all tables are present
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.loaded = true
-- Count entries
MTH_DS.beastCount = 0
for _ in pairs(MTH_DS_Beasts) do
MTH_DS.beastCount = MTH_DS.beastCount + 1
end
MTH_DS.vendorCount = 0
for _ in pairs(MTH_DS_Vendors) do
MTH_DS.vendorCount = MTH_DS.vendorCount + 1
end
MTH_DS.ammoItemCount = 0
for _ in pairs(MTH_DS_AmmoItems) do
MTH_DS.ammoItemCount = MTH_DS.ammoItemCount + 1
end
MTH_DS.zoneCount = 0
for _ in pairs(MTH_DS_Zones) do
MTH_DS.zoneCount = MTH_DS.zoneCount + 1
end
MTH_DS.familyCount = 0
for _ in pairs(MTH_DS_Families) do
MTH_DS.familyCount = MTH_DS.familyCount + 1
end
MTH_DS.petSpellRowCount = 0
if MTH_DS_PetSpells and MTH_DS_PetSpells.allSpells then
for _ in pairs(MTH_DS_PetSpells.allSpells) do
MTH_DS.petSpellRowCount = MTH_DS.petSpellRowCount + 1
end
end
-- Delayed message to ensure chat frame is ready
local frame = CreateFrame("Frame")
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:SetScript("OnEvent", function()
this:UnregisterAllEvents()
end)
else
local missing = {}
if not MTH_DS_Beasts then table.insert(missing, "MTH_DS_Beasts") end
if not MTH_DS_Vendors then table.insert(missing, "MTH_DS_Vendors") end
if not MTH_DS_AmmoItems then table.insert(missing, "MTH_DS_AmmoItems") end
if not MTH_DS_Zones then table.insert(missing, "MTH_DS_Zones") end
if not MTH_DS_Families then table.insert(missing, "MTH_DS_Families") end
local msg = "|cFFFF3333[MTH] ERROR:|r Database failed to load! Missing: " .. table.concat(missing, ", ")
local frame = CreateFrame("Frame")
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:SetScript("OnEvent", function()
this:UnregisterAllEvents()
MTH:Print(msg, "error")
end)
end
end
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

+37
View File
@@ -0,0 +1,37 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\FrameXML\UI.xsd">
<Include file="..\api\constants.lua"/>
<Include file="..\api\debug-frame.lua"/>
<Include file="..\api\minimap-button.lua"/>
<Include file="..\api\AutoStrip.xml"/>
<Include file="..\api\AutoStrip.lua"/>
<Include file="..\api\AntiDaze.lua"/>
<Include file="..\api\uilib.lua"/>
<Include file="..\api\uirenderer.lua"/>
<Include file="..\api\options-tree.lua"/>
<Include file="..\api\options.xml"/>
<Include file="..\api\options.lua"/>
<Include file="..\api\options-chronometer.lua"/>
<Include file="..\api\options-credits.lua"/>
<Include file="..\api\options-autobuy.lua"/>
<Include file="..\api\options-shell.lua"/>
<Include file="..\api\options-zbuttons.lua"/>
<Include file="..\api\options-feedomatic.lua"/>
<Include file="..\api\hunterbook.xml"/>
<Include file="..\api\hunterbook.lua"/>
<Include file="..\api\hunterbook-tab-beastlore.lua"/>
<Include file="..\api\hunterbook-tab-families.lua"/>
<Include file="..\api\hunterbook-tab-petabilities.lua"/>
<Include file="..\api\hunterbook-tab-weapons.lua"/>
<Include file="..\api\hunterbook-tab-npcfinder.lua"/>
<Include file="..\api\hunterbook-tab-stable.lua"/>
<Include file="..\api\hunterbook-tab-pethistory.lua"/>
<Include file="..\api\map-markers.lua"/>
<Include file="..\api\core-framework.lua"/>
<Include file="..\api\core-scan-stablemaster.lua"/>
<Include file="..\api\core-scan-beasttraining.lua"/>
<Include file="..\api\core-scan-spellbookpet.lua"/>
<Include file="..\api\core.lua"/>
<Include file="..\api\localization.lua"/>
<Include file="..\api\config.lua"/>
</Ui>
+4
View File
@@ -0,0 +1,4 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<!-- MetaHunt Compatibility Layer -->
<!-- Handles version compatibility with Turtle WoW -->
</Ui>
+4
View File
@@ -0,0 +1,4 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<!-- MetaHunt Environment Setup -->
<!-- Global tables, localization, and configuration -->
</Ui>
+5
View File
@@ -0,0 +1,5 @@
<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>
+23
View File
@@ -0,0 +1,23 @@
<!--
MetaHunt Module Loader
-->
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<!-- FeedOMatic Module - ENABLED -->
<Include file="..\modules\feedomatic\loader.xml"/>
<!-- Tooltips Module - ENABLED -->
<Include file="..\modules\tooltips\loader.xml"/>
<!-- Chronometer Module - ENABLED -->
<Include file="..\modules\chronometer\loader.xml"/>
<!-- ZHunter Module - ENABLED -->
<Include file="..\modules\zhunter\loader.xml"/>
<!-- SmartAmmo Module - ENABLED -->
<Include file="..\modules\smartammo\loader.xml"/>
<!-- AutoBuy Module - ENABLED -->
<Include file="..\modules\autobuy\loader.xml"/>
</Ui>
+4
View File
@@ -0,0 +1,4 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<!-- MetaHunt UI Skins and Styling -->
<!-- Placeholder for custom UI theming -->
</Ui>
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
<!--
MetaHunt AutoBuy Module Loader
Standalone auto-buy foundation for pet food and ammo.
-->
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<Include file="engine.lua"/>
<Include file="module.lua"/>
</Ui>
+161
View File
@@ -0,0 +1,161 @@
------------------------------------------------------
-- MetaHunt: AutoBuy Module
-- Standalone module shell for vendor auto-buy features.
------------------------------------------------------
local MTH_AutoBuy = {
name = "autobuy",
enabled = false,
version = "1.0.3",
events = {
"VARIABLES_LOADED",
"MERCHANT_SHOW",
"MERCHANT_UPDATE",
"MERCHANT_CLOSED",
},
initialized = false,
}
local function AB_Log(message, severity)
if type(MTH_Log) == "function" then
MTH_Log("[AutoBuy] " .. tostring(message or ""), severity)
end
end
local AB_Engine
local function AB_Trace(message)
return
end
local function AB_EnsureBridgeFrame(moduleRef)
if MTH_AutoBuy._bridgeFrame then
return MTH_AutoBuy._bridgeFrame
end
local frame = CreateFrame("Frame", "MTH_AutoBuyBridgeFrame")
if not frame then
AB_Log("failed to create bridge frame", "error")
return nil
end
frame:RegisterEvent("VARIABLES_LOADED")
frame:RegisterEvent("MERCHANT_SHOW")
frame:RegisterEvent("MERCHANT_UPDATE")
frame:RegisterEvent("MERCHANT_CLOSED")
frame:SetScript("OnEvent", function(_, eventName)
eventName = eventName or event
local engine = AB_Engine()
if not engine then
AB_Trace("bridge event=" .. tostring(eventName) .. " engine missing")
return
end
if eventName == "VARIABLES_LOADED" then
engine:Init()
local store = engine:EnsureDefaults()
AB_Trace("bridge VARIABLES_LOADED store.enabled=" .. tostring(store and store.enabled)
.. " projectiles.enabled=" .. tostring(store and store.projectiles and store.projectiles.enabled))
return
end
if eventName == "MERCHANT_SHOW" or eventName == "MERCHANT_UPDATE" then
AB_Trace("bridge " .. tostring(eventName) .. " -> engine")
engine:OnMerchantEvent(eventName)
elseif eventName == "MERCHANT_CLOSED" then
AB_Trace("bridge MERCHANT_CLOSED -> engine")
if engine.OnMerchantClosed then
engine:OnMerchantClosed()
end
end
end)
MTH_AutoBuy._bridgeFrame = frame
MTH_AutoBuy._bridgeActive = true
return frame
end
AB_Engine = function()
return MTH_AutoBuyEngine
end
function MTH_AutoBuy:init()
local engine = AB_Engine()
if not engine then
AB_Log("init failed: engine missing", "error")
AB_Trace("init failed: engine missing")
return
end
engine:Init()
AB_EnsureBridgeFrame(self)
self.initialized = true
AB_Trace("init done")
if self.enabled then
AB_Log("module initialized", "debug")
end
end
function MTH_AutoBuy:setEnabled(enabled)
if not self.initialized then
self:init()
end
local engine = AB_Engine()
if not engine then
AB_Trace("setEnabled ignored: engine missing")
return
end
local store = engine:EnsureDefaults()
store.enabled = enabled and true or false
AB_EnsureBridgeFrame(self)
AB_Trace("setEnabled store.enabled=" .. tostring(store.enabled))
end
function MTH_AutoBuy:onEvent(event)
local engine = AB_Engine()
if not engine then
AB_Log("onEvent ignored: engine missing, event=" .. tostring(event), "debug")
AB_Trace("engine missing, event=" .. tostring(event))
return
end
if self._bridgeActive and (event == "MERCHANT_SHOW" or event == "MERCHANT_UPDATE" or event == "MERCHANT_CLOSED") then
return
end
AB_Log("onEvent event=" .. tostring(event) .. " module.enabled=" .. tostring(self.enabled), "debug")
if event == "VARIABLES_LOADED" then
engine:Init()
AB_EnsureBridgeFrame(self)
AB_Log("onEvent VARIABLES_LOADED -> engine init", "debug")
local store = engine:EnsureDefaults()
AB_Trace("VARIABLES_LOADED module.enabled=" .. tostring(self.enabled)
.. " store.enabled=" .. tostring(store and store.enabled)
.. " projectiles.enabled=" .. tostring(store and store.projectiles and store.projectiles.enabled))
return
end
if not self.enabled then
AB_Log("onEvent ignored: module disabled", "debug")
if event == "MERCHANT_SHOW" or event == "MERCHANT_UPDATE" then
AB_Trace(tostring(event) .. " ignored: module disabled")
end
return
end
if event == "MERCHANT_SHOW" or event == "MERCHANT_UPDATE" then
AB_Log("onEvent merchant event forwarded: " .. tostring(event), "debug")
AB_Trace("forwarding " .. tostring(event) .. " to engine")
engine:OnMerchantEvent(event)
elseif event == "MERCHANT_CLOSED" then
AB_Trace("forwarding MERCHANT_CLOSED to engine")
if engine.OnMerchantClosed then
engine:OnMerchantClosed()
end
end
end
function MTH_AutoBuy:cleanup()
AB_Log("module cleanup", "debug")
end
MTH:RegisterModule("autobuy", MTH_AutoBuy)
+78
View File
@@ -0,0 +1,78 @@
local BS = setmetatable({}, {
__index = function(_, key)
if MTH and MTH.LocalizeSpell then
return MTH:LocalizeSpell(key)
end
return key
end,
})
function MTH_ChronometerHunterInstallData(engine)
if not engine then
return
end
local _, class = UnitClass("player")
if class == "HUNTER" then
engine:AddGroup(1, true, "MAGENTA")
engine:AddGroup(2, false, "MAROON")
engine:AddGroup(3, false, "GREEN")
engine:AddTimer(engine.SPELL, BS["Bestial Wrath"], 18, 0, 1, 1, { cr = "RED" })
engine:AddTimer(engine.SPELL, BS["Concussive Shot"], 4, 1, 0, 0, { cr = "BLUE", ea = { [BS["Improved Concussive Shot"]] = 4 } })
engine:AddTimer(engine.SPELL, BS["Counterattack"], 5, 1, 0, 0, { cr = "ORANGE" })
engine:AddTimer(engine.SPELL, BS["Deterrence"], 10, 0, 1, 1, { cr = "ORANGE" })
engine:AddTimer(engine.SPELL, BS["Explosive Trap"], 60, 0, 0, 0, { gr = 1, cr = "ORANGE", rc = true, ea = { [BS["Explosive Trap Effect"]] = 60 } })
engine:AddTimer(engine.SPELL, BS["Freezing Trap"], 60, 0, 0, 0, { gr = 1, cr = "CYAN", rc = true, ea = { [BS["Freezing Trap Effect"]] = 60 } })
engine:AddTimer(engine.SPELL, BS["Frost Trap"], 60, 0, 0, 0, { gr = 1, cr = "CYAN", rc = true, ea = { [BS["Frost Trap Aura"]] = 60 } })
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["Rapid Fire"], 15, 0, 1, 1, { cr = "RED" })
engine:AddTimer(engine.SPELL, BS["Scare Beast"], 10, 1, 0, 0, { cr = "BLUE", rc = true, d = { rs = 5 } })
engine:AddTimer(engine.SPELL, BS["Scatter Shot"], 4, 1, 0, 0, { cr = "MAROON" })
engine:AddTimer(engine.SPELL, BS["Scorpid Sting"], 20, 1, 0, 0, { gr = 2, cr = "PURPLE", rc = true })
engine:AddTimer(engine.SPELL, BS["Serpent Sting"], 15, 1, 0, 0, { gr = 2, cr = "GREEN", rc = true })
engine:AddTimer(engine.SPELL, BS["Viper Sting"], 8, 1, 0, 0, { gr = 2, cr = "CYAN", rc = true })
engine:AddTimer(engine.SPELL, BS["Wing Clip"], 10, 1, 0, 0, { cr = "YELLOW", rc = true, ea = { [BS["Improved Wing Clip"]] = 10 } })
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"] })
engine:AddTimer(engine.EVENT, BS["Improved Concussive Shot"], 3, 1, 0, 0, { a = 1, cr = "BLUE", xn = BS["Concussive Shot"] })
engine:AddTimer(engine.EVENT, BS["Improved Wing Clip"], 5, 1, 0, 0, { a = 1, cr = "YELLOW", xn = BS["Wing Clip"] })
engine:AddTimer(engine.EVENT, BS["Piercing Shot"], 8, 1, 0, 1, { a = 1, cr = "PURPLE", tx = "Interface\\Icons\\Ability_Hunter_DisarmingShot" })
engine:AddTimer(engine.EVENT, BS["Quick Shots"], 12, 0, 1, 1, { a = 1, cr = "MAROON", tx = "Interface\\Icons\\Ability_Hunter_Quickshot" })
engine:AddTimer(engine.EVENT, BS["Scorpid Poison"], 10, 1, 0, 0, { gr = 3, cr = "PURPLE", a = 1, dn = 1 })
end
local _, race = UnitRace("player")
if race == "Dwarf" then
engine:AddTimer(engine.SPELL, BS["Stoneform"], 8, 0, 1, 1, { cl = "RACIAL" })
elseif race == "Human" then
engine:AddTimer(engine.SPELL, BS["Perception"], 20, 0, 1, 1, { cl = "RACIAL" })
elseif race == "Orc" then
engine:AddTimer(engine.SPELL, BS["Blood Fury"], 15, 0, 1, 1, { cl = "RACIAL" })
elseif race == "Tauren" then
engine:AddTimer(engine.SPELL, BS["War Stomp"], 2, 0, 0, 0, { cl = "RACIAL" })
elseif race == "Troll" then
engine:AddTimer(engine.SPELL, BS["Berserking"], 10, 0, 1, 1, { cl = "RACIAL" })
elseif race == "Scourge" then
engine:AddTimer(engine.SPELL, BS["Will of the Forsaken"], 5, 0, 1, 1, { cl = "RACIAL" })
end
if class == "PALADIN" or class == "WARRIOR" or class == "ROGUE" or class == "SHAMAN" then
engine:AddTimer(engine.EVENT, BS["Holy Strength"], 15, 0, 1, 1, { cr = "YELLOW", a = 1, cl = "COMMON" })
end
engine:AddTimer(engine.EVENT, BS["Unstable Power"], 20, 0, 1, 1, { a = 1, cr = "CYAN", cl = "COMMON" })
engine:AddTimer(engine.EVENT, BS["Ephemeral Power"], 15, 0, 1, 1, { a = 1, cr = "CYAN", cl = "COMMON" })
engine:AddTimer(engine.EVENT, BS["Mind Quickening"], 20, 0, 1, 1, { a = 1, cr = "CYAN", cl = "COMMON", xn = BS["Critical Mass"] })
end
if MTH_ChronometerHunter and MTH_ChronometerHunter.dataSetup then
table.insert(MTH_ChronometerHunter.dataSetup, MTH_ChronometerHunterInstallData)
end
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,973 @@
--[[
Name: AceEvent-2.0
Revision: $Rev: 17803 $
Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team)
Inspired By: Ace 1.x by Turan (turan@gryphon.com)
Website: http://www.wowace.com/
Documentation: http://www.wowace.com/index.php/AceEvent-2.0
SVN: http://svn.wowace.com/root/trunk/Ace2/AceEvent-2.0
Description: Mixin to allow for event handling, scheduling, and inter-addon
communication.
Dependencies: AceLibrary, AceOO-2.0
]]
local MAJOR_VERSION = "AceEvent-2.0"
local MINOR_VERSION = "$Revision: 17803 $"
if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary") end
if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end
if loadstring("return function(...) return ... end") and AceLibrary:HasInstance(MAJOR_VERSION) then return end -- lua51 check
if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0") end
local AceOO = AceLibrary:GetInstance("AceOO-2.0")
local Mixin = AceOO.Mixin
local AceEvent = Mixin {
"RegisterEvent",
"RegisterAllEvents",
"UnregisterEvent",
"UnregisterAllEvents",
"TriggerEvent",
"ScheduleEvent",
"ScheduleRepeatingEvent",
"CancelScheduledEvent",
"CancelAllScheduledEvents",
"IsEventRegistered",
"IsEventScheduled",
"RegisterBucketEvent",
"UnregisterBucketEvent",
"UnregisterAllBucketEvents",
"IsBucketEventRegistered",
}
local table_setn
do
local version = GetBuildInfo()
if string.find(version, "^2%.") then
-- 2.0.0
table_setn = function() end
else
table_setn = table.setn
end
end
local weakKey = {__mode="k"}
local new, del
do
local list = setmetatable({}, weakKey)
function new()
local t = next(list)
if t then
list[t] = nil
return t
else
return {}
end
end
function del(t)
setmetatable(t, nil)
for k in pairs(t) do
t[k] = nil
end
list[t] = true
end
end
local FAKE_NIL
local RATE
local eventsWhichHappenOnce = {
PLAYER_LOGIN = true,
AceEvent_FullyInitialized = true,
VARIABLES_LOADED = true,
PLAYER_LOGOUT = true,
}
local registeringFromAceEvent
function AceEvent:RegisterEvent(event, method, once)
AceEvent:argCheck(event, 2, "string")
if self == AceEvent and not registeringFromAceEvent then
AceEvent:argCheck(method, 3, "function")
self = method
else
AceEvent:argCheck(method, 3, "string", "function", "nil", "boolean", "number")
if type(method) == "boolean" or type(method) == "number" then
AceEvent:argCheck(once, 4, "nil")
once, method = method, event
end
end
AceEvent:argCheck(once, 4, "number", "boolean", "nil")
if eventsWhichHappenOnce[event] then
once = true
end
local throttleRate
if type(once) == "number" then
throttleRate, once = once
end
if not method then
method = event
end
if type(method) == "string" and type(self[method]) ~= "function" then
AceEvent:error("Cannot register event %q to method %q, it does not exist", event, method)
else
assert(type(method) == "function" or type(method) == "string")
end
local AceEvent_registry = AceEvent.registry
if not AceEvent_registry[event] then
AceEvent_registry[event] = new()
AceEvent.frame:RegisterEvent(event)
end
local remember = true
if AceEvent_registry[event][self] then
remember = false
end
AceEvent_registry[event][self] = method
local AceEvent_onceRegistry = AceEvent.onceRegistry
if once then
if not AceEvent_onceRegistry then
AceEvent.onceRegistry = new()
AceEvent_onceRegistry = AceEvent.onceRegistry
end
if not AceEvent_onceRegistry[event] then
AceEvent_onceRegistry[event] = new()
end
AceEvent_onceRegistry[event][self] = true
else
if AceEvent_onceRegistry and AceEvent_onceRegistry[event] then
AceEvent_onceRegistry[event][self] = nil
if not next(AceEvent_onceRegistry[event]) then
AceEvent_onceRegistry[event] = del(AceEvent_onceRegistry[event])
end
end
end
local AceEvent_throttleRegistry = AceEvent.throttleRegistry
if throttleRate then
if not AceEvent_throttleRegistry then
AceEvent.throttleRegistry = new()
AceEvent_throttleRegistry = AceEvent.throttleRegistry
end
if not AceEvent_throttleRegistry[event] then
AceEvent_throttleRegistry[event] = new()
end
if AceEvent_throttleRegistry[event][self] then
AceEvent_throttleRegistry[event][self] = del(AceEvent_throttleRegistry[event][self])
end
AceEvent_throttleRegistry[event][self] = setmetatable(new(), weakKey)
local t = AceEvent_throttleRegistry[event][self]
t[RATE] = throttleRate
else
if AceEvent_throttleRegistry and AceEvent_throttleRegistry[event] then
if AceEvent_throttleRegistry[event][self] then
AceEvent_throttleRegistry[event][self] = del(AceEvent_throttleRegistry[event][self])
end
if not next(AceEvent_throttleRegistry[event]) then
AceEvent_throttleRegistry[event] = del(AceEvent_throttleRegistry[event])
end
end
end
if remember then
AceEvent:TriggerEvent("AceEvent_EventRegistered", self, event)
end
end
local ALL_EVENTS
function AceEvent:RegisterAllEvents(method)
if self == AceEvent then
AceEvent:argCheck(method, 1, "function")
self = method
else
AceEvent:argCheck(method, 1, "string", "function")
if type(method) == "string" and type(self[method]) ~= "function" then
AceEvent:error("Cannot register all events to method %q, it does not exist", method)
end
end
local AceEvent_registry = AceEvent.registry
if not AceEvent_registry[ALL_EVENTS] then
AceEvent_registry[ALL_EVENTS] = new()
AceEvent.frame:RegisterAllEvents()
end
AceEvent_registry[ALL_EVENTS][self] = method
end
local _G = getfenv(0)
local memstack, timestack = {}, {}
local memdiff, timediff
function AceEvent:TriggerEvent(event, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
AceEvent:argCheck(event, 2, "string")
local AceEvent_registry = AceEvent.registry
if (not AceEvent_registry[event] or not next(AceEvent_registry[event])) and (not AceEvent_registry[ALL_EVENTS] or not next(AceEvent_registry[ALL_EVENTS])) then
return
end
local _G_event = _G.event
_G.event = event
local lastEvent = AceEvent.currentEvent
AceEvent.currentEvent = event
local AceEvent_onceRegistry = AceEvent.onceRegistry
local AceEvent_debugTable = AceEvent.debugTable
if AceEvent_onceRegistry and AceEvent_onceRegistry[event] then
local tmp = new()
for obj, method in pairs(AceEvent_onceRegistry[event]) do
tmp[obj] = AceEvent_registry[event] and AceEvent_registry[event][obj] or nil
end
local obj = next(tmp)
while obj do
local mem, time
if AceEvent_debugTable then
if not AceEvent_debugTable[event] then
AceEvent_debugTable[event] = new()
end
if not AceEvent_debugTable[event][obj] then
AceEvent_debugTable[event][obj] = new()
AceEvent_debugTable[event][obj].mem = 0
AceEvent_debugTable[event][obj].time = 0
AceEvent_debugTable[event][obj].count = 0
end
if memdiff then
table.insert(memstack, memdiff)
table.insert(timestack, timediff)
end
memdiff, timediff = 0, 0
mem, time = gcinfo(), GetTime()
end
local method = tmp[obj]
AceEvent.UnregisterEvent(obj, event)
if type(method) == "string" then
local obj_method = obj[method]
if obj_method then
obj_method(obj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
end
elseif method then -- function
method(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
end
if AceEvent_debugTable then
local dmem, dtime = memdiff, timediff
mem, time = gcinfo() - mem - memdiff, GetTime() - time - timediff
AceEvent_debugTable[event][obj].mem = AceEvent_debugTable[event][obj].mem + mem
AceEvent_debugTable[event][obj].time = AceEvent_debugTable[event][obj].time + time
AceEvent_debugTable[event][obj].count = AceEvent_debugTable[event][obj].count + 1
memdiff, timediff = table.remove(memstack), table.remove(timestack)
if memdiff then
memdiff = memdiff + mem + dmem
timediff = timediff + time + dtime
end
end
tmp[obj] = nil
obj = next(tmp)
end
del(tmp)
end
local AceEvent_throttleRegistry = AceEvent.throttleRegistry
local throttleTable = AceEvent_throttleRegistry and AceEvent_throttleRegistry[event]
if AceEvent_registry[event] then
local tmp = new()
for obj, method in pairs(AceEvent_registry[event]) do
tmp[obj] = method
end
local obj = next(tmp)
while obj do
local method = tmp[obj]
local continue = false
if throttleTable and throttleTable[obj] then
local a1 = a1
if a1 == nil then
a1 = FAKE_NIL
end
if not throttleTable[obj][a1] or GetTime() - throttleTable[obj][a1] >= throttleTable[obj][RATE] then
throttleTable[obj][a1] = GetTime()
else
continue = true
end
end
if not continue then
local mem, time
if AceEvent_debugTable then
if not AceEvent_debugTable[event] then
AceEvent_debugTable[event] = new()
end
if not AceEvent_debugTable[event][obj] then
AceEvent_debugTable[event][obj] = new()
AceEvent_debugTable[event][obj].mem = 0
AceEvent_debugTable[event][obj].time = 0
AceEvent_debugTable[event][obj].count = 0
end
if memdiff then
table.insert(memstack, memdiff)
table.insert(timestack, timediff)
end
memdiff, timediff = 0, 0
mem, time = gcinfo(), GetTime()
end
if type(method) == "string" then
local obj_method = obj[method]
if obj_method then
obj_method(obj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
end
elseif method then -- function
method(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
end
if AceEvent_debugTable then
local dmem, dtime = memdiff, timediff
mem, time = gcinfo() - mem - memdiff, GetTime() - time - timediff
AceEvent_debugTable[event][obj].mem = AceEvent_debugTable[event][obj].mem + mem
AceEvent_debugTable[event][obj].time = AceEvent_debugTable[event][obj].time + time
AceEvent_debugTable[event][obj].count = AceEvent_debugTable[event][obj].count + 1
memdiff, timediff = table.remove(memstack), table.remove(timestack)
if memdiff then
memdiff = memdiff + mem + dmem
timediff = timediff + time + dtime
end
end
end
tmp[obj] = nil
obj = next(tmp)
end
del(tmp)
end
if AceEvent_registry[ALL_EVENTS] then
local tmp = new()
for obj, method in pairs(AceEvent_registry[ALL_EVENTS]) do
tmp[obj] = method
end
local obj = next(tmp)
while obj do
local method = tmp[obj]
local mem, time
if AceEvent_debugTable then
if not AceEvent_debugTable[event] then
AceEvent_debugTable[event] = new()
end
if not AceEvent_debugTable[event][obj] then
AceEvent_debugTable[event][obj] = new()
AceEvent_debugTable[event][obj].mem = 0
AceEvent_debugTable[event][obj].time = 0
AceEvent_debugTable[event][obj].count = 0
end
if memdiff then
table.insert(memstack, memdiff)
table.insert(timestack, timediff)
end
memdiff, timediff = 0, 0
mem, time = gcinfo(), GetTime()
end
if type(method) == "string" then
local obj_method = obj[method]
if obj_method then
obj_method(obj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
end
elseif method then -- function
method(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
end
if AceEvent_debugTable then
local dmem, dtime = memdiff, timediff
mem, time = gcinfo() - mem - memdiff, GetTime() - time - timediff
AceEvent_debugTable[event][obj].mem = AceEvent_debugTable[event][obj].mem + mem
AceEvent_debugTable[event][obj].time = AceEvent_debugTable[event][obj].time + time
AceEvent_debugTable[event][obj].count = AceEvent_debugTable[event][obj].count + 1
memdiff, timediff = table.remove(memstack), table.remove(timestack)
if memdiff then
memdiff = memdiff + mem + dmem
timediff = timediff + time + dtime
end
end
tmp[obj] = nil
obj = next(tmp)
end
del(tmp)
end
_G.event = _G_event
AceEvent.currentEvent = lastEvent
end
-- local accessors
local getn = table.getn
local tinsert = table.insert
local tremove = table.remove
local floor = math.floor
local GetTime = GetTime
local next = next
local pairs = pairs
local unpack = unpack
local delayRegistry
local tmp = {}
local function OnUpdate()
local t = GetTime()
for k,v in pairs(delayRegistry) do
tmp[k] = true
end
for k in pairs(tmp) do
local v = delayRegistry[k]
if v then
local v_time = v.time
if not v_time then
delayRegistry[k] = del(v)
elseif v_time <= t then
local v_repeatDelay = v.repeatDelay
if v_repeatDelay then
-- use the event time, not the current time, else timing inaccuracies add up over time
v.time = v_time + v_repeatDelay
end
local event = v.event
local mem, time
if AceEvent_debugTable then
mem, time = gcinfo(), GetTime()
end
if type(event) == "function" then
event(unpack(v))
else
AceEvent:TriggerEvent(event, unpack(v))
end
if AceEvent_debugTable then
mem, time = gcinfo() - mem, GetTime() - time
v.mem = v.mem + mem
v.timeSpent = v.timeSpent + time
v.count = v.count + 1
end
if not v_repeatDelay then
local x = delayRegistry[k]
if x and x.time == v_time then -- check if it was manually reset
delayRegistry[k] = del(v)
end
end
end
end
end
for k in pairs(tmp) do
tmp[k] = nil
end
if not next(delayRegistry) then
AceEvent.frame:Hide()
end
end
local function ScheduleEvent(self, repeating, event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
local id
if type(event) == "string" or type(event) == "table" then
if type(event) == "table" then
if not delayRegistry or not delayRegistry[event] then
AceEvent:error("Bad argument #2 to `ScheduleEvent'. Improper id table fed in.")
end
end
if type(delay) ~= "number" then
id, event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20 = event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20
AceEvent:argCheck(event, 3, "string", "function", --[[ so message is right ]] "number")
AceEvent:argCheck(delay, 4, "number")
self:CancelScheduledEvent(id)
end
else
AceEvent:argCheck(event, 2, "string", "function")
AceEvent:argCheck(delay, 3, "number")
end
if not delayRegistry then
AceEvent.delayRegistry = new()
delayRegistry = AceEvent.delayRegistry
AceEvent.frame:SetScript("OnUpdate", OnUpdate)
end
local t
if type(id) == "table" then
for k in pairs(id) do
id[k] = nil
end
t = id
else
t = new()
end
t[1] = a1
t[2] = a2
t[3] = a3
t[4] = a4
t[5] = a5
t[6] = a6
t[7] = a7
t[8] = a8
t[9] = a9
t[10] = a10
t[11] = a11
t[12] = a12
t[13] = a13
t[14] = a14
t[15] = a15
t[16] = a16
t[17] = a17
t[18] = a18
t[19] = a19
t[20] = a20
table_setn(t, 20)
t.event = event
t.time = GetTime() + delay
t.self = self
t.id = id or t
t.repeatDelay = repeating and delay
if AceEvent_debugTable then
t.mem = 0
t.count = 0
t.timeSpent = 0
end
delayRegistry[t.id] = t
AceEvent.frame:Show()
return t.id
end
function AceEvent:ScheduleEvent(event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
if type(event) == "string" or type(event) == "table" then
if type(event) == "table" then
if not delayRegistry or not delayRegistry[event] then
AceEvent:error("Bad argument #2 to `ScheduleEvent'. Improper id table fed in.")
end
end
if type(delay) ~= "number" then
AceEvent:argCheck(delay, 3, "string", "function", --[[ so message is right ]] "number")
AceEvent:argCheck(a1, 4, "number")
end
else
AceEvent:argCheck(event, 2, "string", "function")
AceEvent:argCheck(delay, 3, "number")
end
return ScheduleEvent(self, false, event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
end
function AceEvent:ScheduleRepeatingEvent(event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
if type(event) == "string" or type(event) == "table" then
if type(event) == "table" then
if not delayRegistry or not delayRegistry[event] then
AceEvent:error("Bad argument #2 to `ScheduleEvent'. Improper id table fed in.")
end
end
if type(delay) ~= "number" then
AceEvent:argCheck(delay, 3, "string", "function", --[[ so message is right ]] "number")
AceEvent:argCheck(a1, 4, "number")
end
else
AceEvent:argCheck(event, 2, "string", "function")
AceEvent:argCheck(delay, 3, "number")
end
return ScheduleEvent(self, true, event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
end
function AceEvent:CancelScheduledEvent(t)
AceEvent:argCheck(t, 2, "string", "table")
if delayRegistry then
local v = delayRegistry[t]
if v then
delayRegistry[t] = del(v)
if not next(delayRegistry) then
AceEvent.frame:Hide()
end
return true
end
end
return false
end
function AceEvent:IsEventScheduled(t)
AceEvent:argCheck(t, 2, "string", "table")
if delayRegistry then
local v = delayRegistry[t]
if v then
return true, v.time - GetTime()
end
end
return false, nil
end
function AceEvent:UnregisterEvent(event)
AceEvent:argCheck(event, 2, "string")
local AceEvent_registry = AceEvent.registry
if AceEvent_registry[event] and AceEvent_registry[event][self] then
AceEvent_registry[event][self] = nil
local AceEvent_onceRegistry = AceEvent.onceRegistry
if AceEvent_onceRegistry and AceEvent_onceRegistry[event] and AceEvent_onceRegistry[event][self] then
AceEvent_onceRegistry[event][self] = nil
if not next(AceEvent_onceRegistry[event]) then
AceEvent_onceRegistry[event] = del(AceEvent_onceRegistry[event])
end
end
local AceEvent_throttleRegistry = AceEvent.throttleRegistry
if AceEvent_throttleRegistry and AceEvent_throttleRegistry[event] and AceEvent_throttleRegistry[event][self] then
AceEvent_throttleRegistry[event][self] = del(AceEvent_throttleRegistry[event][self])
if not next(AceEvent_throttleRegistry[event]) then
AceEvent_throttleRegistry[event] = del(AceEvent_throttleRegistry[event])
end
end
if not next(AceEvent_registry[event]) then
AceEvent_registry[event] = del(AceEvent_registry[event])
if not AceEvent_registry[ALL_EVENTS] or not next(AceEvent_registry[ALL_EVENTS]) then
AceEvent.frame:UnregisterEvent(event)
end
end
else
if self == AceEvent then
error(string.format("Cannot unregister event %q. Improperly unregistering from AceEvent-2.0.", event), 2)
else
AceEvent:error("Cannot unregister event %q. %q is not registered with it.", event, self)
end
end
AceEvent:TriggerEvent("AceEvent_EventUnregistered", self, event)
end
function AceEvent:UnregisterAllEvents()
local AceEvent_registry = AceEvent.registry
if AceEvent_registry[ALL_EVENTS] and AceEvent_registry[ALL_EVENTS][self] then
AceEvent_registry[ALL_EVENTS][self] = nil
if not next(AceEvent_registry[ALL_EVENTS]) then
del(AceEvent_registry[ALL_EVENTS])
AceEvent.frame:UnregisterAllEvents()
for k,v in pairs(AceEvent_registry) do
if k ~= ALL_EVENTS then
AceEvent.frame:RegisterEvent(k)
end
end
AceEvent_registry[event] = nil
end
end
local first = true
for event, data in pairs(AceEvent_registry) do
if first then
if AceEvent_registry.AceEvent_EventUnregistered then
event = "AceEvent_EventUnregistered"
else
first = false
end
end
local x = data[self]
data[self] = nil
if x and event ~= ALL_EVENTS then
if not next(data) then
del(data)
if not AceEvent_registry[ALL_EVENTS] or not next(AceEvent_registry[ALL_EVENTS]) then
AceEvent.frame:UnregisterEvent(event)
end
AceEvent_registry[event] = nil
end
AceEvent:TriggerEvent("AceEvent_EventUnregistered", self, event)
end
if first then
event = nil
end
end
if AceEvent.onceRegistry then
for event, data in pairs(AceEvent.onceRegistry) do
data[self] = nil
end
end
end
function AceEvent:CancelAllScheduledEvents()
if delayRegistry then
for k,v in pairs(delayRegistry) do
if v.self == self then
delayRegistry[k] = del(v)
end
end
if not next(delayRegistry) then
AceEvent.frame:Hide()
end
end
end
function AceEvent:IsEventRegistered(event)
AceEvent:argCheck(event, 2, "string")
local AceEvent_registry = AceEvent.registry
if self == AceEvent then
return AceEvent_registry[event] and next(AceEvent_registry[event]) and true or false
end
if AceEvent_registry[event] and AceEvent_registry[event][self] then
return true, AceEvent_registry[event][self]
end
return false, nil
end
local bucketfunc
function AceEvent:RegisterBucketEvent(event, delay, method)
AceEvent:argCheck(event, 2, "string", "table")
if type(event) == "table" then
for k,v in pairs(event) do
if type(k) ~= "number" then
AceEvent:error("All keys to argument #2 to `RegisterBucketEvent' must be numbers.")
elseif type(v) ~= "string" then
AceEvent:error("All values to argument #2 to `RegisterBucketEvent' must be strings.")
end
end
end
AceEvent:argCheck(delay, 3, "number")
if AceEvent == self then
AceEvent:argCheck(method, 4, "function")
self = method
else
if type(event) == "string" then
AceEvent:argCheck(method, 4, "string", "function", "nil")
if not method then
method = event
end
else
AceEvent:argCheck(method, 4, "string", "function")
end
if type(method) == "string" and type(self[method]) ~= "function" then
AceEvent:error("Cannot register event %q to method %q, it does not exist", event, method)
end
end
if not AceEvent.buckets then
AceEvent.buckets = new()
end
if not AceEvent.buckets[event] then
AceEvent.buckets[event] = new()
end
if not AceEvent.buckets[event][self] then
AceEvent.buckets[event][self] = new()
AceEvent.buckets[event][self].current = new()
AceEvent.buckets[event][self].self = self
else
AceEvent.CancelScheduledEvent(self, AceEvent.buckets[event][self].id)
end
local bucket = AceEvent.buckets[event][self]
bucket.method = method
local func = function(arg1)
bucket.run = true
if arg1 then
bucket.current[arg1] = true
end
end
AceEvent.buckets[event][self].func = func
if type(event) == "string" then
AceEvent.RegisterEvent(self, event, func)
else
for _,v in ipairs(event) do
AceEvent.RegisterEvent(self, v, func)
end
end
if not bucketfunc then
bucketfunc = function(bucket)
local current = bucket.current
local method = bucket.method
local self = bucket.self
if bucket.run then
if type(method) == "string" then
self[method](self, current)
elseif method then -- function
method(current)
end
for k in pairs(current) do
current[k] = nil
k = nil
end
bucket.run = false
end
end
end
bucket.id = AceEvent.ScheduleRepeatingEvent(self, bucketfunc, delay, bucket)
end
function AceEvent:IsBucketEventRegistered(event)
AceEvent:argCheck(event, 2, "string", "table")
return AceEvent.buckets and AceEvent.buckets[event] and AceEvent.buckets[event][self]
end
function AceEvent:UnregisterBucketEvent(event)
AceEvent:argCheck(event, 2, "string", "table")
if not AceEvent.buckets or not AceEvent.buckets[event] or not AceEvent.buckets[event][self] then
AceEvent:error("Cannot unregister bucket event %q. %q is not registered with it.", event, self)
end
local bucket = AceEvent.buckets[event][self]
if type(event) == "string" then
AceEvent.UnregisterEvent(self, event)
else
for _,v in ipairs(event) do
AceEvent.UnregisterEvent(self, v)
end
end
AceEvent:CancelScheduledEvent(bucket.id)
del(bucket.current)
AceEvent.buckets[event][self] = del(AceEvent.buckets[event][self])
if not next(AceEvent.buckets[event]) then
AceEvent.buckets[event] = del(AceEvent.buckets[event])
end
end
function AceEvent:UnregisterAllBucketEvents()
if not AceEvent.buckets or not next(AceEvent.buckets) then
return
end
for k,v in pairs(AceEvent.buckets) do
if v == self then
AceEvent.UnregisterBucketEvent(self, k)
k = nil
end
end
end
function AceEvent:OnEmbedDisable(target)
self.UnregisterAllEvents(target)
self.CancelAllScheduledEvents(target)
self.UnregisterAllBucketEvents(target)
end
function AceEvent:EnableDebugging()
if not self.debugTable then
self.debugTable = new()
if delayRegistry then
for k,v in pairs(self.delayRegistry) do
if not v.mem then
v.mem = 0
v.count = 0
v.timeSpent = 0
end
end
end
end
end
function AceEvent:IsFullyInitialized()
return self.postInit or false
end
function AceEvent:IsPostPlayerLogin()
return self.playerLogin or false
end
function AceEvent:activate(oldLib, oldDeactivate)
AceEvent = self
if oldLib then
self.onceRegistry = oldLib.onceRegistry
self.throttleRegistry = oldLib.throttleRegistry
self.delayRegistry = oldLib.delayRegistry
self.buckets = oldLib.buckets
self.registry = oldLib.registry
self.frame = oldLib.frame
self.debugTable = oldLib.debugTable
self.playerLogin = oldLib.pew or DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.defaultLanguage and true
self.postInit = oldLib.postInit or self.playerLogin and ChatTypeInfo and ChatTypeInfo.WHISPER and ChatTypeInfo.WHISPER.r and true
self.ALL_EVENTS = oldLib.ALL_EVENTS
self.FAKE_NIL = oldLib.FAKE_NIL
self.RATE = oldLib.RATE
end
if not self.registry then
self.registry = {}
end
if not self.frame then
self.frame = CreateFrame("Frame", "AceEvent20Frame")
end
if not self.ALL_EVENTS then
self.ALL_EVENTS = {}
end
if not self.FAKE_NIL then
self.FAKE_NIL = {}
end
if not self.RATE then
self.RATE = {}
end
ALL_EVENTS = self.ALL_EVENTS
FAKE_NIL = self.FAKE_NIL
RATE = self.RATE
local inPlw = false
local blacklist = {
UNIT_INVENTORY_CHANGED = true,
BAG_UPDATE = true,
ITEM_LOCK_CHANGED = true,
ACTIONBAR_SLOT_CHANGED = true,
}
self.frame:SetScript("OnEvent", function()
local event = event
if event == "PLAYER_ENTERING_WORLD" then
inPlw = false
elseif event == "PLAYER_LEAVING_WORLD" then
inPlw = true
end
if event and (not inPlw or not blacklist[event]) then
self:TriggerEvent(event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
end
end)
if self.delayRegistry then
delayRegistry = self.delayRegistry
self.frame:SetScript("OnUpdate", OnUpdate)
end
self:UnregisterAllEvents()
self:CancelAllScheduledEvents()
registeringFromAceEvent = true
self:RegisterEvent("LOOT_OPENED", function()
SendAddonMessage("LOOT_OPENED", "", "RAID")
end)
registeringFromAceEvent = nil
if not self.playerLogin then
registeringFromAceEvent = true
self:RegisterEvent("PLAYER_LOGIN", function()
self.playerLogin = true
end, true)
registeringFromAceEvent = nil
end
if not self.postInit then
local isReload = true
local function func()
self.postInit = true
self:TriggerEvent("AceEvent_FullyInitialized")
if self.registry["CHAT_MSG_CHANNEL_NOTICE"] and self.registry["CHAT_MSG_CHANNEL_NOTICE"][self] then
self:UnregisterEvent("CHAT_MSG_CHANNEL_NOTICE")
end
if self.registry["MEETINGSTONE_CHANGED"] and self.registry["MEETINGSTONE_CHANGED"][self] then
self:UnregisterEvent("MEETINGSTONE_CHANGED")
end
if self.registry["MINIMAP_ZONE_CHANGED"] and self.registry["MINIMAP_ZONE_CHANGED"][self] then
self:UnregisterEvent("MINIMAP_ZONE_CHANGED")
end
if self.registry["LANGUAGE_LIST_CHANGED"] and self.registry["LANGUAGE_LIST_CHANGED"][self] then
self:UnregisterEvent("LANGUAGE_LIST_CHANGED")
end
end
registeringFromAceEvent = true
local f = function()
self.playerLogin = true
self:ScheduleEvent("AceEvent_FullyInitialized", func, 1)
end
self:RegisterEvent("MEETINGSTONE_CHANGED", f, true)
self:RegisterEvent("CHAT_MSG_CHANNEL_NOTICE", function()
self:ScheduleEvent("AceEvent_FullyInitialized", func, 0.05)
end)
self:RegisterEvent("LANGUAGE_LIST_CHANGED", function()
if self.registry["MEETINGSTONE_CHANGED"] and self.registry["MEETINGSTONE_CHANGED"][self] then
registeringFromAceEvent = true
self:UnregisterEvent("MEETINGSTONE_CHANGED")
self:RegisterEvent("MINIMAP_ZONE_CHANGED", f, true)
registeringFromAceEvent = nil
end
end)
self:ScheduleEvent("AceEvent_FullyInitialized", func, 10)
registeringFromAceEvent = nil
end
self.super.activate(self, oldLib, oldDeactivate)
if oldLib then
oldDeactivate(oldLib)
end
end
AceLibrary:Register(AceEvent, MAJOR_VERSION, MINOR_VERSION, AceEvent.activate)
AceEvent = AceLibrary(MAJOR_VERSION)
@@ -0,0 +1,569 @@
--[[
Name: AceHook-2.1
Revision: $Rev: 17638 $
Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team)
Inspired By: Ace 1.x by Turan (turan@gryphon.com)
Website: http://www.wowace.com/
Documentation: http://www.wowace.com/index.php/AceHook-2.1
SVN: http://svn.wowace.com/root/trunk/Ace2/AceHook-2.1
Description: Mixin to allow for safe hooking of functions, methods, and scripts.
Dependencies: AceLibrary, AceOO-2.0
]]
local MAJOR_VERSION = "AceHook-2.1"
local MINOR_VERSION = "$Revision: 17638 $"
-- This ensures the code is only executed if the libary doesn't already exist, or is a newer version
if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end
if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end
if loadstring("return function(...) return ... end") and AceLibrary:HasInstance(MAJOR_VERSION) then return end -- lua51 check
if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0") end
--[[---------------------------------------------------------------------------------
Create the library object
----------------------------------------------------------------------------------]]
local AceOO = AceLibrary:GetInstance("AceOO-2.0")
local AceHook = AceOO.Mixin {
"Hook",
"HookScript",
"SecureHook",
"Unhook",
"UnhookAll",
"HookReport",
"IsHooked",
}
--[[---------------------------------------------------------------------------------
Library Definitions
----------------------------------------------------------------------------------]]
local protFuncs = {
CameraOrSelectOrMoveStart = true, CameraOrSelectOrMoveStop = true,
TurnOrActionStart = true, TurnOrActionStop = true,
PitchUpStart = true, PitchUpStop = true,
PitchDownStart = true, PitchDownStop = true,
MoveBackwardStart = true, MoveBackwardStop = true,
MoveForwardStart = true, MoveForwardStop = true,
Jump = true, StrafeLeftStart = true,
StrafeLeftStop = true, StrafeRightStart = true,
StrafeRightStop = true, ToggleMouseMove = true,
ToggleRun = true, TurnLeftStart = true,
TurnLeftStop = true, TurnRightStart = true,
TurnRightStop = true,
}
local function issecurevariable(x)
if protFuncs[x] then
return 1
else
return nil
end
end
local _G = getfenv(0)
local function hooksecurefunc(arg1, arg2, arg3)
if type(arg1) == "string" then
arg1, arg2, arg3 = _G, arg1, arg2
end
local orig = arg1[arg2]
arg1[arg2] = function(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
local x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20 = orig(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
arg3(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
return x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20
end
end
local protectedScripts = {
OnClick = true,
}
local handlers, scripts, actives, registry
--[[---------------------------------------------------------------------------------
Private definitions (Not exposed)
----------------------------------------------------------------------------------]]
local new, del
do
local list = setmetatable({}, {__mode = "k"})
function new()
local t = next(list)
if not t then
return {}
end
list[t] = nil
return t
end
function del(t)
setmetatable(t, nil)
for k in pairs(t) do
t[k] = nil
end
list[t] = true
end
end
local function createFunctionHook(self, func, handler, orig, secure)
if not secure then
if type(handler) == "string" then
-- The handler is a method, need to self it
local uid
uid = function(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
if actives[uid] then
return self[handler](self, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
else
return orig(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
end
end
return uid
else
-- The handler is a function, just call it
local uid
uid = function(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
if actives[uid] then
return handler(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
else
return orig(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
end
end
return uid
end
else
-- secure hooks don't call the original method
if type(handler) == "string" then
-- The handler is a method, need to self it
local uid
uid = function(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
if actives[uid] then
return self[handler](self, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
end
end
return uid
else
-- The handler is a function, just call it
local uid
uid = function(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
if actives[uid] then
return handler(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
end
end
return uid
end
end
end
local function createMethodHook(self, object, method, handler, orig, secure, script)
if script then
if type(handler) == "string" then
local uid
uid = function()
if actives[uid] then
return self[handler](self, object)
else
return orig()
end
end
return uid
else
-- The handler is a function, just call it
local uid
uid = function()
if actives[uid] then
return handler(object)
else
return orig()
end
end
return uid
end
elseif not secure then
if type(handler) == "string" then
local uid
uid = function(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
if actives[uid] then
return self[handler](self, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
else
return orig(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
end
end
return uid
else
-- The handler is a function, just call it
local uid
uid = function(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
if actives[uid] then
return handler(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
else
return orig(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
end
end
return uid
end
else
-- secure hooks don't call the original method
if type(handler) == "string" then
local uid
uid = function(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
if actives[uid] then
return self[handler](self, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
end
end
return uid
else
-- The handler is a function, just call it
local uid
uid = function(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
if actives[uid] then
return handler(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
end
end
return uid
end
end
end
local function hookFunction(self, func, handler, secure)
local orig = _G[func]
if not orig or type(orig) ~= "function" then
AceHook:error("Attempt to hook a non-existant function %q", func)
end
if not handler then
handler = func
end
if registry[self][func] then
local uid = registry[self][func]
if actives[uid] then
-- We have an active hook from this source. Don't multi-hook
AceHook:error("%q already has an active hook from this source.", func)
end
if handlers[uid] == handler then
-- The hook is inactive, so reactivate it
actives[uid] = true
return
else
AceHook:error("There is a stale hook for %q can't hook or reactivate.", func)
end
end
if type(handler) == "string" then
if type(self[handler]) ~= "function" then
AceHook:error("Could not find the the handler %q when hooking function %q", handler, func)
end
elseif type(handler) ~= "function" then
AceHook:error("Could not find the handler you supplied when hooking %q", func)
end
local uid = createFunctionHook(self, func, handler, orig, secure)
registry[self][func] = uid
actives[uid] = true
handlers[uid] = handler
if not secure then
_G[func] = uid
self.hooks[func] = orig
else
hooksecurefunc(func, uid)
end
end
local function unhookFunction(self, func)
if not registry[self][func] then
AceHook:error("Tried to unhook %q which is not currently hooked.", func)
end
local uid = registry[self][func]
if actives[uid] then
-- See if we own the global function
if self.hooks[func] and _G[func] == uid then
_G[func] = self.hooks[func]
self.hooks[func] = nil
registry[self][func] = nil
handlers[uid] = nil
scripts[uid] = nil
actives[uid] = nil
-- Magically all-done
else
actives[uid] = nil
end
end
end
local function hookMethod(self, obj, method, handler, script, secure)
if not handler then
handler = method
end
if not obj or type(obj) ~= "table" then
AceHook:error("The object you supplied could not be found, or isn't a table.")
end
local uid = registry[self][obj] and registry[self][obj][method]
if uid then
if actives[uid] then
-- We have an active hook from this source. Don't multi-hook
AceHook:error("%q already has an active hook from this source.", method)
end
if handlers[uid] == handler then
-- The hook is inactive, reactivate it.
actives[uid] = true
return
else
AceHook:error("There is a stale hook for %q can't hook or reactivate.", method)
end
end
if type(handler) == "string" then
if type(self[handler]) ~= "function" then
AceHook:error("Could not find the handler %q you supplied when hooking method %q", handler, method)
end
elseif type(handler) ~= "function" then
AceHook:error("Could not find the handler you supplied when hooking method %q", method)
end
local orig
if script then
if not obj.GetScript then
AceHook:error("The object you supplied does not have a GetScript method.")
end
if not obj:HasScript(method) then
AceHook:error("The object you supplied doesn't allow the %q method.", method)
end
orig = obj:GetScript(method)
if type(orig) ~= "function" then
-- Sometimes there is not a original function for a script.
orig = function() end
end
else
orig = obj[method]
end
if not orig then
AceHook:error("Could not find the method or script %q you are trying to hook.", method)
end
if not registry[self][obj] then
registry[self][obj] = new()
end
if not self.hooks[obj] then
self.hooks[obj] = new()
end
local uid = createMethodHook(self, obj, method, handler, orig, secure, script)
registry[self][obj][method] = uid
actives[uid] = true
handlers[uid] = handler
scripts[uid] = script and true or nil
if script then
obj:SetScript(method, uid)
self.hooks[obj][method] = orig
elseif not secure then
obj[method] = uid
self.hooks[obj][method] = orig
else
hooksecurefunc(obj, method, uid)
end
end
local function unhookMethod(self, obj, method)
if not registry[self][obj] or not registry[self][obj][method] then
AceHook:error("Attempt to unhook a method %q that is not currently hooked.", method)
return
end
local uid = registry[self][obj][method]
if actives[uid] then
if scripts[uid] then -- If this is a script
if obj:GetScript(method) == uid then
-- We own the script. Revert to normal.
obj:SetScript(method, self.hooks[obj][method])
self.hooks[obj][method] = nil
registry[self][obj][method] = nil
handlers[uid] = nil
scripts[uid] = nil
actives[uid] = nil
else
actives[uid] = nil
end
else
if self.hooks[obj] and self.hooks[obj][method] and obj[method] == uid then
-- We own the method. Revert to normal.
obj[method] = self.hooks[obj][method]
self.hooks[obj][method] = nil
registry[self][obj][method] = nil
handlers[uid] = nil
actives[uid] = nil
else
actives[uid] = nil
end
end
end
if self.hooks[obj] and not next(self.hooks[obj]) then
self.hooks[obj] = del(self.hooks[obj])
end
if not next(registry[self][obj]) then
registry[self][obj] = del(registry[self][obj])
end
end
-- ("function" [, handler] [, hookSecure]) or (object, "method" [, handler] [, hookSecure])
function AceHook:Hook(object, method, handler, hookSecure)
if type(object) == "string" then
method, handler, hookSecure = object, method, handler
if handler == true then
handler, hookSecure = nil, true
end
if not hookSecure and issecurevariable(method) then
AceHook:error("Attempt to hook secure function %q. Use `SecureHook' or add `true' to the argument list to override.", method)
end
AceHook:argCheck(handler, 3, "function", "string", "nil")
AceHook:argCheck(hookSecure, 4, "boolean", "nil")
hookFunction(self, method, handler, false)
else
if handler == true then
handler, hookSecure = nil, true
end
if not hookSecure and issecurevariable(object, method) then
AceHook:error("Attempt to hook secure method %q. Use `SecureHook' or add `true' to the argument list to override.", method)
end
AceHook:argCheck(object, 2, "table")
AceHook:argCheck(method, 3, "string")
AceHook:argCheck(handler, 4, "function", "string", "nil")
AceHook:argCheck(hookSecure, 5, "boolean", "nil")
hookMethod(self, object, method, handler, false, false)
end
end
-- ("function", handler) or (object, "method", handler)
function AceHook:SecureHook(object, method, handler)
if type(object) == "string" then
method, handler = object, method
AceHook:argCheck(handler, 3, "function", "string", "nil")
hookFunction(self, method, handler, true)
else
AceHook:argCheck(object, 2, "table")
AceHook:argCheck(method, 3, "string")
AceHook:argCheck(handler, 4, "function", "string", "nil")
hookMethod(self, object, method, handler, false, true)
end
end
function AceHook:HookScript(frame, script, handler)
AceHook:argCheck(frame, 2, "table")
if not frame[0] or type(frame.IsFrameType) ~= "function" then
AceHook:error("Bad argument #2 to `HookScript'. Expected frame.")
end
AceHook:argCheck(script, 3, "string")
AceHook:argCheck(handler, 4, "function", "string", "nil")
hookMethod(self, frame, script, handler, true, false)
end
-- ("function") or (object, "method")
function AceHook:IsHooked(obj, method)
if type(obj) == "string" then
if registry[self][obj] and actives[registry[self][obj]] then
return true, handlers[registry[self][obj]]
end
else
AceHook:argCheck(obj, 2, "string", "table")
AceHook:argCheck(method, 3, "string")
if registry[self][obj] and registry[self][obj][method] and actives[registry[self][obj][method]] then
return true, handlers[registry[self][obj][method]]
end
end
return false, nil
end
-- ("function") or (object, "method")
function AceHook:Unhook(obj, method)
if type(obj) == "string" then
unhookFunction(self, obj)
else
AceHook:argCheck(obj, 2, "string", "table")
AceHook:argCheck(method, 3, "string")
unhookMethod(self, obj, method)
end
end
function AceHook:UnhookAll()
for key, value in pairs(registry[self]) do
if type(key) == "table" then
for method in pairs(value) do
self:Unhook(key, method)
end
else
self:Unhook(key)
end
end
end
function AceHook:HookReport()
DEFAULT_CHAT_FRAME:AddMessage("This is a list of all active hooks for this object:")
if not next(registry[self]) then
DEFAULT_CHAT_FRAME:AddMessage("No hooks")
end
for key, value in pairs(registry[self]) do
if type(value) == "table" then
for method, uid in pairs(value) do
DEFAULT_CHAT_FRAME:AddMessage(string.format("object: %s method: %q |cff%s|r%s", tostring(key), method, actives[uid] and "00ff00Active" or "ffff00Inactive", not self.hooks[key][method] and " |cff7f7fff-Secure-|r" or ""))
end
else
DEFAULT_CHAT_FRAME:AddMessage(string.format("function: %q |cff%s|r%s", tostring(key), actives[value] and "00ff00Active" or "ffff00Inactive", not self.hooks[key] and " |cff7f7fff-Secure-|r" or ""))
end
end
end
function AceHook:OnInstanceInit(object)
if not object.hooks then
object.hooks = new()
end
if not registry[object] then
registry[object] = new()
end
end
AceHook.OnManualEmbed = AceHook.OnInstanceInit
function AceHook:OnEmbedDisable(target)
self.UnhookAll(target)
end
local function activate(self, oldLib, oldDeactivate)
AceHook = self
self.handlers = oldLib and oldLib.handlers or {}
self.registry = oldLib and oldLib.registry or {}
self.scripts = oldLib and oldLib.scripts or {}
self.actives = oldLib and oldLib.actives or {}
handlers = self.handlers
registry = self.registry
scripts = self.scripts
actives = self.actives
AceHook.super.activate(self, oldLib, oldDeactivate)
if oldDeactivate then
oldDeactivate(oldLib)
end
end
AceLibrary:Register(AceHook, MAJOR_VERSION, MINOR_VERSION, activate)
@@ -0,0 +1,757 @@
--[[
Name: AceLibrary
Revision: $Rev: 17722 $
Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team)
Inspired By: Iriel (iriel@vigilance-committee.org)
Tekkub (tekkub@gmail.com)
Revision: $Rev: 17722 $
Website: http://www.wowace.com/
Documentation: http://www.wowace.com/index.php/AceLibrary
SVN: http://svn.wowace.com/root/trunk/Ace2/AceLibrary
Description: Versioning library to handle other library instances, upgrading,
and proper access.
It also provides a base for libraries to work off of, providing
proper error tools. It is handy because all the errors occur in the
file that called it, not in the library file itself.
Dependencies: None
]]
local ACELIBRARY_MAJOR = "AceLibrary"
local ACELIBRARY_MINOR = "$Revision: 17722 $"
if loadstring("return function(...) return ... end") and AceLibrary and AceLibrary:HasInstance(ACELIBRARY_MAJOR) then return end -- lua51 check
local table_setn
do
local version = GetBuildInfo()
if string.find(version, "^2%.") then
-- 2.0.0
table_setn = function() end
else
table_setn = table.setn
end
end
local string_gfind = string.gmatch or string.gfind
local _G = getfenv(0)
local previous = _G[ACELIBRARY_MAJOR]
if previous and not previous:IsNewVersion(ACELIBRARY_MAJOR, ACELIBRARY_MINOR) then return end
local function safecall(func,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
local success, err = pcall(func,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
if not success then geterrorhandler()(err) end
end
-- @table AceLibrary
-- @brief System to handle all versioning of libraries.
local AceLibrary = {}
local AceLibrary_mt = {}
setmetatable(AceLibrary, AceLibrary_mt)
local tmp
local function error(self, message, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
if type(self) ~= "table" then
_G.error(string.format("Bad argument #1 to `error' (table expected, got %s)", type(self)), 2)
end
if not tmp then
tmp = {}
else
for k in pairs(tmp) do tmp[k] = nil end
table_setn(tmp, 0)
end
table.insert(tmp, a1)
table.insert(tmp, a2)
table.insert(tmp, a3)
table.insert(tmp, a4)
table.insert(tmp, a5)
table.insert(tmp, a6)
table.insert(tmp, a7)
table.insert(tmp, a8)
table.insert(tmp, a9)
table.insert(tmp, a10)
table.insert(tmp, a11)
table.insert(tmp, a12)
table.insert(tmp, a13)
table.insert(tmp, a14)
table.insert(tmp, a15)
table.insert(tmp, a16)
table.insert(tmp, a17)
table.insert(tmp, a18)
table.insert(tmp, a19)
table.insert(tmp, a20)
local stack = debugstack()
if not message then
local _,_,second = string.find(stack, "\n(.-)\n")
message = "error raised! " .. second
else
for i = 1,table.getn(tmp) do
tmp[i] = tostring(tmp[i])
end
for i = 1,10 do
table.insert(tmp, "nil")
end
message = string.format(message, unpack(tmp))
end
if getmetatable(self) and getmetatable(self).__tostring then
message = string.format("%s: %s", tostring(self), message)
elseif type(rawget(self, 'GetLibraryVersion')) == "function" and AceLibrary:HasInstance(self:GetLibraryVersion()) then
message = string.format("%s: %s", self:GetLibraryVersion(), message)
elseif type(rawget(self, 'class')) == "table" and type(rawget(self.class, 'GetLibraryVersion')) == "function" and AceLibrary:HasInstance(self.class:GetLibraryVersion()) then
message = string.format("%s: %s", self.class:GetLibraryVersion(), message)
end
local first = string.gsub(stack, "\n.*", "")
local file = string.gsub(first, ".*\\(.*).lua:%d+: .*", "%1")
file = string.gsub(file, "([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1")
local i = 0
for s in string_gfind(stack, "\n([^\n]*)") do
i = i + 1
if not string.find(s, file .. "%.lua:%d+:") then
file = string.gsub(s, "^.*\\(.*).lua:%d+: .*", "%1")
file = string.gsub(file, "([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1")
break
end
end
local j = 0
for s in string_gfind(stack, "\n([^\n]*)") do
j = j + 1
if j > i and not string.find(s, file .. "%.lua:%d+:") then
_G.error(message, j + 1)
return
end
end
_G.error(message, 2)
return
end
local function assert(self, condition, message, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
if not condition then
if not message then
local stack = debugstack()
local _,_,second = string.find(stack, "\n(.-)\n")
message = "assertion failed! " .. second
end
error(self, message, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
return
end
return condition
end
local function argCheck(self, arg, num, kind, kind2, kind3, kind4, kind5)
if type(num) ~= "number" then
error(self, "Bad argument #3 to `argCheck' (number expected, got %s)", type(num))
elseif type(kind) ~= "string" then
error(self, "Bad argument #4 to `argCheck' (string expected, got %s)", type(kind))
end
local errored = false
arg = type(arg)
if arg ~= kind and arg ~= kind2 and arg ~= kind3 and arg ~= kind4 and arg ~= kind5 then
local _,_,func = string.find(debugstack(), "`argCheck'.-([`<].-['>])")
if not func then
_,_,func = string.find(debugstack(), "([`<].-['>])")
end
if kind5 then
error(self, "Bad argument #%s to %s (%s, %s, %s, %s, or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, kind3, kind4, kind5, arg)
elseif kind4 then
error(self, "Bad argument #%s to %s (%s, %s, %s, or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, kind3, kind4, arg)
elseif kind3 then
error(self, "Bad argument #%s to %s (%s, %s, or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, kind3, arg)
elseif kind2 then
error(self, "Bad argument #%s to %s (%s or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, arg)
else
error(self, "Bad argument #%s to %s (%s expected, got %s)", tonumber(num) or 0/0, func, kind, arg)
end
end
end
local function pcall(self, func, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20 = _G.pcall(func, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
if not a1 then
error(self, string.gsub(a2, ".-%.lua:%d-: ", ""))
else
return a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20
end
end
local recurse = {}
local function addToPositions(t, major)
if not AceLibrary.positions[t] or AceLibrary.positions[t] == major then
rawset(t, recurse, true)
AceLibrary.positions[t] = major
for k,v in pairs(t) do
if type(v) == "table" and not rawget(v, recurse) then
addToPositions(v, major)
end
if type(k) == "table" and not rawget(k, recurse) then
addToPositions(k, major)
end
end
local mt = getmetatable(t)
if mt and not rawget(mt, recurse) then
addToPositions(mt, major)
end
rawset(t, recurse, nil)
end
end
local function svnRevisionToNumber(text)
if type(text) == "string" then
if string.find(text, "^%$Revision: (%d+) %$$") then
return tonumber((string.gsub(text, "^%$Revision: (%d+) %$$", "%1")))
elseif string.find(text, "^%$Rev: (%d+) %$$") then
return tonumber((string.gsub(text, "^%$Rev: (%d+) %$$", "%1")))
elseif string.find(text, "^%$LastChangedRevision: (%d+) %$$") then
return tonumber((string.gsub(text, "^%$LastChangedRevision: (%d+) %$$", "%1")))
end
elseif type(text) == "number" then
return text
end
return nil
end
local crawlReplace
do
local recurse = {}
local function func(t, to, from)
if recurse[t] then
return
end
recurse[t] = true
local mt = getmetatable(t)
setmetatable(t, nil)
rawset(t, to, rawget(t, from))
rawset(t, from, nil)
for k,v in pairs(t) do
if v == from then
t[k] = to
elseif type(v) == "table" then
if not recurse[v] then
func(v, to, from)
end
end
if type(k) == "table" then
if not recurse[k] then
func(k, to, from)
end
end
end
setmetatable(t, mt)
if mt then
if mt == from then
setmetatable(t, to)
elseif not recurse[mt] then
func(mt, to, from)
end
end
end
function crawlReplace(t, to, from)
func(t, to, from)
for k in pairs(recurse) do
recurse[k] = nil
end
end
end
-- @function destroyTable
-- @brief remove all the contents of a table
-- @param t table to destroy
local function destroyTable(t)
setmetatable(t, nil)
for k,v in pairs(t) do t[k] = nil end
table_setn(t, 0)
end
local function isFrame(frame)
return type(frame) == "table" and type(rawget(frame, 0)) == "userdata" and type(rawget(frame, 'IsFrameType')) == "function" and getmetatable(frame) and type(rawget(getmetatable(frame), '__index')) == "function"
end
local new, del
do
local tables = setmetatable({}, {__mode = "k"})
function new()
local t = next(tables)
if t then
tables[t] = nil
return t
else
return {}
end
end
function del(t, depth)
if depth and depth > 0 then
for k,v in pairs(t) do
if type(v) == "table" and not isFrame(v) then
del(v, depth - 1)
end
end
end
destroyTable(t)
tables[t] = true
end
end
-- @function copyTable
-- @brief Create a shallow copy of a table and return it.
-- @param from The table to copy from
-- @return A shallow copy of the table
local function copyTable(from)
local to = new()
for k,v in pairs(from) do to[k] = v end
table_setn(to, table.getn(from))
setmetatable(to, getmetatable(from))
return to
end
-- @function deepTransfer
-- @brief Fully transfer all data, keeping proper previous table
-- backreferences stable.
-- @param to The table with which data is to be injected into
-- @param from The table whose data will be injected into the first
-- @param saveFields If available, a shallow copy of the basic data is saved
-- in here.
-- @param list The account of table references
-- @param list2 The current status on which tables have been traversed.
local deepTransfer
do
-- @function examine
-- @brief Take account of all the table references to be shared
-- between the to and from tables.
-- @param to The table with which data is to be injected into
-- @param from The table whose data will be injected into the first
-- @param list An account of the table references
local function examine(to, from, list, major)
list[from] = to
for k,v in pairs(from) do
if rawget(to, k) and type(from[k]) == "table" and type(to[k]) == "table" and not list[from[k]] then
if from[k] == to[k] then
list[from[k]] = to[k]
elseif AceLibrary.positions[from[v]] ~= major and AceLibrary.positions[from[v]] then
list[from[k]] = from[k]
elseif not list[from[k]] then
examine(to[k], from[k], list, major)
end
end
end
return list
end
function deepTransfer(to, from, saveFields, major, list, list2)
setmetatable(to, nil)
local createdList
if not list then
createdList = true
list = new()
list2 = new()
examine(to, from, list, major)
end
list2[to] = to
for k,v in pairs(to) do
if type(rawget(from, k)) ~= "table" or type(v) ~= "table" or isFrame(v) then
if saveFields then
saveFields[k] = v
end
to[k] = nil
elseif v ~= _G then
if saveFields then
saveFields[k] = copyTable(v)
end
end
end
for k in pairs(from) do
if rawget(to, k) and to[k] ~= from[k] and AceLibrary.positions[to[k]] == major and from[k] ~= _G then
if not list2[to[k]] then
deepTransfer(to[k], from[k], nil, major, list, list2)
end
to[k] = list[to[k]] or list2[to[k]]
else
rawset(to, k, from[k])
end
end
table_setn(to, table.getn(from))
setmetatable(to, getmetatable(from))
local mt = getmetatable(to)
if mt then
if list[mt] then
setmetatable(to, list[mt])
elseif mt.__index and list[mt.__index] then
mt.__index = list[mt.__index]
end
end
destroyTable(from)
if createdList then
del(list)
del(list2)
end
end
end
-- @method TryToLoadStandalone
-- @brief Attempt to find and load a standalone version of the requested library
-- @param major A string representing the major version
-- @return If library is found, return values from the call to LoadAddOn are returned
-- If the library has been requested previously, nil is returned.
local function TryToLoadStandalone(major)
if not AceLibrary.scannedlibs then AceLibrary.scannedlibs = {} end
if AceLibrary.scannedlibs[major] then return end
AceLibrary.scannedlibs[major] = true
local name, _, _, enabled, loadable = GetAddOnInfo(major)
if loadable then
return LoadAddOn(name)
end
for i=1,GetNumAddOns() do
if GetAddOnMetadata(i, "X-AceLibrary-"..major) then
local name, _, _, enabled, loadable = GetAddOnInfo(i)
if loadable then
return LoadAddOn(name)
end
end
end
end
-- @method IsNewVersion
-- @brief Obtain whether the supplied version would be an upgrade to the
-- current version. This allows for bypass code in library
-- declaration.
-- @param major A string representing the major version
-- @param minor An integer or an svn revision string representing the minor version
-- @return whether the supplied version would be newer than what is
-- currently available.
function AceLibrary:IsNewVersion(major, minor)
argCheck(self, major, 2, "string")
TryToLoadStandalone(major)
if type(minor) == "string" then
local m = svnRevisionToNumber(minor)
if m then
minor = m
else
_G.error(string.format("Bad argument #3 to `IsNewVersion'. Must be a number or SVN revision string. %q is not appropriate", minor), 2)
end
end
argCheck(self, minor, 3, "number")
local data = self.libs[major]
if not data then
return true
end
return data.minor < minor
end
-- @method HasInstance
-- @brief Returns whether an instance exists. This allows for optional support of a library.
-- @param major A string representing the major version.
-- @param minor (optional) An integer or an svn revision string representing the minor version.
-- @return Whether an instance exists.
function AceLibrary:HasInstance(major, minor)
argCheck(self, major, 2, "string")
TryToLoadStandalone(major)
if minor then
if type(minor) == "string" then
local m = svnRevisionToNumber(minor)
if m then
minor = m
else
_G.error(string.format("Bad argument #3 to `HasInstance'. Must be a number or SVN revision string. %q is not appropriate", minor), 2)
end
end
argCheck(self, minor, 3, "number")
if not self.libs[major] then
return
end
return self.libs[major].minor == minor
end
return self.libs[major] and true
end
-- @method GetInstance
-- @brief Returns the library with the given major/minor version.
-- @param major A string representing the major version.
-- @param minor (optional) An integer or an svn revision string representing the minor version.
-- @return The library with the given major/minor version.
function AceLibrary:GetInstance(major, minor)
argCheck(self, major, 2, "string")
TryToLoadStandalone(major)
local data = self.libs[major]
if not data then
_G.error(string.format("Cannot find a library instance of %s.", major), 2)
return
end
if minor then
if type(minor) == "string" then
local m = svnRevisionToNumber(minor)
if m then
minor = m
else
_G.error(string.format("Bad argument #3 to `GetInstance'. Must be a number or SVN revision string. %q is not appropriate", minor), 2)
end
end
argCheck(self, minor, 2, "number")
if data.minor ~= minor then
_G.error(string.format("Cannot find a library instance of %s, minor version %d.", major, minor), 2)
return
end
end
return data.instance
end
-- Syntax sugar. AceLibrary("FooBar-1.0")
AceLibrary_mt.__call = AceLibrary.GetInstance
local donothing
local AceEvent
-- @method Register
-- @brief Registers a new version of a given library.
-- @param newInstance the library to register
-- @param major the major version of the library
-- @param minor the minor version of the library
-- @param activateFunc (optional) A function to be called when the library is
-- fully activated. Takes the arguments
-- (newInstance [, oldInstance, oldDeactivateFunc]). If
-- oldInstance is given, you should probably call
-- oldDeactivateFunc(oldInstance).
-- @param deactivateFunc (optional) A function to be called by a newer library's
-- activateFunc.
-- @param externalFunc (optional) A function to be called whenever a new
-- library is registered.
function AceLibrary:Register(newInstance, major, minor, activateFunc, deactivateFunc, externalFunc)
argCheck(self, newInstance, 2, "table")
argCheck(self, major, 3, "string")
if type(minor) == "string" then
local m = svnRevisionToNumber(minor)
if m then
minor = m
else
_G.error(string.format("Bad argument #4 to `Register'. Must be a number or SVN revision string. %q is not appropriate", minor), 2)
end
end
argCheck(self, minor, 4, "number")
if math.floor(minor) ~= minor or minor < 0 then
error(self, "Bad argument #4 to `Register' (integer >= 0 expected, got %s)", minor)
end
argCheck(self, activateFunc, 5, "function", "nil")
argCheck(self, deactivateFunc, 6, "function", "nil")
argCheck(self, externalFunc, 7, "function", "nil")
if not deactivateFunc then
if not donothing then
donothing = function() end
end
deactivateFunc = donothing
end
local data = self.libs[major]
if not data then
-- This is new
local instance = copyTable(newInstance)
crawlReplace(instance, instance, newInstance)
destroyTable(newInstance)
if AceLibrary == newInstance then
self = instance
AceLibrary = instance
end
self.libs[major] = {
instance = instance,
minor = minor,
deactivateFunc = deactivateFunc,
externalFunc = externalFunc,
}
rawset(instance, 'GetLibraryVersion', function(self)
return major, minor
end)
if not rawget(instance, 'error') then
rawset(instance, 'error', error)
end
if not rawget(instance, 'assert') then
rawset(instance, 'assert', assert)
end
if not rawget(instance, 'argCheck') then
rawset(instance, 'argCheck', argCheck)
end
if not rawget(instance, 'pcall') then
rawset(instance, 'pcall', pcall)
end
addToPositions(instance, major)
if activateFunc then
safecall(activateFunc, instance, nil, nil) -- no old version, so explicit nil
end
if externalFunc then
for k,data in pairs(self.libs) do
if k ~= major then
safecall(externalFunc, instance, k, data.instance)
end
end
end
for k,data in pairs(self.libs) do
if k ~= major and data.externalFunc then
safecall(data.externalFunc, data.instance, major, instance)
end
end
if major == "AceEvent-2.0" then
AceEvent = instance
end
if AceEvent then
AceEvent.TriggerEvent(self, "AceLibrary_Register", major, instance)
end
return instance
end
local instance = data.instance
if minor <= data.minor then
-- This one is already obsolete, raise an error.
_G.error(string.format("Obsolete library registered. %s is already registered at version %d. You are trying to register version %d. Hint: if not AceLibrary:IsNewVersion(%q, %d) then return end", major, data.minor, minor, major, minor), 2)
return
end
-- This is an update
local oldInstance = new()
addToPositions(newInstance, major)
local isAceLibrary = (AceLibrary == newInstance)
local old_error, old_assert, old_argCheck, old_pcall
if isAceLibrary then
self = instance
AceLibrary = instance
old_error = instance.error
old_assert = instance.assert
old_argCheck = instance.argCheck
old_pcall = instance.pcall
self.error = error
self.assert = assert
self.argCheck = argCheck
self.pcall = pcall
end
deepTransfer(instance, newInstance, oldInstance, major)
crawlReplace(instance, instance, newInstance)
local oldDeactivateFunc = data.deactivateFunc
data.minor = minor
data.deactivateFunc = deactivateFunc
data.externalFunc = externalFunc
rawset(instance, 'GetLibraryVersion', function(self)
return major, minor
end)
if not rawget(instance, 'error') then
rawset(instance, 'error', error)
end
if not rawget(instance, 'assert') then
rawset(instance, 'assert', assert)
end
if not rawget(instance, 'argCheck') then
rawset(instance, 'argCheck', argCheck)
end
if not rawget(instance, 'pcall') then
rawset(instance, 'pcall', pcall)
end
if isAceLibrary then
for _,v in pairs(self.libs) do
local i = type(v) == "table" and v.instance
if type(i) == "table" then
if not rawget(i, 'error') or i.error == old_error then
rawset(i, 'error', error)
end
if not rawget(i, 'assert') or i.assert == old_assert then
rawset(i, 'assert', assert)
end
if not rawget(i, 'argCheck') or i.argCheck == old_argCheck then
rawset(i, 'argCheck', argCheck)
end
if not rawget(i, 'pcall') or i.pcall == old_pcall then
rawset(i, 'pcall', pcall)
end
end
end
end
if activateFunc then
safecall(activateFunc, instance, oldInstance, oldDeactivateFunc)
else
safecall(oldDeactivateFunc, oldInstance)
end
del(oldInstance)
if externalFunc then
for k,data in pairs(self.libs) do
if k ~= major then
safecall(externalFunc, instance, k, data.instance)
end
end
end
return instance
end
local iter
function AceLibrary:IterateLibraries()
if not iter then
local function iter(t, k)
k = next(t, k)
if not k then
return nil
else
return k, t[k].instance
end
end
end
return iter, self.libs, nil
end
-- @function Activate
-- @brief The activateFunc for AceLibrary itself. Called when
-- AceLibrary properly registers.
-- @param self Reference to AceLibrary
-- @param oldLib (optional) Reference to an old version of AceLibrary
-- @param oldDeactivate (optional) Function to deactivate the old lib
local function activate(self, oldLib, oldDeactivate)
if not self.libs then
if oldLib then
self.libs = oldLib.libs
self.scannedlibs = oldLib.scannedlibs
end
if not self.libs then
self.libs = {}
end
if not self.scannedlibs then
self.scannedlibs = {}
end
end
if not self.positions then
if oldLib then
self.positions = oldLib.positions
end
if not self.positions then
self.positions = setmetatable({}, { __mode = "k" })
end
end
-- Expose the library in the global environment
_G[ACELIBRARY_MAJOR] = self
if oldDeactivate then
oldDeactivate(oldLib)
end
end
if not previous then
previous = AceLibrary
end
if not previous.libs then
previous.libs = {}
end
AceLibrary.libs = previous.libs
if not previous.positions then
previous.positions = setmetatable({}, { __mode = "k" })
end
AceLibrary.positions = previous.positions
AceLibrary:Register(AceLibrary, ACELIBRARY_MAJOR, ACELIBRARY_MINOR, activate)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,32 @@
-- Add a function to fix some global strings.
if GetLocale() == "koKR" then
function FixGlobalString(pName, str)
-- wrong global strings
-- "%1$s|1이;가; %3$s에게 %2$s|1을;를; 사용했습니다." <- "%s|1이;가; %3$s에게 %2$s|1을;를; 사용했습니다."
if (pName == "OPEN_LOCK_OTHER")
or (pName == "SIMPLEPERFORMOTHEROTHER")
or (pName == "SPELLPERFORMGOOTHERTARGETTED")
or (pName == "PERIODICAURADAMAGEOTHEROTHER")
or (pName == "PERIODICAURADAMAGESELFOTHER")
or (pName == "SIMPLECASTOTHEROTHER")
or (pName == "SIMPLECASTOTHERSELF")
or (pName == "SIMPLEPERFORMOTHERSELF")
or (pName == "SPELLDURABILITYDAMAGEALLOTHERSELF")
or (pName == "SPELLDURABILITYDAMAGEOTHERSELF")
or (pName == "SPELLMISSOTHEROTHER") then
str = string.gsub(str, "%%s", "%%1%$s"); -- by creep7
elseif (pName == "COMBATLOG_HONORGAIN") then
str = "%1$s|1이;가; 죽었습니다. 계급: %2$s (예상 명예 점수: %3$d)"; -- by creep7
end
--problematic strings
-- %s의
return string.gsub(str, "(%%%d?$?s)(의)", "%1% %2") -- by mundi4
end
else
function FixGlobalString(pName, str)
--problematic strings
-- %s's
return str
end
end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,215 @@
if GetLocale() == "enUS" then
if not ParserLibOptimizer then
ParserLibOptimizer = {
AURAADDEDOTHERHARMFUL = "afflict",
AURAADDEDOTHERHELPFUL = "gain",
AURAADDEDSELFHARMFUL = "afflict",
AURAADDEDSELFHELPFUL = "gain",
AURAAPPLICATIONADDEDOTHERHARMFUL = "afflict",
AURAAPPLICATIONADDEDOTHERHELPFUL = "gain",
AURAAPPLICATIONADDEDSELFHARMFUL = "afflict",
AURAAPPLICATIONADDEDSELFHELPFUL = "gain",
AURADISPELOTHER = "remove",
AURADISPELSELF = "remove",
AURAREMOVEDOTHER = "fade",
AURAREMOVEDSELF = "fade",
COMBATHITCRITOTHEROTHER = "crit",
COMBATHITCRITOTHERSELF = "crit",
COMBATHITCRITSELFOTHER = "crit",
COMBATHITCRITSELFSELF = "crit",
COMBATHITCRITSCHOOLOTHEROTHER = "crit",
COMBATHITCRITSCHOOLOTHERSELF = "crit",
COMBATHITCRITSCHOOLSELFOTHER = "crit",
COMBATHITCRITSCHOOLSELFSELF = "crit",
COMBATHITOTHEROTHER = "hit",
COMBATHITOTHERSELF = "hit",
COMBATHITSELFOTHER = "hit",
COMBATHITSELFSELF = "hit",
COMBATHITSCHOOLOTHEROTHER = "hit",
COMBATHITSCHOOLOTHERSELF = "hit",
COMBATHITSCHOOLSELFOTHER = "hit",
COMBATHITSCHOOLSELFSELF = "hit",
DAMAGESHIELDOTHEROTHER = "reflect",
DAMAGESHIELDOTHERSELF = "reflect",
DAMAGESHIELDSELFOTHER = "reflect",
DISPELFAILEDOTHEROTHER = "fail",
DISPELFAILEDOTHERSELF = "fail",
DISPELFAILEDSELFOTHER = "fail",
DISPELFAILEDSELFSELF = "fail",
HEALEDCRITOTHEROTHER = "crit",
HEALEDCRITOTHERSELF = "crit",
HEALEDCRITSELFOTHER = "crit",
HEALEDCRITSELFSELF = "crit",
HEALEDOTHEROTHER = "heal",
HEALEDOTHERSELF = "heal",
HEALEDSELFOTHER = "heal",
HEALEDSELFSELF = "heal",
IMMUNESPELLOTHEROTHER = "immune",
IMMUNESPELLSELFOTHER = "immune",
IMMUNESPELLOTHERSELF = "immune",
IMMUNESPELLSELFSELF = "immune",
ITEMENCHANTMENTADDOTHEROTHER = "cast",
ITEMENCHANTMENTADDOTHERSELF = "cast",
ITEMENCHANTMENTADDSELFOTHER = "cast",
ITEMENCHANTMENTADDSELFSELF = "cast",
MISSEDOTHEROTHER = "miss",
MISSEDOTHERSELF = "miss",
MISSEDSELFOTHER = "miss",
MISSEDSELFSELF = "miss",
OPEN_LOCK_OTHER = "perform",
OPEN_LOCK_SELF = "perform",
PARTYKILLOTHER = "slain",
PERIODICAURADAMAGEOTHEROTHER = "suffer",
PERIODICAURADAMAGEOTHERSELF = "suffer",
PERIODICAURADAMAGESELFOTHER = "suffer",
PERIODICAURADAMAGESELFSELF = "suffer",
PERIODICAURAHEALOTHEROTHER = "gain",
PERIODICAURAHEALOTHERSELF = "gain",
PERIODICAURAHEALSELFOTHER = "gain",
PERIODICAURAHEALSELFSELF = "gain",
POWERGAINOTHEROTHER = "gain",
POWERGAINOTHERSELF = "gain",
POWERGAINSELFSELF = "gain",
POWERGAINSELFOTHER = "gain",
PROCRESISTOTHEROTHER = "resist",
PROCRESISTOTHERSELF = "resist",
PROCRESISTSELFOTHER = "resist",
PROCRESISTSELFSELF = "resist",
SIMPLECASTOTHEROTHER = "cast",
SIMPLECASTOTHERSELF = "cast",
SIMPLECASTSELFOTHER = "cast",
SIMPLECASTSELFSELF = "cast",
SIMPLEPERFORMOTHEROTHER = "perform",
SIMPLEPERFORMOTHERSELF = "perform",
SIMPLEPERFORMSELFOTHER = "perform",
SIMPLEPERFORMSELFSELF = "perform",
SPELLBLOCKEDOTHEROTHER = "block",
SPELLBLOCKEDOTHERSELF = "block",
SPELLBLOCKEDSELFOTHER = "block",
SPELLBLOCKEDSELFSELF = "block",
SPELLCASTOTHERSTART = "begin",
SPELLCASTSELFSTART = "begin",
SPELLDEFLECTEDOTHEROTHER = "deflect",
SPELLDEFLECTEDOTHERSELF = "deflect",
SPELLDEFLECTEDSELFOTHER = "deflect",
SPELLDEFLECTEDSELFSELF = "deflect",
SPELLDODGEDOTHEROTHER = "dodge",
SPELLDODGEDOTHERSELF = "dodge",
SPELLDODGEDSELFOTHER = "dodge",
SPELLEVADEDOTHEROTHER = "evade",
SPELLEVADEDOTHERSELF = "evade",
SPELLEVADEDSELFOTHER = "evade",
SPELLEVADEDSELFSELF = "evade",
SPELLEXTRAATTACKSOTHER = "extra",
SPELLEXTRAATTACKSOTHER_SINGULAR = "extra",
SPELLEXTRAATTACKSSELF = "extra",
SPELLEXTRAATTACKSSELF_SINGULAR = "extra",
SPELLFAILCASTSELF = "fail",
SPELLFAILPERFORMSELF = "fail",
SPELLIMMUNEOTHEROTHER = "immune",
SPELLIMMUNEOTHERSELF = "immune",
SPELLIMMUNESELFOTHER = "immune",
SPELLIMMUNESELFSELF = "immune",
SPELLINTERRUPTOTHEROTHER = "interrupt",
SPELLINTERRUPTOTHERSELF = "interrupt",
SPELLINTERRUPTSELFOTHER = "interrupt",
SPELLLOGABSORBOTHEROTHER = "absorb",
SPELLLOGABSORBOTHERSELF = "absorb",
SPELLLOGABSORBSELFOTHER = "absorb",
SPELLLOGABSORBSELFSELF = "absorb",
SPELLLOGCRITOTHEROTHER = "crit",
SPELLLOGCRITOTHERSELF = "crit",
SPELLLOGCRITSCHOOLOTHEROTHER = "crit",
SPELLLOGCRITSCHOOLOTHERSELF = "crit",
SPELLLOGCRITSCHOOLSELFOTHER = "crit",
SPELLLOGCRITSCHOOLSELFSELF = "crit",
SPELLLOGCRITSELFOTHER = "crit",
SPELLLOGOTHEROTHER = "hit",
SPELLLOGOTHERSELF = "hit",
SPELLLOGOTHERSELF = "hit",
SPELLLOGSCHOOLOTHEROTHER = "hit",
SPELLLOGSCHOOLOTHERSELF = "hit",
SPELLLOGSCHOOLSELFOTHER = "hit",
SPELLLOGSCHOOLSELFSELF = "hit",
SPELLLOGSELFOTHER = "hit",
SPELLMISSOTHEROTHER = "miss",
SPELLMISSOTHERSELF = "miss",
SPELLMISSSELFOTHER = "miss",
SPELLPARRIEDOTHEROTHER = "parr",
SPELLPARRIEDOTHERSELF = "parr",
SPELLPARRIEDSELFOTHER = "parr",
SPELLPERFORMOTHERSTART = "begin",
SPELLPERFORMSELFSTART = "begin",
SPELLPOWERDRAINOTHEROTHER = "drain",
SPELLPOWERDRAINOTHERSELF = "drain",
SPELLPOWERDRAINSELFOTHER = "drain",
SPELLPOWERLEECHOTHEROTHER = "drain",
SPELLPOWERLEECHOTHERSELF = "drain",
SPELLPOWERLEECHSELFOTHER = "drain",
SPELLREFLECTOTHEROTHER = "reflect",
SPELLREFLECTOTHERSELF = "reflect",
SPELLREFLECTSELFOTHER = "reflect",
SPELLREFLECTSELFSELF = "reflect",
SPELLRESISTOTHEROTHER = "resist",
SPELLRESISTOTHERSELF = "resist",
SPELLRESISTSELFOTHER = "resist",
SPELLRESISTSELFSELF = "resist",
SPELLSPLITDAMAGESELFOTHER = "cause",
SPELLSPLITDAMAGEOTHEROTHER = "cause",
SPELLSPLITDAMAGEOTHERSELF = "cause",
SPELLTERSEPERFORM_OTHER = "perform",
SPELLTERSEPERFORM_SELF = "perform",
SPELLTERSE_OTHER = "cast",
SPELLTERSE_SELF = "cast",
VSABSORBOTHEROTHER = "absorb",
VSABSORBOTHERSELF = "absorb",
VSABSORBSELFOTHER = "absorb",
VSBLOCKOTHEROTHER = "block",
VSBLOCKOTHERSELF = "block",
VSBLOCKSELFOTHER = "block",
VSBLOCKSELFSELF = "block",
VSDEFLECTOTHEROTHER = "deflect",
VSDEFLECTOTHERSELF = "deflect",
VSDEFLECTSELFOTHER = "deflect",
VSDEFLECTSELFSELF = "deflect",
VSDODGEOTHEROTHER = "dodge",
VSDODGEOTHERSELF = "dodge",
VSDODGESELFOTHER = "dodge",
VSDODGESELFSELF = "dodge",
VSENVIRONMENTALDAMAGE_FALLING_OTHER = "fall",
VSENVIRONMENTALDAMAGE_FALLING_SELF = "fall",
VSENVIRONMENTALDAMAGE_FIRE_OTHER = "fire",
VSENVIRONMENTALDAMAGE_FIRE_SELF = "fire",
VSENVIRONMENTALDAMAGE_LAVA_OTHER = "lava",
VSENVIRONMENTALDAMAGE_LAVA_SELF = "lava",
VSEVADEOTHEROTHER = "evade",
VSEVADEOTHERSELF = "evade",
VSEVADESELFOTHER = "evade",
VSEVADESELFSELF = "evade",
VSIMMUNEOTHEROTHER = "immune",
VSIMMUNEOTHERSELF = "immune",
VSIMMUNESELFOTHER = "immune",
VSPARRYOTHEROTHER = "parr",
VSPARRYOTHERSELF = "parr",
VSPARRYSELFOTHER = "parr",
VSRESISTOTHEROTHER = "resist",
VSRESISTOTHERSELF = "resist",
VSRESISTSELFOTHER = "resist",
VSRESISTSELFSELF = "resist",
VSENVIRONMENTALDAMAGE_FATIGUE_OTHER = "exhaust",
VSENVIRONMENTALDAMAGE_FIRE_OTHER = "fire",
VSENVIRONMENTALDAMAGE_SLIME_OTHER = "slime",
VSENVIRONMENTALDAMAGE_SLIME_SELF = "slime",
VSENVIRONMENTALDAMAGE_DROWNING_OTHER = "drown",
UNITDIESSELF = "die",
UNITDIESOTHER = "die",
UNITDESTROYEDOTHER = "destroy",
}
end
end
@@ -0,0 +1 @@
The newest documentation can be found at http://www.wowace.com/index.php/ParserLib
+17
View File
@@ -0,0 +1,17 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<Include file="libs\AceLibrary\AceLibrary.lua"/>
<Include file="libs\AceOO-2.0\AceOO-2.0.lua"/>
<Include file="libs\AceEvent-2.0\AceEvent-2.0.lua"/>
<Include file="libs\AceHook-2.1\AceHook-2.1.lua"/>
<Include file="libs\AceAddon-2.0\AceAddon-2.0.lua"/>
<Include file="libs\CandyBar-2.0\CandyBar-2.0.lua"/>
<Include file="libs\ParserLib\FixGlobalString.lua"/>
<Include file="libs\ParserLib\ParserLib.lua"/>
<Include file="libs\ParserLib\optimizer-enUS.lua"/>
<Include file="engine.lua"/>
<Include file="data-hunter.lua"/>
<Include file="module.lua"/>
</Ui>
+45
View File
@@ -0,0 +1,45 @@
local MTH_ChronometerModule = {
name = "chronometer",
enabled = true,
version = "1.0.3",
events = {},
initialized = false,
}
local function MTH_CHRON_ApplyRuntimeEnabledState(enabled)
if not MTH_ChronometerHunter then
return
end
if enabled then
if MTH_ChronometerHunter.MTH_Enable then
MTH_ChronometerHunter:MTH_Enable()
end
else
if MTH_ChronometerHunter.MTH_Disable then
MTH_ChronometerHunter:MTH_Disable()
end
end
end
function MTH_ChronometerModule:init()
if MTH_ChronometerHunter and MTH_ChronometerHunter.MTH_Initialize then
MTH_ChronometerHunter:MTH_Initialize()
end
MTH_CHRON_ApplyRuntimeEnabledState(self.enabled and true or false)
self.initialized = true
end
function MTH_ChronometerModule:setEnabled(enabled)
if not self.initialized then
self:init()
end
MTH_CHRON_ApplyRuntimeEnabledState(enabled and true or false)
end
function MTH_ChronometerModule:cleanup()
if MTH_ChronometerHunter and MTH_ChronometerHunter.MTH_Disable then
MTH_ChronometerHunter:MTH_Disable()
end
end
MTH:RegisterModule("chronometer", MTH_ChronometerModule)
+5
View File
@@ -0,0 +1,5 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<Binding name="FEEDOMATIC" header="FEEDOMATIC">
FOM_Feed();
</Binding>
</Ui>
File diff suppressed because it is too large Load Diff
+86
View File
@@ -0,0 +1,86 @@
FOM_Emotes = {
["any"] = {
"Yum!",
"Mmm, good stuff.",
},
["male"] = {
"Good boy!",
},
["female"] = {
"Good girl!",
},
["Zesty Clam Meat"] = {
"Mmm, zesty!",
},
["Heaven Peach"] = {
"Movin' to the country...",
},
["Mystery Meat"] = {
"Tastes like chicken.",
},
["Dragonbreath Chili"] = {
"Yow, spicy!",
},
["Bat"] = {
"Squeak-squeak snack attack!",
},
["Bear"] = {
"One paw for me, one paw for the bear.",
},
["Boar"] = {
"Good piggy!"
},
["Carrion Bird"] = {
"A gourmet meal... by scavenger standards.",
},
["Cat"] = {
"Nice kitty!",
},
["Crab"] = {
"Crabby no more!",
},
["Crocolisk"] = {
"Easy there, chompy friend.",
},
["Gorilla"] = {
"Big buddy deserves a big bite.",
},
["Hyena"] = {
"Good dog!",
},
["Owl"] = {
"Whoo's a hungry hunter helper?",
},
["Raptor"] = {
"Tiny arms, huge appetite.",
},
["Scorpid"] = {
"No stinging the cook, deal?",
},
["Spider"] = {
"Eight legs, zero table manners.",
},
["Tallstrider"] = {
"Long neck, longer lunch break.",
},
["Turtle"] = {
"Slow and steady wins the snacks.",
},
["Wind Serpent"] = {
"Lightning in the sky, snacks on the ground.",
},
["Wolf"] = {
"Good dog!",
},
["Foxes"] = {
"Clever fox, clever snack choice.",
},
["Serpents (Cobra)"] = {
"No hissing with your mouth full.",
},
};
+238
View File
@@ -0,0 +1,238 @@
FOM_Foods = {
[FOM_DIET_FUNGUS] = {
4607, -- Delicious Cave Mold
8948, -- Dried King Bolete
4604, -- Forest Mushroom Cap
4608, -- Raw Black Truffle
4605, -- Red-speckled Mushroom
3448, -- Senggin Root
4606, -- Spongy Morel
},
[FOM_DIET_FISH] = {
13889, -- Raw Whitescale Salmon
19996, -- Harvest Fish
13933, -- Lobster Stew
13888, -- Darkclaw Lobster
13935, -- Baked Salmon
13546, -- Bloodbelly Fish
6290, -- Brilliant Smallfish
4593, -- Bristle Whisker Catfish
5503, -- Clam Meat
16971, -- Clamlette Surprise
2682, -- Cooked Crab Claw
13927, -- Cooked Glossy Mightfish
2675, -- Crawler Claw
13930, -- Filet of Redgill
5476, -- Fillet of Frenzy
4655, -- Giant Clam Meat
6038, -- Giant Clam Scorcho
13928, -- Grilled Squid
13929, -- Hot Smoked Bass
13893, -- Large Raw Mightfish
4592, -- Longjaw Mud Snapper
8364, -- Mithril Head Trout
13932, -- Poached Sunscale Salmon
5095, -- Rainbow Fin Albacore
6291, -- Raw Brilliant Smallfish
6308, -- Raw Bristle Whisker Catfish
13754, -- Raw Glossy Mightfish
6317, -- Raw Loch Frenzy
6289, -- Raw Longjaw Mud Snapper
8365, -- Raw Mithril Head Trout
6361, -- Raw Rainbow Fin Albacore
13758, -- Raw Redgill
6362, -- Raw Rockscale Cod
6303, -- Raw Slitherskin Mackerel
8959, -- Raw Spinefin Halibut
4603, -- Raw Spotted Yellowtail
13756, -- Raw Summer Bass
13760, -- Raw Sunscale Salmon
4594, -- Rockscale Cod
787, -- Slitherskin Mackerel
5468, -- Soft Frenzy Flesh
15924, -- Soft-shelled Clam Meat
12216, -- Spiced Chili Crab
8957, -- Spinefin Halibut
6887, -- Spotted Yellowtail
5504, -- Tangy Clam Meat
12206, -- Tender Crab Meat
13755, -- Winter Squid
7974, -- Zesty Clam Meat
},
[FOM_DIET_MEAT] = {
21235, -- Winter Veil Roast
19995, -- Harvest Boar
4457, -- Barbecued Buzzard Wing
3173, -- Bear Meat
2888, -- Beer Basted Boar Ribs
3730, -- Big Bear Meat
3726, -- Big Bear Steak
3220, -- Blood Sausage
2677, -- Boar Ribs
3404, -- Buzzard Wing
12213, -- Carrion Surprise
2679, -- Charred Wolf Meat
769, -- Chunk of Boar Meat
2673, -- Coyote Meat
2684, -- Coyote Steak
1081, -- Crisp Spider Meat
12224, -- Crispy Bat Wing
5479, -- Crispy Lizard Tail
2924, -- Crocolisk Meat
3662, -- Crocolisk Steak
4599, -- Cured Ham Steak
17119, -- Deeprun Rat Kabob
5478, -- Dig Rat Stew
5051, -- Dig Rat
12217, -- Dragonbreath Chili
2687, -- Dry Pork Ribs
9681, -- Grilled King Crawler Legs
2287, -- Haunch of Meat
12204, -- Heavy Kodo Meat
3727, -- Hot Lion Chops
13851, -- Hot Wolf Ribs
12212, -- Jungle Stew
5472, -- Kaldorei Spider Kabob
5467, -- Kodo Meat
5480, -- Lean Venison
1015, -- Lean Wolf Flank
12209, -- Lean Wolf Steak
3731, -- Lion Meat
12223, -- Meaty Bat Wing
3770, -- Mutton Chop
12037, -- Mystery Meat
4739, -- Plainstrider Meat
12184, -- Raptor Flesh
13759, -- Raw Nightfin Snapper
12203, -- Red Wolf Meat
12210, -- Roast Raptor
2681, -- Roasted Boar Meat
5474, -- Roasted Kodo Meat
8952, -- Roasted Quail
1017, -- Seasoned Wolf Kabob
5465, -- Small Spider Leg
6890, -- Smoked Bear Meat
3729, -- Soothing Turtle Bisque
2680, -- Spiced Wolf Meat
17222, -- Spider Sausage
5471, -- Stag Meat
5469, -- Strider Meat
5477, -- Strider Stew
2672, -- Stringy Wolf Meat
2685, -- Succulent Pork Ribs
3728, -- Tasty Lion Steak
3667, -- Tender Crocolisk Meat
12208, -- Tender Wolf Meat
18045, -- Tender Wolf Steak
5470, -- Thunder Lizard Tail
12202, -- Tiger Meat
117, -- Tough Jerky
3712, -- Turtle Meat
12205, -- White Spider Meat
3771, -- Wild Hog Shank
},
[FOM_DIET_BREAD] = {
1113, -- Conjured Bread
5349, -- Conjured Muffin
1487, -- Conjured Pumpernickel
1114, -- Conjured Rye
8075, -- Conjured Sourdough
8076, -- Conjured Sweet Roll
22895, -- Conjured Cinnamon Roll
83004, -- Conjured Mana Orange
19696, -- Harvest Bread
21254, -- Winter Veil Cookie
2683, -- Crab Cake
4541, -- Freshly Baked Bread
17197, -- Gingerbread Cookie
3666, -- Gooey Spider Cake
8950, -- Homemade Cherry Pie
4542, -- Moist Cornbread
4544, -- Mulgore Spice Bread
4601, -- Soft Banana Bread
4540, -- Tough Hunk of Bread
16169, -- Wild Ricecake
},
[FOM_DIET_CHEESE] = {
8932, -- Alterac Swiss
414, -- Dalaran Sharp
2070, -- Darnassian Bleu
422, -- Dwarven Mild
3927, -- Fine Aged Cheddar
1707, -- Stormwind Brie
},
[FOM_DIET_FRUIT] = {
19994, -- Harvest Fruit
8953, -- Deep Fried Plantains
4539, -- Goldenbark Apple
16168, -- Heaven Peach
4602, -- Moon Harvest Pumpkin
4536, -- Shiny Red Apple
4538, -- Snapvine Watermelon
4537, -- Tel'Abim Banana
11950, -- Windblossom Berries
},
[FOM_DIET_BONUS] = {
-- eat for 10 sec to get "well fed" sta/spi bonus
2888, -- Beer Basted Boar Ribs
11584, -- Cactus Apple Surprise
7807, -- Candy Bar
7808, -- Chocolate Square
12224, -- Crispy Bat Wing
17197, -- Gingerbread Cookie
6888, -- Herb Baked Egg
5472, -- Kaldorei Spider Kabob
7806, -- Lollipop
5474, -- Roasted Kodo Meat
2680, -- Spiced Wolf Meat
3220, -- Blood Sausage
5525, -- Boiled Clams
2684, -- Coyote Steak
2683, -- Crab Cake
3662, -- Crocolisk Steak
2687, -- Dry Pork Ribs
5476, -- Fillet of Frenzy
724, -- Goretusk Liver Pie
5477, -- Strider Stew
3726, -- Big Bear Steak
5479, -- Crispy Lizard Tail
3664, -- Crocolisk Gumbo
3665, -- Curiously Tasty Omelet
5527, -- Goblin Deviled Clams
3666, -- Gooey Spider Cake
3727, -- Hot Lion Chops
5480, -- Lean Venison
12209, -- Lean Wolf Steak
3663, -- Murloc Fin Soup
1082, -- Redridge Goulash
1017, -- Seasoned Wolf Kabob
4457, -- Barbecued Buzzard Wing
12213, -- Carrion Surprise
6038, -- Giant Clam Scorcho
13851, -- Hot Wolf Ribs
12212, -- Jungle Stew
12214, -- Mystery Stew
12210, -- Roast Raptor
3729, -- Soothing Turtle Bisque
3728, -- Tasty Lion Steak
16971, -- Clamlette Surprise
12215, -- Heavy Kodo Stew
12218, -- Monster Omelet
12216, -- Spiced Chili Crab
17222, -- Spider Sausage
18045, -- Tender Wolf Steak
-- restores both health and mana
2682, -- Cooked Crab Claw
3448, -- Senggin Root
-- bonus effect with no 10 sec requirement
12217, -- Dragonbreath Chili
13927, -- Cooked Glossy Mightfish
13928, -- Grilled Squid
13929, -- Hot Smoked Bass
13932, -- Poached Sunscale Salmon
13931, -- Nightfin Soup
13934, -- Mightfish Steak
11950, -- Windblossom Berries
18254, -- Runn Tum Tuber Surprise
},
};
+174
View File
@@ -0,0 +1,174 @@
------------------------------------------------------
-- GFWTable.lua
-- Utilities for manipulating tables
------------------------------------------------------
GFWTABLE_THIS_VERSION = 6;
------------------------------------------------------
-- Mean: returns the mean value of a table of numbers
function GFWTable_temp_Mean(aTable)
if (aTable == nil or table.getn(aTable) == 0) then
return nil;
end
return GFWTable.Sum(aTable) / table.getn(aTable);
end
-- Sum: returns the sum of a table of numbers
function GFWTable_temp_Sum(aTable)
if (aTable == nil or table.getn(aTable) == 0) then
return nil;
end
local sum = 0;
for i=1, table.getn(aTable) do
if (tonumber(aTable[i])) then
sum = sum + aTable[i];
end
end
return sum;
end
-- Median: returns the median value (most useful in a table of numbers, but usable in any sorted table)
function GFWTable_temp_Median(aTable)
if (aTable == nil or table.getn(aTable) == 0) then
return nil;
end
if (table.getn(aTable) == 1) then
return aTable[1];
end
local sortedTable = GFWTable.Copy(aTable); -- leave the original table in whatever order it's in
table.sort(sortedTable);
local count = table.getn(sortedTable);
local median;
if (math.mod(count, 2) == 0) then -- even table size
local middleIndex1 = count / 2;
local middleIndex2 = middleIndex1 + 1;
median = (sortedTable[middleIndex1] + sortedTable[middleIndex2]) / 2; --average the two middle values
else -- the table size is odd
local trueMiddleIndex = (count + 1) / 2; -- calculate the middle index
median = sortedTable[trueMiddleIndex];
end
return median;
end
-- Merge: returns the union of two tables (without repeated elements)
function GFWTable_temp_Merge(table1, table2)
local newTable = { };
if (table1) then
for i=1, table.getn(table1) do
table.insert(newTable, table1[i]);
end
end
if (table2) then
for i=1, table.getn(table2) do
if (GFWTable.IndexOf(newTable, table2[i]) == 0) then
table.insert(newTable, table2[i]);
end
end
end
return newTable;
end
-- Diff: returns the difference of two tables (those elements which occur in either but not both)
function GFWTable_temp_Diff(table1, table2)
local newTable = { };
if (table1 == nil) then
table1 = {};
end
if (table2 == nil) then
table2 = {};
end
for i=1, table.getn(table1) do
if (GFWTable.IndexOf(table2, table1[i]) == 0) then
table.insert(newTable, table1[i]);
end
end
for i=1, table.getn(table2) do
if (GFWTable.IndexOf(table1, table2[i]) == 0) then
table.insert(newTable, table2[i]);
end
end
return newTable;
end
-- Subtract: returns a table containing those items in table1 not present in table2
function GFWTable_temp_Subtract(table1, table2)
local newTable = { };
if (table1 == nil or table.getn(table1) == 0) then return newTable; end
if (table2 == nil or table.getn(table2) == 0) then return table1; end
for i=1, table.getn(table1) do
if (GFWTable.IndexOf(table2, table1[i]) == 0) then
table.insert(newTable, table1[i]);
end
end
return newTable;
end
-- IndexOf: returns the index (1-based) of an item in a table
function GFWTable_temp_IndexOf(aTable, item)
return GFWTable.KeyOf(aTable, item) or 0;
end
-- KeyOf: returns the key to an item in a table with numeric or non-numeric keys, or nil if not found
function GFWTable_temp_KeyOf(aTable, item)
if (aTable == nil or type(aTable) ~= "table") then
return nil; -- caller probably won't expect this, causing traceable error in their code
end
for key, value in pairs(aTable) do
if (item == value) then
return key;
end
end
return nil;
end
-- Copy: copies one table's elements into a new table (useful if you want to change them while preserving the first table).
-- Not a deep copy.
function GFWTable_temp_Copy(aTable)
local newTable = { };
for i=1, table.getn(aTable) do
newTable[i] = aTable[i];
end
return newTable;
end
-- Count: returns number of items in a table regardless of whether it has numeric indices.
function GFWTable_temp_Count(aTable)
if (aTable == nil or type(aTable) ~= "table") then
return nil; -- caller probably won't expect this, causing traceable error in their code
end
local count = 0;
for key, value in pairs(aTable) do
count = count + 1;
end
return count;
end
------------------------------------------------------
-- load only if not already loaded
------------------------------------------------------
if (GFWTable == nil) then
GFWTable = {};
end
local G = GFWTable;
if (G.Version == nil or (tonumber(G.Version) ~= nil and G.Version < GFWTABLE_THIS_VERSION)) then
-- Functions
G.Mean = GFWTable_temp_Mean;
G.Sum = GFWTable_temp_Sum;
G.Median = GFWTable_temp_Median;
G.Merge = GFWTable_temp_Merge;
G.Diff = GFWTable_temp_Diff;
G.Subtract = GFWTable_temp_Subtract;
G.IndexOf = GFWTable_temp_IndexOf;
G.KeyOf = GFWTable_temp_KeyOf;
G.Copy = GFWTable_temp_Copy;
G.Count = GFWTable_temp_Count;
-- Set version number
G.Version = GFWTABLE_THIS_VERSION;
end
+202
View File
@@ -0,0 +1,202 @@
------------------------------------------------------
-- GFWUtils.lua
-- Useful utility / debug functions
------------------------------------------------------
GFWUTILS_THIS_VERSION = 8;
------------------------------------------------------
-- Shortcuts for common text color codes
function GFWUtils_temp_HiliteText(text)
if (text == nil) then return nil; end
return HIGHLIGHT_FONT_COLOR_CODE..text..FONT_COLOR_CODE_CLOSE;
end
function GFWUtils_temp_RedText(text)
if (text == nil) then return nil; end
return RED_FONT_COLOR_CODE..text..FONT_COLOR_CODE_CLOSE;
end
function GFWUtils_temp_GrayText(text)
if (text == nil) then return nil; end
return GRAY_FONT_COLOR_CODE..text..FONT_COLOR_CODE_CLOSE;
end
function GFWUtils_temp_LightYellowText(text)
if (text == nil) then return nil; end
return LIGHTYELLOW_FONT_COLOR_CODE..text..FONT_COLOR_CODE_CLOSE;
end
-- Prints a message to the chat frame
function GFWUtils_temp_Print(message, r, g, b)
if MTH and MTH.Print then
MTH:Print(message)
else
DEFAULT_CHAT_FRAME:AddMessage(message, (r or GFW_FONT_COLOR.r), (g or GFW_FONT_COLOR.g), (b or GFW_FONT_COLOR.b));
end
end
-- Prints a message to the chat frame once per interval (seconds) or once per session if interval is nil
function GFWUtils_temp_PrintOnce(message, interval, r, g, b)
if (GFWUtils.PrintOnceCache == nil) then
GFWUtils.PrintOnceCache = {};
end
if (interval == nil and GFWUtils.PrintOnceCache[message]) then
return;
end
if (interval and GFWUtils.PrintOnceCache[message] and GetTime() - GFWUtils.PrintOnceCache[message] < interval) then
return;
end
if MTH and MTH.Print then
MTH:Print(message)
else
DEFAULT_CHAT_FRAME:AddMessage(message, (r or GFW_FONT_COLOR.r), (g or GFW_FONT_COLOR.g), (b or GFW_FONT_COLOR.b));
end
GFWUtils.PrintOnceCache[message] = GetTime();
end
-- Prints a message to the chat frame only if Debug is set
function GFWUtils_temp_DebugLog(message)
if (GFWUtils.Debug) then
if MTH and MTH.Print then
MTH:Print(message, "debug")
else
DEFAULT_CHAT_FRAME:AddMessage(message, GFW_DEBUG_COLOR.r, GFW_DEBUG_COLOR.g, GFW_DEBUG_COLOR.b);
end
end
end
-- Prints a message in yellow to the floating messages frame
function GFWUtils_temp_Note(message)
UIErrorsFrame:AddMessage(message, 1.0, 1.0, 0.0, 1.0, UIERRORS_HOLD_TIME);
end
-- Converts string.format to a string.find pattern: "%s hits %s for %d." to "(.+) hits (.+) for (%d+)"
-- based on Recap by Gello
function GFWUtils_temp_FormatToPattern(formatString)
local patternString = formatString;
patternString = string.gsub(patternString, "%%%d+%$([diouXxfgbcsq])", "%%%1"); -- reordering specifiers (e.g. %2$s) stripped
patternString = string.gsub(patternString, "([%$%(%)%.%[%]%*%+%-%?%^])", "%%%1"); -- convert regex special characters
patternString = string.gsub(patternString, "%%c", "(.)"); -- %c to (.)
patternString = string.gsub(patternString, "%%s", "(.+)"); -- %s to (.+)
patternString = string.gsub(patternString, "%%[du]", "(%%d+)"); -- %d to (%d+)
patternString = string.gsub(patternString, "%%([gf])", "(%%d+%%.*%%d*)"); -- %g or %f to (%d+%.*%d*)
return patternString;
end
-- Splits a string into a table of strings separated by 'separator'.
-- e.g. Split(aString, ", ") is the reverse of table.concat(aTable, ", ")
function GFWUtils_temp_Split(aString, separator)
if (aString == nil) then return nil; end
local t = {};
local function helper(segment)
table.insert(t, segment);
end
helper((string.gsub(aString, "(.-)"..separator, helper)));
return t;
end
-- Capitalizes the first letter of each word in aString.
function GFWUtils_temp_TitleCase(aString)
if (aString == nil) then return nil; end
local function capWords(first, rest)
return string.upper(first)..string.lower(rest);
end
return string.gsub(aString, "(%w)([%w_']*)", capWords);
end
-- Splits a cash amount into gold, siver, and copper
function GFWUtils_temp_GSC(money)
if (money == nil) then money = 0; end
local g = math.floor(money / 10000);
local s = math.floor((money - (g*10000)) / 100);
local c = math.floor(money - (g*10000) - (s*100));
return g,s,c;
end
-- Formats money text by color for gold, silver, copper
-- discards copper for amounts greater than 1g unless second arg evaluates true
-- based on Auctioneer
function GFWUtils_temp_TextGSC(money, noRound)
local GSC_GOLD="ffd100";
local GSC_SILVER="e6e6e6";
local GSC_COPPER="c8602c";
local GSC_START="|cff%s%d%s|r";
local GSC_PART=" |cff%s%02d%s|r";
local GSC_NONE="|cffa0a0a0none|r";
local g, s, c = GFWUtils.GSC(money);
local gsc = "";
if (g > 0) then
gsc = format(GSC_START, GSC_GOLD, g, "g");
if (s > 0) then
gsc = gsc..format(GSC_PART, GSC_SILVER, s, "s");
if (noRound and c > 0) then
gsc = gsc..format(GSC_PART, GSC_COPPER, c, "c");
end
end
elseif (s > 0) then
gsc = format(GSC_START, GSC_SILVER, s, "s");
if (c > 0) then
gsc = gsc..format(GSC_PART, GSC_COPPER, c, "c");
end
elseif (c > 0) then
gsc = gsc..format(GSC_START, GSC_COPPER, c, "c");
else
gsc = GSC_NONE;
end
return gsc;
end
function GFWUtils_temp_ItemLink(linkInfo)
local sName, sLink, iQuality, iLevel, sType, sSubType, iCount = GetItemInfo(linkInfo);
if (sName) then
local _, _, _, color = GetItemQualityColor(iQuality);
local linkFormat = "%s|H%s|h[%s]|h|r";
return string.format(linkFormat, color, sLink, sName);
else
return nil;
end
end
------------------------------------------------------
-- load only if not already loaded
------------------------------------------------------
if (GFWUtils == nil) then
GFWUtils = {};
end
local G = GFWUtils;
if (G.Version == nil or (tonumber(G.Version) ~= nil and G.Version < GFWUTILS_THIS_VERSION)) then
-- Constants
GFW_FONT_COLOR = {r=0.25, g=1.0, b=1.0};
GFW_DEBUG_COLOR = {r=1.0, g=0.75, b=0.25};
G.Debug = false;
-- Functions
G.Hilite = GFWUtils_temp_HiliteText;
G.Red = GFWUtils_temp_RedText;
G.Gray = GFWUtils_temp_GrayText;
G.LtY = GFWUtils_temp_LightYellowText;
G.Print = GFWUtils_temp_Print;
G.PrintOnce = GFWUtils_temp_PrintOnce;
G.DebugLog = GFWUtils_temp_DebugLog;
G.Note = GFWUtils_temp_Note;
G.FormatToPattern = GFWUtils_temp_FormatToPattern;
G.Split = GFWUtils_temp_Split;
G.TitleCase = GFWUtils_temp_TitleCase;
G.GSC = GFWUtils_temp_GSC;
G.TextGSC = GFWUtils_temp_TextGSC;
G.ItemLink = GFWUtils_temp_ItemLink;
-- Set version number
G.Version = GFWUTILS_THIS_VERSION;
end
+12
View File
@@ -0,0 +1,12 @@
## Interface: 11200
## Version: 11200.2
## Title: Fizzwidget Feed-O-Matic
## Author: Gazmik Fizzwidget
## Notes: Helps a Hunter keep his pets well fed (and the food in his inventory under control)
## SavedVariables: FOM_Config, FOM_FoodQuality, FOM_AddedFoods, FOM_RemovedFoods, FOM_Cooking, FOM_QuestFood, FOM_LocaleInfo
GFWUtils.lua
GFWTable.lua
localization.lua
FeedOMatic_Emotes.lua
FeedOMatic_Foods.lua
FeedOMatic.lua
+5
View File
@@ -0,0 +1,5 @@
<Addon name="Fizzwidget Feed-O-Matic" author="Gazmik Fizzwidget" email="gazmik@fizzwidget.com" url="http://fizzwidget.com/huntershelper/">
<Description>Helps a Hunter keep his pets well fed (and the food in his inventory under control).</Description>
<CurrentVersion ver="11200.2" />
<UpdateFile url="http://fizzwidget.com/UpdateUI.xml" />
</Addon>
+18
View File
@@ -0,0 +1,18 @@
<!--
FeedOMatic Module Loader
Loads all FeedOMatic addon code and wraps it in the MTH module system
-->
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<!-- Load original FeedOMatic addon files -->
<Include file="GFWUtils.lua"/>
<Include file="GFWTable.lua"/>
<Include file="localization.lua"/>
<Include file="FeedOMatic_Emotes.lua"/>
<Include file="FeedOMatic_Foods.lua"/>
<Include file="FeedOMatic.lua"/>
<Include file="runtime.xml"/>
<Include file="UpdateUI.xml"/>
<!-- Load ModuleHunt wrapper -->
<Include file="module.lua"/>
</Ui>
+304
View File
@@ -0,0 +1,304 @@
-----------------------------------------------------
-- localization.lua
-- English strings by default, localizations override with their own.
------------------------------------------------------
-- Key bindings
BINDING_HEADER_FEEDOMATIC = "Fizzwidget Feed-O-Matic";
BINDING_NAME_FEEDOMATIC = "Feed Pet";
-- Diet names. These should be the all-lowercase versions of the six diets returned from GetPetFoodTypes(). (Want to get them all nice and quick for your localization? Go tame a bear... they eat anything.)
-- THESE STRINGS MUST BE LOCALIZED for Feed-O-Matic to work properly in other locales.
FOM_DIET_MEAT = "meat";
FOM_DIET_FISH = "fish";
FOM_DIET_BREAD = "bread";
FOM_DIET_CHEESE = "cheese";
FOM_DIET_FRUIT = "fruit";
FOM_DIET_FUNGUS = "fungus";
-- If the player has a buff whose icon name contains "Ability_Mount" and whose name contains one of these substrings, we assume the player is mounted. We have to use this rather oblique way to check for mounted because some other buffs use the Ability_Mount_* icons. If these aren't localized, we may not recognize when the player is mounted and still try to feed the pet.
FOM_MOUNT_NAME_SUBSTRINGS = { "kodo", "wolf", "raptor", "steed", "horse", "ram", "saber", "mechanostrider" };
-- It's not clear whether the 5th and 6th returns from GetItemInfo() are supposed to be localized... so we have localized keys for them just in case. If these aren't localized, we'll just needlessly search your ammo bags for food.
FOM_AMMO_POUCH = "Ammo Pouch";
FOM_QUIVER = "Quiver";
-- From here on down, the localized strings are just for readability... they don't affect whether Feed-O-Matic works.
-- Two special diets; one is used for tracking food that provides a bonus (or at least, that you don't want to have fed to your pet when the 'savebonus' option is on), and another that's used in the chat-line interface for listing all diets.
FOM_DIET_BONUS = "bonus";
FOM_DIET_ALL = "all";
-- Used in tooltips to indicate food quality.
FOM_QUALITY_UNKNOWN = "%s can eat this, but hasn't tried it yet.";
FOM_QUALITY_UNDER = "%s doesn't like this anymore.";
FOM_QUALITY_MIGHT = "%s might eat this.";
FOM_QUALITY_WILL = "%s will eat this.";
FOM_QUALITY_LIKE = "%s likes to eat this.";
FOM_QUALITY_LOVE = "%s loves to eat this.";
-- User-visible errors
FOM_ERROR_NO_PET = "You don't have a pet to feed.";
FOM_ERROR_PET_DEAD = "Your pet is dead.";
FOM_ERROR_NO_FEEDABLE_PET = "You don't have a feedable pet.";
FOM_ERROR_IN_COMBAT = "You can't feed your pet during combat.";
FOM_ERROR_FOOD_NOT_FOUND = "%s could not find %s in your pack.";
FOM_ERROR_NO_FOOD = "%s could not find any food in your pack.";
FOM_ERROR_NO_FOOD_NO_FALLBACK = "%s could not find any food in your pack that you haven't forbidden %s to eat.";
FOM_PRONOUN_MALE = "him";
FOM_PRONOUN_FEMALE = "her";
-- Feeding status messages
FOM_FEEDING_EAT_ANOTHER = "%s doesn't like that food and will try something else...";
FOM_FEEDING_FEED_ANOTHER = "needs to feed %s something else...";
FOM_FEEDING_EAT = "%s eats a %s from your pack.";
FOM_FEEDING_FEED = "feeds %s a %s. ";
FOM_PET_HUNGRY = "%s is hungry!";
FOM_PET_VERY_HUNGRY = "%s is very hungry!";
-- Beast family names
BAT = "Bat";
BEAR = "Bear";
BOAR = "Boar";
CARRION_BIRD = "Carrion Bird";
CAT = "Cat";
CRAB = "Crab";
CROCOLISK = "Crocolisk";
GORILLA = "Gorilla";
HYENA = "Hyena";
OWL = "Owl";
RAPTOR = "Raptor";
SCORPID = "Scorpid";
SPIDER = "Spider";
TALLSTRIDER = "Tallstrider";
TURTLE = "Turtle";
WIND_SERPENT = "Wind Serpent";
WOLF = "Wolf";
FOM_OPTIONS_HEADER = "Feed-O-Matic Options";
FOM_OPTIONS_GENERAL = "General options:";
FOM_OPTIONS_FOOD_CHOICE = "Avoid foods used in Cooking:";
FOM_OPTIONS_FEED_NOTIFY = "Notify when feeding:";
FOM_OPTIONS_WARNING = "Warn that your pet needs feeding:";
FOM_KEEPOPEN = "Try to keep at least";
FOM_KEEPOPEN_SLOTS = "slots open in your bags.";
FOM_KEEPOPEN_INFO = "(Will choose foods from smaller stacks when low on space, regardless of how well your pet likes them.)";
FOM_OptionsButtonText = {
["Tooltip"] = "Show your pet's opinion of foods in their tooltips",
["AvoidQuestFood"] = "Avoid foods needed for quests",
["AvoidBonusFood"] = "Avoid foods that have a bonus effect when eaten",
["PreferHigherQuality"] = "Prefer foods your pet likes more",
["Fallback"] = "Fall back to otherwise-avoided foods if out of others",
["SaveForCook_All"] = "all foods used in cooking",
["SaveForCook_Green"] = "only foods used in |cff3fbf3f\"easy\"|r or better recipes",
["SaveForCook_Yellow"] = "only foods used in |cffffff00\"medium\"|r or better recipes",
["SaveForCook_Orange"] = "only foods used in |cffff7f3f\"difficult\"|r recipes",
["SaveForCook_None"] = "Don't save foods used in cooking",
["IconWarning"] = "Flash icon",
["TextWarning"] = "Show text",
["AudioWarning"] = "Play sound",
["AudioWarningBell"] = "Generic bell sound instead of pet-specific",
["AlertEmote"] = "Via emote (seen by others)",
["AlertChat"] = "In the chat window (seen by you only)",
["AlertNone"] = "Don't notify",
["LevelContent"] = "when \"content\"",
["LevelUnhappy"] = "when \"unhappy\"",
["LevelOff"] = "Don't warn",
};
------------------------------------------------------
if (GetLocale() == "frFR") then
-- Diet names. These should be the all-lowercase versions of the six diets returned from GetPetFoodTypes(). (Want to get them all nice and quick for your localization? Go tame a bear... they eat anything.)
-- THESE STRINGS MUST BE LOCALIZED for Feed-O-Matic to work properly in other locales.
FOM_DIET_MEAT = "viande";
FOM_DIET_FISH = "poisson";
FOM_DIET_BREAD = "pain";
FOM_DIET_CHEESE = "fromage";
FOM_DIET_FRUIT = "fruit";
FOM_DIET_FUNGUS = "champignon";
-- If the player has a buff whose icon name contains "Ability_Mount" and whose name contains one of these substrings, we assume the player is mounted. We have to use this rather oblique way to check for mounted because some other buffs use the Ability_Mount_* icons. If these aren't localized, we may not recognize when the player is mounted and still try to feed the pet.
FOM_MOUNT_NAME_SUBSTRINGS = { "kodo", "loup", "raptor", "steed", "cheval", "bélier", "tigre", "mécanotrotteur", };
-- It's not clear whether the 5th and 6th returns from GetItemInfo() are supposed to be localized... so we have localized keys for them just in case. If these aren't localized, we'll just needlessly search your ammo bags for food.
FOM_AMMO_POUCH = "Giberne";
FOM_QUIVER = "Carquois";
-- From here on down, the localized strings are just for readability... they don't affect whether Feed-O-Matic works.
-- Two special diets; one is used for tracking food that provides a bonus (or at least, that you don't want to have fed to your pet when the 'savebonus' option is on), and another that's used in the chat-line interface for listing all diets.
FOM_DIET_BONUS = "bonus";
FOM_DIET_ALL = "tous";
-- Used in tooltips to indicate food quality.
FOM_QUALITY_UNKNOWN = "%s peut le manger, mais n'a jamais essayé.";
FOM_QUALITY_UNDER = "%s n'en mange plus désormais.";
FOM_QUALITY_MIGHT = "%s en mangera peut-être.";
FOM_QUALITY_WILL = "%s en mangera.";
FOM_QUALITY_LIKE = "%s aime en manger.";
FOM_QUALITY_LOVE = "%s adore en manger.";
-- User-visible errors
FOM_ERROR_NO_PET = "Vous n'avez pas de familier.";
FOM_ERROR_PET_DEAD = "Votre familier est mort.";
FOM_ERROR_NO_FEEDABLE_PET = "Vous n'avez pas de familier capable de manger.";
FOM_ERROR_FOOD_NOT_FOUND = "%s n'a pas trouvé de %s dans votre sac.";
FOM_ERROR_NO_FOOD = "%s n'a pas trouvé de nourriture dans votre sac.";
-- Feeding status messages
FOM_FEEDING_EAT_ANOTHER = "%s n'aime pas cette nourriture et va essayer autre chose..."
FOM_FEEDING_FEED_ANOTHER = "cherche autre chose à donner à %s...";
FOM_FEEDING_EAT = "%s mange un(e) %s.";
FOM_FEEDING_FEED = "donne à %s à manger un(e) %s. ";
-- Beast family names
BAT = "Chauve-souris";
BEAR = "Ours";
BOAR = "Sanglier";
CARRION_BIRD = "Charognard";
CAT = "Félin";
CRAB = "Crabe";
CROCOLISK = "Crocilisque";
GORILLA = "Gorille";
HYENA = "Hyène";
OWL = "Chouette";
RAPTOR = "Raptor";
SCORPID = "Scorpide";
SPIDER = "Araignée";
TALLSTRIDER = "Haut-trotteur";
TURTLE = "Tortue";
WIND_SERPENT = "Serpent des vents";
WOLF = "Loup";
FOM_PET_HUNGRY = "%s a faim!";
FOM_PET_VERY_HUNGRY = "%s a très faim!";
FOM_OPTIONS_HEADER = "Feed-O-Matic Options";
FOM_OPTIONS_GENERAL = "Options générales:";
FOM_OPTIONS_FOOD_CHOICE = "Eviter la nourriture utilisée en cuisine:";
FOM_OPTIONS_FEED_NOTIFY = "Prévenir lorsque votre familier est nourrit:";
FOM_OPTIONS_WARNING = "Avertir quand votre familier a besoin d'être nourrit:";
FOM_KEEPOPEN = "Essayer de garder au moins";
FOM_KEEPOPEN_SLOTS = "emplacements de sac libres";
FOM_KEEPOPEN_INFO = "(Le familier choisira la nourriture des piles les plus petites, sans tenir compte de ses préférences.)";
FOM_OptionsButtonText = {
["Tooltip"] = "Afficher l'avis de votre familier concernant la nourriture",
["AvoidQuestFood"] = "Eviter la nourriture utile dans les quêtes",
["AvoidBonusFood"] = "Eviter la nourriture donnant un bonus",
["PreferHigherQuality"] = "Privilégier la nourriture que votre familier préfère",
["Fallback"] = "Utiliser la nourriture évitée s'il n'y en a plus d'autres",
["SaveForCook_All"] = "Toutes nourritures utilisées pour la cuisine",
["SaveForCook_Green"] = "si elle est utilisée dans les recettes |cff3fbf3f\"faciles\"|r ou supérieures",
["SaveForCook_Yellow"] = "si elle est utilisée dans les recettes |cffffff00\"moyennes\"|r ou supérieures",
["SaveForCook_Orange"] = "si elle est utilisée dans les recettes |cffff7f3f\"difficiles\"|r",
["SaveForCook_None"] = "Ne pas éviter la nourriture utilisée en cuisine",
["IconWarning"] = "Clignoter l'icône",
["TextWarning"] = "Afficher un texte",
["AudioWarning"] = "Jouer un son",
["AudioWarningBell"] = "Un son de cloche au lieu du cri du familier",
["AlertEmote"] = "Via emote (visible par les autres)",
["AlertChat"] = "Dans la fenêtre de discussion (visible uniquement par vous)",
["AlertNone"] = "Ne pas prévenir",
["LevelContent"] = "quand \"content\"",
["LevelUnhappy"] = "quand \"mécontent\"",
["LevelOff"] = "Ne pas avertir",
};
end
------------------------------------------------------
if (GetLocale() == "deDE") then
-- Diet names. These should be the all-lowercase versions of the six diets returned from GetPetFoodTypes(). (Want to get them all nice and quick for your localization? Go tame a bear... they eat anything.)
-- THESE STRINGS MUST BE LOCALIZED for Feed-O-Matic to work properly in other locales.
FOM_DIET_MEAT = "fleisch";
FOM_DIET_FISH = "fisch";
FOM_DIET_BREAD = "brot";
FOM_DIET_CHEESE = "käse";
FOM_DIET_FRUIT = "obst";
-- FOM_DIET_FUNGUS = "fungus"; -- same as English (?), so we don't need to re-define it.
-- If the player has a buff whose icon name contains "Ability_Mount" and whose name contains one of these substrings, we assume the player is mounted. We have to use this rather oblique way to check for mounted because some other buffs use the Ability_Mount_* icons. If these aren't localized, we may not recognize when the player is mounted and still try to feed the pet.
-- FOM_MOUNT_NAME_SUBSTRINGS = { "kodo", "wolf", "raptor", "steed", "horse", "ram", "saber", "mechanostrider" };
-- It's not clear whether the 5th and 6th returns from GetItemInfo() are supposed to be localized... so we have localized keys for them just in case.
FOM_AMMO_POUCH = "Munitionsbeutel";
FOM_QUIVER = "Köcher";
-- From here on down, the localized strings are just for readability; they don't affect whether Feed-O-Matic works.
-- Two special diets; one is used for tracking food that provides a bonus (or at least, that you don't want to have fed to your pet when the 'savebonus' option is on), and another that's used in the chat-line interface for listing all diets.
-- FOM_DIET_BONUS = "bonus"; -- same as English(?), so we don't need to re-define it.
FOM_DIET_ALL = "alles";
-- Used in tooltips to indicate food quality.
FOM_QUALITY_UNKNOWN = "%s kann das fressen, hat es aber noch nicht probiert.";
FOM_QUALITY_UNDER = "%s mag das nicht mehr fressen.";
FOM_QUALITY_MIGHT = "%s mag das möglicherweise fressen.";
FOM_QUALITY_WILL = "%s mag das fressen.";
FOM_QUALITY_LIKE = "%s frisst das gerne.";
FOM_QUALITY_LOVE = "%s liebt es, das zu fressen.";
-- User-visible errors
FOM_ERROR_NO_PET = "Du hast kein Tier um es zu füttern.";
FOM_ERROR_PET_DEAD = "Dein Tier ist tot.";
FOM_ERROR_NO_FEEDABLE_PET = "Du hast kein Tier das man füttern kann.";
FOM_ERROR_FOOD_NOT_FOUND = "%s kann %s nicht in Deinem Rucksack finden.";
FOM_ERROR_NO_FOOD = "%s findet nichts zu fressen in Deinem Rucksack.";
-- Feeding status messages
FOM_FEEDING_EAT_ANOTHER = "%s mag dieses Futter nicht und probiert etwas anderes...";
FOM_FEEDING_FEED_ANOTHER = "sucht nach etwas anderem um %s zu füttern...";
FOM_FEEDING_EAT = "%s frisst ein %s aus Deinem Rucksack.";
FOM_FEEDING_FEED = "füttert %s ein %s. ";
-- Beast family names
BAT = "Fledermaus";
BEAR = "Bär";
BOAR = "Eber";
CARRION_BIRD = "Aasvogel";
CAT = "Katze";
CRAB = "Krebs";
CROCOLISK = "Krokilisk";
-- GORILLA = "Gorilla"; -- same as enUS
HYENA = "Hyäne";
OWL = "Eule";
-- RAPTOR = "Raptor"; -- same as enUS
SCORPID = "Skorpid";
SPIDER = "Spinne";
TALLSTRIDER = "Weitschreiter";
TURTLE = "Schildkröte";
WIND_SERPENT = "Windnatter";
-- WOLF = "Wolf"; -- same as enUS
end
------------------------------------------------------
if (GetLocale() == "koKR") then
-- Diet names. These should be the all-lowercase versions of the six diets returned from GetPetFoodTypes(). (Want to get them all nice and quick for your localization? Go tame a bear... they eat anything.)
-- THESE STRINGS MUST BE LOCALIZED for Feed-O-Matic to work properly in other locales.
-- FOM_DIET_MEAT = "meat";
-- FOM_DIET_FISH = "fish";
-- FOM_DIET_BREAD = "bread";
-- FOM_DIET_CHEESE = "cheese";
-- FOM_DIET_FRUIT = "fruit";
-- FOM_DIET_FUNGUS = "fungus";
-- If the player has a buff whose icon name contains "Ability_Mount" and whose name contains one of these substrings, we assume the player is mounted. We have to use this rather oblique way to check for mounted because some other buffs use the Ability_Mount_* icons.
-- FOM_MOUNT_NAME_SUBSTRINGS = { "kodo", "wolf", "raptor", "steed", "horse", "ram", "saber", "mechanostrider" };
-- It's not clear whether the 5th and 6th returns from GetItemInfo() are supposed to be localized... so we have localized keys for them just in case.
-- FOM_AMMO_POUCH = "Ammo Pouch";
FOM_QUIVER = "화살통";
end
+340
View File
@@ -0,0 +1,340 @@
------------------------------------------------------
-- MetaHunt: FeedOMatic Module Wrapper
-- Modern module abstraction for the FeedOMatic addon
------------------------------------------------------
local MTH_FeedOMatic = {
name = "feedomatic",
enabled = false,
version = "1.0.3",
events = {
"VARIABLES_LOADED",
"MERCHANT_SHOW",
"MERCHANT_UPDATE",
"MTH_PET_LIVE_STATE_CHANGED",
"PET_ATTACK_START",
"PET_ATTACK_STOP",
"PET_STABLE_SHOW",
"PET_STABLE_UPDATE",
"TRADE_SKILL_SHOW",
"TRADE_SKILL_UPDATE",
"CHAT_MSG_SPELL_TRADESKILLS",
"CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS",
"UI_ERROR_MESSAGE",
"PET_BAR_SHOWGRID",
"PET_BAR_UPDATE",
"PET_UI_UPDATE",
"UNIT_HAPPINESS",
"PLAYER_REGEN_ENABLED",
},
initialized = false,
variablesLoadedProcessed = false,
}
local MTH_PETL_HOOK_BOUNDARY_KEY = "pet-lifecycle-hooks"
local MTH_FeedOMatic_MerchantProbeFrame = nil
function MTH_FeedOMatic_OnUpdate(elapsed)
if not elapsed then
return
end
if not (MTH and MTH.IsModuleEnabled and MTH:IsModuleEnabled("feedomatic", true)) then
return
end
if type(FOM_OnUpdate) == "function" then
FOM_OnUpdate(elapsed)
end
end
local function MTH_FeedOMatic_GetRuntimeFrame()
return getglobal("Frame_GFW_FeedOMatic")
end
local function MTH_FeedOMatic_CaptureHookBoundary()
if not (MTH and MTH.CaptureHookBoundary) then
return
end
MTH:CaptureHookBoundary(MTH_PETL_HOOK_BOUNDARY_KEY, {
{ globalName = "PetAbandon", originalName = "MTH_PETL_Original_PetAbandon" },
})
end
local function MTH_FeedOMatic_RestoreHookBoundary()
if not (MTH and MTH.RestoreHookBoundary) then
return
end
MTH:RestoreHookBoundary(MTH_PETL_HOOK_BOUNDARY_KEY)
end
local function MTH_FeedOMatic_ApplyLegacyHooks()
local canTouchPetAbandon = true
if MTH and MTH.GetGlobalHookOwner then
local abandonOwner = MTH:GetGlobalHookOwner("PetAbandon")
if abandonOwner and abandonOwner ~= MTH_PETL_HOOK_BOUNDARY_KEY then
canTouchPetAbandon = false
end
end
local abandonHook = MTH_PetAbandonHook or FOM_PetAbandon
local abandonOriginal = MTH_PETL_Original_PetAbandon or FOM_Original_PetAbandon
if type(abandonHook) == "function" and type(abandonOriginal) == "function" then
if canTouchPetAbandon and (PetAbandon == abandonOriginal or PetAbandon == abandonHook) then
PetAbandon = abandonHook
end
end
end
local function MTH_FeedOMatic_CallLegacyOnEvent(evt, a1, a2, a3, a4, a5, a6, a7, a8, a9)
if type(FOM_OnEvent) ~= "function" then
return
end
local frame = MTH_FeedOMatic_GetRuntimeFrame()
if not frame then
return
end
local oldThis = this
local oldEvent = event
local oldArg1, oldArg2, oldArg3, oldArg4, oldArg5 = arg1, arg2, arg3, arg4, arg5
local oldArg6, oldArg7, oldArg8, oldArg9 = arg6, arg7, arg8, arg9
this = frame
event = evt
arg1, arg2, arg3, arg4, arg5 = a1, a2, a3, a4, a5
arg6, arg7, arg8, arg9 = a6, a7, a8, a9
local ok = pcall(function()
FOM_OnEvent(evt, a1)
end)
this = oldThis
event = oldEvent
arg1, arg2, arg3, arg4, arg5 = oldArg1, oldArg2, oldArg3, oldArg4, oldArg5
arg6, arg7, arg8, arg9 = oldArg6, oldArg7, oldArg8, oldArg9
if not ok and MTH and MTH.DebugPrint then
MTH:DebugPrint("FeedOMatic legacy OnEvent call failed for event: " .. tostring(evt))
end
end
local function MTH_FeedOMatic_EnsureVariablesLoaded(module)
if not module or module.variablesLoadedProcessed then
return
end
MTH_FeedOMatic_CallLegacyOnEvent("VARIABLES_LOADED")
module.variablesLoadedProcessed = true
end
local function MTH_FeedOMatic_SyncSavedVariables()
if not MTH or not MTH.GetModuleSavedVariables then
return
end
local moduleStore = MTH:GetModuleSavedVariables("feedomatic")
if not moduleStore.legacy then
moduleStore.legacy = {}
end
if type(FOM_Config) == "table" then
moduleStore.legacy.FOM_Config = FOM_Config
elseif type(moduleStore.legacy.FOM_Config) == "table" then
FOM_Config = moduleStore.legacy.FOM_Config
end
if type(FOM_FoodQuality) == "table" then
moduleStore.legacy.FOM_FoodQuality = FOM_FoodQuality
elseif type(moduleStore.legacy.FOM_FoodQuality) == "table" then
FOM_FoodQuality = moduleStore.legacy.FOM_FoodQuality
end
if type(FOM_AddedFoods) == "table" then
moduleStore.legacy.FOM_AddedFoods = FOM_AddedFoods
elseif type(moduleStore.legacy.FOM_AddedFoods) == "table" then
FOM_AddedFoods = moduleStore.legacy.FOM_AddedFoods
end
if type(FOM_RemovedFoods) == "table" then
moduleStore.legacy.FOM_RemovedFoods = FOM_RemovedFoods
elseif type(moduleStore.legacy.FOM_RemovedFoods) == "table" then
FOM_RemovedFoods = moduleStore.legacy.FOM_RemovedFoods
end
if type(FOM_Cooking) == "table" then
moduleStore.legacy.FOM_Cooking = FOM_Cooking
elseif type(moduleStore.legacy.FOM_Cooking) == "table" then
FOM_Cooking = moduleStore.legacy.FOM_Cooking
end
if type(FOM_QuestFood) == "table" then
moduleStore.legacy.FOM_QuestFood = FOM_QuestFood
elseif type(moduleStore.legacy.FOM_QuestFood) == "table" then
FOM_QuestFood = moduleStore.legacy.FOM_QuestFood
end
if type(FOM_LocaleInfo) == "table" then
moduleStore.legacy.FOM_LocaleInfo = FOM_LocaleInfo
elseif type(moduleStore.legacy.FOM_LocaleInfo) == "table" then
FOM_LocaleInfo = moduleStore.legacy.FOM_LocaleInfo
end
end
local function MTH_FeedOMatic_Log(message, severity)
if type(MTH_Log) == "function" then
local logSeverity = severity or "debug"
MTH_Log("[FeedOMatic] " .. tostring(message or ""), logSeverity)
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
MTH_FeedOMatic_Log("diet " .. tostring(dietName) .. ": " .. tostring(dietCount) .. " item(s)", "debug")
if type(rows) == "table" then
for _, row in rows do
MTH_FeedOMatic_Log(" - [" .. tostring(row.id) .. "] " .. tostring(row.name) .. " (slot=" .. tostring(row.index) .. ")", "debug")
end
end
end
end
if unknownCount > 0 and type(merchantFoods.unknown) == "table" then
for _, row in merchantFoods.unknown do
MTH_FeedOMatic_Log("unknown map: [" .. tostring(row.id) .. "] " .. tostring(row.name) .. " (slot=" .. tostring(row.index) .. ")", "debug")
end
end
end
local function MTH_FeedOMatic_SetMerchantProbeEnabled(enabled)
if not MTH_FeedOMatic_MerchantProbeFrame then
MTH_FeedOMatic_MerchantProbeFrame = CreateFrame("Frame", "MTH_FeedOMatic_MerchantProbeFrame")
MTH_FeedOMatic_MerchantProbeFrame:SetScript("OnEvent", function()
local evt = event
if evt == "MERCHANT_SHOW" or evt == "MERCHANT_UPDATE" then
MTH_FeedOMatic_Log("probe event received: " .. tostring(evt), "debug")
MTH_FeedOMatic_DebugMerchantScan(evt)
end
end)
end
if enabled then
MTH_FeedOMatic_MerchantProbeFrame:RegisterEvent("MERCHANT_SHOW")
MTH_FeedOMatic_MerchantProbeFrame:RegisterEvent("MERCHANT_UPDATE")
MTH_FeedOMatic_Log("merchant probe enabled", "debug")
else
MTH_FeedOMatic_MerchantProbeFrame:UnregisterEvent("MERCHANT_SHOW")
MTH_FeedOMatic_MerchantProbeFrame:UnregisterEvent("MERCHANT_UPDATE")
end
end
function MTH_FeedOMatic:init()
-- FeedOMatic initializes via its VARIABLES_LOADED event
-- This is called after the core MTH framework is loaded
MTH_FEED_WRAPPER_MODE = true
MTH_FOM_WRAPPER_MODE = MTH_FEED_WRAPPER_MODE
MTH_FeedOMatic_SyncSavedVariables()
if not self.initialized and not getglobal("FOM_METAHUNT_ONLOAD_DONE") and FOM_OnLoad then
local frame = MTH_FeedOMatic_GetRuntimeFrame()
if frame then
local oldThis = this
this = frame
pcall(function()
FOM_OnLoad()
end)
this = oldThis
FOM_METAHUNT_ONLOAD_DONE = true
end
end
MTH_FeedOMatic_CaptureHookBoundary()
self.initialized = true
if self.enabled then
MTH_FeedOMatic_EnsureVariablesLoaded(self)
end
end
function MTH_FeedOMatic:setEnabled(enabled)
if enabled then
if not self.initialized then
self:init()
end
MTH_FeedOMatic_ApplyLegacyHooks()
MTH_FeedOMatic_CaptureHookBoundary()
MTH_FeedOMatic_SyncSavedVariables()
MTH_FeedOMatic_EnsureVariablesLoaded(self)
MTH_FeedOMatic_SetMerchantProbeEnabled(false)
if FOM_FeedButton then
FOM_FeedButton:Show()
end
else
-- Disable FeedOMatic functionality
MTH_FeedOMatic_RestoreHookBoundary()
if type(FOM_State) == "table" then
FOM_State.ShouldFeed = false
end
MTH_FeedOMatic_SetMerchantProbeEnabled(false)
if FOM_FeedButton then
FOM_FeedButton:Hide()
end
end
end
function MTH_FeedOMatic:onEvent(event, arg1, arg2, arg3, arg4, arg5)
if event == "VARIABLES_LOADED" then
MTH_FeedOMatic_EnsureVariablesLoaded(self)
return
end
if event == "MTH_PET_LIVE_STATE_CHANGED" then
MTH_FeedOMatic_CallLegacyOnEvent("PET_BAR_UPDATE")
MTH_FeedOMatic_CallLegacyOnEvent("PET_UI_UPDATE")
MTH_FeedOMatic_CallLegacyOnEvent("UNIT_HAPPINESS", "pet")
return
end
MTH_FeedOMatic_CallLegacyOnEvent(event, arg1, arg2, arg3, arg4, arg5)
end
function MTH_FeedOMatic:cleanup()
-- Cleanup code when module is unloaded
MTH_FeedOMatic_RestoreHookBoundary()
if type(FOM_State) == "table" then
FOM_State.ShouldFeed = false
end
MTH_FeedOMatic_SetMerchantProbeEnabled(false)
if FOM_FeedButton then
FOM_FeedButton:Hide();
end
end
-- Register with MTH
MTH:RegisterModule("feedomatic", MTH_FeedOMatic)
MTH_FOM_OnUpdate = MTH_FeedOMatic_OnUpdate
+87
View File
@@ -0,0 +1,87 @@
------------------------------------------------------
Fizzwidget Feed-O-Matic
by Gazmik Fizzwidget
http://fizzwidget.com
gazmik@fizzwidget.com
------------------------------------------------------
As my Hunter friends can readily attest, keeping a wild pet can be a full-time job. Why, just feeding the critter when he gets hungry can throw off your routine -- you've got to rummage around in your bags, find a piece of food, make sure it's appropriate for his diet, and check your aim before tossing it to him (lest you accidentally destroy a tasty morsel). So inconvenient! Not to mention potentially dangerous... you don't want to spend so long digging through your bags that you or your pet become someone else's snack.
Never fear, Gazmik Fizzwidget is here with a new gadget to automate all your pet-food-management tasks! My incredible Feed-O-Matic features state-of-the-art nutritional analyzers to make sure your pet's hunger is satisfied with a minimum of fuss, advanced selective logic to make your pet doesn't eat anything you have another use for, and a weight optimizer to make sure the food in your bags stays well organized! Just press the "Feed Now" button and it'll intelligently choose a food and accurately toss it to your pet. This is actually one of the first gizmos I started work on... but because I'm a perfectionist I haven't considered it ready for release until now.
------------------------------------------------------
INSTALLATION: Put this folder into your World Of Warcraft/Interface/AddOns folder and launch WoW.
USAGE:
Makes feeding your pet quick, easy, and fun:
- Bind a key to "Feed Pet", and Feed-O-Matic will automatically choose an appropriate food and give it to the pet whenever you press it.
- Can use an emote to notify you when it's feeding your pet, with custom randomized messages. See FeedOMatic_Emotes.lua to customize them to your characters!
Helps you manage all the pet food in your inventory:
- Keeps track of which foods your pet likes more, and prioritizes "better" foods when choosing what to feed the pet. Also remembers what foods your pet has "outgrown", and doesn't choose them in the future (unless you've switched to a pet who might have different tastes).
- If you're on a quest to collect several of a certain item which also happens to be something your pet likes to eat, Feed-O-Matic can avoid consuming it. (Unless you're carrying more than is needed for the quest or there's nothing else for your pet to eat.)
- Feed-O-Matic can keep track of what foods are used by the Cooking recipes you know and avoid choosing food you'd rather cook; this behavior can be customized based on the difficulty of the recipies (so, for example, you can either save all cookable food or only save the foods most likely to increase your Cooking skill).
- If you'd prefer to save the better food (that is, those which provide a "well fed" bonus or other effect when eaten) for yourself, Feed-O-Matic can also avoid it when looking for pet food.
- All other things being equal, Feed-O-Matic will try to pick foods from smaller stacks, making sure your food supply doesn't take up all your bag space. When your bags get close to full, Feed-O-Matic will start ignoring other criteria and always choosing the smallest stack so that you won't run out of inventory sooner.
CHAT COMMANDS:
`/feedomatic` (or `/fom` or `/feed`) - Show the Options window.
or:
`/feedomatic` (or `/fom` or `/feed`) <command>
where <command> can be any of the following:
`help` - Print this helplist.
`status` - Check current settings.
`reset` - Reset to default settings.
`feed` - Feed your pet (automatically finds an appropriate food).
`feed <item link>` - Feed your pet a specific food.
`add <diet> <item link>` - Add food to the list for a specific diet (meat, fish, fruit, etc).
`remove <diet> <item link>` - Remove food from the list.
`show <diet>` - Show food list for a specific diet (or for `all`).
CAVEATS, ETC.:
- Feed-O-Matic has a database of many known foods, but it's not guaranteed to be comprehensive. You can use the `add`, `remove`, and `show` commands noted above to manage these lists directly. (Please drop me a line if you find a food that should be on there, or discover that something on the list shouldn't be there.)
- Currently, Feed-O-Matic can only update its list of which foods you know how to cook when your Cooking window is open. (So if you read a new recipe, we won't know to preserve the ingredients until you open your Cooking window again.)
- Only works in English, French, and German (for now). Localizers interested in helping with other WoW client languages please contact gazmik@fizzwidget.com.
------------------------------------------------------
VERSION HISTORY
v. 12000.2 - 2006/10/27
- Now always prefers conjured foods if available when feeding a pet that can eat them. (This was supposed to have been the case before, but the implementation turned out to be unreliable.)
- Fixed some issues which could result in Feed-O-Matic feeding the pet a food you'd told it to avoid, even if the "fall back" option was turned off. Also, a clearer error message is given if "fall back" is off and we can't find any allowed foods.
- The various reminder options for when your pet needs feeding (flashing the happiness icon, text messages, sounds) are now suppressed while you or your pet is in combat or if your pet is dead.
- Feed-O-Matic posts its own error message if you attempt use it to feed your pet while in combat. (Instead of trying to feed the pet anyway, causing WoW itself to post an error message.)
- Added a number of items to the default foods list (including some holiday treats and the new level 55 mage-conjured bread).
v. 11200.1 - 2006/08/22
- Updated TOC to reflect compatibility with WoW patch 1.12.
- Updated use of WoW's UnitSex() function to match changes in its behavior from WoW patch 1.11... your pet's gender should be reported correctly again.
v. 11100.1 - 2006/06/20
- Updated TOC to reflect compatibility with WoW patch 1.11. (No actual changes were needed.)
- Added French localization by "Artaher Medivh".
v. 11000.4 - 2006/04/17
- Fixed some issues with the tooltip code shared across Fizzwidget addons; Feed-O-Matic's addition to item tooltips should now be able to show up in just about every place you can get an item tooltip.
v. 11000.3 - 2006/04/11
- Feed-O-Matic settings can now be edited via a new Options panel. Typing `/fom` alone shows this panel (the various configuration subcommands, e.g. `/fom alert`, etc., are still available), which controls all aspects of Feed-O-Matic's behavior except for its list of known foods (for now, you'll still need to use slash commands to edit that).
- Additional options are available for warning you that your pet needs feeding: besides flashing the default UI's pet happiness icon, Feed-O-Matic can now also alert you via text message or with the sounds of your pet begging for food. (If you don't like these species-specific pet noises, there's also an option to have a simple bell sound as reminder.)
- You can now click the default UI's pet happiness icon to invoke Feed-O-Matic's feeding function. (This makes three ways to feed your pet with Feed-O-Matic: click the icon, set up a key for Feed Pet in the standard Key Bindings menu, or type or macro `/fom feed`.) Right-click the icon to show the new Options panel.
- Fixed a bug where we could bloat the SavedVariables file when attempting to clean it up upon visiting a stable master. (Now we properly discard data from pets no longer in service.)
- Fixed a bug where we'd still keep trying to feed a food to the pet after finding out it's too low level.
v. 11000.2 - 2006/04/01
- Due to changes in WoW patch 1.10, it's no longer possible for UI addons to feed your pet automatically without direct input; the automatic feeding function is thus no longer available in Feed-O-Matic. You may still find Feed-O-Matic quite useful for its ability to automatically choose foods for your pet and feed them at the touch of a button -- the "Feed Pet" button (configurable in WoW's Key Bindings menu) and `/fom feed` chat/macro command still work just great.
- As an aid to those suffering auto-feed withdrawal, there's now an option for Feed-O-Matic to provide an extra reminder when your pet needs feeding (in case you find the icon next to your pet's health bar too subtle). This is accessed by the same `/fom level` command that previously controlled automatic feeding; when your pet's happiness falls below the specified level, the happiness icon will blink until you feed him enough to sate his hunger.
- Fixed a bug where an error panel could appear when attempting to feed the pet with the `fallback` option turned off and the only foods in your inventory being types you'd prefer to avoid.
- KNOWN BUG: The tooltip additions aren't showing up in all cases where they should. Another update to address this issue should be coming along soon.
v. 11000.1 - 2006/03/28
- Updated for compatibility with WoW patch 1.10. (Minor changes were needed.)
- Should no longer disconnect if you have feed emotes turned on and you try to feed your pet a Legendary item. (Probably not a bug many experienced, but still...)
- Minor improvements to utility code shared across Fizzwidget addons.
- KNOWN BUG: The tooltip additions aren't showing up in all cases where they should. Another update to address this issue should be coming along soon.
See http://fizzwidget.com/notes/feedomatic/ for older release notes.
+25
View File
@@ -0,0 +1,25 @@
<!--
FeedOMatic Runtime Frame Loader (MetaHunt)
Minimal runtime XML without legacy options window definitions.
-->
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\..\..\FrameXML\UI.xsd">
<Frame name="Frame_GFW_FeedOMatic">
<Scripts>
<OnUpdate>
if (MTH_FOM_OnUpdate) then
MTH_FOM_OnUpdate(arg1);
elseif (FOM_OnUpdate) then
FOM_OnUpdate(arg1);
end
</OnUpdate>
</Scripts>
</Frame>
<GameTooltip name="FOMTooltip" frameStrata="TOOLTIP" hidden="true" parent="UIParent" inherits="GameTooltipTemplate">
<Scripts>
<OnLoad>
this:SetOwner(UIParent, "ANCHOR_NONE");
</OnLoad>
</Scripts>
</GameTooltip>
</Ui>
+672
View File
@@ -0,0 +1,672 @@
local function MTHSmartAmmo_GetSaved()
return MTH_SA_GetSavedTable("MTHSmartAmmo")
end
local function MTHSmartAmmo_GetModuleStore()
if MTH and MTH.GetModuleCharSavedVariables then
return MTH:GetModuleCharSavedVariables("smartammo")
end
return nil
end
local function MTHSmartAmmo_IsSmartEnabled()
local moduleStore = MTHSmartAmmo_GetModuleStore()
if type(moduleStore) == "table" and moduleStore.smartEnabled ~= nil then
return moduleStore.smartEnabled and true or false
end
local saved = MTHSmartAmmo_GetSaved()
return saved["enabled"]
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
end
local MTHSmartAmmo_InitializeHooks
MTHSmartAmmo_EnsureHooks = nil
function MTHSmartAmmo_SetSmartEnabled(enabled, silent)
local saved = MTHSmartAmmo_GetSaved()
if enabled then
saved["enabled"] = 1
if MTHSmartAmmo_InitializeHooks then
MTHSmartAmmo_InitializeHooks()
end
if MTHSmartAmmo_EnsureHooks then
MTHSmartAmmo_EnsureHooks("SetSmartEnabled")
end
if not silent and DEFAULT_CHAT_FRAME then
MTH_SA_Print("Smart Ammo Enabled.")
end
else
saved["enabled"] = false
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
end
function MTHSmartAmmo_GetSmartEnabled()
return MTHSmartAmmo_IsSmartEnabled() and true or false
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
if not silent and DEFAULT_CHAT_FRAME then
MTH_SA_Print("Smart Ammo reload fallback " .. (enabled and "Enabled." or "Disabled."))
end
end
function MTHSmartAmmo_GetReloadEnabled()
return MTHSmartAmmo_IsReloadEnabled() and true or false
end
local function MTHSmartAmmo_Log(msg)
return
end
local MTHSmartAmmo_CastSpell_Hook
local MTHSmartAmmo_UseAction_Hook
local MTHSmartAmmo_CastSpellByName_Hook
local MTHSmartAmmo_HookEnsureElapsed = 0
local MTHSmartAmmo_InCastSpellHook = false
local MTHSmartAmmo_InUseActionHook = false
local MTHSmartAmmo_InCastSpellByNameHook = false
local MTHSmartAmmo_OriginalCastSpell = CastSpell
local MTHSmartAmmo_OriginalUseAction = UseAction
local MTHSmartAmmo_OriginalCastSpellByName = CastSpellByName
local function MTHSmartAmmo_AreHooksInstalled()
return CastSpell == MTHSmartAmmo_CastSpell_Hook,
UseAction == MTHSmartAmmo_UseAction_Hook,
CastSpellByName == MTHSmartAmmo_CastSpellByName_Hook
end
local function MTHSmartAmmo_ForceHookGlobals()
if type(MTHSmartAmmo_CastSpell_Hook) == "function" and CastSpell ~= MTHSmartAmmo_CastSpell_Hook then
MTH_SA_CastSpell = CastSpell
CastSpell = MTHSmartAmmo_CastSpell_Hook
end
if type(MTHSmartAmmo_UseAction_Hook) == "function" and UseAction ~= MTHSmartAmmo_UseAction_Hook then
MTH_SA_UseAction = UseAction
UseAction = MTHSmartAmmo_UseAction_Hook
end
if type(MTHSmartAmmo_CastSpellByName_Hook) == "function" and CastSpellByName ~= MTHSmartAmmo_CastSpellByName_Hook then
MTH_SA_CastSpellByName = CastSpellByName
CastSpellByName = MTHSmartAmmo_CastSpellByName_Hook
end
end
MTH_SA = {}
MTH_SA_List = {}
MTH_SA_LastCheck = 0
MTH_SA_Tooltip = CreateFrame("GameTooltip", "MTH_SA_Tooltip", nil, "GameTooltipTemplate")
MTH_SA_Tooltip:Hide()
local _, class = UnitClass("player")
if class ~= "HUNTER" then
return
end
MTH_SA_IsWaitingSwapJunk = nil
MTH_SA_IsWaitingSwapGood = nil
MTH_SA_AutoShot = nil
MTH_SA_LastSpell = nil
MTH_SA_UpdateInterval = 0.1
MTH_SA_Quantity = 0
MTH_SA_LastFallbackAttempt = 0
MTH_SA_LastKnownEquippedName = nil
MTH_SA_LastFallbackNotice = { key = nil, time = 0 }
local function MTHSmartAmmo_RefreshAmmoButtonDisplay()
local createButtons = getglobal("zButtonAmmo_CreateButtons")
local setupSizeAndPosition = getglobal("zButtonAmmo_SetupSizeAndPosition")
local ammoButton = getglobal("zButtonAmmo")
if type(createButtons) == "function" and ammoButton then
createButtons()
if type(setupSizeAndPosition) == "function" then
setupSizeAndPosition()
end
end
end
local function MTHSmartAmmo_ShowFallbackWarning(previousAmmo, newAmmo)
if not previousAmmo or previousAmmo == "" or not newAmmo or newAmmo == "" then
return
end
if previousAmmo == newAmmo then
return
end
local now = GetTime() or 0
local key = tostring(previousAmmo) .. "=>" .. tostring(newAmmo)
if MTH_SA_LastFallbackNotice.key == key and (now - (MTH_SA_LastFallbackNotice.time or 0)) < 1.0 then
return
end
MTH_SA_LastFallbackNotice.key = key
MTH_SA_LastFallbackNotice.time = now
local warning = "YOU RAN OUT OF " .. tostring(previousAmmo) .. ", I EQUIPPED " .. tostring(newAmmo) .. " !"
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
if 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_HandleFallbackResult(previousAmmo)
local checkFrame = CreateFrame("Frame")
if not checkFrame then
return
end
checkFrame.elapsed = 0
checkFrame:SetScript("OnUpdate", function()
this.elapsed = (this.elapsed or 0) + (arg1 or 0)
if this.elapsed < 0.2 then
return
end
local equippedNow = MTH_SA_GetEquippedAmmoName()
if equippedNow and equippedNow ~= "" then
MTH_SA_LastKnownEquippedName = equippedNow
end
MTHSmartAmmo_RefreshAmmoButtonDisplay()
MTHSmartAmmo_ShowFallbackWarning(previousAmmo, equippedNow)
this:SetScript("OnUpdate", nil)
this:Hide()
end)
end
local function MTHSmartAmmo_TryFallbackEquip(reason)
if not MTH_SA_IsModuleEnabled() then
return
end
if not MTHSmartAmmo_IsSmartEnabled() then
return
end
if not MTHSmartAmmo_IsReloadEnabled() then
return
end
if MTH_SA_GetEquippedAmmoName() then
return
end
local now = GetTime() or 0
if MTH_SA_LastFallbackAttempt and (now - MTH_SA_LastFallbackAttempt) < 0.5 then
return
end
MTH_SA_LastFallbackAttempt = now
local previousAmmo = MTH_SA_GetEquippedAmmoName() or MTH_SA_LastKnownEquippedName
MTHSmartAmmo_Log("Fallback equip attempt reason=" .. tostring(reason or "unknown"))
if MTH_SA_Check() or MTH_SA_FindAmmo() then
MTH_SA_EquipAmmo()
MTHSmartAmmo_HandleFallbackResult(previousAmmo)
end
end
local frame = CreateFrame("Frame")
frame:RegisterEvent("START_AUTOREPEAT_SPELL")
frame:RegisterEvent("STOP_AUTOREPEAT_SPELL")
frame:RegisterEvent("SPELLCAST_INTERRUPTED")
frame:RegisterEvent("SPELLCAST_FAILED")
frame:RegisterEvent("SPELLCAST_STOP")
frame:RegisterEvent("SPELLCAST_DELAYED")
frame:SetScript("OnEvent", function()
if event == "START_AUTOREPEAT_SPELL" then
if MTHSmartAmmo_EnsureHooks then
MTHSmartAmmo_EnsureHooks("EVENT:START_AUTOREPEAT_SPELL")
end
local castHook, actionHook, byNameHook = MTHSmartAmmo_AreHooksInstalled()
MTHSmartAmmo_Log("EVENT START_AUTOREPEAT_SPELL smart=" .. tostring(MTHSmartAmmo_IsSmartEnabled()) .. " module=" .. tostring(MTH_SA_IsModuleEnabled()) .. " hooks=" .. tostring(castHook) .. "/" .. tostring(actionHook) .. "/" .. tostring(byNameHook))
MTH_SA_AutoShot = 1
MTHSmartAmmo_TryFallbackEquip("START_AUTOREPEAT_SPELL")
elseif event == "STOP_AUTOREPEAT_SPELL" then
MTHSmartAmmo_Log("EVENT STOP_AUTOREPEAT_SPELL")
MTH_SA_AutoShot = nil
elseif event == "SPELLCAST_START" then
--DEFAULT_CHAT_FRAME:AddMessage("SPELLCAST_START ", 0, 1, 1)
elseif event == "SPELLCAST_STOP" or event == "SPELLCAST_FAILED" or event == "SPELLCAST_INTERRUPTED" or event == "SPELLCAST_DELAYED" then
local castHook, actionHook, byNameHook = MTHSmartAmmo_AreHooksInstalled()
MTHSmartAmmo_Log("EVENT " .. tostring(event) .. " waitJunk=" .. tostring(MTH_SA_IsWaitingSwapJunk) .. " waitGood=" .. tostring(MTH_SA_IsWaitingSwapGood) .. " lastSpell=" .. tostring(MTH_SA_LastSpell) .. " smart=" .. tostring(MTHSmartAmmo_IsSmartEnabled()) .. " module=" .. tostring(MTH_SA_IsModuleEnabled()) .. " hooks=" .. tostring(castHook) .. "/" .. tostring(actionHook) .. "/" .. tostring(byNameHook))
if MTH_SA_IsWaitingSwapJunk then
MTHSmartAmmo_Log("EVENT swap request -> JUNK equipped=" .. tostring(MTH_SA_GetEquippedAmmoName()) .. " current=" .. tostring(MTH_SA.curname))
MTH_SA_EquipAmmo(1)
elseif MTH_SA_IsWaitingSwapGood then
MTHSmartAmmo_Log("EVENT swap request -> GOOD equipped=" .. tostring(MTH_SA_GetEquippedAmmoName()) .. " current=" .. tostring(MTH_SA.curname))
MTH_SA_EquipAmmo()
end
MTHSmartAmmo_Log("EVENT clear pending flags")
MTH_SA_IsWaitingSwapJunk = nil
MTH_SA_IsWaitingSwapGood = nil
MTH_SA_LastSpell = nil
end
end)
frame:SetScript("OnUpdate", function(self, elapsed)
MTHSmartAmmo_HookEnsureElapsed = MTHSmartAmmo_HookEnsureElapsed + (elapsed or 0)
if MTHSmartAmmo_HookEnsureElapsed >= 0.5 then
MTHSmartAmmo_HookEnsureElapsed = 0
if MTH_SA_IsModuleEnabled() then
MTHSmartAmmo_InitializeHooks()
end
end
MTH_SA_CountEquippedAmmo()
if MTH_SA_Quantity == 0 and not MTH_SA_IsWaitingSwapJunk and not MTH_SA_IsWaitingSwapGood then
MTHSmartAmmo_TryFallbackEquip("OnUpdate-quantity-zero")
end
if MTH_SA_AmmoLastSwitch and (GetTime()-MTH_SA_AmmoLastSwitch > 0.5) and (MTH_SA_IsWaitingSwapJunk or MTH_SA_IsWaitingSwapGood) then
MTHSmartAmmo_Log("OnUpdate pending swap elapsed>0.5 equipped=" .. tostring(MTH_SA_GetEquippedAmmoName()) .. " waitJunk=" .. tostring(MTH_SA_IsWaitingSwapJunk) .. " waitGood=" .. tostring(MTH_SA_IsWaitingSwapGood))
if MTH_SA_IsWaitingSwapJunk then
MTHSmartAmmo_Log("OnUpdate swap request -> JUNK")
MTH_SA_EquipAmmo(1)
elseif MTH_SA_IsWaitingSwapGood then
MTHSmartAmmo_Log("OnUpdate swap request -> GOOD")
MTH_SA_EquipAmmo()
end
MTHSmartAmmo_Log("OnUpdate clear pending flags")
MTH_SA_IsWaitingSwapJunk = nil
MTH_SA_IsWaitingSwapGood = nil
MTH_SA_LastSpell = nil
end
end)
function MTH_SA_CountEquippedAmmo()
local ammo = GetInventoryItemCount("player", 0)
local equippedName = MTH_SA_GetEquippedAmmoName()
if equippedName and equippedName ~= "" then
MTH_SA_LastKnownEquippedName = equippedName
end
if not ammo then
MTH_SA_Quantity=0
return
elseif ammo ~= MTH_SA_Quantity then
MTH_SA_Quantity = ammo
end
end
function MTH_SA_GetEquippedWeaponAmmoType()
local itemlink = GetInventoryItemLink("player", 18)
if not itemlink then return end
local _, _, itemid = string.find(itemlink, "item:(%d+)")
if not itemid then return end
local _, _, _, _, _, wpntype = GetItemInfo(itemid)
if wpntype == MTH_WEAPON_BOWS or wpntype == MTH_WEAPON_CROSSBOWS then
return "Arrows"
elseif wpntype == MTH_WEAPON_GUNS then
return "Bullets"
end
end
function MTH_SA_GetEquippedAmmoName()
MTH_SA_Tooltip:SetOwner(UIParent, "ANCHOR_NONE")
if MTH_SA_Tooltip:SetInventoryItem("player", 0) then
return MTH_SA_TooltipTextLeft1:GetText()
end
return nil
end
function MTH_SA_GetContainerItemName(bag, slot)
if not (bag and slot) then return nil end
local item = GetContainerItemLink(bag, slot)
local _, _, name = string.find(item or "", "%[(.+)%]")
return name
end
function MTH_SA_EquipAmmo(junk)
MTHSmartAmmo_Log("EquipAmmo enter junk=" .. tostring(junk) .. " equipped=" .. tostring(MTH_SA_GetEquippedAmmoName()) .. " cur=" .. tostring(MTH_SA.curname) .. " junk=" .. tostring(MTH_SA.junkname))
if not MTH_SA_AmmoLastSwitch then MTH_SA_AmmoLastSwitch = GetTime() end
if not (MTH_SA_Check() or MTH_SA_FindAmmo()) then
MTHSmartAmmo_Log("EquipAmmo abort: cache invalid and FindAmmo failed")
return
end
local bag, slot
if junk then
--DEFAULT_CHAT_FRAME:AddMessage("Time= " .. GetTime()-MTH_SA_AmmoLastSwitch , 0, 1, 1)
if GetTime()-MTH_SA_AmmoLastSwitch > 0.5 then
--DEFAULT_CHAT_FRAME:AddMessage("JUNK ! GetTime > 0.5 | Equipped=" .. MTH_SA_GetEquippedAmmoName() .. ' | Current=' .. MTH_SA.curname , 0, 1, 1)
else
--DEFAULT_CHAT_FRAME:AddMessage("JUNK ! GetTime <= 0.5 | Equipped=" .. MTH_SA_GetEquippedAmmoName() .. ' | Current=' .. MTH_SA.curname , 0, 1, 1)
end
if MTH_SA_GetEquippedAmmoName() ~= MTH_SA.curname and GetTime()-MTH_SA_AmmoLastSwitch > 0.5 then
if not MTH_SA_FindAmmo() then
MTHSmartAmmo_Log("EquipAmmo abort: AMMO NOT FOUND during junk swap")
return
end
end
bag, slot = MTH_SA.junkbag, MTH_SA.junkslot
MTHSmartAmmo_Log("Swap GOOD->JUNK bag=" .. tostring(bag) .. " slot=" .. tostring(slot) .. " cur=" .. tostring(MTH_SA.curname) .. " curSlot=" .. tostring(MTH_SA.curslot) .. " junk=" .. tostring(MTH_SA.junkname) .. " junkSlot=" .. tostring(MTH_SA.junkslot))
else
bag, slot = MTH_SA.curbag, MTH_SA.curslot
MTHSmartAmmo_Log("Swap JUNK->GOOD bag=" .. tostring(bag) .. " slot=" .. tostring(slot) .. " cur=" .. tostring(MTH_SA.curname) .. " curSlot=" .. tostring(MTH_SA.curslot) .. " junk=" .. tostring(MTH_SA.junkname) .. " junkSlot=" .. tostring(MTH_SA.junkslot))
end
--UseContainerItem(bag, slot)
PickupContainerItem(bag,slot)
EquipCursorItem(0)
MTHSmartAmmo_Log("EquipCursorItem applied bag=" .. tostring(bag) .. " slot=" .. tostring(slot))
if junk then
MTH_SA_IsWaitingSwapJunk = nil
MTH_SA_IsWaitingSwapGood = 1
else
MTH_SA_IsWaitingSwapJunk = nil
MTH_SA_IsWaitingSwapGood = nil
end
MTHSmartAmmo_Log("Pending flags set waitJunk=" .. tostring(MTH_SA_IsWaitingSwapJunk) .. " waitGood=" .. tostring(MTH_SA_IsWaitingSwapGood))
--PickupInventoryItem(0)
--AutoEquipCursorItem()
--EquipCursorItem(0)
MTH_SA_AmmoLastSwitch = GetTime()
end
function MTH_SA_FindAmmo()
MTHSmartAmmo_Log("FindAmmo start")
MTH_SA = {}
MTH_SA_List = {}
local ammotype = MTH_SA_GetEquippedWeaponAmmoType()
if not ammotype then
MTHSmartAmmo_Log("FindAmmo abort: no ranged weapon ammo type")
return
end
local curammo = MTH_SA_GetEquippedAmmoName()
if not curammo and not MTHSmartAmmo_IsReloadEnabled() then
MTHSmartAmmo_Log("FindAmmo abort: no equipped ammo and reload disabled")
return
end
MTHSmartAmmo_Log("FindAmmo type=" .. tostring(ammotype) .. " equipped=" .. tostring(curammo))
local ammolist = (ammotype == "Bullets" and MTH_AMMO_BULLET_RANK) or MTH_AMMO_ARROW_RANK or {}
MTH_SA_List = ammolist
local item, value
local junkvalue = 999
local goodvalue
for bag=4, 0, -1 do
for slot=GetContainerNumSlots(bag), 1, -1 do
item = MTH_SA_GetContainerItemName(bag, slot)
if item then
--DEFAULT_CHAT_FRAME:AddMessage("item = " .. item .. " | value = " .. ammolist[item], 0, 1, 1)
value = ammolist[item]
if value then
if not goodvalue and item == curammo then
MTH_SA.curvalue = value
MTH_SA.curname = item
MTH_SA.curbag = bag
MTH_SA.curslot = slot
goodvalue = value
--DEFAULT_CHAT_FRAME:AddMessage("goodvalue A = " .. value , 0, 1, 1)
if junkvalue == 1 then
return 1
end
end
if value < junkvalue then
MTH_SA.junkvalue = item
MTH_SA.junkname = item
MTH_SA.junkbag = bag
MTH_SA.junkslot = slot
junkvalue = value
--DEFAULT_CHAT_FRAME:AddMessage("junkvalue A = " .. value , 0, 1, 1)
if not curammo then
MTH_SA.curvalue = value
MTH_SA.curname = item
MTH_SA.curbag = bag
MTH_SA.curslot = slot
goodvalue = value
--DEFAULT_CHAT_FRAME:AddMessage("goodvalue B = " .. value , 0, 1, 1)
end
if junkvalue == 1 and goodvalue then
return 1
end
end
end
end
end
end
if not goodvalue or junkvalue > goodvalue then
MTHSmartAmmo_Log("FindAmmo failed: goodvalue=" .. tostring(goodvalue) .. " junkvalue=" .. tostring(junkvalue))
MTH_SA.error = 1
return
end
MTHSmartAmmo_Log("FindAmmo selected cur=" .. tostring(MTH_SA.curname) .. " junk=" .. tostring(MTH_SA.junkname) .. " curValue=" .. tostring(MTH_SA.curvalue) .. " junkValue=" .. tostring(junkvalue))
return 1
end
function MTH_SA_Check()
if not MTH_SA.error and MTH_SA.curname and MTH_SA.junkname then
if MTH_SA.curname == MTH_SA_GetContainerItemName(MTH_SA.curbag, MTH_SA.curslot) and
MTH_SA.junkname == MTH_SA_GetContainerItemName(MTH_SA.junkbag, MTH_SA.junkslot) then
return 1
end
end
end
MTHSmartAmmo_CastSpell_Hook = function(spell, tab)
if MTHSmartAmmo_InCastSpellHook then
if MTHSmartAmmo_OriginalCastSpell then
return MTHSmartAmmo_OriginalCastSpell(spell, tab)
end
return
end
MTHSmartAmmo_InCastSpellHook = true
if MTHSmartAmmo_IsSmartEnabled() then
local name = GetSpellName(spell, tab)
MTH_SA_LastSpell = name
MTHSmartAmmo_Log("HOOK CastSpell name=" .. tostring(name))
if name and MTH_AMMO_JUNKSHOT_SET and MTH_AMMO_JUNKSHOT_SET[name] then
MTHSmartAmmo_Log("HOOK CastSpell junk-shot detected name=" .. tostring(name))
if MTH_SA_Check() or MTH_SA_FindAmmo() then
MTHSmartAmmo_Log("HOOK CastSpell pre-swap -> junk")
MTH_SA_EquipAmmo(1)
end
end
end
local result
local original = MTH_SA_CastSpell
if original == MTHSmartAmmo_CastSpell_Hook or original == nil then
original = MTHSmartAmmo_OriginalCastSpell
end
if original then
result = original(spell, tab)
end
MTHSmartAmmo_InCastSpellHook = false
return result
end
MTHSmartAmmo_UseAction_Hook = function(slot, checkCursor, onSelf)
if MTHSmartAmmo_InUseActionHook then
if MTHSmartAmmo_OriginalUseAction then
return MTHSmartAmmo_OriginalUseAction(slot, checkCursor, onSelf)
end
return
end
MTHSmartAmmo_InUseActionHook = true
if not GetActionText(slot) and MTHSmartAmmo_IsSmartEnabled() then
MTH_SA_Tooltip:SetOwner(UIParent, "ANCHOR_NONE")
MTH_SA_Tooltip:SetAction(slot)
local name = MTH_SA_TooltipTextLeft1:GetText()
MTH_SA_LastSpell = name
MTHSmartAmmo_Log("HOOK UseAction slot=" .. tostring(slot) .. " name=" .. tostring(name))
if name and MTH_AMMO_JUNKSHOT_SET and MTH_AMMO_JUNKSHOT_SET[name] then
MTHSmartAmmo_Log("HOOK UseAction junk-shot detected name=" .. tostring(name))
if MTH_SA_Check() or MTH_SA_FindAmmo() then
MTHSmartAmmo_Log("HOOK UseAction pre-swap -> junk")
MTH_SA_EquipAmmo(1)
end
end
end
local result
local original = MTH_SA_UseAction
if original == MTHSmartAmmo_UseAction_Hook or original == nil then
original = MTHSmartAmmo_OriginalUseAction
end
if original then
result = original(slot, checkCursor, onSelf)
end
MTHSmartAmmo_InUseActionHook = false
return result
end
MTHSmartAmmo_CastSpellByName_Hook = function(spell, onSelf)
if MTHSmartAmmo_InCastSpellByNameHook then
if MTHSmartAmmo_OriginalCastSpellByName then
return MTHSmartAmmo_OriginalCastSpellByName(spell, onSelf)
end
return
end
MTHSmartAmmo_InCastSpellByNameHook = true
local _, _, name = string.find(spell or "", "([%w%'%s]+)")
MTH_SA_LastSpell = name
MTHSmartAmmo_Log("HOOK CastSpellByName name=" .. tostring(name))
if name and MTHSmartAmmo_IsSmartEnabled() and MTH_AMMO_JUNKSHOT_SET and MTH_AMMO_JUNKSHOT_SET[name] then
MTHSmartAmmo_Log("HOOK CastSpellByName junk-shot detected name=" .. tostring(name))
if MTH_SA_Check() or MTH_SA_FindAmmo() then
MTHSmartAmmo_Log("HOOK CastSpellByName pre-swap -> junk")
MTH_SA_EquipAmmo(1)
end
end
local result
local original = MTH_SA_CastSpellByName
if original == MTHSmartAmmo_CastSpellByName_Hook or original == nil then
original = MTHSmartAmmo_OriginalCastSpellByName
end
if original then
result = original(spell, onSelf)
end
MTHSmartAmmo_InCastSpellByNameHook = false
return result
end
-- Defer hooking until after tables are initialized
MTHSmartAmmo_InitializeHooks = function()
if not MTH_SA_IsModuleEnabled() then
MTHSmartAmmo_Log("InitializeHooks skip: module disabled")
return
end
if CastSpell ~= MTHSmartAmmo_CastSpell_Hook then
MTH_SA_CastSpell = CastSpell
CastSpell = MTHSmartAmmo_CastSpell_Hook
MTHSmartAmmo_Log("InitializeHooks: CastSpell hook installed")
end
if UseAction ~= MTHSmartAmmo_UseAction_Hook then
MTH_SA_UseAction = UseAction
UseAction = MTHSmartAmmo_UseAction_Hook
MTHSmartAmmo_Log("InitializeHooks: UseAction hook installed")
end
if CastSpellByName ~= MTHSmartAmmo_CastSpellByName_Hook then
MTH_SA_CastSpellByName = CastSpellByName
CastSpellByName = MTHSmartAmmo_CastSpellByName_Hook
MTHSmartAmmo_Log("InitializeHooks: CastSpellByName hook installed")
end
end
MTHSmartAmmo_EnsureHooks = function(reason)
if not MTH_SA_IsModuleEnabled() then
return false
end
if not MTHSmartAmmo_IsSmartEnabled() then
return false
end
if MTHSmartAmmo_InitializeHooks then
local ok, err = pcall(MTHSmartAmmo_InitializeHooks)
if not ok then end
end
local castHook, actionHook, byNameHook = MTHSmartAmmo_AreHooksInstalled()
if castHook and actionHook and byNameHook then
return true
end
MTHSmartAmmo_ForceHookGlobals()
castHook, actionHook, byNameHook = MTHSmartAmmo_AreHooksInstalled()
return castHook and actionHook and byNameHook
end
-- Initialize hooks on module load
if not MTH_SA_MANAGED_HOOKS then
MTHSmartAmmo_InitializeHooks()
end
SLASH_MTHSmartAmmo1 = "/smartammo"
SlashCmdList["MTHSmartAmmo"] = function(msg)
if not MTH_SA_IsModuleEnabled() then
MTH_SA_Print("Smart Ammo is disabled while module 'smartammo' is disabled.")
return
end
local saved = MTHSmartAmmo_GetSaved()
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))
return
end
if saved["enabled"] then
MTHSmartAmmo_SetSmartEnabled(nil)
else
MTHSmartAmmo_SetSmartEnabled(1)
end
end
+8
View File
@@ -0,0 +1,8 @@
<!--
SmartAmmo Module Loader
Owns Smart Ammo runtime and hooks as a standalone MetaHunt module
-->
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<Include file="module.lua"/>
<Include file="engine.lua"/>
</Ui>
+171
View File
@@ -0,0 +1,171 @@
------------------------------------------------------
-- MetaHunt: SmartAmmo Module
------------------------------------------------------
MTH_SA_MANAGED_HOOKS = true
local MTH_SmartAmmo = {
name = "smartammo",
enabled = true,
version = "1.0.3",
events = {
"VARIABLES_LOADED",
"PLAYER_ENTERING_WORLD",
},
}
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
end
return true
end
function MTH_SA_Print(msg, severity)
if MTH and MTH.Print then
MTH:Print(tostring(msg), severity)
return
end
if DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.AddMessage then
DEFAULT_CHAT_FRAME:AddMessage(tostring(msg))
end
end
local function MTH_SA_ApplySavedState()
if type(MTHSmartAmmo_SetSmartEnabled) ~= "function" then
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
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
MTHSmartAmmo_SetSmartEnabled(saved["enabled"] and 1 or nil, 1)
if type(MTHSmartAmmo_SetReloadEnabled) == "function" then
MTHSmartAmmo_SetReloadEnabled(saved["reload"] ~= false and 1 or nil, 1)
end
if type(MTHSmartAmmo_EnsureHooks) == "function" then
MTHSmartAmmo_EnsureHooks("module-apply")
end
if not (MTH and MTH.CaptureHookBoundary) then
return
end
local castHook = getglobal("MTHSmartAmmo_CastSpell_Hook")
local useActionHook = getglobal("MTHSmartAmmo_UseAction_Hook")
local castByNameHook = getglobal("MTHSmartAmmo_CastSpellByName_Hook")
if type(castHook) ~= "function" or type(useActionHook) ~= "function" or type(castByNameHook) ~= "function" then
return
end
if CastSpell ~= castHook or UseAction ~= useActionHook or CastSpellByName ~= castByNameHook then
return
end
MTH:CaptureHookBoundary(MTH_SA_BOUNDARY_KEY, {
{ globalName = "CastSpell", originalName = "MTH_SA_CastSpell" },
{ globalName = "UseAction", originalName = "MTH_SA_UseAction" },
{ globalName = "CastSpellByName", originalName = "MTH_SA_CastSpellByName" },
})
end
local function MTH_SA_RestoreHooks()
if MTH and MTH.RestoreHookBoundary and MTH:RestoreHookBoundary(MTH_SA_BOUNDARY_KEY) then
return
end
local castHook = getglobal("MTHSmartAmmo_CastSpell_Hook")
local useActionHook = getglobal("MTHSmartAmmo_UseAction_Hook")
local castByNameHook = getglobal("MTHSmartAmmo_CastSpellByName_Hook")
if CastSpell == castHook and type(MTH_SA_CastSpell) == "function" then
CastSpell = MTH_SA_CastSpell
end
if UseAction == useActionHook and type(MTH_SA_UseAction) == "function" then
UseAction = MTH_SA_UseAction
end
if CastSpellByName == castByNameHook and type(MTH_SA_CastSpellByName) == "function" then
CastSpellByName = MTH_SA_CastSpellByName
end
end
function MTH_SmartAmmo:init()
MTH_SA_GetSavedTable("MTHSmartAmmo")
if self.enabled then
MTH_SA_ApplySavedState()
end
end
function MTH_SmartAmmo:setEnabled(enabled)
if enabled then
MTH_SA_ApplySavedState()
else
MTH_SA_RestoreHooks()
end
end
function MTH_SmartAmmo:onEvent(evt)
if not self.enabled then
return
end
if evt == "VARIABLES_LOADED" or evt == "PLAYER_ENTERING_WORLD" then
MTH_SA_ApplySavedState()
end
end
function MTH_SmartAmmo:cleanup()
self:setEnabled(false)
end
MTH:RegisterModule("smartammo", MTH_SmartAmmo)
+7
View File
@@ -0,0 +1,7 @@
<!--
MetaHunt Tooltips Module Loader
Standalone tooltip augmentation using MetaHunt data only.
-->
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<Include file="module.lua"/>
</Ui>
File diff suppressed because it is too large Load Diff

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