mirror of
https://codeberg.org/Duvelcorp/MetaHunt.git
synced 2026-09-25 00:56:04 +00:00
First version
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# Ignore all hidden files and hidden directories
|
||||
.*
|
||||
**/.*
|
||||
|
||||
# Keep this ignore file tracked
|
||||
!.gitignore
|
||||
+121
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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 },
|
||||
}
|
||||
@@ -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
@@ -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" },
|
||||
},
|
||||
}
|
||||
+25145
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+2229
File diff suppressed because it is too large
Load Diff
@@ -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
File diff suppressed because it is too large
Load Diff
+598
@@ -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
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
@@ -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>
|
||||
@@ -0,0 +1,4 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/">
|
||||
<!-- MetaHunt Compatibility Layer -->
|
||||
<!-- Handles version compatibility with Turtle WoW -->
|
||||
</Ui>
|
||||
@@ -0,0 +1,4 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/">
|
||||
<!-- MetaHunt Environment Setup -->
|
||||
<!-- Global tables, localization, and configuration -->
|
||||
</Ui>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
@@ -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>
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
@@ -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
@@ -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.",
|
||||
},
|
||||
};
|
||||
@@ -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
|
||||
},
|
||||
};
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
@@ -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
Reference in New Issue
Block a user