First version

This commit is contained in:
DuvelCorp
2026-02-24 23:00:59 +01:00
commit ee8328e903
125 changed files with 140652 additions and 0 deletions
+147
View File
@@ -0,0 +1,147 @@
local function AntiDaze_Print(msg)
if MTH and MTH.Print then
MTH:Print(msg)
elseif DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.AddMessage then
DEFAULT_CHAT_FRAME:AddMessage(tostring(msg))
end
end
local function AntiDaze_GetSaved()
if type(MTH_CharSavedVariables) ~= "table" then
MTH_CharSavedVariables = {}
end
if type(MTH_CharSavedVariables.antiDaze) ~= "table" then
MTH_CharSavedVariables.antiDaze = {}
end
return MTH_CharSavedVariables.antiDaze
end
local function AntiDaze_IsEnabled()
local saved = AntiDaze_GetSaved()
return saved["enabled"] and true or false
end
local function AntiDaze_RegisterCombatEvents()
if not AntiDazeFrame then
return
end
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE")
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE")
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
AntiDazeFrame:RegisterEvent("CHAT_MSG_SPELL_PARTY_DAMAGE")
end
local function AntiDaze_UnregisterCombatEvents()
if not AntiDazeFrame then
return
end
AntiDazeFrame:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE")
AntiDazeFrame:UnregisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE")
AntiDazeFrame:UnregisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
AntiDazeFrame:UnregisterEvent("CHAT_MSG_SPELL_PARTY_DAMAGE")
end
function AntiDaze_SetEnabled(enabled, silent)
local saved = AntiDaze_GetSaved()
if enabled then
saved["enabled"] = 1
AntiDaze_RegisterCombatEvents()
if not silent then
AntiDaze_Print("AntiDaze enabled.")
end
else
saved["enabled"] = nil
AntiDaze_UnregisterCombatEvents()
if not silent then
AntiDaze_Print("AntiDaze disabled.")
end
end
end
function AntiDaze_GetEnabled()
return AntiDaze_IsEnabled()
end
local function AntiDaze_ResolvePlayerFromMessage(msg)
local afflictedPattern = _G["ZHUNTER_AFFLICTED_DAZED"] or "(.+) (.+) afflicted by Dazed"
local _, _, player = string.find(msg or "", afflictedPattern)
if player then
return player
end
local lowerMsg = string.lower(msg or "")
if string.find(lowerMsg, "dazed", 1, true) or string.find(lowerMsg, "daze", 1, true) then
local playerName = UnitName("player")
if string.find(msg or "", "You", 1, true) or (playerName and string.find(msg or "", playerName, 1, true)) then
return _G["ZHUNTER_YOU"] or "You"
end
for i = 1, GetNumPartyMembers() do
local partyName = UnitName("party" .. i)
if partyName and string.find(msg or "", partyName, 1, true) then
return partyName
end
end
end
return nil
end
local function AntiDaze_CancelRelevantAspect(isSelfDazed)
local packName = _G["ZHUNTER_ASPECT_PACK"] or "Aspect of the Pack"
local cheetahName = _G["ZHUNTER_ASPECT_CHEETAH"] or "Aspect of the Cheetah"
for i = 0, 32 do
if GetPlayerBuff(i, "HELPFUL") < 0 then
break
end
AntiDazeTooltip:SetOwner(UIParent, "ANCHOR_NONE")
AntiDazeTooltip:SetPlayerBuff(i, "HELPFUL")
local text = AntiDazeTooltipTextLeft1 and AntiDazeTooltipTextLeft1:GetText()
if text == packName or (isSelfDazed and text == cheetahName) then
CancelPlayerBuff(i)
return 1
end
end
return nil
end
local function AntiDaze_HandleSlash()
AntiDaze_SetEnabled(not AntiDaze_IsEnabled())
end
AntiDazeTooltip = CreateFrame("GameTooltip", "AntiDazeTooltip", nil, "GameTooltipTemplate")
AntiDazeFrame = CreateFrame("Frame", "AntiDazeFrame")
if AntiDazeFrame then
AntiDazeFrame:RegisterEvent("VARIABLES_LOADED")
AntiDazeFrame:SetScript("OnEvent", function()
if event == "VARIABLES_LOADED" then
if AntiDaze_IsEnabled() then
AntiDaze_RegisterCombatEvents()
else
AntiDaze_UnregisterCombatEvents()
end
return
end
local player = AntiDaze_ResolvePlayerFromMessage(arg1)
if player then
local you = _G["ZHUNTER_YOU"] or "You"
local group = {}
group[you] = 1
for i = 1, GetNumPartyMembers() do
group[UnitName("party" .. i)] = 1
end
if group[player] then
AntiDaze_CancelRelevantAspect(player == you)
end
end
end)
end
SLASH_ANTIDAZE1 = "/antidaze"
SlashCmdList["ANTIDAZE"] = AntiDaze_HandleSlash
SLASH_ZANTIDAZE1 = "/zantidaze"
SlashCmdList["ZANTIDAZE"] = AntiDaze_HandleSlash
+189
View File
@@ -0,0 +1,189 @@
local function AutoStrip_Print(msg)
if MTH and MTH.Print then
MTH:Print(msg)
elseif DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.AddMessage then
DEFAULT_CHAT_FRAME:AddMessage(tostring(msg))
end
end
function AutoStrip_GetSaved()
if type(MTH_CharSavedVariables) ~= "table" then
MTH_CharSavedVariables = {}
end
if type(MTH_CharSavedVariables.autoStrip) ~= "table" then
MTH_CharSavedVariables.autoStrip = {}
end
return MTH_CharSavedVariables.autoStrip
end
local AUTO_STRIP_ORDER = {16, 17, 18, 5, 7, 1, 3, 10, 8, 6, 9}
AutoStrip_On = nil
AutoStrip_WasInCombat = nil
AutoStrip_Frame = nil
function AutoStrip_SetDisplayIcon()
local icon = getglobal("AutoStripDisplayIcon") or getglobal("AutoStripDisplayIconTexture")
if icon then
icon:SetTexture("Interface\\Icons\\Ability_Creature_Cursed_02")
icon:Show()
end
end
function AutoStrip_GetEnabled()
return AutoStrip_On and true or false
end
function AutoStrip_SetAutoStripToggle(enabled, silent)
local saved = AutoStrip_GetSaved()
if enabled then
saved["autostrip"] = 1
AutoStrip_On = 1
if AutoStripDisplay and AutoStripDisplay:IsVisible() and AutoStripDisplayAutoCast then
AutoStripDisplayAutoCast:Show()
end
if not silent then
AutoStrip_Print("Auto-Strip enabled.")
end
else
saved["autostrip"] = nil
AutoStrip_On = nil
if AutoStripDisplayAutoCast then
AutoStripDisplayAutoCast:Hide()
end
if not silent then
AutoStrip_Print("Auto-Strip disabled.")
end
end
end
function AutoStrip_SetDisplayToggle(enabled, silent)
local saved = AutoStrip_GetSaved()
if enabled then
saved["display"] = 1
if AutoStripDisplay then
AutoStripDisplay:Show()
AutoStrip_SetDisplayIcon()
if AutoStrip_On and AutoStripDisplayAutoCast then
AutoStripDisplayAutoCast:Show()
end
end
if not silent then
AutoStrip_Print("AutoStrip button is now visible.")
end
else
saved["display"] = nil
if AutoStripDisplay then
AutoStripDisplay:Hide()
end
if not silent then
AutoStrip_Print("AutoStrip button is now hidden.")
end
end
end
function AutoStrip_UnequipAll(weaponsOnly)
AutoStrip_On = nil
if AutoStripDisplayAutoCast then
AutoStripDisplayAutoCast:Hide()
end
local hasEmptySlot = nil
for bag = 0, 4 do
for slot = 1, GetContainerNumSlots(bag) do
if not GetContainerItemLink(bag, slot) then
hasEmptySlot = 1
break
end
end
if hasEmptySlot then
break
end
end
if not hasEmptySlot then
AutoStrip_Print("You need at least one empty bag slot to auto-strip.")
return
end
local startIndex = 1
local finishIndex = weaponsOnly and 3 or table.getn(AUTO_STRIP_ORDER)
for bag = 0, 4 do
for slot = 1, GetContainerNumSlots(bag) do
if not GetContainerItemLink(bag, slot) then
for index = startIndex, finishIndex do
if GetInventoryItemLink("player", AUTO_STRIP_ORDER[index]) then
PickupInventoryItem(AUTO_STRIP_ORDER[index])
PickupContainerItem(bag, slot)
startIndex = index + 1
break
end
end
end
end
end
end
local function AutoStrip_OnEvent()
if event == "VARIABLES_LOADED" then
local saved = AutoStrip_GetSaved()
AutoStrip_WasInCombat = UnitAffectingCombat("player") and 1 or nil
AutoStrip_SetDisplayToggle(saved["display"] and true or false, 1)
AutoStrip_SetAutoStripToggle(saved["autostrip"] and true or false, 1)
return
end
if event == "PLAYER_ENTERING_WORLD" then
AutoStrip_WasInCombat = UnitAffectingCombat("player") and 1 or nil
return
end
if event == "PLAYER_REGEN_DISABLED" then
AutoStrip_WasInCombat = 1
return
end
if event == "PLAYER_REGEN_ENABLED" then
if AutoStrip_On and AutoStrip_WasInCombat then
AutoStrip_UnequipAll()
end
AutoStrip_WasInCombat = nil
end
end
local function AutoStrip_HandleSlash(msg)
local saved = AutoStrip_GetSaved()
if msg == "display" then
AutoStrip_SetDisplayToggle(not (saved["display"] and true or false))
elseif msg == "toggle" then
local enableAutoStrip = not AutoStrip_GetEnabled()
if enableAutoStrip and not saved["display"] then
AutoStrip_SetDisplayToggle(true, 1)
end
AutoStrip_SetAutoStripToggle(enableAutoStrip, 1)
if enableAutoStrip then
AutoStrip_Print("You will strip when you leave combat.")
else
AutoStrip_Print("You will no longer strip when you leave combat.")
end
else
AutoStrip_Print("Possible Commands: \"display\", \"toggle\"")
end
end
AutoStrip_Frame = CreateFrame("Frame", "AutoStripFrame")
if AutoStrip_Frame then
AutoStrip_Frame:RegisterEvent("VARIABLES_LOADED")
AutoStrip_Frame:RegisterEvent("PLAYER_REGEN_DISABLED")
AutoStrip_Frame:RegisterEvent("PLAYER_REGEN_ENABLED")
AutoStrip_Frame:RegisterEvent("PLAYER_ENTERING_WORLD")
AutoStrip_Frame:SetScript("OnEvent", AutoStrip_OnEvent)
end
if AutoStripDisplay then
AutoStrip_SetDisplayIcon()
end
SLASH_AUTOSTRIP1 = "/autostrip"
SlashCmdList["AUTOSTRIP"] = AutoStrip_HandleSlash
SLASH_ZSTRIP1 = "/zstrip"
SlashCmdList["ZSTRIP"] = AutoStrip_HandleSlash
+97
View File
@@ -0,0 +1,97 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\FrameXML\UI.xsd">
<Button name="AutoStripDisplay" parent="UIParent" enableMouse="true" movable="true" inherits="ActionButtonTemplate" frameStrata="DIALOG" hidden="true">
<Size>
<AbsDimension x="22.5" y="22.5"/>
</Size>
<Anchors>
<Anchor point="CENTER">
<Offset>
<AbsDimension x="0" y="0"/>
</Offset>
</Anchor>
</Anchors>
<Layers>
<Layer level="OVERLAY">
<Texture name="$parentAutoCastable" file="Interface\Buttons\UI-AutoCastableOverlay">
<Size>
<AbsDimension x="43.5" y="43.5"/>
</Size>
<Anchors>
<Anchor point="CENTER">
<Offset>
<AbsDimension x="0" y="0"/>
</Offset>
</Anchor>
</Anchors>
</Texture>
</Layer>
</Layers>
<Frames>
<Model name="$parentAutoCast" file="Interface\Buttons\UI-AutoCastButton.mdx" scale="0.9" hidden="true" setAllPoints="true">
<Scripts>
<OnLoad>
this:SetSequence(0);
this:SetSequenceTime(0, 0);
</OnLoad>
</Scripts>
</Model>
</Frames>
<Scripts>
<OnLoad>
this:RegisterForDrag("LeftButton");
this:RegisterForClicks("LeftButtonUp", "RightButtonUp");
</OnLoad>
<OnClick>
if arg1 == "LeftButton" then
if not UnitAffectingCombat("player") then
AutoStrip_UnequipAll()
else
AutoStrip_UnequipAll(1)
end
elseif arg1 == "RightButton" then
AutoStrip_SetAutoStripToggle(not AutoStrip_GetEnabled())
end
</OnClick>
<OnEnter>
GameTooltip:SetOwner(this, "ANCHOR_TOPLEFT");
GameTooltip:SetText("AutoStrip", 1, 1, 1)
GameTooltip:AddLine("Left-Click to strip now.")
GameTooltip:AddLine("Right-Click to toggle auto-strip")
GameTooltip:AddLine("Alt-Drag to move")
GameTooltip:Show()
</OnEnter>
<OnLeave>
GameTooltip:Hide()
</OnLeave>
<OnMouseUp>
if ( this.isMoving ) then
this:StopMovingOrSizing()
this.isMoving = false
end
</OnMouseUp>
<OnDragStart>
if IsAltKeyDown() then
this:StartMoving()
this.isMoving = true
end
</OnDragStart>
<OnDragStop>
this:StopMovingOrSizing()
this.isMoving = false
</OnDragStop>
</Scripts>
<NormalTexture name="$parentNormalTexture" file="Interface\Buttons\UI-Quickslot2">
<Size>
<AbsDimension x="40.5" y="40.5"/>
</Size>
<Anchors>
<Anchor point="CENTER">
<Offset>
<AbsDimension x="0" y="-1"/>
</Offset>
</Anchor>
</Anchors>
</NormalTexture>
</Button>
</Ui>
+58
View File
@@ -0,0 +1,58 @@
------------------------------------------------------
-- MetaHunt Configuration System
------------------------------------------------------
-- Global config management for modules
MTH.config = {}
local function MTH_ConfigEnsureStore(module_name)
if type(module_name) ~= "string" or module_name == "" then
module_name = "core"
end
local root = _G and _G.MTH_SavedVariables or MTH_SavedVariables
if type(root) ~= "table" then
if MTH and MTH.InitSavedVariables then
MTH:InitSavedVariables()
end
root = _G and _G.MTH_SavedVariables or MTH_SavedVariables
end
if type(root) ~= "table" then
root = { modules = {} }
if _G then
_G.MTH_SavedVariables = root
end
MTH_SavedVariables = root
end
if type(root.modules) ~= "table" then
root.modules = {}
end
if type(root.modules[module_name]) ~= "table" then
local legacy = root[module_name]
if type(legacy) ~= "table" then
legacy = {}
end
root.modules[module_name] = legacy
end
root[module_name] = root.modules[module_name]
MTH_SavedVariables = root
return root.modules[module_name]
end
function MTH:GetConfig(module_name, key, default)
local store = MTH_ConfigEnsureStore(module_name)
local value = store[key]
if value == nil then
return default
end
return value
end
function MTH:SetConfig(module_name, key, value)
local store = MTH_ConfigEnsureStore(module_name)
store[key] = value
end
+152
View File
@@ -0,0 +1,152 @@
MTH_CONST = MTH_CONST or {}
MTH_CONST.version = MTH_CONST.version or "1.0.3"
MTH_CONST.WEAPON_TYPES = {
BOWS = "Bows",
CROSSBOWS = "Crossbows",
GUNS = "Guns",
}
MTH_CONST.AMMO = {
BULLETS = {
LIGHT = "Light Shot",
CRAFTLIGHT = "Crafted Light Shot",
FLASH = "Flash Pellet",
HEAVY = "Heavy Shot",
PEBBLE = "Smooth Pebble",
CRAFTHEAVY = "Crafted Heavy Shot",
SOLID = "Solid Shot",
CRAFTSOLID = "Crafted Solid Shot",
EXPLODING = "Exploding Shot",
MITHRILSLUG = "Hi-impact Mithril Slugs",
ACCURATE = "Accurate Slugs",
GYROSHOT = "Mithril Gyro-Shot",
ICETHREADED = "Ice Threaded Bullet",
THORIUM = "Thorium Shells",
ROCKSHARD = "Rockshard Pellets",
CANNONBALL = "Miniature Cannon Balls",
},
ARROWS = {
ROUGH = "Rough Arrow",
SHARP = "Sharp Arrow",
RAZOR = "Razor Arrow",
FEATHER = "Feathered Arrow",
PRECISION = "Precision Arrow",
JAGGED = "Jagged Arrow",
ICETHREADED = "Ice Threaded Arrow",
THORIUM = "Thorium Headed Arrow",
DOOMSHOT = "Doomshot",
},
JUNKSHOTS = {
SERPENT = "Serpent Sting",
SCORPID = "Scorpid Sting",
VIPER = "Viper Sting",
ARCANE = "Arcane Shot",
CONCUSSIVE = "Concussive Shot",
DISTRACTING = "Distracting Shot",
BAITED = "Baited Shot",
TRANQ = "Tranquilizing Shot",
},
}
MTH_WEAPON_BOWS = MTH_CONST.WEAPON_TYPES.BOWS
MTH_WEAPON_CROSSBOWS = MTH_CONST.WEAPON_TYPES.CROSSBOWS
MTH_WEAPON_GUNS = MTH_CONST.WEAPON_TYPES.GUNS
BULLETS_LIGHT = MTH_CONST.AMMO.BULLETS.LIGHT
BULLETS_CRAFTLIGHT = MTH_CONST.AMMO.BULLETS.CRAFTLIGHT
BULLETS_FLASH = MTH_CONST.AMMO.BULLETS.FLASH
BULLETS_HEAVY = MTH_CONST.AMMO.BULLETS.HEAVY
BULLETS_PEBBLE = MTH_CONST.AMMO.BULLETS.PEBBLE
BULLETS_CRAFTHEAVY = MTH_CONST.AMMO.BULLETS.CRAFTHEAVY
BULLETS_SOLID = MTH_CONST.AMMO.BULLETS.SOLID
BULLETS_CRAFTSOLID = MTH_CONST.AMMO.BULLETS.CRAFTSOLID
BULLETS_EXPLODING = MTH_CONST.AMMO.BULLETS.EXPLODING
BULLETS_MITHRILSLUG = MTH_CONST.AMMO.BULLETS.MITHRILSLUG
BULLETS_ACCURATE = MTH_CONST.AMMO.BULLETS.ACCURATE
BULLETS_GYROSHOT = MTH_CONST.AMMO.BULLETS.GYROSHOT
BULLETS_ICETHREADED = MTH_CONST.AMMO.BULLETS.ICETHREADED
BULLETS_THORIUM = MTH_CONST.AMMO.BULLETS.THORIUM
BULLETS_ROCKSHARD = MTH_CONST.AMMO.BULLETS.ROCKSHARD
BULLETS_CANNONBALL = MTH_CONST.AMMO.BULLETS.CANNONBALL
ARROWS_ROUGH = MTH_CONST.AMMO.ARROWS.ROUGH
ARROWS_SHARP = MTH_CONST.AMMO.ARROWS.SHARP
ARROWS_RAZOR = MTH_CONST.AMMO.ARROWS.RAZOR
ARROWS_FEATHER = MTH_CONST.AMMO.ARROWS.FEATHER
ARROWS_PRECISION = MTH_CONST.AMMO.ARROWS.PRECISION
ARROWS_JAGGED = MTH_CONST.AMMO.ARROWS.JAGGED
ARROWS_ICETHREADED = MTH_CONST.AMMO.ARROWS.ICETHREADED
ARROWS_THORIUM = MTH_CONST.AMMO.ARROWS.THORIUM
ARROWS_DOOMSHOT = MTH_CONST.AMMO.ARROWS.DOOMSHOT
JUNKSHOT_SERPENT = MTH_CONST.AMMO.JUNKSHOTS.SERPENT
JUNKSHOT_SCORPID = MTH_CONST.AMMO.JUNKSHOTS.SCORPID
JUNKSHOT_VIPER = MTH_CONST.AMMO.JUNKSHOTS.VIPER
JUNKSHOT_ARCANE = MTH_CONST.AMMO.JUNKSHOTS.ARCANE
JUNKSHOT_CONCUSSIVE = MTH_CONST.AMMO.JUNKSHOTS.CONCUSSIVE
JUNKSHOT_DISTRACTING = MTH_CONST.AMMO.JUNKSHOTS.DISTRACTING
MTH_AMMO_BULLETS = {
BULLETS_LIGHT,
BULLETS_CRAFTLIGHT,
BULLETS_FLASH,
BULLETS_HEAVY,
BULLETS_PEBBLE,
BULLETS_CRAFTHEAVY,
BULLETS_SOLID,
BULLETS_CRAFTSOLID,
BULLETS_EXPLODING,
BULLETS_MITHRILSLUG,
BULLETS_ACCURATE,
BULLETS_GYROSHOT,
BULLETS_ICETHREADED,
BULLETS_THORIUM,
BULLETS_ROCKSHARD,
BULLETS_CANNONBALL,
}
MTH_AMMO_ARROWS = {
ARROWS_ROUGH,
ARROWS_SHARP,
ARROWS_RAZOR,
ARROWS_FEATHER,
ARROWS_PRECISION,
ARROWS_JAGGED,
ARROWS_ICETHREADED,
ARROWS_THORIUM,
ARROWS_DOOMSHOT,
}
MTH_AMMO_JUNKSHOTS = {
JUNKSHOT_SERPENT,
JUNKSHOT_SCORPID,
JUNKSHOT_VIPER,
JUNKSHOT_ARCANE,
JUNKSHOT_CONCUSSIVE,
JUNKSHOT_DISTRACTING,
}
MTH_AMMO_BULLET_RANK = {}
for i = 1, table.getn(MTH_AMMO_BULLETS) do
local ammoName = MTH_AMMO_BULLETS[i]
if ammoName then
MTH_AMMO_BULLET_RANK[ammoName] = i
end
end
MTH_AMMO_ARROW_RANK = {}
for i = 1, table.getn(MTH_AMMO_ARROWS) do
local ammoName = MTH_AMMO_ARROWS[i]
if ammoName then
MTH_AMMO_ARROW_RANK[ammoName] = i
end
end
MTH_AMMO_JUNKSHOT_SET = {}
for i = 1, table.getn(MTH_AMMO_JUNKSHOTS) do
local spellName = MTH_AMMO_JUNKSHOTS[i]
if spellName then
MTH_AMMO_JUNKSHOT_SET[spellName] = 1
end
end
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+912
View File
@@ -0,0 +1,912 @@
if not MTH then
error("MetaHunt core framework missing: api/core-framework.lua must load before api/core-scan-beasttraining.lua")
end
local function MTH_PT_SafeLower(text)
if text == nil then return "" end
return string.lower(tostring(text))
end
local function MTH_PT_GetGlobal(name)
if type(getglobal) == "function" then
return getglobal(name)
end
if _G then
return _G[name]
end
return nil
end
local MTH_PT_ApplyScan
local MTH_PT_AbilityHasPositiveRanks
local MTH_PT_PromoteUnrankedKnownAliases
local MTH_PT_VERBOSE_SCAN_LOGS = false
local function MTH_PT_GetStore()
if not MTH_CharSavedVariables then MTH_CharSavedVariables = {} end
if not MTH_CharSavedVariables.trainScan and MTH_CharSavedVariables.petTraining then
MTH_CharSavedVariables.trainScan = MTH_CharSavedVariables.petTraining
end
if not MTH_CharSavedVariables.trainScan then
MTH_CharSavedVariables.trainScan = {
spellMap = {},
hunterKnownMap = {},
lastScan = 0,
lastPrompt = 0,
hasCompletedPetScan = nil,
}
end
if not MTH_CharSavedVariables.trainScan.spellMap then
MTH_CharSavedVariables.trainScan.spellMap = {}
end
if not MTH_CharSavedVariables.trainScan.hunterKnownMap then
MTH_CharSavedVariables.trainScan.hunterKnownMap = {}
end
if MTH_CharSavedVariables.trainScan.hasCompletedPetScan == nil then
if tonumber(MTH_CharSavedVariables.trainScan.lastScan) and tonumber(MTH_CharSavedVariables.trainScan.lastScan) > 0 then
MTH_CharSavedVariables.trainScan.hasCompletedPetScan = 1
elseif MTH_CharSavedVariables.trainScan.spellMap then
for _ in pairs(MTH_CharSavedVariables.trainScan.spellMap) do
MTH_CharSavedVariables.trainScan.hasCompletedPetScan = 1
break
end
end
end
MTH_CharSavedVariables.petTraining = MTH_CharSavedVariables.trainScan
MTH_PT_PromoteUnrankedKnownAliases(MTH_CharSavedVariables.trainScan)
return MTH_CharSavedVariables.trainScan
end
function MTH_GetPetTrainingSnapshot()
return MTH_PT_GetStore()
end
function MTH_GetKnownPetSpellMap()
local store = MTH_PT_GetStore()
if type(store) ~= "table" then
return nil
end
return store.spellMap
end
local function MTH_PT_ParseRank(name, subText)
local rankNumber = nil
local sub = tostring(subText or "")
if sub ~= "" then
local _, _, subRank = string.find(sub, "(%d+)")
rankNumber = tonumber(subRank)
end
if not rankNumber then
local nm = tostring(name or "")
local _, _, nameRank = string.find(nm, "(%d+)")
rankNumber = tonumber(nameRank)
end
if not rankNumber then
local full = tostring(name or "") .. " " .. tostring(subText or "")
local _, _, fullRank = string.find(full, "(%d+)")
rankNumber = tonumber(fullRank)
end
if rankNumber then
return "Rank " .. tostring(rankNumber), rankNumber
end
return "", nil
end
local function MTH_PT_NormalizeName(name)
local label = tostring(name or "")
label = string.gsub(label, "^%s+", "")
label = string.gsub(label, "%s+$", "")
return label
end
local function MTH_PT_BaseAbility(name)
local base = MTH_PT_NormalizeName(name)
base = string.gsub(base, "%s*%([^%)]*%d+[^%)]*%)", "")
base = string.gsub(base, "%s*%([Rr]ank[^%)]*%)", "")
base = string.gsub(base, "%s+[Rr]ank%s+%d+", "")
base = string.gsub(base, "%s+%d+$", "")
base = string.gsub(base, "%s+$", "")
return base
end
local function MTH_PT_MakeToken(name, subText)
local base = MTH_PT_BaseAbility(name)
local rankText, rankNumber = MTH_PT_ParseRank(name, subText)
if rankNumber and rankNumber > 0 and not MTH_PT_AbilityHasPositiveRanks(base) then
rankText = ""
rankNumber = nil
end
if rankText ~= "" then
return MTH_PT_SafeLower(base) .. "#" .. tostring(rankNumber), base, rankText, rankNumber
end
return MTH_PT_SafeLower(base), base, "", nil
end
local function MTH_PT_HasAnyLearnedSpells()
local store = MTH_PT_GetStore()
local spellMap = store.spellMap
if not spellMap then return false end
for _ in pairs(spellMap) do
return true
end
return false
end
local function MTH_PT_IsBeastTrainingContextActive()
local frameNames = { "BeastTrainingFrame", "PetTrainingFrame", "CraftFrame" }
for i = 1, table.getn(frameNames) do
local frameName = frameNames[i]
local frame = (type(getglobal) == "function" and getglobal(frameName)) or (_G and _G[frameName])
if frame then
if frame.IsVisible and frame:IsVisible() then
return true
end
if frame.IsShown and frame:IsShown() then
return true
end
end
end
return false
end
local function MTH_PT_GetBeastTrainingRowCount()
if not MTH_PT_IsBeastTrainingContextActive() then
return 0
end
local getNumCrafts = MTH_PT_GetGlobal("GetNumCrafts")
if type(getNumCrafts) ~= "function" then
return 0
end
return tonumber(getNumCrafts()) or 0
end
function MTH_PT_HasBeastTrainingRows()
return MTH_PT_GetBeastTrainingRowCount() > 0
end
local function MTH_PT_DebugLog(line)
if not MTH_PT_VERBOSE_SCAN_LOGS then return end
local text = "[TRAINSCAN] " .. tostring(line or "")
MTH:Print(text, "debug")
end
local function MTH_PT_MakeTokenFromAbilityRank(abilityName, rankNumber)
local ability = MTH_PT_NormalizeName(abilityName)
local key = MTH_PT_SafeLower(ability)
if key == "" then return "", ability, "", nil end
local rank = tonumber(rankNumber)
if rank and rank > 0 and not MTH_PT_AbilityHasPositiveRanks(ability) then
rank = nil
end
if rank and rank > 0 then
return key .. "#" .. tostring(rank), ability, "Rank " .. tostring(rank), rank
end
return key, ability, "", nil
end
local function MTH_PT_AbilityExistsInData(baseAbility)
local ability = MTH_PT_NormalizeName(baseAbility)
if ability == "" then return false end
if not MTH_DS_PetSpells or not MTH_DS_PetSpells.byAbility then
return false
end
if MTH_DS_PetSpells.byAbility[ability] then
return true
end
for abilityName in pairs(MTH_DS_PetSpells.byAbility) do
if MTH_PT_SafeLower(abilityName) == MTH_PT_SafeLower(ability) then
return true
end
end
return false
end
MTH_PT_AbilityHasPositiveRanks = function(baseAbility)
local ability = MTH_PT_NormalizeName(baseAbility)
if ability == "" then return false end
if not MTH_DS_PetSpells or not MTH_DS_PetSpells.byAbility then
return false
end
local bucket = MTH_DS_PetSpells.byAbility[ability]
if not bucket then
for abilityName, row in pairs(MTH_DS_PetSpells.byAbility) do
if MTH_PT_SafeLower(abilityName) == MTH_PT_SafeLower(ability) then
bucket = row
break
end
end
end
if not (bucket and bucket.spells) then
return false
end
for i = 1, table.getn(bucket.spells) do
local spell = bucket.spells[i]
if spell then
local rank = tonumber(spell.rankNumber)
if not rank and spell.rank then
local _, _, parsed = string.find(tostring(spell.rank), "(%d+)")
rank = tonumber(parsed)
end
if rank and rank > 0 then
return true
end
end
end
return false
end
MTH_PT_PromoteUnrankedKnownAliases = function(store)
if not store or not store.hunterKnownMap or not store.spellMap then
return
end
for token, isKnown in pairs(store.hunterKnownMap) do
if isKnown == true then
local _, _, baseToken = string.find(tostring(token), "^(.-)#%d+$")
if baseToken and baseToken ~= "" then
local baseRow = store.spellMap[baseToken]
if baseRow and ((tonumber(baseRow.rankNumber) or 0) <= 0) then
store.hunterKnownMap[baseToken] = true
if baseRow.isKnown ~= true then
baseRow.isKnown = true
end
end
end
end
end
end
local function MTH_PT_MarkHunterKnownToken(token)
if token == nil or token == "" then
return false
end
local store = MTH_PT_GetStore()
local markToken = token
local _, _, baseToken = string.find(tostring(token), "^(.-)#%d+$")
if baseToken and baseToken ~= "" then
local baseRow = store.spellMap and store.spellMap[baseToken]
if baseRow and ((tonumber(baseRow.rankNumber) or 0) <= 0) then
markToken = baseToken
end
end
local changed = false
if store.hunterKnownMap[markToken] ~= true then
store.hunterKnownMap[markToken] = true
changed = true
end
if markToken ~= token and store.hunterKnownMap[token] ~= true then
store.hunterKnownMap[token] = true
changed = true
end
if store.spellMap and store.spellMap[markToken] and store.spellMap[markToken].isKnown ~= true then
store.spellMap[markToken].isKnown = true
changed = true
end
if markToken ~= token and store.spellMap and store.spellMap[token] and store.spellMap[token].isKnown ~= true then
store.spellMap[token].isKnown = true
changed = true
end
if changed and MTH_PT_DebugLog then
MTH_PT_DebugLog("hunter-known token promoted: " .. tostring(token))
end
return changed
end
local function MTH_PT_IsHunterTokenKnown(token)
if token == nil or token == "" then
return false
end
local store = MTH_PT_GetStore()
if store.hunterKnownMap and store.hunterKnownMap[token] == true then
return true
end
if store.spellMap and store.spellMap[token] and store.spellMap[token].isKnown == true then
return true
end
local _, _, baseToken = string.find(tostring(token), "^(.-)#%d+$")
if baseToken and baseToken ~= "" then
if store.hunterKnownMap and store.hunterKnownMap[baseToken] == true then
return true
end
if store.spellMap and store.spellMap[baseToken] and store.spellMap[baseToken].isKnown == true then
return true
end
end
return false
end
local function MTH_PT_ProcessLearnSystemMessage(rawMessage)
local message = tostring(rawMessage or "")
if message == "" then return false end
message = string.gsub(message, "|c%x%x%x%x%x%x%x%x", "")
message = string.gsub(message, "|r", "")
message = string.gsub(message, "^%s+", "")
message = string.gsub(message, "%s+$", "")
local _, _, abilityName, rankText = string.find(message, "^[Yy]ou have learned a new spell:%s*(.-)%s*%([Rr]ank%s*(%d+)%)%.?$")
if not abilityName or not rankText then
_, _, abilityName, rankText = string.find(message, "^[Yy]ou have learned a new spell:%s*(.-)%s*[Rr]ank%s*(%d+)%.?$")
end
if not abilityName or not rankText then
return false
end
local rankNumber = tonumber(rankText)
if not rankNumber then
return false
end
abilityName = MTH_PT_NormalizeName(abilityName)
if abilityName == "" then return false end
if not MTH_PT_AbilityExistsInData(abilityName) then
if MTH_PT_DebugLog then
MTH_PT_DebugLog("learn-msg ignored (not pet ability dataset): '" .. tostring(abilityName) .. "'")
end
return false
end
local token = MTH_PT_MakeTokenFromAbilityRank(abilityName, rankNumber)
if token == "" then return false end
local alreadyKnown = MTH_PT_IsHunterTokenKnown(token)
local changed = false
if not alreadyKnown then
changed = MTH_PT_MarkHunterKnownToken(token)
end
local petRecorded = false
if type(MTH_PETS_RecordCurrentPetLearnedAbility) == "function" then
petRecorded = MTH_PETS_RecordCurrentPetLearnedAbility(abilityName, rankNumber, "beasttraining:learn-msg") and true or false
end
if changed or petRecorded then
if MTH_PT_DebugLog then
MTH_PT_DebugLog("learn-msg captured: source='you have learned a new spell' ability='" .. tostring(abilityName) .. "' rank=" .. tostring(rankNumber) .. " token='" .. tostring(token) .. "' changed=" .. tostring(changed) .. " petRecorded=" .. tostring(petRecorded))
end
local label = tostring(abilityName)
if rankNumber and rankNumber > 0 then
label = label .. " (Rank " .. tostring(rankNumber) .. ")"
end
if MTH and MTH.Print then
if changed then
MTH:Print("Pet ability learned and recorded: " .. label .. ". It is now marked as known in MetaHunt.")
else
MTH:Print("Pet ability learned for current pet: " .. label .. ".")
end
end
if changed then
MTH_PT_ApplyScan("learn-msg")
end
if type(MTH_PSP_RequestScan) == "function" then
MTH_PSP_RequestScan("train-learn-msg")
end
elseif alreadyKnown and MTH_PT_DebugLog then
MTH_PT_DebugLog("learn-msg ignored (already known and no active pet row update): token='" .. tostring(token) .. "'")
end
return changed or petRecorded
end
local function MTH_PT_CollectKnownBeastTrainingMap()
if not MTH_PT_IsBeastTrainingContextActive() then
if MTH_PT_DebugLog then
MTH_PT_DebugLog("collect beast-training known skipped: explicit beast training context not active")
end
return {}
end
local getNumCrafts = MTH_PT_GetGlobal("GetNumCrafts")
local getCraftInfo = MTH_PT_GetGlobal("GetCraftInfo")
if type(getNumCrafts) ~= "function" or type(getCraftInfo) ~= "function" then
if MTH_PT_DebugLog then
MTH_PT_DebugLog("collect beast-training known unavailable: GetNumCrafts=" .. tostring(type(getNumCrafts)) .. ", GetCraftInfo=" .. tostring(type(getCraftInfo)))
end
return {}
end
local totalCrafts = tonumber(getNumCrafts()) or 0
local knownMap = {}
if totalCrafts <= 0 then
if MTH_PT_DebugLog then
MTH_PT_DebugLog("collect beast-training known empty: totalRows=0")
end
return knownMap
end
if MTH_PT_DebugLog then
MTH_PT_DebugLog("collect beast-training known start: totalRows=" .. tostring(totalCrafts))
end
for i = 1, totalCrafts do
local craftName, craftSubSpellName, craftType = getCraftInfo(i)
local typeText = tostring(craftType or "")
local token, base, parsedRankText, rankNumber = MTH_PT_MakeToken(craftName, craftSubSpellName)
if token ~= "" then
if MTH_PT_DebugLog then
MTH_PT_DebugLog("beast-training row=" .. tostring(i) .. " token='" .. tostring(token) .. "' name='" .. tostring(base or "") .. "' rank='" .. tostring(parsedRankText or "") .. "' rankNumber=" .. tostring(rankNumber) .. " type='" .. typeText .. "'")
end
knownMap[token] = {
name = MTH_PT_NormalizeName(base),
rank = parsedRankText,
rankNumber = rankNumber,
fullName = MTH_PT_NormalizeName(craftName),
subText = tostring(craftSubSpellName or ""),
isKnown = true,
scannedAt = time(),
rowType = typeText,
}
end
end
if MTH_PT_DebugLog then
local total = 0
for _ in pairs(knownMap) do total = total + 1 end
MTH_PT_DebugLog("collect beast-training known end: tokens=" .. tostring(total))
end
return knownMap
end
function MTH_PT_ApplyScan(triggerReason)
if MTH_PT_DebugLog then
MTH_PT_DebugLog("scan start: trigger=" .. tostring(triggerReason or ""))
end
local store = MTH_PT_GetStore()
local oldMap = store.spellMap
if not oldMap then oldMap = {} end
local hunterKnownMap = store.hunterKnownMap or {}
local knownMap = MTH_PT_CollectKnownBeastTrainingMap()
local knownCount = 0
for _ in pairs(knownMap) do knownCount = knownCount + 1 end
if knownCount <= 0 then
if MTH_PT_DebugLog then
MTH_PT_DebugLog("scan abort: beast-training known count=0")
end
return false, 0, 0, 0
end
for token in pairs(knownMap) do
hunterKnownMap[token] = true
end
for token, row in pairs(oldMap) do
if row and row.isKnown then
hunterKnownMap[token] = true
end
end
for token in pairs(knownMap) do
if hunterKnownMap[token] == nil then
hunterKnownMap[token] = false
end
end
store.hunterKnownMap = hunterKnownMap
local newMap = {}
if MTH_DS_PetSpells and MTH_DS_PetSpells.byAbility then
for abilityName, bucket in pairs(MTH_DS_PetSpells.byAbility) do
if bucket and bucket.spells then
for i = 1, table.getn(bucket.spells) do
local spell = bucket.spells[i]
if spell then
local rankNumber = tonumber(spell.rankNumber)
if not rankNumber and spell.rank then
local _, _, parsed = string.find(tostring(spell.rank), "(%d+)")
rankNumber = tonumber(parsed)
end
local token, baseName, rankText, parsedRankNumber = MTH_PT_MakeTokenFromAbilityRank(abilityName, rankNumber)
if token ~= "" then
local tpCost = tonumber(spell.trainingPointCost)
if not tpCost and spell.cost then
local _, _, parsedCost = string.find(tostring(spell.cost), "(%d+)")
tpCost = tonumber(parsedCost)
end
newMap[token] = {
name = baseName,
rank = rankText,
rankNumber = parsedRankNumber,
fullName = rankText ~= "" and (baseName .. " " .. rankText) or baseName,
subText = rankText,
serviceType = "datastore",
reqLevel = tonumber(spell.trainLevel),
isKnown = hunterKnownMap[token] and true or false,
isKnownPet = knownMap[token] and true or false,
scannedAt = time(),
trainingPointCost = tpCost,
learnMethod = spell.learnMethod,
icon = spell.icon,
}
end
end
end
end
end
end
for token, row in pairs(knownMap) do
if newMap[token] then
newMap[token].isKnownPet = true
newMap[token].scannedAt = time()
else
newMap[token] = {
name = row.name,
rank = row.rank,
rankNumber = row.rankNumber,
fullName = row.fullName,
subText = row.subText,
serviceType = "beast-training",
reqLevel = nil,
isKnown = hunterKnownMap[token] and true or false,
isKnownPet = true,
scannedAt = time(),
trainingPointCost = nil,
}
end
end
local discovered = 0
for _ in pairs(newMap) do discovered = discovered + 1 end
if discovered <= 0 then
return false, 0, 0, 0
end
local added = 0
local removed = 0
local addedDetails = {}
for token in pairs(newMap) do
if not oldMap[token] then
added = added + 1
local row = newMap[token]
local spellName = MTH_PT_NormalizeName((row and row.name) or "")
local rankText = tostring((row and row.rank) or "")
if spellName == "" then
spellName = MTH_PT_NormalizeName((row and row.fullName) or "")
end
if spellName ~= "" then
local label = spellName
if rankText ~= "" then
label = label .. " " .. rankText
end
table.insert(addedDetails, label)
end
end
end
for token in pairs(oldMap) do
if not newMap[token] then
removed = removed + 1
end
end
store.spellMap = newMap
store.lastScan = time()
store.hasCompletedPetScan = 1
MTH_CharSavedVariables.trainScan = store
MTH_CharSavedVariables.petTraining = store
local totalRanks = 0
local baseSet = {}
for _, row in pairs(newMap) do
totalRanks = totalRanks + 1
local base = MTH_PT_SafeLower(row and row.name)
if base ~= "" then baseSet[base] = true end
end
local totalBaselines = 0
for _ in pairs(baseSet) do totalBaselines = totalBaselines + 1 end
if triggerReason == "initial" then
MTH:Print("Initial train scan complete: " .. tostring(totalBaselines) .. " baselines / " .. tostring(totalRanks) .. " ranks recorded for this character.")
elseif added > 0 then
table.sort(addedDetails)
local detailsText = table.concat(addedDetails, ", ")
if detailsText ~= "" then
local msg = "New pet data recorded (" .. tostring(added) .. " new rank(s)): " .. detailsText .. ". Known totals for this character: " .. tostring(totalBaselines) .. " ability type(s), " .. tostring(totalRanks) .. " rank(s)."
MTH:Print(msg)
MTH:Print("INFO: " .. msg, "debug")
else
local msg = "New pet data recorded (" .. tostring(added) .. " new rank(s)). Known totals for this character: " .. tostring(totalBaselines) .. " ability type(s), " .. tostring(totalRanks) .. " rank(s)."
MTH:Print(msg)
MTH:Print("INFO: " .. msg, "debug")
end
end
return true, added, totalBaselines, totalRanks
end
local function MTH_PT_ShouldScanNow()
return MTH_PT_HasBeastTrainingRows()
end
MTH_PT_IsPetBookVisible = function()
local frameNames = { "BeastTrainingFrame", "PetTrainingFrame", "CraftFrame" }
for i = 1, table.getn(frameNames) do
local frameName = frameNames[i]
local frame = (type(getglobal) == "function" and getglobal(frameName)) or (_G and _G[frameName])
if frame then
if frame.IsVisible and frame:IsVisible() then
return true
end
if frame.IsShown and frame:IsShown() then
return true
end
end
end
return false
end
local function MTH_PT_RunDoubleScan(triggerBase)
local triggerText = tostring(triggerBase or "")
local feedbackOnOpen = (
triggerText == "petbook-open"
or triggerText == "craft-open"
or triggerText == "craft-update"
or triggerText == "beast-training-rows-open"
or triggerText == "petbook-visible-poll"
)
if not MTH_PT_ShouldScanNow() then
if MTH_PT_DebugLog then
MTH_PT_DebugLog("scan skipped: no beast-training rows, trigger=" .. tostring(triggerBase or ""))
end
if feedbackOnOpen and MTH and MTH.Print then
MTH:Print("Beast Training opened, but scan did not run yet (no scannable rows detected).")
end
return false
end
local t0 = time()
local ok, added, baselines, ranks = MTH_PT_ApplyScan(triggerText .. ":single")
local t1 = time()
if MTH_PT_DebugLog then
MTH_PT_DebugLog("scan complete: trigger=" .. tostring(triggerBase or "") .. " ok=" .. tostring(ok and true or false) .. " dt=" .. tostring((t1 or 0) - (t0 or 0)) .. "s")
end
if feedbackOnOpen and MTH and MTH.Print then
if ok then
local addedCount = tonumber(added) or 0
local msg = "Beast Training scan complete: " .. tostring(baselines or 0) .. " baselines / " .. tostring(ranks or 0) .. " ranks."
if addedCount > 0 then
msg = msg .. " Added +" .. tostring(addedCount) .. " new rank(s)."
end
MTH:Print(msg)
else
MTH:Print("Beast Training scan attempted, but no data was recorded.")
end
end
return ok and true or false
end
local MTH_PetTrainingFrame = nil
local MTH_PT_PetBookWasVisible = false
local MTH_PT_PetBookPollElapsed = 0
local MTH_PT_TrainingRowsWerePresent = false
local MTH_PT_LastTrainingRowCount = 0
local MTH_PT_LastAutoDoubleScanAt = 0
local function MTH_PT_RunDoubleScanThrottled(triggerBase, minIntervalSeconds)
local now = tonumber(time()) or 0
local minInterval = tonumber(minIntervalSeconds) or 0
if minInterval > 0 and (now - (MTH_PT_LastAutoDoubleScanAt or 0)) < minInterval then
return false
end
MTH_PT_LastAutoDoubleScanAt = now
return MTH_PT_RunDoubleScan(triggerBase)
end
local function MTH_PT_OnEvent(_, evt, eventArg1)
evt = evt or event
eventArg1 = eventArg1 or arg1
if not evt then
return
end
if evt == "PLAYER_ENTERING_WORLD" then
if UnitClass then
local _, classToken = UnitClass("player")
if classToken ~= "HUNTER" then return end
end
if not MTH_PT_HasAnyLearnedSpells() then
local store = MTH_PT_GetStore()
local now = time()
if not store.lastPrompt or (now - store.lastPrompt) > 60 then
store.lastPrompt = now
MTH:Print("No Beast Training scan found for this character. Open Beast Training (right panel) to record pet spell ranks.")
end
end
return
end
if evt == "LEARNED_SPELL_IN_TAB" then
if MTH_PT_ShouldScanNow() then
MTH_PT_RunDoubleScan("petbook-visible:" .. tostring(evt))
end
return
end
if evt == "PET_TRAINING_SHOW" then
MTH_PT_RunDoubleScan("petbook-open")
return
end
if evt == "CRAFT_SHOW" then
MTH_PT_RunDoubleScanThrottled("craft-open", 1)
return
end
if evt == "CRAFT_UPDATE" then
if MTH_PT_ShouldScanNow() then
MTH_PT_RunDoubleScanThrottled("craft-update", 1)
end
return
end
if evt == "SPELLS_CHANGED" then
if MTH_PT_ShouldScanNow() then
MTH_PT_RunDoubleScan("petbook-visible:" .. tostring(evt))
end
return
end
if evt == "UNIT_PET" or evt == "PET_BAR_UPDATE" then
if evt == "UNIT_PET" then
local unit = eventArg1
if unit ~= "player" then return end
end
if MTH_PT_ShouldScanNow() then
MTH_PT_RunDoubleScan("petbook-visible:" .. tostring(evt))
end
return
end
if evt == "CHAT_MSG_SYSTEM" then
MTH_PT_ProcessLearnSystemMessage(eventArg1)
return
end
end
function MTH_PT_InitService()
if MTH and MTH.ApplyClassGate and MTH:ApplyClassGate("trainscan-init") then
return
end
if MTH_PetTrainingFrame then
return
end
MTH_PetTrainingFrame = CreateFrame("Frame", "MTHPetTrainingScanFrame")
MTH_PetTrainingFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
MTH_PetTrainingFrame:RegisterEvent("LEARNED_SPELL_IN_TAB")
MTH_PetTrainingFrame:RegisterEvent("PET_TRAINING_SHOW")
MTH_PetTrainingFrame:RegisterEvent("SPELLS_CHANGED")
MTH_PetTrainingFrame:RegisterEvent("UNIT_PET")
MTH_PetTrainingFrame:RegisterEvent("PET_BAR_UPDATE")
MTH_PetTrainingFrame:RegisterEvent("CHAT_MSG_SYSTEM")
MTH_PetTrainingFrame:RegisterEvent("CRAFT_SHOW")
MTH_PetTrainingFrame:RegisterEvent("CRAFT_UPDATE")
MTH_PetTrainingFrame:SetScript("OnEvent", MTH_PT_OnEvent)
MTH_PetTrainingFrame:SetScript("OnUpdate", function(_, elapsed)
elapsed = elapsed or arg1
MTH_PT_PetBookPollElapsed = (MTH_PT_PetBookPollElapsed or 0) + (elapsed or 0)
if MTH_PT_PetBookPollElapsed < 0.20 then
return
end
MTH_PT_PetBookPollElapsed = 0
local rowCount = MTH_PT_GetBeastTrainingRowCount()
local hasRows = rowCount > 0
if hasRows and not MTH_PT_TrainingRowsWerePresent then
MTH_PT_RunDoubleScanThrottled("beast-training-rows-open", 1)
end
if hasRows and rowCount ~= (MTH_PT_LastTrainingRowCount or 0) then
MTH_PT_RunDoubleScanThrottled("beast-training-rows-changed", 1)
end
if hasRows then
MTH_PT_RunDoubleScanThrottled("beast-training-rows-present", 8)
end
MTH_PT_TrainingRowsWerePresent = hasRows
MTH_PT_LastTrainingRowCount = rowCount
local visible = MTH_PT_IsPetBookVisible()
if visible and not MTH_PT_PetBookWasVisible then
MTH_PT_RunDoubleScanThrottled("petbook-visible-poll", 1)
end
MTH_PT_PetBookWasVisible = visible and true or false
end)
end
function MTH_PT_ShutdownService(_reason)
if not MTH_PetTrainingFrame then
return
end
MTH_PetTrainingFrame:UnregisterAllEvents()
MTH_PetTrainingFrame:SetScript("OnEvent", nil)
MTH_PetTrainingFrame:SetScript("OnUpdate", nil)
MTH_PetTrainingFrame = nil
MTH_PT_PetBookWasVisible = false
MTH_PT_PetBookPollElapsed = 0
MTH_PT_TrainingRowsWerePresent = false
MTH_PT_LastTrainingRowCount = 0
MTH_PT_LastAutoDoubleScanAt = 0
end
function MTH_CommandPetScan()
local function requestBeastTrainingOpen()
if MTH_PT_IsPetBookVisible and MTH_PT_IsPetBookVisible() then
return true
end
local bookTypePet = MTH_PT_GetGlobal("BOOKTYPE_PET")
local openSpellBook = MTH_PT_GetGlobal("OpenSpellBook")
if type(openSpellBook) == "function" and bookTypePet then
openSpellBook(bookTypePet)
end
if MTH_PT_IsPetBookVisible and MTH_PT_IsPetBookVisible() then
return true
end
local toggleSpellBook = MTH_PT_GetGlobal("ToggleSpellBook")
if type(toggleSpellBook) == "function" then
if bookTypePet then
toggleSpellBook(bookTypePet)
else
toggleSpellBook()
end
end
return MTH_PT_IsPetBookVisible and MTH_PT_IsPetBookVisible() or false
end
if UnitClass then
local _, classToken = UnitClass("player")
if classToken ~= "HUNTER" then
MTH:Print("Train scan is hunter-only.")
return
end
end
if not MTH_PT_ShouldScanNow() then
local opened = requestBeastTrainingOpen()
if not MTH_PT_ShouldScanNow() then
if opened then
MTH:Print("Beast Training is open, but no scannable rows were detected yet. Try once more after the list fully loads.")
else
MTH:Print("Could not detect Beast Training rows. Open Beast Training (right panel) and run /mth trainscan again.")
end
return
end
end
local ok, added, baselines, ranks = MTH_PT_ApplyScan("manual")
if not ok then
MTH:Print("Train scan failed. Keep Beast Training open and try /mth trainscan again.")
return
end
MTH:Print("Train scan saved for this character: " .. tostring(baselines) .. " baselines / " .. tostring(ranks) .. " ranks.")
if (tonumber(added) or 0) > 0 then
MTH:Print("Newly recorded ranks: +" .. tostring(added))
end
end
function MTH_CommandTrainScan()
return MTH_CommandPetScan()
end
MTH_TR_InitService = MTH_PT_InitService
MTH_TR_ShutdownService = MTH_PT_ShutdownService
+295
View File
@@ -0,0 +1,295 @@
if not MTH then
error("MetaHunt core framework missing: api/core-framework.lua must load before api/core-scan-spellbookpet.lua")
end
local MTH_PS_Frame = nil
local MTH_PS_LastScanAt = 0
local MTH_PS_VERBOSE_LOGS = false
local MTH_PS_ForceScanUntil = 0
local MTH_PS_ForceScanBudget = 0
local function MTH_PS_ClearForceWindow()
MTH_PS_ForceScanUntil = 0
MTH_PS_ForceScanBudget = 0
end
local function MTH_PS_ArmForceWindow(durationSeconds, maxScans)
local now = tonumber(time()) or 0
local duration = tonumber(durationSeconds) or 4
local budget = tonumber(maxScans) or 6
if duration < 1 then duration = 1 end
if budget < 1 then budget = 1 end
MTH_PS_ForceScanUntil = now + duration
MTH_PS_ForceScanBudget = budget
end
local function MTH_PS_IsFollowupEventTrigger(trigger)
local t = tostring(trigger or "")
return t == "event:UNIT_PET"
or t == "event:PET_BAR_UPDATE"
or t == "event:SPELLS_CHANGED"
or t == "event:LEARNED_SPELL_IN_TAB"
end
local function MTH_PS_IsPriorityTrigger(trigger)
local t = tostring(trigger or "")
return t == "unit-pet-current-change" or t == "refresh-current-change"
end
local function MTH_PS_DebugPrint(message, force)
return
end
local function MTH_PS_TraceTrigger(trigger, accepted, reason)
return
end
local function MTH_PS_SafeLower(text)
if text == nil then return "" end
return string.lower(tostring(text))
end
local function MTH_PS_GetGlobal(name)
if type(getglobal) == "function" then
return getglobal(name)
end
if _G then
return _G[name]
end
return nil
end
local function MTH_PS_GetStore()
if not MTH_CharSavedVariables then MTH_CharSavedVariables = {} end
if type(MTH_CharSavedVariables.petSpellScan) ~= "table" then
MTH_CharSavedVariables.petSpellScan = {
lastScan = 0,
lastTrigger = "",
lastCount = 0,
lastHash = "",
lastChangeAt = 0,
}
end
return MTH_CharSavedVariables.petSpellScan
end
local function MTH_PS_ParseRank(name, subText)
local rankNumber = nil
local sub = tostring(subText or "")
if sub ~= "" then
local _, _, subRank = string.find(sub, "(%d+)")
rankNumber = tonumber(subRank)
end
if not rankNumber then
local _, _, nameRank = string.find(tostring(name or ""), "(%d+)")
rankNumber = tonumber(nameRank)
end
if rankNumber and rankNumber > 0 then
return rankNumber
end
return nil
end
local function MTH_PS_MakeToken(name, rankNumber)
local base = MTH_PS_SafeLower(tostring(name or ""))
if base == "" then return "" end
local rank = tonumber(rankNumber)
if rank and rank > 0 then
return base .. "#" .. tostring(rank)
end
return base
end
local function MTH_PS_CollectPetSpellbookRows()
local getSpellName = MTH_PS_GetGlobal("GetSpellName")
local getSpellTexture = MTH_PS_GetGlobal("GetSpellTexture")
local isPassiveSpell = MTH_PS_GetGlobal("IsPassiveSpell")
local bookTypePet = MTH_PS_GetGlobal("BOOKTYPE_PET")
if type(getSpellName) ~= "function" or not bookTypePet then
MTH_PS_DebugPrint("Collect rows aborted: GetSpellName=" .. tostring(type(getSpellName)) .. " BOOKTYPE_PET=" .. tostring(bookTypePet), true)
return {}
end
local rows = {}
for slot = 1, 200 do
local name, subText = getSpellName(slot, bookTypePet)
if not name or name == "" then
if slot == 1 then
MTH_PS_DebugPrint("Slot 1 returned empty name (pet spellbook not ready/visible in this client context)", true)
end
break
end
local rankNumber = MTH_PS_ParseRank(name, subText)
local token = MTH_PS_MakeToken(name, rankNumber)
table.insert(rows, {
slot = slot,
name = tostring(name or ""),
rank = rankNumber,
icon = (type(getSpellTexture) == "function") and getSpellTexture(slot, bookTypePet) or nil,
isPassive = (type(isPassiveSpell) == "function") and (isPassiveSpell(slot, bookTypePet) and true or false) or false,
token = token,
})
end
MTH_PS_DebugPrint("Collected rows: " .. tostring(table.getn(rows)), true)
return rows
end
local function MTH_PS_HashRows(rows)
if type(rows) ~= "table" or table.getn(rows) == 0 then
return ""
end
local parts = {}
for i = 1, table.getn(rows) do
local row = rows[i]
parts[i] = tostring(row.token or "") .. ":" .. tostring(row.slot or "")
end
return table.concat(parts, "|")
end
function MTH_PS_ScanNow(trigger)
if UnitClass then
local _, classToken = UnitClass("player")
if classToken ~= "HUNTER" then
return false, 0, false
end
end
local rows = MTH_PS_CollectPetSpellbookRows()
local count = table.getn(rows)
local hash = MTH_PS_HashRows(rows)
local store = MTH_PS_GetStore()
local changed = (hash ~= tostring(store.lastHash or ""))
MTH_PS_DebugPrint("Scan trigger=" .. tostring(trigger or "auto") .. " count=" .. tostring(count) .. " changed=" .. tostring(changed and true or false), true)
store.lastScan = time()
store.lastTrigger = tostring(trigger or "auto")
store.lastCount = count
if changed then
store.lastHash = hash
store.lastChangeAt = store.lastScan
end
if type(MTH_PETS_RecordCurrentPetSpellbookSnapshot) == "function" then
local persistedCount, updated = MTH_PETS_RecordCurrentPetSpellbookSnapshot(rows, "petspellscan:" .. tostring(trigger or "auto"))
if updated == false then
MTH_PS_DebugPrint("Snapshot skipped; preserved existing count=" .. tostring(persistedCount or 0), true)
else
MTH_PS_DebugPrint("Snapshot persisted to current pet row", true)
end
else
MTH_PS_DebugPrint("Snapshot function missing: MTH_PETS_RecordCurrentPetSpellbookSnapshot", true)
end
MTH_PS_LastScanAt = store.lastScan
return true, count, changed
end
function MTH_PSP_RequestScan(trigger, minIntervalSeconds)
local triggerText = tostring(trigger or "request")
local now = tonumber(time()) or 0
local minInterval = tonumber(minIntervalSeconds) or 1
local bypassThrottle = false
local bypassReason = ""
if MTH_PS_IsPriorityTrigger(triggerText) then
bypassThrottle = true
bypassReason = "priority"
MTH_PS_ArmForceWindow(4, 6)
elseif MTH_PS_IsFollowupEventTrigger(triggerText)
and MTH_PS_ForceScanBudget > 0
and now <= (tonumber(MTH_PS_ForceScanUntil) or 0)
then
bypassThrottle = true
bypassReason = "followup"
MTH_PS_ForceScanBudget = MTH_PS_ForceScanBudget - 1
end
if (not bypassThrottle) and minInterval > 0 and (now - (MTH_PS_LastScanAt or 0)) < minInterval then
MTH_PS_TraceTrigger(triggerText, false, "throttled")
return false
end
if bypassThrottle then
MTH_PS_TraceTrigger(triggerText, true, bypassReason)
else
MTH_PS_TraceTrigger(triggerText, true, "queued")
end
local ok, count = MTH_PS_ScanNow(triggerText)
if ok and tonumber(count) and tonumber(count) > 0 then
MTH_PS_ClearForceWindow()
elseif MTH_PS_IsPriorityTrigger(triggerText) and type(UnitExists) == "function" and UnitExists("pet") then
MTH_PS_ArmForceWindow(4, 6)
end
if not ok then
MTH_PS_TraceTrigger(triggerText, false, "scan-failed")
end
return ok and true or false
end
local function MTH_PS_OnEvent(_, evt, eventArg1)
evt = evt or event
eventArg1 = eventArg1 or arg1
if not evt then return end
if evt == "UNIT_PET" and eventArg1 ~= "player" then
return
end
if evt == "PLAYER_ENTERING_WORLD" or evt == "UNIT_PET" or evt == "PET_BAR_UPDATE" or evt == "SPELLS_CHANGED" or evt == "LEARNED_SPELL_IN_TAB" then
MTH_PSP_RequestScan("event:" .. tostring(evt), 1)
end
end
function MTH_PS_InitService()
if MTH and MTH.ApplyClassGate and MTH:ApplyClassGate("petspellscan-init") then
return
end
if MTH_PS_Frame then
return
end
MTH_PS_Frame = CreateFrame("Frame", "MTHPetSpellScanFrame")
MTH_PS_Frame:RegisterEvent("PLAYER_ENTERING_WORLD")
MTH_PS_Frame:RegisterEvent("UNIT_PET")
MTH_PS_Frame:RegisterEvent("PET_BAR_UPDATE")
MTH_PS_Frame:RegisterEvent("SPELLS_CHANGED")
MTH_PS_Frame:RegisterEvent("LEARNED_SPELL_IN_TAB")
MTH_PS_Frame:SetScript("OnEvent", MTH_PS_OnEvent)
end
function MTH_PS_ShutdownService(_reason)
if not MTH_PS_Frame then
return
end
MTH_PS_Frame:UnregisterAllEvents()
MTH_PS_Frame:SetScript("OnEvent", nil)
MTH_PS_Frame = nil
MTH_PS_LastScanAt = 0
MTH_PS_ClearForceWindow()
end
function MTH_CommandPetSpellScan()
MTH_PS_VERBOSE_LOGS = true
local petName = UnitName and UnitName("pet") or nil
local hasPet = petName and petName ~= ""
local _, playerClass = UnitClass and UnitClass("player") or nil, nil
if UnitClass then
_, playerClass = UnitClass("player")
end
MTH_PS_DebugPrint("Manual command start: class=" .. tostring(playerClass) .. " pet=" .. tostring(petName or "<none>") .. " hasPet=" .. tostring(hasPet and true or false), true)
local bookTypePet = MTH_PS_GetGlobal("BOOKTYPE_PET")
local getSpellName = MTH_PS_GetGlobal("GetSpellName")
MTH_PS_DebugPrint("API check: BOOKTYPE_PET=" .. tostring(bookTypePet) .. " GetSpellNameType=" .. tostring(type(getSpellName)), true)
local ok, count, changed = MTH_PS_ScanNow("manual")
if not ok then
MTH:Print("Pet spellbook scan failed.")
MTH_PS_VERBOSE_LOGS = false
return
end
MTH:Print("Pet spellbook scan: " .. tostring(count or 0) .. " spell(s). changed=" .. tostring(changed and true or false))
local store = MTH_PS_GetStore()
MTH_PS_DebugPrint("Store status: lastScan=" .. tostring(store.lastScan or 0) .. " lastCount=" .. tostring(store.lastCount or 0) .. " lastTrigger=" .. tostring(store.lastTrigger or "") .. " lastChangeAt=" .. tostring(store.lastChangeAt or 0), true)
MTH_PS_VERBOSE_LOGS = false
end
File diff suppressed because it is too large Load Diff
+264
View File
@@ -0,0 +1,264 @@
if not MTH then
error("MetaHunt core framework missing: api/core-framework.lua must load before api/core.lua")
end
local function MTH_PT_GetGlobal(name)
if type(getglobal) == "function" then
return getglobal(name)
end
if _G then
return _G[name]
end
return nil
end
local MTH_ItemLinkCacheTooltip = nil
local function MTH_GetQualityHexColor(quality)
local q = tonumber(quality)
if type(GetItemQualityColor) == "function" and q then
local _, _, _, colorCode = GetItemQualityColor(q)
if colorCode and colorCode ~= "" then
local hex = string.gsub(tostring(colorCode), "|c", "")
hex = string.gsub(hex, "|r", "")
hex = string.gsub(hex, "^#", "")
if string.len(hex) == 8 then return hex end
if string.len(hex) == 6 then return "ff" .. hex end
end
end
return "ffffffff"
end
function MTH_PrimeItemCache(itemId)
local id = tonumber(itemId)
if not id or id <= 0 then return false end
if type(GetItemInfo) == "function" and GetItemInfo(id) then return true end
if not MTH_ItemLinkCacheTooltip then
MTH_ItemLinkCacheTooltip = CreateFrame("GameTooltip", "MTH_ItemLinkCacheTooltip", UIParent, "GameTooltipTemplate")
if not MTH_ItemLinkCacheTooltip then return false end
MTH_ItemLinkCacheTooltip:SetOwner(UIParent, "ANCHOR_NONE")
end
MTH_ItemLinkCacheTooltip:ClearLines()
MTH_ItemLinkCacheTooltip:SetHyperlink("item:" .. tostring(id) .. ":0:0:0")
return type(GetItemInfo) == "function" and (GetItemInfo(id) ~= nil) or false
end
function MTH_GetClickableItemLink(itemId, fallbackName, tryPrimeCache)
local id = tonumber(itemId)
if not id then return nil end
local itemName = nil
local itemLink = nil
local itemQuality = nil
if type(GetItemInfo) == "function" then
itemName, itemLink, itemQuality = GetItemInfo(id)
if (not itemName or itemName == "") and tryPrimeCache then
MTH_PrimeItemCache(id)
itemName, itemLink, itemQuality = GetItemInfo(id)
end
end
local displayName = tostring(itemName or fallbackName or ("Item " .. tostring(id)))
local coreLink = nil
if itemLink and itemLink ~= "" then
local _, _, parsedCore = string.find(tostring(itemLink), "|H(item:[^|]+)|h")
if parsedCore and parsedCore ~= "" then
coreLink = parsedCore
elseif string.find(tostring(itemLink), "^item:", 1, true) then
coreLink = tostring(itemLink)
end
end
if not coreLink then
coreLink = "item:" .. tostring(id) .. ":0:0:0"
end
local colorHex = MTH_GetQualityHexColor(itemQuality)
return "|c" .. tostring(colorHex) .. "|H" .. tostring(coreLink) .. "|h[" .. tostring(displayName) .. "]|h|r"
end
function MTH_InsertLinkToChat(link)
if type(link) ~= "string" or link == "" then return false end
local wimEditBox = MTH_PT_GetGlobal("WIM_EditBoxInFocus")
if wimEditBox and type(wimEditBox.Insert) == "function" then
wimEditBox:Insert(link)
return true
end
local chatEditBox = MTH_PT_GetGlobal("ChatFrameEditBox")
if not chatEditBox then return false end
if type(chatEditBox.IsVisible) == "function" and chatEditBox:IsVisible() and type(chatEditBox.Insert) == "function" then
chatEditBox:Insert(link)
return true
end
if type(chatEditBox.Show) == "function" then chatEditBox:Show() end
if type(chatEditBox.Insert) == "function" then
chatEditBox:Insert(link)
elseif type(chatEditBox.SetText) == "function" then
chatEditBox:SetText(link)
if type(chatEditBox.HighlightText) == "function" then
chatEditBox:HighlightText()
end
end
return true
end
-- Slash command handler (original, from backup)
MTH:RegisterSlashAliases("MTH")
local function MTH_CommandBook()
if type(MTH_OpenHunterBook) == "function" then
MTH_OpenHunterBook()
elseif type(MTH_ToggleHunterBook) == "function" then
MTH_ToggleHunterBook()
else
MTH:Print("Hunter Book is not available yet")
end
end
local function MTH_CommandOptions()
if MTH_OpenOptions then
MTH_OpenOptions("General")
else
MTH:Print("Options window is not available yet")
end
end
local function MTH_CommandPetSpellScanFallback()
local getSpellName = (type(getglobal) == "function" and getglobal("GetSpellName")) or (_G and _G["GetSpellName"])
local getSpellTexture = (type(getglobal) == "function" and getglobal("GetSpellTexture")) or (_G and _G["GetSpellTexture"])
local isPassiveSpell = (type(getglobal) == "function" and getglobal("IsPassiveSpell")) or (_G and _G["IsPassiveSpell"])
local bookTypePet = (type(getglobal) == "function" and getglobal("BOOKTYPE_PET")) or (_G and _G["BOOKTYPE_PET"])
local petName = UnitName and UnitName("pet") or nil
local pets = (type(MTH_PETS_GetRootStore) == "function") and MTH_PETS_GetRootStore() or nil
local currentPetId = (type(pets) == "table" and type(pets.currentPet) == "table") and pets.currentPet.id or nil
if type(getSpellName) ~= "function" or not bookTypePet then
return false, 0
end
local rows = {}
for slot = 1, 200 do
local name, subText = getSpellName(slot, bookTypePet)
if not name or name == "" then
break
end
local rankNumber = nil
if tostring(subText or "") ~= "" then
local _, _, r = string.find(tostring(subText), "(%d+)")
rankNumber = tonumber(r)
end
if not rankNumber then
local _, _, r2 = string.find(tostring(name), "(%d+)")
rankNumber = tonumber(r2)
end
local token = string.lower(tostring(name or ""))
if rankNumber and rankNumber > 0 then
token = token .. "#" .. tostring(rankNumber)
end
table.insert(rows, {
slot = slot,
name = tostring(name or ""),
rank = rankNumber,
icon = (type(getSpellTexture) == "function") and getSpellTexture(slot, bookTypePet) or nil,
isPassive = (type(isPassiveSpell) == "function") and (isPassiveSpell(slot, bookTypePet) and true or false) or false,
token = token,
})
end
local count = table.getn(rows)
if count > 0 then
local preview = {}
local maxPreview = math.min(6, count)
for i = 1, maxPreview do
local row = rows[i]
if type(row) == "table" then
local line = tostring(row.name or "?")
local rank = tonumber(row.rank)
if rank and rank > 0 then
line = line .. " R" .. tostring(rank)
end
table.insert(preview, line)
end
end
end
if type(MTH_PETS_RecordCurrentPetSpellbookSnapshot) == "function" then
local persisted, updated = MTH_PETS_RecordCurrentPetSpellbookSnapshot(rows, "petspellscan:core-fallback")
return true, tonumber(persisted) or count
else
return false, count
end
end
if type(MTH_PS_ScanNow) ~= "function" then
function MTH_PS_ScanNow(trigger)
local ok, count = MTH_CommandPetSpellScanFallback()
return ok and true or false, tonumber(count) or 0, false
end
end
if type(MTH_PSP_RequestScan) ~= "function" then
local mthPsFallbackLastScan = 0
function MTH_PSP_RequestScan(trigger, minIntervalSeconds)
local now = tonumber(time()) or 0
local minInterval = tonumber(minIntervalSeconds) or 1
local triggerText = tostring(trigger or "request")
if minInterval > 0 and (now - mthPsFallbackLastScan) < minInterval then
return false
end
local ok = MTH_PS_ScanNow(triggerText)
if ok then
mthPsFallbackLastScan = now
end
return ok and true or false
end
end
if type(MTH_CommandPetSpellScan) ~= "function" then
function MTH_CommandPetSpellScan()
local ok, count = MTH_PS_ScanNow("manual-core-fallback")
MTH:Print("Pet spellbook scan: " .. tostring(count or 0) .. " spell(s). ok=" .. tostring(ok and true or false))
end
end
function SlashCmdList.MTH(msg, editbox)
if MTH and MTH.ApplyClassGate and MTH:ApplyClassGate("slash") then
return
end
msg = msg or ""
msg = string.gsub(tostring(msg), "^%s+", "")
msg = string.gsub(msg, "%s+$", "")
local lowerMsg = string.lower(msg)
if msg == "" then
MTH:Print("Available: /mth options, /mth book")
elseif lowerMsg == "options" then
MTH_CommandOptions()
elseif lowerMsg == "book" or lowerMsg == "hunterbook" then
MTH_CommandBook()
else
MTH:Print("Unknown command: " .. tostring(msg))
MTH:Print("Available: /mth options, /mth book")
end
end
-- Initialize on load
MTH:InitSavedVariables()
if type(MTH_ST_InitService) == "function" then
MTH_ST_InitService()
end
if type(MTH_TR_InitService) == "function" then
MTH_TR_InitService()
elseif type(MTH_PT_InitService) == "function" then
MTH_PT_InitService()
end
if type(MTH_PS_InitService) == "function" then
MTH_PS_InitService()
end
+374
View File
@@ -0,0 +1,374 @@
------------------------------------------------------
-- MetaHunt: Debug Frame
-- Displays errors and debug messages in a scrollable frame
------------------------------------------------------
MTH_DebugFrame = {
frame = nil,
scrollFrame = nil,
scrollText = nil,
errors = {},
maxErrors = 500,
isVisible = false,
initialized = false,
captureCount = 0,
infoCount = 0
}
function MTH_DebugFrame:Initialize()
if self.initialized then
return
end
self.initialized = true
-- Create main frame
self.frame = CreateFrame("Frame", "MTH_DebugFrameWindow", UIParent)
self.frame:SetWidth(800)
self.frame:SetHeight(400)
self.frame:SetPoint("CENTER", UIParent, "CENTER")
self.frame:SetFrameStrata("DIALOG")
self.frame:SetBackdrop({
bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
tile = true,
tileSize = 32,
edgeSize = 32,
insets = { left = 11, right = 12, top = 12, bottom = 11 }
})
self.frame:SetBackdropColor(0, 0, 0, 0.8)
self.frame:SetMovable(true)
self.frame:EnableMouse(true)
self.frame:RegisterForDrag("LeftButton")
self.frame:SetScript("OnDragStart", function() self.frame:StartMoving() end)
self.frame:SetScript("OnDragStop", function() self.frame:StopMovingOrSizing() end)
if UISpecialFrames then
local already = false
for i = 1, table.getn(UISpecialFrames) do
if UISpecialFrames[i] == "MTH_DebugFrameWindow" then
already = true
break
end
end
if not already then
table.insert(UISpecialFrames, "MTH_DebugFrameWindow")
end
end
-- Title
local title = self.frame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
title:SetPoint("TOPLEFT", self.frame, "TOPLEFT", 20, -15)
title:SetText("MetaHunt Debug - Errors & Messages")
-- Close button
local closeButton = CreateFrame("Button", nil, self.frame, "UIPanelButtonTemplate")
closeButton:SetPoint("TOPRIGHT", self.frame, "TOPRIGHT", -8, -8)
closeButton:SetWidth(22)
closeButton:SetHeight(22)
closeButton:SetText("X")
closeButton:SetScript("OnClick", function() MTH_DebugFrame:Hide() end)
-- Clear button
local clearButton = CreateFrame("Button", nil, self.frame, "GameMenuButtonTemplate")
clearButton:SetPoint("BOTTOMLEFT", self.frame, "BOTTOMLEFT", 10, 10)
clearButton:SetWidth(100)
clearButton:SetHeight(25)
clearButton:SetText("Clear")
clearButton:SetScript("OnClick", function() MTH_DebugFrame:Clear() end)
-- Select-all button
local copyButton = CreateFrame("Button", nil, self.frame, "GameMenuButtonTemplate")
copyButton:SetPoint("LEFT", clearButton, "RIGHT", 10, 0)
copyButton:SetWidth(100)
copyButton:SetHeight(25)
copyButton:SetText("Select All")
copyButton:SetScript("OnClick", function() MTH_DebugFrame:SelectAllText() end)
-- Scroll frame + text content with proper vertical scrollbar
self.scrollFrame = CreateFrame("ScrollFrame", "MTH_DebugScrollFrame", self.frame, "UIPanelScrollFrameTemplate")
self.scrollFrame:SetPoint("TOPLEFT", self.frame, "TOPLEFT", 15, -50)
self.scrollFrame:SetPoint("BOTTOMRIGHT", self.frame, "BOTTOMRIGHT", -35, 45)
self.scrollText = CreateFrame("EditBox", "MTH_DebugScrollText", self.scrollFrame)
self.scrollText:SetMultiLine(true)
self.scrollText:SetMaxLetters(999999)
self.scrollText:SetFont("Fonts\\FRIZQT__.TTF", 11, "")
self.scrollText:SetTextColor(0.8, 0.8, 0.8, 1)
self.scrollText:SetWidth(730)
self.scrollText:SetHeight(1)
self.scrollText:EnableMouse(true)
self.scrollText:SetScript("OnCursorChanged", function()
if not MTH_DebugFrame.scrollFrame then
return
end
local width = MTH_DebugFrame.scrollFrame:GetWidth()
if width and width > 20 then
MTH_DebugFrame.scrollText:SetWidth(width - 20)
end
end)
self.scrollText:SetScript("OnEscapePressed", function() self.scrollText:ClearFocus() end)
self.scrollText:SetAutoFocus(false)
self.scrollText:EnableMouseWheel(true)
self.scrollText:SetScript("OnMouseWheel", function()
if not MTH_DebugFrame.scrollFrame then
return
end
local curr = MTH_DebugFrame.scrollFrame:GetVerticalScroll()
local step = 24
if arg1 and arg1 > 0 then
MTH_DebugFrame.scrollFrame:SetVerticalScroll(curr - step)
else
MTH_DebugFrame.scrollFrame:SetVerticalScroll(curr + step)
end
end)
self.scrollText:SetScript("OnTextChanged", nil)
self.scrollFrame:SetScrollChild(self.scrollText)
-- Hide by default
self.frame:Hide()
self.isVisible = false
end
function MTH_DebugFrame:AddError(message)
if not message then return end
self.captureCount = self.captureCount + 1
if string.len(message) > 1000 then
message = string.sub(message, 1, 1000) .. "..."
end
table.insert(self.errors, 1, {
time = date("%H:%M:%S"),
message = message,
level = "error"
})
-- Limit stored errors
if table.getn(self.errors) > self.maxErrors then
table.remove(self.errors, self.maxErrors + 1)
end
self:UpdateDisplay()
end
function MTH_DebugFrame:AddInfo(message)
if not message then return end
self.infoCount = self.infoCount + 1
if string.len(message) > 1000 then
message = string.sub(message, 1, 1000) .. "..."
end
table.insert(self.errors, 1, {
time = date("%H:%M:%S"),
message = message,
level = "info"
})
if table.getn(self.errors) > self.maxErrors then
table.remove(self.errors, self.maxErrors + 1)
end
self:UpdateDisplay()
end
function MTH_DebugFrame:UpdateDisplay()
if not self.scrollText or not self.initialized then
return
end
local text = "=== Errors Captured: " .. self.captureCount .. " ===\n"
text = text .. "=== Info Captured: " .. (self.infoCount or 0) .. " ===\n"
text = text .. "=== Total in queue: " .. table.getn(self.errors) .. " ===\n\n"
for i = 1, table.getn(self.errors) do
local err = self.errors[i]
text = text .. "[" .. err.time .. "] " .. err.message .. "\n"
end
local _, lineCount = string.gsub(text, "\n", "\n")
local lineHeight = 13
local contentHeight = (lineCount + 1) * lineHeight + 8
if contentHeight < 1 then
contentHeight = 1
end
self.scrollText:SetHeight(contentHeight)
self.scrollText:SetText(text)
if self.scrollFrame then
self.scrollFrame:SetVerticalScroll(0)
end
end
function MTH_DebugFrame:Toggle()
if self.isVisible then
self.frame:Hide()
self.isVisible = false
else
if not self.initialized then
self:Initialize()
end
self.frame:Show()
self.isVisible = true
self:UpdateDisplay()
end
end
function MTH_DebugFrame:Show()
if not self.initialized then
self:Initialize()
end
self.frame:Show()
self.isVisible = true
self:UpdateDisplay()
end
function MTH_DebugFrame:Hide()
if self.frame then
self.frame:Hide()
end
self.isVisible = false
end
function MTH_DebugFrame:Clear()
self.errors = {}
self.captureCount = 0
self.infoCount = 0
self:UpdateDisplay()
if self.initialized and self.isVisible then
if MTH and MTH.Print then
MTH:Print("Debug frame cleared")
end
end
end
function MTH_DebugFrame:SelectAllText()
if not self.scrollText or not self.initialized then
return
end
-- Select all text
self.scrollText:HighlightText(0, -1)
self.scrollText:SetFocus()
if MTH and MTH.Print then
MTH:Print("All text selected - press Ctrl+C to copy")
end
end
------------------------------------------------------
-- Error Capture Hooks
------------------------------------------------------
local function MTH_DF_AddMessageCapture(msg)
if msg then
local msgStr = tostring(msg)
if string.find(msgStr, "%.lua:%d+:") then
MTH_DebugFrame:AddError(msgStr)
end
end
end
local function MTH_DF_IsGlobalCaptureEnabled()
if MTH and MTH.GetConfig then
return MTH:GetConfig("debug", "globalErrorCapture", false) and true or false
end
return false
end
local function MTH_DF_SetGlobalCaptureEnabled(enabled)
if MTH and MTH.SetConfig then
MTH:SetConfig("debug", "globalErrorCapture", enabled and true or false)
end
end
local function MTH_DF_InstallHooks()
if MTH_DebugFrame.hooksInstalled then
return
end
if not MTH_DF_IsGlobalCaptureEnabled() then
return
end
if DEFAULT_CHAT_FRAME and type(DEFAULT_CHAT_FRAME.AddMessage) == "function" then
MTH_DebugFrame.originalAddMessage = DEFAULT_CHAT_FRAME.AddMessage
MTH_DebugFrame.addMessageHook = function(self, msg, arg1, arg2, arg3, arg4, arg5)
MTH_DF_AddMessageCapture(msg)
if MTH_DebugFrame.originalAddMessage then
return MTH_DebugFrame.originalAddMessage(self, msg, arg1, arg2, arg3, arg4, arg5)
end
end
DEFAULT_CHAT_FRAME.AddMessage = MTH_DebugFrame.addMessageHook
end
if _G and type(_G["ScriptErrors"]) == "function" then
MTH_DebugFrame.originalScriptErrors = _G["ScriptErrors"]
MTH_DebugFrame.scriptErrorsHook = function(msg)
if msg then
MTH_DebugFrame:AddError(tostring(msg))
end
if MTH_DebugFrame.originalScriptErrors then
return MTH_DebugFrame.originalScriptErrors(msg)
end
end
_G["ScriptErrors"] = MTH_DebugFrame.scriptErrorsHook
end
MTH_DebugFrame.hooksInstalled = true
end
local function MTH_DF_UninstallHooks()
if not MTH_DebugFrame.hooksInstalled then
return
end
if DEFAULT_CHAT_FRAME
and MTH_DebugFrame.addMessageHook
and DEFAULT_CHAT_FRAME.AddMessage == MTH_DebugFrame.addMessageHook
and MTH_DebugFrame.originalAddMessage
then
DEFAULT_CHAT_FRAME.AddMessage = MTH_DebugFrame.originalAddMessage
end
if _G
and MTH_DebugFrame.scriptErrorsHook
and _G["ScriptErrors"] == MTH_DebugFrame.scriptErrorsHook
and MTH_DebugFrame.originalScriptErrors
then
_G["ScriptErrors"] = MTH_DebugFrame.originalScriptErrors
end
MTH_DebugFrame.hooksInstalled = false
end
function MTH_DebugFrame:IsGlobalCaptureEnabled()
return MTH_DF_IsGlobalCaptureEnabled()
end
function MTH_DebugFrame:SetGlobalCaptureEnabled(enabled)
local want = enabled and true or false
MTH_DF_SetGlobalCaptureEnabled(want)
if want then
MTH_DF_InstallHooks()
else
MTH_DF_UninstallHooks()
end
end
MTH_DebugFrame.InstallHooks = MTH_DF_InstallHooks
MTH_DebugFrame.UninstallHooks = MTH_DF_UninstallHooks
-- Event listener for addon load
local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("ADDON_LOADED")
eventFrame:SetScript("OnEvent", function(self, event, addonName)
if event == "ADDON_LOADED" and addonName == "MetaHunt" then
if MTH and MTH.GetConfig then
MTH_DF_SetGlobalCaptureEnabled(MTH:GetConfig("debug", "globalErrorCapture", false))
end
MTH_DF_InstallHooks()
MTH_DebugFrame:Initialize()
if MTH and MTH.Print then
local mode = MTH_DF_IsGlobalCaptureEnabled() and "legacy-global" or "safe-local"
MTH:Print("=== MetaHunt Debug Frame Initialized (" .. mode .. ") ===", "debug")
end
end
end)
+390
View File
@@ -0,0 +1,390 @@
if type(MTH_HUNTERBOOK_TABS) ~= "table" then MTH_HUNTERBOOK_TABS = {} end
MTH_HUNTERBOOK_TABS.pets = {
headerLabel = "Beasts",
columnLabels = { "ID", "Lvl", "Family", "Name", "Abilities", "Zone", "R", "E", "U" },
columnLayout = {
{ x = 8, width = 28, align = "LEFT" },
{ x = 38, width = 30, align = "LEFT" },
{ x = 68, width = 74, align = "LEFT" },
{ x = 142, width = 130, align = "LEFT" },
{ x = 272, width = 180, align = "LEFT" },
{ x = 452, width = 68, align = "LEFT" },
{ x = 520, width = 10, align = "CENTER" },
{ x = 532, width = 10, align = "CENTER" },
{ x = 544, width = 10, align = "CENTER" },
},
}
function MTH_BOOK_GetBeastAbilitiesSummary(beast)
if not beast or not beast.abilities or beast.abilities == "" or beast.abilities == "None" then
return "-"
end
return tostring(beast.abilities)
end
function MTH_BOOK_GetBeastZoneSummary(beast)
if not beast or not beast.coords or table.getn(beast.coords) == 0 then
return "-"
end
local seen = {}
local zoneNames = {}
for i = 1, table.getn(beast.coords) do
local c = beast.coords[i]
if c and c[3] and not seen[c[3]] then
seen[c[3]] = true
table.insert(zoneNames, MTH_BOOK_GetZoneName(c[3]))
end
end
if table.getn(zoneNames) == 0 then return "-" end
if table.getn(zoneNames) == 1 then return zoneNames[1] end
return zoneNames[1] .. " +" .. tostring(table.getn(zoneNames) - 1)
end
function MTH_BOOK_SplitAbilities(text)
local out = {}
if not text or text == "" or text == "None" then return out end
local startPos = 1
local textLen = string.len(text)
while startPos <= textLen do
local commaPos = string.find(text, ",", startPos, true)
local token
if commaPos then
token = string.sub(text, startPos, commaPos - 1)
startPos = commaPos + 1
else
token = string.sub(text, startPos)
startPos = textLen + 1
end
token = string.gsub(token, "^%s+", "")
token = string.gsub(token, "%s+$", "")
if token ~= "" then
table.insert(out, token)
end
end
return out
end
function MTH_BOOK_HasUsableAbilities(beast)
if not beast then return false end
local abilities = MTH_BOOK_SplitAbilities(beast.abilities)
if table.getn(abilities) == 0 then return false end
for i = 1, table.getn(abilities) do
local token = MTH_BOOK_SafeLower(abilities[i])
if token ~= "" and token ~= "none" and token ~= "unknown" then
return true
end
end
return false
end
function MTH_BOOK_HasKnownLevel(beast)
if not beast then return false end
if MTH_BOOK_IsUnknownText(beast.lvl) then return false end
return MTH_BOOK_ParseLevel(beast.lvl) ~= nil
end
function MTH_BOOK_HasKnownZone(beast)
if not beast or not beast.coords or table.getn(beast.coords) == 0 then
return false
end
for i = 1, table.getn(beast.coords) do
local c = beast.coords[i]
if c and c[3] then
local zoneName = MTH_BOOK_SafeLower(MTH_BOOK_GetZoneName(c[3]))
if zoneName ~= "" and zoneName ~= "unknown" and string.sub(zoneName, 1, 5) ~= "zone " then
return true
end
end
end
return false
end
function MTH_BOOK_ParseAbilityToken(token)
local text = tostring(token or "")
text = string.gsub(text, "^%s+", "")
text = string.gsub(text, "%s+$", "")
if text == "" then return "", "", nil end
local _, _, ability, rank = string.find(text, "^(.-)%s*(%d+)$")
if ability and ability ~= "" then
ability = string.gsub(ability, "^%s+", "")
ability = string.gsub(ability, "%s+$", "")
if ability ~= "" then
return MTH_BOOK_SafeLower(ability), ability, tonumber(rank)
end
end
return MTH_BOOK_SafeLower(text), text, nil
end
function MTH_BOOK_BeastHasAbilityRank(beast, abilityValue, rankValue)
if not beast then return false end
local wantedAbility = MTH_BOOK_SafeLower(abilityValue)
if wantedAbility == "" or wantedAbility == "all" then return true end
local wantedRank = tonumber(rankValue)
local abilities = MTH_BOOK_SplitAbilities(beast.abilities)
for i = 1, table.getn(abilities) do
local abilityLower, _, rank = MTH_BOOK_ParseAbilityToken(abilities[i])
if abilityLower == wantedAbility then
if not wantedRank then
return true
end
if rank and rank == wantedRank then
return true
end
end
end
return false
end
function MTH_BOOK_ParseBeastTraits(beast)
local traits = {
rare = beast and beast.rare and true or false,
elite = beast and (beast.elite == true or beast.isElite == true) or false,
unique = beast and (beast.unique == true or beast.isUnique == true) or false,
}
if not beast then return traits end
local rank = MTH_BOOK_SafeLower(beast.rank)
if rank ~= "" then
if string.find(rank, "rare", 1, true) then traits.rare = true end
if string.find(rank, "elite", 1, true) then traits.elite = true end
if string.find(rank, "unique", 1, true) then traits.unique = true end
end
local cls = MTH_BOOK_SafeLower(beast.classification)
if cls ~= "" then
if string.find(cls, "rare", 1, true) then traits.rare = true end
if string.find(cls, "elite", 1, true) then traits.elite = true end
if string.find(cls, "unique", 1, true) then traits.unique = true end
end
local name = MTH_BOOK_SafeLower(beast.name)
if name ~= "" then
local scanPos = 1
while true do
local startPos, endPos = string.find(name, "%b()", scanPos)
if not startPos then break end
local token = string.sub(name, startPos, endPos)
scanPos = endPos + 1
local inner = string.sub(token, 2, -2)
inner = string.gsub(inner, "^%s+", "")
inner = string.gsub(inner, "%s+$", "")
local normalized = string.gsub(inner, "[%s%-_/]", "")
if inner == "rare" or normalized == "r" then traits.rare = true end
if inner == "elite" or normalized == "e" then traits.elite = true end
if inner == "unique" or normalized == "u" then traits.unique = true end
if string.find(inner, "rare", 1, true) then traits.rare = true end
if string.find(inner, "elite", 1, true) then traits.elite = true end
if string.find(inner, "unique", 1, true) then traits.unique = true end
if normalized ~= "" and string.find(normalized, "[^reu]", 1) == nil then
if string.find(normalized, "r", 1, true) then traits.rare = true end
if string.find(normalized, "e", 1, true) then traits.elite = true end
if string.find(normalized, "u", 1, true) then traits.unique = true end
end
end
end
return traits
end
function MTH_BOOK_BeastMatches(beastId, beast)
if not beast then return false end
local forcedBeastId = MTH_BOOK_STATE and tonumber(MTH_BOOK_STATE.forcedBeastId) or nil
if forcedBeastId and tonumber(beastId) ~= forcedBeastId then
return false
end
local beastLevel = MTH_BOOK_ParseLevel(beast.lvl)
local traits = MTH_BOOK_ParseBeastTraits(beast)
local familyLower = MTH_BOOK_SafeLower(beast.family or "")
if MTH_BOOK_STATE.minLevel and (not beastLevel or beastLevel < MTH_BOOK_STATE.minLevel) then return false end
if MTH_BOOK_STATE.maxLevel and (not beastLevel or beastLevel > MTH_BOOK_STATE.maxLevel) then return false end
if MTH_BOOK_STATE.flag1 and not traits.rare then return false end
if MTH_BOOK_STATE.flag2 and not traits.elite then return false end
if MTH_BOOK_STATE.flag3 and not traits.unique then return false end
if not MTH_BOOK_BeastHasAbilityRank(beast, MTH_BOOK_STATE.petAbility, MTH_BOOK_STATE.petRank) then return false end
if MTH_BOOK_STATE.petHideNoAbilities and not MTH_BOOK_HasUsableAbilities(beast) then return false end
if MTH_BOOK_STATE.petHideUnknown then
if familyLower == "unknown" then return false end
if not MTH_BOOK_HasKnownLevel(beast) then return false end
if not MTH_BOOK_HasKnownZone(beast) then return false end
end
if MTH_BOOK_STATE.quick ~= "all" then
if familyLower ~= MTH_BOOK_STATE.quick then
return false
end
end
if MTH_BOOK_STATE.petInZoneOnly then
local currentZoneId = MTH_BOOK_GetCurrentZoneId()
if currentZoneId and not MTH_BOOK_BeastHasZoneId(beast, currentZoneId) then
return false
end
end
if MTH_BOOK_STATE.search ~= "" then
local name = MTH_BOOK_SafeLower(beast.name)
local family = MTH_BOOK_SafeLower(beast.family)
local idText = tostring(beastId)
if string.find(name, MTH_BOOK_STATE.search, 1, true) == nil
and string.find(family, MTH_BOOK_STATE.search, 1, true) == nil
and string.find(idText, MTH_BOOK_STATE.search, 1, true) == nil
then
return false
end
end
return true
end
function MTH_BOOK_BeastSort(a, b)
local beasts = MTH_DS_Beasts
local ba = beasts and beasts[a]
local bb = beasts and beasts[b]
if not ba or not bb then return a < b end
local la = MTH_BOOK_ParseLevel(ba.lvl) or 0
local lb = MTH_BOOK_ParseLevel(bb.lvl) or 0
if la ~= lb then return la < lb end
local na = ba.name or ""
local nb = bb.name or ""
if na ~= nb then return na < nb end
return a < b
end
local function MTH_BOOKTAB_BeastLoreTrim(value)
local text = tostring(value or "")
text = string.gsub(text, "^%s+", "")
text = string.gsub(text, "%s+$", "")
return text
end
local function MTH_BOOKTAB_BeastLoreFindAbilityBundle(abilityName)
if not (MTH_DS_PetSpells and type(MTH_DS_PetSpells.byAbility) == "table") then
return nil
end
local direct = MTH_DS_PetSpells.byAbility[abilityName]
if type(direct) == "table" then
return direct
end
local wanted = MTH_BOOK_SafeLower(abilityName)
for key, bundle in pairs(MTH_DS_PetSpells.byAbility) do
if MTH_BOOK_SafeLower(key) == wanted then
return bundle
end
end
return nil
end
function MTH_BOOKTAB_BuildBeastAbilityEntries(beast)
local entries = {}
if type(beast) ~= "table" then
return entries
end
local tokens = MTH_BOOK_SplitAbilities(beast.abilities)
local seen = {}
for i = 1, table.getn(tokens) do
local rawToken = MTH_BOOKTAB_BeastLoreTrim(tokens[i])
if rawToken ~= "" then
local tokenKey = MTH_BOOK_SafeLower(rawToken)
if tokenKey ~= "" and not seen[tokenKey] then
seen[tokenKey] = true
local _, abilityLabel, requestedRank = MTH_BOOK_ParseAbilityToken(rawToken)
local bundle = MTH_BOOKTAB_BeastLoreFindAbilityBundle(abilityLabel)
local canonical = abilityLabel
if type(bundle) == "table" and type(bundle.ability) == "string" and bundle.ability ~= "" then
canonical = bundle.ability
end
local spellRows = (type(bundle) == "table" and type(bundle.spells) == "table") and bundle.spells or {}
local preferredSpell = nil
local firstBeastSpell = nil
local rankMin = nil
local rankMax = nil
local rankMap = {}
for s = 1, table.getn(spellRows) do
local spell = spellRows[s]
if type(spell) == "table" and MTH_BOOK_SafeLower(spell.learnMethod or "beast") ~= "trainer" then
if not firstBeastSpell then
firstBeastSpell = spell
end
local rank = tonumber(spell.rankNumber)
if rank and rank > 0 then
rankMap[rank] = true
if not rankMin or rank < rankMin then rankMin = rank end
if not rankMax or rank > rankMax then rankMax = rank end
if requestedRank and requestedRank == rank then
preferredSpell = spell
end
end
end
end
local rankCount = 0
for _ in pairs(rankMap) do
rankCount = rankCount + 1
end
if not preferredSpell then
preferredSpell = firstBeastSpell
end
local displayName = canonical
if requestedRank then
displayName = canonical .. " " .. tostring(requestedRank)
end
table.insert(entries, {
name = displayName,
tooltipName = canonical,
requestedRank = requestedRank,
icon = preferredSpell and preferredSpell.icon or nil,
description = preferredSpell and preferredSpell.description or "",
rankMin = rankMin,
rankMax = rankMax,
rankCount = rankCount,
})
end
end
end
return entries
end
function MTH_BOOKTAB_ShowBeastAbilityTooltip(anchor, abilityEntry)
if not anchor or type(abilityEntry) ~= "table" then return end
local tooltip = MTH_BOOK_GetSpellTooltip and MTH_BOOK_GetSpellTooltip() or GameTooltip
if not tooltip then return end
tooltip:SetOwner(anchor, "ANCHOR_RIGHT")
tooltip:ClearLines()
if MTH_BOOK_SetSpellTooltipIcon then
MTH_BOOK_SetSpellTooltipIcon(tooltip, abilityEntry.icon)
end
local title = tostring(abilityEntry.tooltipName or abilityEntry.name or "Unknown")
if abilityEntry.requestedRank then
title = title .. " " .. tostring(abilityEntry.requestedRank)
end
tooltip:AddLine(title, 1.00, 0.82, 0.00)
if abilityEntry.description and abilityEntry.description ~= "" then
tooltip:AddLine(" ")
tooltip:AddLine(tostring(abilityEntry.description), 0.90, 0.90, 0.90, true)
end
tooltip:Show()
end
+398
View File
@@ -0,0 +1,398 @@
if type(MTH_HUNTERBOOK_TABS) ~= "table" then MTH_HUNTERBOOK_TABS = {} end
MTH_HUNTERBOOK_TABS.families = {
headerLabel = "Families",
columnLabels = { "Family", "Named", "Coords", "Abilities", "Diet" },
columnLayout = {
{ x = 10, width = 106, align = "LEFT" },
{ x = 122, width = 52, align = "LEFT" },
{ x = 176, width = 52, align = "LEFT" },
{ x = 232, width = 292, align = "LEFT" },
{ x = 460, width = 184, align = "LEFT" },
},
}
local function MTH_BOOKTAB_FamiliesTrace(message)
return
end
local function MTH_BOOKTAB_FamiliesSafeLower(value)
return string.lower(tostring(value or ""))
end
local function MTH_BOOKTAB_FamiliesTrim(value)
local text = tostring(value or "")
text = string.gsub(text, "^%s+", "")
text = string.gsub(text, "%s+$", "")
return text
end
local function MTH_BOOKTAB_FindAbilityBundle(abilityName)
if not (MTH_DS_PetSpells and type(MTH_DS_PetSpells.byAbility) == "table") then
return nil
end
local direct = MTH_DS_PetSpells.byAbility[abilityName]
if type(direct) == "table" then
return direct
end
local wanted = MTH_BOOKTAB_FamiliesSafeLower(abilityName)
for key, bundle in pairs(MTH_DS_PetSpells.byAbility) do
if MTH_BOOKTAB_FamiliesSafeLower(key) == wanted then
return bundle
end
end
return nil
end
function MTH_BOOKTAB_BuildFamiliesRows()
local results = {}
local families = MTH_DS_Families
if type(families) ~= "table" then
return results
end
local abilityCounts = {}
local canonicalMap = {}
for _, familyRow in pairs(families) do
if type(familyRow) == "table" and type(familyRow.abilities) == "table" then
local seenInFamily = {}
for i = 1, table.getn(familyRow.abilities) do
local rawName = MTH_BOOKTAB_FamiliesTrim(familyRow.abilities[i])
if rawName ~= "" then
local bundle = MTH_BOOKTAB_FindAbilityBundle(rawName)
local canonical = rawName
if type(bundle) == "table" and type(bundle.ability) == "string" and bundle.ability ~= "" then
canonical = bundle.ability
end
local token = MTH_BOOKTAB_FamiliesSafeLower(canonical)
if token ~= "" and not seenInFamily[token] then
seenInFamily[token] = true
abilityCounts[token] = (abilityCounts[token] or 0) + 1
canonicalMap[token] = canonical
end
end
end
end
end
for familyName, familyRow in pairs(families) do
if type(familyRow) == "table" then
local abilities = {}
local seen = {}
if type(familyRow.abilities) == "table" then
for i = 1, table.getn(familyRow.abilities) do
local rawName = MTH_BOOKTAB_FamiliesTrim(familyRow.abilities[i])
if rawName ~= "" then
local bundle = MTH_BOOKTAB_FindAbilityBundle(rawName)
local canonical = rawName
if type(bundle) == "table" and type(bundle.ability) == "string" and bundle.ability ~= "" then
canonical = bundle.ability
end
local token = MTH_BOOKTAB_FamiliesSafeLower(canonical)
if token ~= "" and not seen[token] then
seen[token] = true
local spellRows = (type(bundle) == "table" and type(bundle.spells) == "table") and bundle.spells or {}
local firstBeastSpell = nil
local rankMin = nil
local rankMax = nil
local rankMap = {}
for s = 1, table.getn(spellRows) do
local spell = spellRows[s]
if type(spell) == "table" and MTH_BOOKTAB_FamiliesSafeLower(spell.learnMethod or "beast") ~= "trainer" then
if not firstBeastSpell then
firstBeastSpell = spell
end
local rank = tonumber(spell.rankNumber)
if rank and rank > 0 then
rankMap[rank] = true
if not rankMin or rank < rankMin then rankMin = rank end
if not rankMax or rank > rankMax then rankMax = rank end
end
end
end
local rankCount = 0
for _ in pairs(rankMap) do
rankCount = rankCount + 1
end
table.insert(abilities, {
name = canonicalMap[token] or canonical,
token = token,
isUnique = (abilityCounts[token] or 0) == 1,
icon = firstBeastSpell and firstBeastSpell.icon or nil,
description = firstBeastSpell and firstBeastSpell.description or "",
rankMin = rankMin,
rankMax = rankMax,
rankCount = rankCount,
})
end
end
end
end
table.sort(abilities, function(a, b)
if a.isUnique ~= b.isUnique then
return a.isUnique and true or false
end
return MTH_BOOKTAB_FamiliesSafeLower(a.name) < MTH_BOOKTAB_FamiliesSafeLower(b.name)
end)
local dietItems = {}
if type(familyRow.food) == "table" then
for i = 1, table.getn(familyRow.food) do
local food = MTH_BOOKTAB_FamiliesTrim(familyRow.food[i])
if food ~= "" then
table.insert(dietItems, string.upper(string.sub(food, 1, 1)) .. string.sub(food, 2))
end
end
table.sort(dietItems, function(a, b)
return MTH_BOOKTAB_FamiliesSafeLower(a) < MTH_BOOKTAB_FamiliesSafeLower(b)
end)
end
table.insert(results, {
family = tostring(familyName),
named = tonumber(familyRow.named) or 0,
coords = tonumber(familyRow.coords) or 0,
abilities = abilities,
dietText = table.concat(dietItems, ", "),
})
end
end
table.sort(results, function(a, b)
return MTH_BOOKTAB_FamiliesSafeLower(a.family) < MTH_BOOKTAB_FamiliesSafeLower(b.family)
end)
return results
end
function MTH_BOOKTAB_ShowFamilyAbilityTooltip(anchor, abilityEntry)
if not anchor or type(abilityEntry) ~= "table" then return end
local tooltip = MTH_BOOK_GetSpellTooltip and MTH_BOOK_GetSpellTooltip() or GameTooltip
if not tooltip then return end
tooltip:SetOwner(anchor, "ANCHOR_RIGHT")
tooltip:ClearLines()
if MTH_BOOK_SetSpellTooltipIcon then
MTH_BOOK_SetSpellTooltipIcon(tooltip, abilityEntry.icon)
end
tooltip:AddLine(tostring(abilityEntry.name or "Unknown"), 1.00, 0.82, 0.00)
tooltip:AddLine(" ")
tooltip:AddLine("Baseline", 1.00, 0.82, 0.00)
if abilityEntry.rankCount and abilityEntry.rankCount > 0 then
if abilityEntry.rankMin and abilityEntry.rankMax then
if abilityEntry.rankMin == abilityEntry.rankMax then
tooltip:AddDoubleLine("Ranks", tostring(abilityEntry.rankMin), 0.85, 0.85, 0.85, 1, 1, 1)
else
tooltip:AddDoubleLine("Ranks", tostring(abilityEntry.rankMin) .. "-" .. tostring(abilityEntry.rankMax), 0.85, 0.85, 0.85, 1, 1, 1)
end
end
else
tooltip:AddDoubleLine("Ranks", "Unranked", 0.85, 0.85, 0.85, 1, 1, 1)
end
tooltip:AddDoubleLine("Family-Unique", abilityEntry.isUnique and "Yes" or "No", 0.85, 0.85, 0.85, 1, 1, 1)
if abilityEntry.description and abilityEntry.description ~= "" then
tooltip:AddLine(" ")
tooltip:AddLine(tostring(abilityEntry.description), 0.90, 0.90, 0.90, true)
end
tooltip:Show()
end
function MTH_BOOKTAB_EnsureFamiliesUI()
if MTH_BOOK_STATE.familiesUI then return end
local listParent = getglobal("MTH_BOOK_ListBackdrop")
if not listParent then
MTH_BOOKTAB_FamiliesTrace("EnsureFamiliesUI failed: missing MTH_BOOK_ListBackdrop")
return
end
local ui = {}
ui.frame = CreateFrame("Frame", nil, listParent)
ui.frame:SetPoint("TOPLEFT", listParent, "TOPLEFT", 6, -6)
ui.frame:SetPoint("BOTTOMRIGHT", listParent, "BOTTOMRIGHT", -6, 6)
ui.frame:Hide()
ui.headerFamily = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerFamily:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 4, -2)
ui.headerFamily:SetTextColor(1.00, 0.82, 0.00)
ui.headerFamily:SetText("Family")
ui.headerNamed = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerNamed:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 122, -2)
ui.headerNamed:SetTextColor(1.00, 0.82, 0.00)
ui.headerNamed:SetText("Named")
ui.headerCoords = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerCoords:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 176, -2)
ui.headerCoords:SetTextColor(1.00, 0.82, 0.00)
ui.headerCoords:SetText("Coords")
ui.headerAbilities = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerAbilities:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 232, -2)
ui.headerAbilities:SetTextColor(1.00, 0.82, 0.00)
ui.headerAbilities:SetText("Abilities")
ui.headerDiet = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerDiet:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 460, -2)
ui.headerDiet:SetTextColor(1.00, 0.82, 0.00)
ui.headerDiet:SetText("Diet")
ui.rows = {}
for i = 1, 28 do
local row = CreateFrame("Frame", nil, ui.frame)
row:SetHeight(18)
if i == 1 then
row:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 0, -18)
row:SetPoint("TOPRIGHT", ui.frame, "TOPRIGHT", 0, -18)
else
row:SetPoint("TOPLEFT", ui.rows[i - 1], "BOTTOMLEFT", 0, -1)
row:SetPoint("TOPRIGHT", ui.rows[i - 1], "BOTTOMRIGHT", 0, -1)
end
row.family = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.family:SetPoint("LEFT", row, "LEFT", 4, 0)
row.family:SetWidth(106)
row.family:SetJustifyH("LEFT")
row.named = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.named:SetPoint("LEFT", row, "LEFT", 122, 0)
row.named:SetWidth(52)
row.named:SetJustifyH("LEFT")
row.coords = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.coords:SetPoint("LEFT", row, "LEFT", 176, 0)
row.coords:SetWidth(52)
row.coords:SetJustifyH("LEFT")
row.diet = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.diet:SetPoint("LEFT", row, "LEFT", 460, 0)
row.diet:SetWidth(184)
row.diet:SetJustifyH("LEFT")
row.abilityButtons = {}
for b = 1, 16 do
local button = CreateFrame("Button", nil, row)
button:SetHeight(14)
button.icon = button:CreateTexture(nil, "ARTWORK")
button.icon:SetPoint("LEFT", button, "LEFT", 0, 0)
button.icon:SetWidth(12)
button.icon:SetHeight(12)
button.text = button:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
button.text:SetPoint("LEFT", button.icon, "RIGHT", 2, 0)
button.text:SetJustifyH("LEFT")
button.text:SetTextColor(0.90, 0.90, 0.90)
button.entry = nil
button:SetScript("OnEnter", function()
if this and this.entry then
MTH_BOOKTAB_ShowFamilyAbilityTooltip(this, this.entry)
end
end)
button:SetScript("OnLeave", function()
if type(MTH_BOOK_HideSpellTooltip) == "function" then
MTH_BOOK_HideSpellTooltip()
end
end)
button:Hide()
row.abilityButtons[b] = button
end
row.divider = row:CreateTexture(nil, "BORDER")
row.divider:SetTexture("Interface\\Buttons\\WHITE8X8")
row.divider:SetVertexColor(0.28, 0.28, 0.28, 0.45)
row.divider:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT", 0, 0)
row.divider:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT", 0, 0)
row.divider:SetHeight(1)
ui.rows[i] = row
end
MTH_BOOK_STATE.familiesUI = ui
MTH_BOOKTAB_FamiliesTrace("EnsureFamiliesUI created rows=" .. tostring(table.getn(ui.rows or {})))
end
function MTH_BOOKTAB_SetFamiliesUIVisible(visible)
local ui = MTH_BOOK_STATE.familiesUI
if visible then
if not ui then
MTH_BOOKTAB_EnsureFamiliesUI()
ui = MTH_BOOK_STATE.familiesUI
end
if not ui then return end
else
if not ui then return end
end
local listParent = getglobal("MTH_BOOK_ListBackdrop")
local detailParent = getglobal("MTH_BOOK_DetailBackdrop")
local detailText = getglobal("MTH_BOOK_DetailBackdropDetailText")
local openMapButton = getglobal("MTH_BOOK_OpenMapButton")
if visible then
if listParent then listParent:Show() end
if detailParent then detailParent:Hide() end
if detailText then detailText:Hide() end
if openMapButton then openMapButton:Hide() end
ui.frame:Show()
else
ui.frame:Hide()
if listParent then listParent:Show() end
if detailParent then detailParent:Show() end
if detailText then detailText:Show() end
end
end
function MTH_BOOKTAB_RenderFamiliesList()
local ui = MTH_BOOK_STATE.familiesUI
if not ui then return end
local rows = MTH_BOOK_STATE.results or {}
MTH_BOOKTAB_FamiliesTrace("RenderFamiliesList rows=" .. tostring(table.getn(rows)))
for i = 1, table.getn(ui.rows) do
local rowFrame = ui.rows[i]
local row = rows[i]
if row then
rowFrame:Show()
rowFrame.family:SetText(tostring(row.family or "-"))
rowFrame.named:SetText(tostring(row.named or 0))
rowFrame.coords:SetText(tostring(row.coords or 0))
rowFrame.diet:SetText(tostring(row.dietText or "-"))
local offsetX = 232
for b = 1, table.getn(rowFrame.abilityButtons) do
local button = rowFrame.abilityButtons[b]
local ability = row.abilities and row.abilities[b] or nil
if ability then
local nameText = tostring(ability.name or "")
if b < table.getn(row.abilities) then
nameText = nameText .. ","
end
button.entry = ability
button.text:SetText(nameText)
local textW = button.text:GetStringWidth() or 40
if textW < 24 then textW = 24 end
button:SetWidth(14 + textW)
button:ClearAllPoints()
button:SetPoint("LEFT", rowFrame, "LEFT", offsetX, 0)
local iconPath = ability.icon and MTH_BOOK_ResolveIconPath and MTH_BOOK_ResolveIconPath(ability.icon) or nil
if iconPath then
button.icon:SetTexture(iconPath)
else
button.icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
end
button:Show()
offsetX = offsetX + button:GetWidth() + 6
else
button.entry = nil
button:Hide()
end
end
else
rowFrame:Hide()
end
end
end
+189
View File
@@ -0,0 +1,189 @@
if type(MTH_HUNTERBOOK_TABS) ~= "table" then MTH_HUNTERBOOK_TABS = {} end
MTH_HUNTERBOOK_TABS.npcs = {
headerLabel = "NPC Finder",
columnLabels = { "ID", "Name", "React", "Function", "Zone" },
columnLayout = {
{ x = 8, width = 52, align = "LEFT" },
{ x = 60, width = 140, align = "LEFT" },
{ x = 200, width = 40, align = "LEFT" },
{ x = 242, width = 160, align = "LEFT" },
{ x = 404, width = 148, align = "LEFT" },
},
}
function MTH_BOOKTAB_GetNPCReactBucket(fac)
local f = tostring(fac or "")
if f == "A" then return "A" end
if f == "H" then return "H" end
return "AH"
end
MTH_BOOK_GetNPCReactBucket = MTH_BOOKTAB_GetNPCReactBucket
function MTH_BOOKTAB_GetNPCFunctionLabel(key)
local map = {
vendor = "Ammo Vendor",
repair = "Repair",
stablemaster = "Stable Master",
huntertrainer = "Hunter Trainer",
pettrainer = "Pet Trainer",
}
return map[key] or (string.upper(string.sub(key, 1, 1)) .. string.sub(key, 2))
end
MTH_BOOK_GetNPCFunctionLabel = MTH_BOOKTAB_GetNPCFunctionLabel
function MTH_BOOKTAB_GetNPCFunctions(vendor)
local out = {}
local seen = {}
local meta = vendor and vendor.meta
if meta then
for key, value in pairs(meta) do
if type(key) == "string" and value and tostring(value) ~= "" then
local normalizedKey = MTH_BOOK_SafeLower(key)
if normalizedKey ~= "" and not seen[normalizedKey] then
seen[normalizedKey] = true
table.insert(out, normalizedKey)
end
end
end
end
table.sort(out, function(a, b)
return MTH_BOOK_SafeLower(MTH_BOOKTAB_GetNPCFunctionLabel(a)) < MTH_BOOK_SafeLower(MTH_BOOKTAB_GetNPCFunctionLabel(b))
end)
return out
end
MTH_BOOK_GetNPCFunctions = MTH_BOOKTAB_GetNPCFunctions
function MTH_BOOKTAB_GetNPCFunctionSummary(vendor)
local keys = MTH_BOOKTAB_GetNPCFunctions(vendor)
if table.getn(keys) == 0 then return "-", {} end
local labels = {}
for i = 1, table.getn(keys) do
table.insert(labels, MTH_BOOKTAB_GetNPCFunctionLabel(keys[i]))
end
return table.concat(labels, ", "), keys
end
MTH_BOOK_GetNPCFunctionSummary = MTH_BOOKTAB_GetNPCFunctionSummary
local function MTH_BOOKTAB_GetZoneAndSubzone(zoneId)
local zid = tonumber(zoneId)
if not zid then return "-", "-" end
local zoneName = MTH_BOOK_GetZoneName(zid)
local row = MTH_DS_Zones and (MTH_DS_Zones[zid] or MTH_DS_Zones[tostring(zid)])
if row and row.parent and tonumber(row.parent) and tonumber(row.parent) ~= zid then
local parentName = MTH_BOOK_GetZoneName(row.parent)
if parentName and string.sub(parentName, 1, 5) ~= "Zone " then
return parentName, zoneName
end
end
return zoneName, "-"
end
function MTH_BOOKTAB_GetNPCZoneSummary(vendor)
if not vendor or not vendor.coords or table.getn(vendor.coords) == 0 then
return "-", "-", {}
end
local zones = {}
local zoneSet = {}
local firstZone = "-"
local firstSubzone = "-"
for i = 1, table.getn(vendor.coords) do
local c = vendor.coords[i]
if c and c[3] then
local zoneName, subzoneName = MTH_BOOKTAB_GetZoneAndSubzone(c[3])
if firstZone == "-" then
firstZone = zoneName
firstSubzone = subzoneName
end
if zoneName ~= "-" and not zoneSet[zoneName] then
zoneSet[zoneName] = true
table.insert(zones, zoneName)
end
end
end
return firstZone, firstSubzone, zones
end
MTH_BOOK_GetNPCZoneSummary = MTH_BOOKTAB_GetNPCZoneSummary
function MTH_BOOK_NPCHasZoneId(vendor, zoneId)
local zid = tonumber(zoneId)
if not zid or not vendor or not vendor.coords then
return false
end
for i = 1, table.getn(vendor.coords) do
local c = vendor.coords[i]
if c and tonumber(c[3]) == zid then
return true
end
end
return false
end
function MTH_BOOK_NPCMatches(npcId, vendor)
if not vendor then return false end
local react = MTH_BOOK_GetNPCReactBucket(vendor.fac)
if react == "A" and not MTH_BOOK_STATE.flag1 then return false end
if react == "H" and not MTH_BOOK_STATE.flag2 then return false end
if react == "AH" and not MTH_BOOK_STATE.flag3 then return false end
if MTH_BOOK_STATE.npcHideNoZone then
local firstZone = MTH_BOOK_GetNPCZoneSummary(vendor)
local zoneText = MTH_BOOK_SafeLower(firstZone)
if firstZone == "-" or zoneText == "" or zoneText == "unknown" or string.sub(zoneText, 1, 5) == "zone " then
return false
end
end
if MTH_BOOK_STATE.npcFunction and MTH_BOOK_STATE.npcFunction ~= "all" then
local hasFunction = false
local functions = MTH_BOOK_GetNPCFunctions(vendor)
for i = 1, table.getn(functions) do
if functions[i] == MTH_BOOK_STATE.npcFunction then
hasFunction = true
break
end
end
if not hasFunction then return false end
end
if MTH_BOOK_STATE.npcZone and MTH_BOOK_STATE.npcZone ~= "all" then
local _, _, zones = MTH_BOOK_GetNPCZoneSummary(vendor)
local hasZone = false
for i = 1, table.getn(zones) do
if zones[i] == MTH_BOOK_STATE.npcZone then
hasZone = true
break
end
end
if not hasZone then return false end
end
if MTH_BOOK_STATE.npcInZoneOnly then
local currentZoneId = MTH_BOOK_GetCurrentZoneId()
if currentZoneId and not MTH_BOOK_NPCHasZoneId(vendor, currentZoneId) then
return false
end
end
if MTH_BOOK_STATE.search ~= "" then
local functionSummary = MTH_BOOK_GetNPCFunctionSummary(vendor)
local firstZone, firstSubzone = MTH_BOOK_GetNPCZoneSummary(vendor)
local hay = MTH_BOOK_SafeLower(tostring(vendor.name or "") .. " " .. tostring(functionSummary or "") .. " " .. tostring(firstZone or "") .. " " .. tostring(firstSubzone or "") .. " " .. tostring(npcId))
if string.find(hay, MTH_BOOK_STATE.search, 1, true) == nil then
return false
end
end
return true
end
function MTH_BOOK_NPCSort(a, b)
local vendors = MTH_DS_Vendors
local va = vendors and vendors[a]
local vb = vendors and vendors[b]
if not va or not vb then return a < b end
local na = MTH_BOOK_SafeLower(va.name)
local nb = MTH_BOOK_SafeLower(vb.name)
if na ~= nb then return na < nb end
return a < b
end
+903
View File
@@ -0,0 +1,903 @@
if type(MTH_HUNTERBOOK_TABS) ~= "table" then MTH_HUNTERBOOK_TABS = {} end
MTH_HUNTERBOOK_TABS.petabilities = {
headerLabel = "Pet Abilities",
columnLabels = { "Ability Name", "Ranks", "Families" },
columnLayout = {
{ x = 8, width = 160, align = "LEFT" },
{ x = 170, width = 52, align = "LEFT" },
{ x = 224, width = 328, align = "LEFT" },
},
}
MTH_HUNTERBOOK_TABS.abilities = {
headerLabel = "Pet Abilities",
columnLabels = { "Ability", "Lvl", "Family", "Beast", "Rare" },
columnLayout = {
{ x = 8, width = 112, align = "LEFT" },
{ x = 120, width = 40, align = "LEFT" },
{ x = 160, width = 78, align = "LEFT" },
{ x = 238, width = 142, align = "LEFT" },
{ x = 380, width = 38, align = "LEFT" },
},
}
function MTH_BOOK_AbilitySort(a, b)
if a.ability ~= b.ability then return a.ability < b.ability end
if a.level ~= b.level then return a.level < b.level end
if a.name ~= b.name then return a.name < b.name end
return a.beastId < b.beastId
end
function MTH_BOOK_PetAbilitySort(a, b)
local aa = MTH_BOOK_SafeLower(a and a.ability)
local ab = MTH_BOOK_SafeLower(b and b.ability)
if aa ~= ab then return aa < ab end
local ra = tonumber(a and a.rankCount) or 0
local rb = tonumber(b and b.rankCount) or 0
if ra ~= rb then return ra > rb end
local fa = tonumber(a and a.familyCount) or 0
local fb = tonumber(b and b.familyCount) or 0
if fa ~= fb then return fa > fb end
return tostring(a and a.familyList or "") < tostring(b and b.familyList or "")
end
function MTH_BOOK_GetSpellRowsForAbility(abilityName)
if not abilityName or abilityName == "" then return 0 end
if not (MTH_DS_PetSpells and MTH_DS_PetSpells.byAbility) then return 0 end
local row = MTH_DS_PetSpells.byAbility[abilityName]
if not row or not row.spells then return 0 end
return table.getn(row.spells)
end
function MTH_BOOK_GetSpellCoverageForAbility(abilityName)
local coverage = {
ability = abilityName,
icon = nil,
spellRows = 0,
hasBeastLearned = false,
hasTrainerLearned = false,
rankCount = 0,
minRank = nil,
maxRank = nil,
minTrainLevel = nil,
maxTrainLevel = nil,
}
if not abilityName or abilityName == "" then return coverage end
if not (MTH_DS_PetSpells and MTH_DS_PetSpells.byAbility) then return coverage end
local row = MTH_DS_PetSpells.byAbility[abilityName]
if not row or not row.spells then return coverage end
local rankMap = {}
local hasPositiveRank = false
for i = 1, table.getn(row.spells) do
local spell = row.spells[i]
if spell then
local rankNumber = tonumber(spell.rankNumber)
if rankNumber and rankNumber > 0 then
hasPositiveRank = true
break
end
end
end
coverage.spellRows = 0
for i = 1, table.getn(row.spells) do
local spell = row.spells[i]
if spell then
local rankNumber = tonumber(spell.rankNumber)
if hasPositiveRank and ((not rankNumber) or rankNumber <= 0) then
-- Ignore legacy/unranked rows when proper ranked variants exist.
else
coverage.spellRows = coverage.spellRows + 1
local learnMethod = MTH_BOOK_SafeLower(spell.learnMethod)
if learnMethod == "trainer" then
coverage.hasTrainerLearned = true
else
coverage.hasBeastLearned = true
end
if not coverage.icon and spell.icon and spell.icon ~= "" then
coverage.icon = spell.icon
end
if rankNumber and rankNumber > 0 then
rankMap[rankNumber] = true
if not coverage.minRank or rankNumber < coverage.minRank then coverage.minRank = rankNumber end
if not coverage.maxRank or rankNumber > coverage.maxRank then coverage.maxRank = rankNumber end
end
local trainLevel = tonumber(spell.trainLevel)
if trainLevel then
if not coverage.minTrainLevel or trainLevel < coverage.minTrainLevel then coverage.minTrainLevel = trainLevel end
if not coverage.maxTrainLevel or trainLevel > coverage.maxTrainLevel then coverage.maxTrainLevel = trainLevel end
end
end
end
end
for _ in pairs(rankMap) do
coverage.rankCount = coverage.rankCount + 1
end
if coverage.rankCount == 0 and coverage.spellRows > 0 then
coverage.rankCount = 1
end
return coverage
end
function MTH_BOOK_GetKnownSpellMap()
if not MTH_CharSavedVariables then return nil end
if not MTH_CharSavedVariables.petTraining then return nil end
local petTraining = MTH_CharSavedVariables.petTraining
if petTraining.hunterKnownMap and petTraining.spellMap then
for token, isKnown in pairs(petTraining.hunterKnownMap) do
if isKnown == true then
local _, _, baseToken = string.find(tostring(token), "^(.-)#%d+$")
if baseToken and baseToken ~= "" then
local baseRow = petTraining.spellMap[baseToken]
if baseRow and ((tonumber(baseRow.rankNumber) or 0) <= 0) then
baseRow.isKnown = true
end
end
end
end
end
return MTH_CharSavedVariables.petTraining.spellMap
end
function MTH_BOOK_GetKnownRankCountForAbility(abilityName)
local spellMap = MTH_BOOK_GetKnownSpellMap()
if not spellMap or not abilityName or abilityName == "" then return 0 end
local abilityLower = MTH_BOOK_SafeLower(abilityName)
if abilityLower == "" then return 0 end
local knownRanks = {}
for _, row in pairs(spellMap) do
if row and row.name and (row.isKnown ~= false) then
if MTH_BOOK_SafeLower(row.name) == abilityLower then
local rankNumber = tonumber(row.rankNumber) or 0
knownRanks[rankNumber] = true
end
end
end
local count = 0
for _ in pairs(knownRanks) do
count = count + 1
end
return count
end
function MTH_BOOK_IsKnownRankForAbility(abilityName, rankNumber)
local spellMap = MTH_BOOK_GetKnownSpellMap()
if not spellMap then return false end
if not abilityName or abilityName == "" then return false end
local rankValue = tonumber(rankNumber)
local abilityLower = MTH_BOOK_SafeLower(abilityName)
for _, row in pairs(spellMap) do
if row and row.name and (row.isKnown ~= false) then
if MTH_BOOK_SafeLower(row.name) == abilityLower then
local rowRank = tonumber(row.rankNumber)
if rankValue and rankValue > 0 then
if rowRank == rankValue then
return true
end
else
return true
end
end
end
end
return false
end
function MTH_BOOK_BuildPetAbilitiesRows(ignoreSearch)
local spellRows = {}
if MTH_DS_PetSpells and MTH_DS_PetSpells.byAbility then
for abilityName in pairs(MTH_DS_PetSpells.byAbility) do
local ability = tostring(abilityName or "")
if ability ~= "" then
local key = MTH_BOOK_SafeLower(ability)
if key ~= "" then
spellRows[key] = {
ability = ability,
familiesMap = {},
}
end
end
end
end
local function ensureRow(abilityName)
local ability = tostring(abilityName or "")
if ability == "" then return nil end
local key = MTH_BOOK_SafeLower(ability)
if key == "" then return nil end
return spellRows[key], key
end
if MTH_DS_Families then
for familyName, row in pairs(MTH_DS_Families) do
if familyName and familyName ~= "" and row and row.abilities then
for i = 1, table.getn(row.abilities) do
local ability = row.abilities[i]
local spellRow = ensureRow(ability)
if spellRow then
spellRow.familiesMap[familyName] = true
end
end
end
end
end
if MTH_DS_Beasts then
for _, beast in pairs(MTH_DS_Beasts) do
if beast and beast.family and beast.family ~= "" then
local tokens = MTH_BOOK_SplitAbilities(beast.abilities)
for i = 1, table.getn(tokens) do
local _, abilityName = MTH_BOOK_ParseAbilityToken(tokens[i])
local spellRow = ensureRow(abilityName)
if spellRow then
spellRow.familiesMap[beast.family] = true
end
end
end
end
end
local results = {}
for _, row in pairs(spellRows) do
local coverage = MTH_BOOK_GetSpellCoverageForAbility(row.ability)
local families = {}
for familyName in pairs(row.familiesMap or {}) do
table.insert(families, familyName)
end
table.sort(families, function(a, b)
return MTH_BOOK_SafeLower(a) < MTH_BOOK_SafeLower(b)
end)
local rankCount = tonumber(coverage.rankCount) or 0
local knownRankCount = MTH_BOOK_GetKnownRankCountForAbility(row.ability)
local minRank = coverage.minRank
local maxRank = coverage.maxRank
local minTrainLevel = coverage.minTrainLevel
local maxTrainLevel = coverage.maxTrainLevel
if MTH_BOOK_STATE.petOnlyMyLevel then
knownRankCount, rankCount, minRank, maxRank, minTrainLevel, maxTrainLevel = MTH_BOOK_GetScopedRankSummary(row.ability)
end
if knownRankCount > rankCount then knownRankCount = rankCount end
local rankText = tostring(knownRankCount) .. "/" .. tostring(rankCount)
local sourceFilter = tostring(MTH_BOOK_STATE.petLearnSource or "all")
local sourceMatch = true
if sourceFilter == "beast" then
sourceMatch = coverage.hasBeastLearned and true or false
elseif sourceFilter == "trainer" then
sourceMatch = coverage.hasTrainerLearned and true or false
end
local familyList = table.concat(families, ", ")
local searchable = MTH_BOOK_SafeLower(tostring(row.ability or "") .. " " .. familyList)
if sourceMatch and rankCount > 0 and (ignoreSearch or MTH_BOOK_STATE.search == "" or string.find(searchable, MTH_BOOK_STATE.search, 1, true) ~= nil) then
table.insert(results, {
ability = row.ability,
icon = coverage.icon,
knownRankCount = knownRankCount,
rankCount = rankCount,
rankText = rankText,
families = families,
familyCount = table.getn(families),
familyList = familyList,
spellRows = coverage.spellRows or 0,
minRank = minRank,
maxRank = maxRank,
minTrainLevel = minTrainLevel,
maxTrainLevel = maxTrainLevel,
hasBeastLearned = coverage.hasBeastLearned,
hasTrainerLearned = coverage.hasTrainerLearned,
})
end
end
table.sort(results, MTH_BOOK_PetAbilitySort)
return results
end
function MTH_BOOK_BuildPetAbilityRankRows(entry)
local rows = {}
if not entry or not entry.ability then return rows end
if not (MTH_DS_PetSpells and MTH_DS_PetSpells.byAbility) then return rows end
local spellBucket = MTH_DS_PetSpells.byAbility[entry.ability]
if not spellBucket or not spellBucket.spells then return rows end
local hasPositiveRank = false
for i = 1, table.getn(spellBucket.spells) do
local spell = spellBucket.spells[i]
if spell then
local rankNumber = tonumber(spell.rankNumber)
if rankNumber and rankNumber > 0 then
hasPositiveRank = true
break
end
end
end
for i = 1, table.getn(spellBucket.spells) do
local spell = spellBucket.spells[i]
if spell then
local rankNumber = tonumber(spell.rankNumber)
if not rankNumber and spell.rank then
local _, _, parsed = string.find(tostring(spell.rank), "(%d+)")
rankNumber = tonumber(parsed)
end
if hasPositiveRank and ((not rankNumber) or rankNumber <= 0) then
-- Ignore legacy/unranked rows when proper ranked variants exist.
elseif MTH_BOOK_IsSpellInLevelScope(tonumber(spell.trainLevel)) then
table.insert(rows, {
id = spell.id,
ability = entry.ability,
icon = spell.icon or spell.iconName or spell.texture or spell.iconTexture or entry.icon,
rankNumber = rankNumber,
isKnown = MTH_BOOK_IsKnownRankForAbility(entry.ability, rankNumber),
trainLevel = tonumber(spell.trainLevel),
description = spell.description,
cost = spell.cost,
castTime = spell.castTime,
range = spell.range,
school = spell.school,
effects = spell.effects,
sourceUrl = spell.sourceUrl,
})
end
end
end
table.sort(rows, function(a, b)
local ra = a.rankNumber or 0
local rb = b.rankNumber or 0
if ra ~= rb then return ra < rb end
local la = a.trainLevel or 0
local lb = b.trainLevel or 0
if la ~= lb then return la < lb end
return (tonumber(a.id) or 0) < (tonumber(b.id) or 0)
end)
return rows
end
function MTH_BOOK_GetRankLabel(rankNumber, includePrefix)
local rankValue = tonumber(rankNumber)
if rankValue and rankValue > 0 then
if includePrefix then
return "Rank " .. tostring(rankValue)
end
return tostring(rankValue)
end
if includePrefix then
return "Unranked"
end
return "None"
end
function MTH_BOOKTAB_EnsurePetAbilitiesUI(listParent, detailParent)
if MTH_BOOK_STATE.petUI then return end
if not listParent or not detailParent then return end
local ui = {}
ui.leftPane = CreateFrame("Frame", nil, listParent)
ui.leftPane:SetPoint("TOPLEFT", listParent, "TOPLEFT", 4, -4)
ui.leftPane:SetWidth(162)
ui.leftPane:SetHeight(346)
ui.rightPane = CreateFrame("Frame", nil, listParent)
ui.rightPane:SetPoint("TOPLEFT", ui.leftPane, "TOPRIGHT", 8, 0)
ui.rightPane:SetWidth(390)
ui.rightPane:SetHeight(346)
ui.vSplit = listParent:CreateTexture(nil, "BORDER")
ui.vSplit:SetTexture("Interface\\Buttons\\WHITE8X8")
ui.vSplit:SetVertexColor(0.30, 0.30, 0.30, 0.60)
ui.vSplit:SetPoint("TOPLEFT", ui.leftPane, "TOPRIGHT", 4, -2)
ui.vSplit:SetPoint("BOTTOMLEFT", ui.leftPane, "BOTTOMRIGHT", 4, 2)
ui.vSplit:SetWidth(1)
ui.leftHeaderAbility = ui.leftPane:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.leftHeaderAbility:SetPoint("TOPLEFT", ui.leftPane, "TOPLEFT", 24, -6)
ui.leftHeaderAbility:SetWidth(102)
ui.leftHeaderAbility:SetJustifyH("LEFT")
ui.leftHeaderAbility:SetTextColor(1.00, 0.82, 0.00)
ui.leftHeaderAbility:SetText("Ability")
ui.leftHeaderRank = ui.leftPane:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.leftHeaderRank:SetPoint("TOPLEFT", ui.leftPane, "TOPLEFT", 126, -6)
ui.leftHeaderRank:SetWidth(28)
ui.leftHeaderRank:SetJustifyH("LEFT")
ui.leftHeaderRank:SetTextColor(1.00, 0.82, 0.00)
ui.leftHeaderRank:SetText("K/T")
ui.rightHeaderRank = ui.rightPane:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.rightHeaderRank:SetPoint("TOPLEFT", ui.rightPane, "TOPLEFT", 2, -6)
ui.rightHeaderRank:SetWidth(36)
ui.rightHeaderRank:SetJustifyH("LEFT")
ui.rightHeaderRank:SetTextColor(1.00, 0.82, 0.00)
ui.rightHeaderRank:SetText("Rank")
ui.rightHeaderLevel = ui.rightPane:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.rightHeaderKnown = ui.rightPane:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.rightHeaderKnown:SetPoint("TOPLEFT", ui.rightPane, "TOPLEFT", 34, -6)
ui.rightHeaderKnown:SetWidth(22)
ui.rightHeaderKnown:SetJustifyH("LEFT")
ui.rightHeaderKnown:SetTextColor(1.00, 0.82, 0.00)
ui.rightHeaderKnown:SetText("K?")
ui.rightHeaderLevel:SetPoint("TOPLEFT", ui.rightPane, "TOPLEFT", 58, -6)
ui.rightHeaderLevel:SetWidth(40)
ui.rightHeaderLevel:SetJustifyH("LEFT")
ui.rightHeaderLevel:SetTextColor(1.00, 0.82, 0.00)
ui.rightHeaderLevel:SetText("Lvl")
ui.rightHeaderDesc = ui.rightPane:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.rightHeaderDesc:SetPoint("TOPLEFT", ui.rightPane, "TOPLEFT", 98, -6)
ui.rightHeaderDesc:SetWidth(286)
ui.rightHeaderDesc:SetJustifyH("LEFT")
ui.rightHeaderDesc:SetTextColor(1.00, 0.82, 0.00)
ui.rightHeaderDesc:SetText("Description")
ui.leftRowsVisible = 20
ui.leftScroll = CreateFrame("Slider", "MTH_BOOK_PetLeftScroll", ui.leftPane, "UIPanelScrollBarTemplate")
ui.leftScroll:SetPoint("TOPRIGHT", ui.leftPane, "TOPRIGHT", -2, -22)
ui.leftScroll:SetPoint("BOTTOMRIGHT", ui.leftPane, "BOTTOMRIGHT", -2, 20)
ui.leftScroll:SetWidth(16)
if not getglobal("MTH_BOOK_PetLeftScrollScrollFrame") then
local leftScrollShim = CreateFrame("ScrollFrame", "MTH_BOOK_PetLeftScrollScrollFrame", ui.leftPane)
leftScrollShim:SetPoint("TOPLEFT", ui.leftPane, "TOPLEFT", 0, 0)
leftScrollShim:SetWidth(1)
leftScrollShim:SetHeight(1)
leftScrollShim:Hide()
end
ui.leftScroll:SetScript("OnValueChanged", function()
local val = this:GetValue() or 0
val = math.floor(val + 0.5)
if val ~= MTH_BOOK_STATE.petLeftOffset then
MTH_BOOK_STATE.petLeftOffset = val
MTH_BOOK_UpdatePetAbilitiesLists()
end
end)
ui.leftScroll:SetMinMaxValues(0, 0)
ui.leftScroll:SetValueStep(1)
ui.leftScroll:SetValue(0)
ui.leftPane:EnableMouseWheel(true)
ui.leftPane:SetScript("OnMouseWheel", function()
local delta = arg1 or 0
if delta == 0 then return end
local current = tonumber(MTH_BOOK_STATE.petLeftOffset or 0) or 0
if delta < 0 then
current = current + 1
else
current = current - 1
end
if current < 0 then current = 0 end
local maxOffset = 0
if MTH_BOOK_STATE.results then
maxOffset = math.max(0, table.getn(MTH_BOOK_STATE.results) - table.getn(ui.leftButtons or {}))
end
if current > maxOffset then current = maxOffset end
MTH_BOOK_STATE.petLeftOffset = current
if ui.leftScroll then
ui.leftScroll:SetValue(current)
end
MTH_BOOK_UpdatePetAbilitiesLists()
end)
ui.leftButtons = {}
local leftY = -24
for i = 1, ui.leftRowsVisible do
local btn = CreateFrame("Button", nil, ui.leftPane)
btn:SetPoint("TOPLEFT", ui.leftPane, "TOPLEFT", 0, leftY)
btn:SetWidth(144)
btn:SetHeight(19)
btn.entry = nil
btn.selected = btn:CreateTexture(nil, "BACKGROUND")
btn.selected:SetAllPoints(btn)
btn.selected:SetTexture("Interface\\Buttons\\WHITE8X8")
btn.selected:SetVertexColor(0.18, 0.35, 0.55, 0.35)
btn.selected:Hide()
local hl = btn:CreateTexture(nil, "HIGHLIGHT")
hl:SetAllPoints(btn)
hl:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
hl:SetBlendMode("ADD")
hl:SetAlpha(0.30)
btn.ability = btn:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
btn.ability:SetPoint("LEFT", btn, "LEFT", 24, 0)
btn.ability:SetWidth(100)
btn.ability:SetJustifyH("LEFT")
btn.rank = btn:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
btn.rank:SetPoint("LEFT", btn, "LEFT", 126, 0)
btn.rank:SetWidth(26)
btn.rank:SetJustifyH("LEFT")
btn.icon = btn:CreateTexture(nil, "ARTWORK")
btn.icon:SetPoint("LEFT", btn, "LEFT", 2, 0)
btn.icon:SetWidth(16)
btn.icon:SetHeight(16)
btn:SetScript("OnClick", function()
if not this.entry then return end
MTH_BOOK_STATE.selectedEntry = this.entry
MTH_BOOK_STATE.selectedPetRankEntry = nil
MTH_BOOK_STATE.petRankRows = MTH_BOOK_BuildPetAbilityRankRows(this.entry)
MTH_BOOK_UpdateResults()
end)
btn:SetScript("OnEnter", function()
if this and this.entry then
MTH_BOOK_ShowBaselineTooltip(this, this.entry)
end
end)
btn:SetScript("OnLeave", function()
MTH_BOOK_HideSpellTooltip()
end)
ui.leftButtons[i] = btn
leftY = leftY - 20
end
local footerParent = listParent:GetParent() or listParent
ui.recapText = footerParent:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.recapText:SetPoint("TOPLEFT", footerParent, "TOPLEFT", 20, -530)
ui.recapText:SetWidth(190)
ui.recapText:SetJustifyH("LEFT")
ui.recapText:SetTextColor(1.00, 0.82, 0.00)
ui.recapText:SetText("Abilities 0 | Known 0/0")
ui.progressBackdrop = CreateFrame("Frame", nil, footerParent)
ui.progressBackdrop:SetPoint("LEFT", ui.recapText, "RIGHT", 8, 0)
ui.progressBackdrop:SetWidth(320)
ui.progressBackdrop:SetHeight(14)
ui.progressBackdrop:SetFrameLevel(footerParent:GetFrameLevel() + 3)
ui.progressBackdrop:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true,
tileSize = 8,
edgeSize = 8,
insets = { left = 2, right = 2, top = 2, bottom = 2 },
})
ui.progressBackdrop:SetBackdropColor(0.08, 0.08, 0.08, 0.95)
ui.progressBackdrop:SetBackdropBorderColor(0.24, 0.24, 0.24, 0.90)
ui.progressBar = CreateFrame("StatusBar", nil, ui.progressBackdrop)
ui.progressBar:SetPoint("TOPLEFT", ui.progressBackdrop, "TOPLEFT", 2, -2)
ui.progressBar:SetPoint("BOTTOMRIGHT", ui.progressBackdrop, "BOTTOMRIGHT", -2, 2)
ui.progressBar:SetStatusBarTexture("Interface\\Buttons\\WHITE8X8")
ui.progressBar:SetStatusBarColor(0.18, 0.75, 0.22, 0.9)
ui.progressBar:SetMinMaxValues(0, 1)
ui.progressBar:SetValue(0)
ui.progressText = ui.progressBackdrop:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
ui.progressText:SetPoint("CENTER", ui.progressBackdrop, "CENTER", 0, 0)
ui.progressText:SetTextColor(1.00, 1.00, 1.00)
ui.progressText:SetText("0%")
ui.rightButtons = {}
local rightRowsVisible = 10
local rightRowHeight = 36
local rightRowStep = 37
local rightY = -24
for i = 1, rightRowsVisible do
local btn = CreateFrame("Button", nil, ui.rightPane)
btn:SetPoint("TOPLEFT", ui.rightPane, "TOPLEFT", 0, rightY)
btn:SetWidth(388)
btn:SetHeight(rightRowHeight)
btn.entry = nil
btn.selected = btn:CreateTexture(nil, "BACKGROUND")
btn.selected:SetAllPoints(btn)
btn.selected:SetTexture("Interface\\Buttons\\WHITE8X8")
btn.selected:SetVertexColor(0.18, 0.35, 0.55, 0.35)
btn.selected:Hide()
local hl = btn:CreateTexture(nil, "HIGHLIGHT")
hl:SetAllPoints(btn)
hl:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
hl:SetBlendMode("ADD")
hl:SetAlpha(0.28)
btn.rank = btn:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
btn.rank:SetPoint("LEFT", btn, "LEFT", 2, 1)
btn.rank:SetWidth(30)
btn.rank:SetJustifyH("LEFT")
btn.knownIcon = btn:CreateTexture(nil, "ARTWORK")
btn.knownIcon:SetPoint("LEFT", btn, "LEFT", 38, 1)
btn.knownIcon:SetWidth(12)
btn.knownIcon:SetHeight(12)
btn.knownIcon:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
btn.knownIcon:SetVertexColor(0.18, 0.90, 0.18, 1.00)
btn.knownIcon:Hide()
btn.level = btn:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
btn.level:SetPoint("LEFT", btn, "LEFT", 58, 1)
btn.level:SetWidth(40)
btn.level:SetJustifyH("LEFT")
btn.desc = btn:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
btn.desc:SetPoint("LEFT", btn, "LEFT", 98, 1)
btn.desc:SetWidth(286)
btn.desc:SetJustifyH("LEFT")
btn:SetScript("OnClick", function()
if not this.entry then return end
MTH_BOOK_STATE.selectedPetRankEntry = this.entry
MTH_BOOK_UpdateDetail()
MTH_BOOK_UpdateResults()
end)
btn:SetScript("OnEnter", function()
if this and this.entry then
MTH_BOOK_ShowRankTooltip(this, this.entry)
end
end)
btn:SetScript("OnLeave", function()
MTH_BOOK_HideSpellTooltip()
end)
ui.rightButtons[i] = btn
rightY = rightY - rightRowStep
end
ui.detailTitleTop = detailParent:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.detailTitleTop:SetPoint("TOPLEFT", detailParent, "TOPLEFT", 8, -8)
ui.detailTitleTop:SetWidth(120)
ui.detailTitleTop:SetJustifyH("LEFT")
ui.detailTitleTop:SetTextColor(1.00, 0.82, 0.00)
ui.detailTitleTop:SetText("Spell baseline")
ui.detailTop = CreateFrame("EditBox", nil, detailParent)
ui.detailTop:SetMultiLine(true)
ui.detailTop:SetAutoFocus(false)
ui.detailTop:SetPoint("TOPLEFT", ui.detailTitleTop, "BOTTOMLEFT", 0, -4)
ui.detailTop:SetWidth(120)
ui.detailTop:SetHeight(132)
ui.detailTopIcon = detailParent:CreateTexture(nil, "ARTWORK")
ui.detailTopIcon:SetPoint("TOPRIGHT", ui.detailTop, "TOPRIGHT", -4, -4)
ui.detailTopIcon:SetWidth(16)
ui.detailTopIcon:SetHeight(16)
ui.detailTopIcon:Hide()
ui.detailTitleBottom = detailParent:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.detailTitleBottom:SetPoint("TOPLEFT", ui.detailTop, "BOTTOMLEFT", 0, -8)
ui.detailTitleBottom:SetWidth(120)
ui.detailTitleBottom:SetJustifyH("LEFT")
ui.detailTitleBottom:SetTextColor(1.00, 0.82, 0.00)
ui.detailTitleBottom:SetText("Current Rank")
ui.detailBottom = CreateFrame("EditBox", nil, detailParent)
ui.detailBottom:SetMultiLine(true)
ui.detailBottom:SetAutoFocus(false)
ui.detailBottom:SetPoint("TOPLEFT", ui.detailTitleBottom, "BOTTOMLEFT", 0, -4)
ui.detailBottom:SetWidth(120)
ui.detailBottom:SetHeight(116)
MTH_BOOK_ConfigureReadOnlyEditBox(ui.detailTop)
MTH_BOOK_ConfigureReadOnlyEditBox(ui.detailBottom)
if ui.detailTop.SetTextColor then ui.detailTop:SetTextColor(1.00, 1.00, 1.00) end
if ui.detailBottom.SetTextColor then ui.detailBottom:SetTextColor(1.00, 1.00, 1.00) end
MTH_BOOK_STATE.petUI = ui
end
function MTH_BOOKTAB_SetPetAbilitiesUIVisible(visible)
local ui = MTH_BOOK_STATE.petUI
if not ui then return end
local detail = getglobal("MTH_BOOK_DetailBackdropDetailText")
if detail then
if visible then detail:Hide() else detail:Show() end
end
local widgets = {
ui.leftPane,
ui.leftScroll,
ui.rightPane,
ui.vSplit,
ui.leftHeaderAbility,
ui.leftHeaderRank,
ui.rightHeaderRank,
ui.rightHeaderKnown,
ui.rightHeaderLevel,
ui.rightHeaderDesc,
ui.recapText,
ui.progressBackdrop,
ui.detailTop,
ui.detailBottom,
ui.detailTitleTop,
ui.detailTitleBottom,
}
for i = 1, table.getn(widgets) do
local widget = widgets[i]
if widget and widget.Show and widget.Hide then
if visible then widget:Show() else widget:Hide() end
end
end
if ui.detailTopIcon and not visible then
ui.detailTopIcon:Hide()
end
if not visible then
MTH_BOOK_HideSpellTooltip()
end
end
function MTH_BOOKTAB_UpdatePetAbilitiesLists()
local ui = MTH_BOOK_STATE.petUI
if not ui then return end
local resultCount = table.getn(MTH_BOOK_STATE.results)
local visibleRows = table.getn(ui.leftButtons)
local maxOffset = math.max(0, resultCount - visibleRows)
if MTH_BOOK_STATE.petLeftOffset < 0 then MTH_BOOK_STATE.petLeftOffset = 0 end
if MTH_BOOK_STATE.petLeftOffset > maxOffset then MTH_BOOK_STATE.petLeftOffset = maxOffset end
if ui.leftScroll then
ui.leftScroll:SetMinMaxValues(0, maxOffset)
ui.leftScroll:SetValueStep(1)
ui.leftScroll:SetValue(MTH_BOOK_STATE.petLeftOffset)
local upButton = getglobal("MTH_BOOK_PetLeftScrollScrollUpButton")
local downButton = getglobal("MTH_BOOK_PetLeftScrollScrollDownButton")
if upButton then
if MTH_BOOK_STATE.petLeftOffset > 0 then
upButton:EnableMouse(true)
upButton:SetAlpha(1)
else
upButton:EnableMouse(false)
upButton:SetAlpha(0.35)
end
end
if downButton then
if MTH_BOOK_STATE.petLeftOffset < maxOffset then
downButton:EnableMouse(true)
downButton:SetAlpha(1)
else
downButton:EnableMouse(false)
downButton:SetAlpha(0.35)
end
end
if maxOffset > 0 then
ui.leftScroll:SetAlpha(1)
ui.leftScroll:Show()
else
ui.leftScroll:SetAlpha(1)
ui.leftScroll:Hide()
end
end
local startIndex = MTH_BOOK_STATE.petLeftOffset + 1
for i = 1, visibleRows do
local btn = ui.leftButtons[i]
if btn then
local rowIndex = startIndex + i - 1
local entry = MTH_BOOK_STATE.results[rowIndex]
btn.entry = entry
if entry then
if btn.icon then
if entry.icon and entry.icon ~= "" then
if string.find(entry.icon, "\\", 1, true) then
btn.icon:SetTexture(entry.icon)
else
btn.icon:SetTexture("Interface\\Icons\\" .. tostring(entry.icon))
end
btn.icon:Show()
else
btn.icon:Hide()
end
end
btn.ability:SetText(tostring(entry.ability or "?"))
btn.rank:SetText(tostring(entry.rankText or ""))
btn.ability:SetTextColor(0.92, 0.92, 0.92)
btn.rank:SetTextColor(0.92, 0.92, 0.92)
if btn.icon then btn.icon:SetVertexColor(1, 1, 1, 1) end
if MTH_BOOK_STATE.selectedEntry and MTH_BOOK_STATE.selectedEntry.ability == entry.ability then
btn.selected:Show()
else
btn.selected:Hide()
end
btn:Show()
else
if btn.icon then btn.icon:Hide() end
btn.ability:SetText("")
btn.rank:SetText("")
btn.selected:Hide()
btn:Hide()
end
end
end
local totalKnown = 0
local totalRanks = 0
for i = 1, resultCount do
local entry = MTH_BOOK_STATE.results[i]
if entry then
totalKnown = totalKnown + (tonumber(entry.knownRankCount) or 0)
totalRanks = totalRanks + (tonumber(entry.rankCount) or 0)
end
end
if ui.recapText then
ui.recapText:SetText("Abilities " .. tostring(resultCount) .. " | Known " .. tostring(totalKnown) .. "/" .. tostring(totalRanks))
end
if ui.progressBar then
ui.progressBar:SetMinMaxValues(0, math.max(1, totalRanks))
ui.progressBar:SetValue(totalKnown)
end
if ui.progressText then
local pct = 0
if totalRanks > 0 then
pct = math.floor((totalKnown / totalRanks) * 100 + 0.5)
end
ui.progressText:SetText(tostring(pct) .. "%")
end
if MTH_BOOK_STATE.selectedEntry then
local keep = false
for i = 1, table.getn(MTH_BOOK_STATE.results) do
local row = MTH_BOOK_STATE.results[i]
if row and row.ability == MTH_BOOK_STATE.selectedEntry.ability then
MTH_BOOK_STATE.selectedEntry = row
keep = true
break
end
end
if not keep then
MTH_BOOK_STATE.selectedEntry = nil
MTH_BOOK_STATE.selectedPetRankEntry = nil
end
end
MTH_BOOK_STATE.petRankRows = MTH_BOOK_BuildPetAbilityRankRows(MTH_BOOK_STATE.selectedEntry)
for i = 1, table.getn(ui.rightButtons) do
local btn = ui.rightButtons[i]
if btn then
local entry = MTH_BOOK_STATE.petRankRows[i]
btn.entry = entry
if entry then
btn.rank:SetText(MTH_BOOK_GetRankLabel(entry.rankNumber, false))
if btn.knownIcon then
if entry.isKnown then
btn.knownIcon:Show()
else
btn.knownIcon:Hide()
end
end
btn.level:SetText(entry.trainLevel and tostring(entry.trainLevel) or "-")
btn.desc:SetText(tostring(entry.description or "-"))
if MTH_BOOK_STATE.selectedPetRankEntry and MTH_BOOK_STATE.selectedPetRankEntry.id == entry.id then
btn.selected:Show()
else
btn.selected:Hide()
end
btn:Show()
else
btn.rank:SetText("")
if btn.knownIcon then btn.knownIcon:Hide() end
btn.level:SetText("")
btn.desc:SetText("")
btn.selected:Hide()
btn:Hide()
end
end
end
end
+29
View File
@@ -0,0 +1,29 @@
if type(MTH_HUNTERBOOK_TABS) ~= "table" then MTH_HUNTERBOOK_TABS = {} end
MTH_HUNTERBOOK_TABS.pethistory = {
headerLabel = "Pet History",
columnLabels = { "Pet ID", "Name", "Family", "Lvl", "Lost Date/Time", "Lost Cause" },
columnLayout = {
{ x = 8, width = 66, align = "LEFT" },
{ x = 76, width = 104, align = "LEFT" },
{ x = 182, width = 84, align = "LEFT" },
{ x = 268, width = 30, align = "LEFT" },
{ x = 300, width = 128, align = "LEFT" },
{ x = 430, width = 78, align = "LEFT" },
},
}
function MTH_BOOK_PetHistorySort(a, b)
local ra = MTH_BOOK_GetPetStoreRow(a)
local rb = MTH_BOOK_GetPetStoreRow(b)
if not ra and not rb then return tostring(a) < tostring(b) end
if not ra then return false end
if not rb then return true end
local ta = tonumber(ra.abandonedAt) or 0
local tb = tonumber(rb.abandonedAt) or 0
if ta ~= tb then return ta > tb end
local nameA = MTH_BOOK_SafeLower(ra.name)
local nameB = MTH_BOOK_SafeLower(rb.name)
if nameA ~= nameB then return nameA < nameB end
return tostring(a) < tostring(b)
end
File diff suppressed because it is too large Load Diff
+73
View File
@@ -0,0 +1,73 @@
if type(MTH_HUNTERBOOK_TABS) ~= "table" then MTH_HUNTERBOOK_TABS = {} end
MTH_HUNTERBOOK_TABS.items = {
headerLabel = "Ranged Weapons",
columnLabels = { "ID", "Req", "Lvl", "Type", "Name", "DPS", "Speed", "Source" },
columnLayout = {
{ x = 8, width = 36, align = "LEFT" },
{ x = 44, width = 30, align = "LEFT" },
{ x = 74, width = 30, align = "LEFT" },
{ x = 104, width = 62, align = "LEFT" },
{ x = 166, width = 188, align = "LEFT" },
{ x = 356, width = 44, align = "LEFT" },
{ x = 402, width = 40, align = "LEFT" },
{ x = 444, width = 108, align = "LEFT" },
},
}
function MTH_BOOK_NormalizeItemSubtype(value)
local subtype = MTH_BOOK_SafeLower(value)
if subtype == "arrows" then return "arrow" end
if subtype == "bullets" then return "bullet" end
if subtype == "bows" then return "bow" end
if subtype == "rifle" or subtype == "rifles" then return "gun" end
if subtype == "cross bow" or subtype == "cross-bow" or subtype == "crossbows" or subtype == "xbow" or subtype == "xbows" then return "crossbow" end
return subtype
end
function MTH_BOOK_IsRangedWeaponSubtype(subtype)
return subtype == "bow" or subtype == "gun" or subtype == "crossbow"
end
function MTH_BOOK_ItemMatches(itemId, item)
if not item then return false end
if item.notingame then return false end
local itemSubtype = MTH_BOOK_NormalizeItemSubtype(item.subtype)
if not MTH_BOOK_IsRangedWeaponSubtype(itemSubtype) then return false end
local selectedSubtype = MTH_BOOK_NormalizeItemSubtype(MTH_BOOK_STATE.itemSubtype)
if selectedSubtype ~= "all" and itemSubtype ~= selectedSubtype then return false end
if MTH_BOOK_STATE.itemOnlyMyLevel then
local playerLevel = MTH_BOOK_GetPlayerLevelValue()
local reqLevel = tonumber(item.reqlevel)
if playerLevel and reqLevel and reqLevel > playerLevel then
return false
end
end
if MTH_BOOK_STATE.flag1 and not MTH_BOOK_HasEntries(item.vendors) then return false end
if MTH_BOOK_STATE.flag2 and not MTH_BOOK_HasEntries(item.drops) then return false end
if MTH_BOOK_STATE.flag3 and not MTH_BOOK_HasEntries(item.objects) then return false end
if MTH_BOOK_STATE.search ~= "" then
local name = MTH_BOOK_SafeLower(item.name)
local idText = tostring(itemId)
if string.find(name, MTH_BOOK_STATE.search, 1, true) == nil and string.find(idText, MTH_BOOK_STATE.search, 1, true) == nil then
return false
end
end
return true
end
function MTH_BOOK_ItemSort(a, b)
local items = MTH_BOOK_GetItemsTable()
local ia = items and items[a]
local ib = items and items[b]
if not ia or not ib then return a < b end
local la = ia.level or 0
local lb = ib.level or 0
if la ~= lb then return la < lb end
local na = ia.name or ""
local nb = ib.name or ""
if na ~= nb then return na < nb end
return a < b
end
+4946
View File
File diff suppressed because it is too large Load Diff
+333
View File
@@ -0,0 +1,333 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\FrameXML\UI.xsd">
<Frame name="MTH_BOOK_Template" toplevel="true" frameStrata="DIALOG" movable="true" enableMouse="true" hidden="true" parent="UIParent" virtual="true">
<Size>
<AbsDimension x="760" y="560"/>
</Size>
<Anchors>
<Anchor point="CENTER"/>
</Anchors>
<Backdrop bgFile="Interface\Buttons\WHITE8X8" edgeFile="Interface\DialogFrame\UI-DialogBox-Border" tile="true">
<BackgroundInsets>
<AbsInset left="11" right="12" top="12" bottom="11"/>
</BackgroundInsets>
<TileSize><AbsValue val="32"/></TileSize>
<EdgeSize><AbsValue val="32"/></EdgeSize>
</Backdrop>
<Layers>
<Layer level="ARTWORK">
<FontString name="$parentHeaderText" inherits="GameFontNormal" text="The Book of Hunters">
<Anchors>
<Anchor point="TOP">
<Offset><AbsDimension x="0" y="-18"/></Offset>
</Anchor>
</Anchors>
</FontString>
<FontString name="$parentStatsText" inherits="GameFontNormalSmall" text="Beasts: 0 | Filtered: 0 | Page 1/1">
<Anchors>
<Anchor point="TOPLEFT">
<Offset><AbsDimension x="20" y="-530"/></Offset>
</Anchor>
</Anchors>
</FontString>
<FontString name="$parentSearchLabel" inherits="GameFontNormalSmall" text="Search">
<Anchors>
<Anchor point="TOPLEFT">
<Offset><AbsDimension x="18" y="-82"/></Offset>
</Anchor>
</Anchors>
</FontString>
<FontString name="$parentMinLabel" inherits="GameFontNormalSmall" text="Min Lvl">
<Anchors>
<Anchor point="TOPLEFT">
<Offset><AbsDimension x="266" y="-82"/></Offset>
</Anchor>
</Anchors>
</FontString>
<FontString name="$parentMaxLabel" inherits="GameFontNormalSmall" text="Max Lvl">
<Anchors>
<Anchor point="TOPLEFT">
<Offset><AbsDimension x="374" y="-82"/></Offset>
</Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<Frame name="$parentListBackdrop">
<Size><AbsDimension x="564" y="354"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="16" y="-166"/></Offset></Anchor>
</Anchors>
<Backdrop bgFile="Interface\Buttons\WHITE8X8" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
<BackgroundInsets><AbsInset left="3" right="3" top="3" bottom="3"/></BackgroundInsets>
<TileSize><AbsValue val="16"/></TileSize>
<EdgeSize><AbsValue val="16"/></EdgeSize>
</Backdrop>
</Frame>
<Frame name="$parentDetailBackdrop">
<Size><AbsDimension x="136" y="324"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="608" y="-166"/></Offset></Anchor>
</Anchors>
<Backdrop bgFile="Interface\Buttons\WHITE8X8" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
<BackgroundInsets><AbsInset left="3" right="3" top="3" bottom="3"/></BackgroundInsets>
<TileSize><AbsValue val="16"/></TileSize>
<EdgeSize><AbsValue val="16"/></EdgeSize>
</Backdrop>
<Frames>
<EditBox name="$parentDetailText" autoFocus="false" multiLine="true" text="Select an entry to view details.">
<Size><AbsDimension x="120" y="306"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="8" y="-8"/></Offset></Anchor>
</Anchors>
</EditBox>
</Frames>
</Frame>
<Button name="$parentOpenMapButton" inherits="UIPanelButtonTemplate" text="Open Map" hidden="true">
<Size><AbsDimension x="136" y="22"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="608" y="-496"/></Offset></Anchor>
</Anchors>
</Button>
<Button name="$parentCloseButton" inherits="UIPanelButtonTemplate" text="X">
<Size><AbsDimension x="22" y="22"/></Size>
<Anchors>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-8" y="-8"/></Offset></Anchor>
</Anchors>
</Button>
<Frame name="$parentTopAreaBackdrop">
<Size><AbsDimension x="728" y="472"/></Size>
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentTabBar" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-2" y="-2"/></Offset></Anchor>
</Anchors>
<Backdrop bgFile="Interface\Buttons\WHITE8X8" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
<BackgroundInsets><AbsInset left="3" right="3" top="3" bottom="3"/></BackgroundInsets>
<TileSize><AbsValue val="16"/></TileSize>
<EdgeSize><AbsValue val="16"/></EdgeSize>
</Backdrop>
</Frame>
<Frame name="$parentTabBar">
<Size><AbsDimension x="620" y="24"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="18" y="-44"/></Offset></Anchor>
</Anchors>
</Frame>
<Button name="$parentSectionStable" inherits="TabButtonTemplate" text="Stable">
<Size><AbsDimension x="86" y="24"/></Size>
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentTabBar"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
</Anchors>
</Button>
<Button name="$parentSectionPetAbilities" inherits="TabButtonTemplate" text="Pet Abilities">
<Size><AbsDimension x="108" y="24"/></Size>
<Anchors>
<Anchor point="LEFT" relativeTo="$parentSectionStable" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
</Anchors>
</Button>
<Button name="$parentSectionPets" inherits="TabButtonTemplate" text="Beast Lore">
<Size><AbsDimension x="98" y="24"/></Size>
<Anchors>
<Anchor point="LEFT" relativeTo="$parentSectionPetAbilities" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
</Anchors>
</Button>
<Button name="$parentSectionFamilies" inherits="TabButtonTemplate" text="Families">
<Size><AbsDimension x="92" y="24"/></Size>
<Anchors>
<Anchor point="LEFT" relativeTo="$parentSectionPets" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
</Anchors>
</Button>
<Button name="$parentSectionNPCs" inherits="TabButtonTemplate" text="NPC Finder">
<Size><AbsDimension x="110" y="24"/></Size>
<Anchors>
<Anchor point="LEFT" relativeTo="$parentSectionFamilies" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
</Anchors>
</Button>
<Button name="$parentSectionItems" inherits="TabButtonTemplate" text="Weapons">
<Size><AbsDimension x="104" y="24"/></Size>
<Anchors>
<Anchor point="LEFT" relativeTo="$parentSectionNPCs" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
</Anchors>
</Button>
<Button name="$parentSectionPetHistory" inherits="TabButtonTemplate" text="Pet History">
<Size><AbsDimension x="104" y="24"/></Size>
<Anchors>
<Anchor point="LEFT" relativeTo="$parentSectionItems" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
</Anchors>
</Button>
<EditBox name="$parentSearch" inherits="InputBoxTemplate">
<Size><AbsDimension x="180" y="20"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="54" y="-80"/></Offset></Anchor>
</Anchors>
</EditBox>
<EditBox name="$parentMinLevel" inherits="InputBoxTemplate">
<Size><AbsDimension x="45" y="20"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="302" y="-80"/></Offset></Anchor>
</Anchors>
</EditBox>
<EditBox name="$parentMaxLevel" inherits="InputBoxTemplate">
<Size><AbsDimension x="45" y="20"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="414" y="-80"/></Offset></Anchor>
</Anchors>
</EditBox>
<Button name="$parentApplyButton" inherits="UIPanelButtonTemplate" text="Apply">
<Size><AbsDimension x="60" y="20"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="478" y="-80"/></Offset></Anchor>
</Anchors>
</Button>
<Frame name="$parentPetLearnSourceDropdown" inherits="UIDropDownMenuTemplate" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="12" y="-80"/></Offset></Anchor>
</Anchors>
</Frame>
<Frame name="$parentItemSubtypeDropdown" inherits="UIDropDownMenuTemplate" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="12" y="-108"/></Offset></Anchor>
</Anchors>
</Frame>
<CheckButton name="$parentPetOnlyMyLevel" inherits="UICheckButtonTemplate" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="228" y="-80"/></Offset></Anchor>
</Anchors>
</CheckButton>
<Button name="$parentPetBookScanButton" inherits="UIPanelButtonTemplate" text="Scan from Pet Book" hidden="true">
<Size><AbsDimension x="130" y="20"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="614" y="-80"/></Offset></Anchor>
</Anchors>
</Button>
<Button name="$parentResetButton" inherits="UIPanelButtonTemplate" text="Reset">
<Size><AbsDimension x="60" y="20"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="546" y="-80"/></Offset></Anchor>
</Anchors>
</Button>
<Button name="$parentFilter1" inherits="UIPanelButtonTemplate" text="All"><Size><AbsDimension x="70" y="20"/></Size><Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="18" y="-114"/></Offset></Anchor></Anchors></Button>
<Button name="$parentFilter2" inherits="UIPanelButtonTemplate" text=""><Size><AbsDimension x="70" y="20"/></Size><Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="92" y="-114"/></Offset></Anchor></Anchors></Button>
<Button name="$parentFilter3" inherits="UIPanelButtonTemplate" text=""><Size><AbsDimension x="70" y="20"/></Size><Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="166" y="-114"/></Offset></Anchor></Anchors></Button>
<Button name="$parentFilter4" inherits="UIPanelButtonTemplate" text=""><Size><AbsDimension x="70" y="20"/></Size><Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="240" y="-114"/></Offset></Anchor></Anchors></Button>
<Button name="$parentFilter5" inherits="UIPanelButtonTemplate" text=""><Size><AbsDimension x="70" y="20"/></Size><Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="314" y="-114"/></Offset></Anchor></Anchors></Button>
<Button name="$parentFilter6" inherits="UIPanelButtonTemplate" text=""><Size><AbsDimension x="70" y="20"/></Size><Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="388" y="-114"/></Offset></Anchor></Anchors></Button>
<Frame name="$parentFamilyDropdown" inherits="UIDropDownMenuTemplate" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="10" y="-108"/></Offset></Anchor>
</Anchors>
</Frame>
<Frame name="$parentAbilityDropdown" inherits="UIDropDownMenuTemplate" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="180" y="-108"/></Offset></Anchor>
</Anchors>
</Frame>
<Frame name="$parentRankDropdown" inherits="UIDropDownMenuTemplate" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="350" y="-108"/></Offset></Anchor>
</Anchors>
</Frame>
<CheckButton name="$parentPetInZoneOnly" inherits="UICheckButtonTemplate" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="488" y="-112"/></Offset></Anchor>
</Anchors>
</CheckButton>
<Button name="$parentShowAllOnMapButton" inherits="UIPanelButtonTemplate" text="Show all on map" hidden="true">
<Size><AbsDimension x="122" y="20"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="602" y="-110"/></Offset></Anchor>
</Anchors>
</Button>
<Frame name="$parentNPCFunctionDropdown" inherits="UIDropDownMenuTemplate" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="242" y="-80"/></Offset></Anchor>
</Anchors>
</Frame>
<CheckButton name="$parentNPCInZoneOnly" inherits="UICheckButtonTemplate" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="404" y="-84"/></Offset></Anchor>
</Anchors>
</CheckButton>
<CheckButton name="$parentRequireVendor" inherits="UICheckButtonTemplate">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="18" y="-140"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="$parentRequireDrop" inherits="UICheckButtonTemplate">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="86" y="-140"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="$parentRequireObject" inherits="UICheckButtonTemplate">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="154" y="-140"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="$parentHideNoAbilities" inherits="UICheckButtonTemplate">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="232" y="-140"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="$parentHideUnknown" inherits="UICheckButtonTemplate">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="372" y="-140"/></Offset></Anchor></Anchors>
</CheckButton>
<Frame name="$parentListSliderBackdrop">
<Size><AbsDimension x="22" y="322"/></Size>
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentListBackdrop" relativePoint="TOPRIGHT"><Offset><AbsDimension x="2" y="-20"/></Offset></Anchor>
</Anchors>
<Backdrop bgFile="Interface\Buttons\WHITE8X8" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
<BackgroundInsets><AbsInset left="2" right="2" top="2" bottom="2"/></BackgroundInsets>
<TileSize><AbsValue val="16"/></TileSize>
<EdgeSize><AbsValue val="16"/></EdgeSize>
</Backdrop>
</Frame>
<Slider name="$parentListSlider" inherits="UISliderTemplate" orientation="VERTICAL" minValue="1" maxValue="1" defaultValue="1" valueStep="1">
<Size><AbsDimension x="20" y="322"/></Size>
<Anchors>
<Anchor point="CENTER" relativeTo="$parentListSliderBackdrop"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
</Anchors>
</Slider>
<Button name="$parentPrevButton" inherits="UIPanelButtonTemplate" text="Prev">
<Size><AbsDimension x="80" y="22"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="280" y="-524"/></Offset></Anchor></Anchors>
</Button>
<Button name="$parentNextButton" inherits="UIPanelButtonTemplate" text="Next">
<Size><AbsDimension x="80" y="22"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="368" y="-524"/></Offset></Anchor></Anchors>
</Button>
</Frames>
<Scripts>
<OnLoad>
this:RegisterForDrag("LeftButton")
</OnLoad>
<OnDragStart>
this:StartMoving();
</OnDragStart>
<OnDragStop>
this:StopMovingOrSizing();
</OnDragStop>
</Scripts>
</Frame>
</Ui>
+180
View File
@@ -0,0 +1,180 @@
------------------------------------------------------
-- MetaHunt Localization System
------------------------------------------------------
MTH.localization = {}
MTH.currentLocale = GetLocale()
-- Binding Headers (must be defined early for keybindings)
BINDING_HEADER_FEEDOMATIC = "[MetaHunt] Feed-O-Matic"
BINDING_HEADER_ZAspectHeader = "[MetaHunt] ZAspect Buttons"
BINDING_HEADER_ZTrackHeader = "[MetaHunt] ZTrack Buttons"
BINDING_HEADER_ZTrapHeader = "[MetaHunt] ZTrap Buttons"
BINDING_HEADER_ZPetHeader = "[MetaHunt] ZPet Buttons"
BINDING_HEADER_ZAmmoHeader = "[MetaHunt] ZAmmo Buttons"
-- Localization table structure
local L = {}
-- English (default)
if MTH.currentLocale == "enUS" or MTH.currentLocale == "enGB" then
-- FeedOMatic
L.FEEDOMATIC_NAME = "Feed-O-Matic"
L.FEEDOMATIC_DESC = "Helps a Hunter keep his pets well fed"
L.TOOLTIPS_OPTION_FOOD = "Add tooltips on food"
L.TOOLTIPS_OPTION_FOOD_HELP = "Show your current pet's food preference directly in item tooltips."
L.TOOLTIPS_OPTION_OWNPET = "Activate on my pet"
L.TOOLTIPS_OPTION_OWNPET_HELP = "Show your own pet status details in tooltip when hovering your pet context."
L.TOOLTIPS_FOOD_QUALITY_UNKNOWN = "%s can eat this, but hasn't tried it yet."
L.TOOLTIPS_FOOD_QUALITY_UNDER = "%s doesn't like this anymore."
L.TOOLTIPS_FOOD_QUALITY_MIGHT = "%s might eat this."
L.TOOLTIPS_FOOD_QUALITY_WILL = "%s will eat this."
L.TOOLTIPS_FOOD_QUALITY_LIKE = "%s likes to eat this."
L.TOOLTIPS_FOOD_QUALITY_LOVE = "%s loves to eat this."
L.TOOLTIPS_FOOD_MARKER_UNKNOWN = "can eat this, but hasn't tried it yet."
L.TOOLTIPS_FOOD_MARKER_UNDER = "doesn't like this anymore."
L.TOOLTIPS_FOOD_MARKER_MIGHT = "might eat this."
L.TOOLTIPS_FOOD_MARKER_WILL = "will eat this."
L.TOOLTIPS_FOOD_MARKER_LIKE = "likes to eat this."
L.TOOLTIPS_FOOD_MARKER_LOVE = "loves to eat this."
-- zhunter
L.ZHUNTER_NAME = "zhunter"
L.ZHUNTER_DESC = "Various features to make a hunter's life easier"
-- German / Deutsch
elseif MTH.currentLocale == "deDE" then
L.FEEDOMATIC_NAME = "Futter-O-Mat"
L.ZHUNTER_NAME = "zhunter"
L.TOOLTIPS_OPTION_FOOD = "Add tooltips on food"
L.TOOLTIPS_OPTION_FOOD_HELP = "Show your current pet's food preference directly in item tooltips."
L.TOOLTIPS_OPTION_OWNPET = "Activate on my pet"
L.TOOLTIPS_OPTION_OWNPET_HELP = "Show your own pet status details in tooltip when hovering your pet context."
L.TOOLTIPS_FOOD_QUALITY_UNKNOWN = "%s can eat this, but hasn't tried it yet."
L.TOOLTIPS_FOOD_QUALITY_UNDER = "%s doesn't like this anymore."
L.TOOLTIPS_FOOD_QUALITY_MIGHT = "%s might eat this."
L.TOOLTIPS_FOOD_QUALITY_WILL = "%s will eat this."
L.TOOLTIPS_FOOD_QUALITY_LIKE = "%s likes to eat this."
L.TOOLTIPS_FOOD_QUALITY_LOVE = "%s loves to eat this."
L.TOOLTIPS_FOOD_MARKER_UNKNOWN = "can eat this, but hasn't tried it yet."
L.TOOLTIPS_FOOD_MARKER_UNDER = "doesn't like this anymore."
L.TOOLTIPS_FOOD_MARKER_MIGHT = "might eat this."
L.TOOLTIPS_FOOD_MARKER_WILL = "will eat this."
L.TOOLTIPS_FOOD_MARKER_LIKE = "likes to eat this."
L.TOOLTIPS_FOOD_MARKER_LOVE = "loves to eat this."
-- French / Français
elseif MTH.currentLocale == "frFR" then
L.FEEDOMATIC_NAME = "Nourricier"
L.ZHUNTER_NAME = "zhunter"
L.TOOLTIPS_OPTION_FOOD = "Add tooltips on food"
L.TOOLTIPS_OPTION_FOOD_HELP = "Show your current pet's food preference directly in item tooltips."
L.TOOLTIPS_OPTION_OWNPET = "Activate on my pet"
L.TOOLTIPS_OPTION_OWNPET_HELP = "Show your own pet status details in tooltip when hovering your pet context."
L.TOOLTIPS_FOOD_QUALITY_UNKNOWN = "%s can eat this, but hasn't tried it yet."
L.TOOLTIPS_FOOD_QUALITY_UNDER = "%s doesn't like this anymore."
L.TOOLTIPS_FOOD_QUALITY_MIGHT = "%s might eat this."
L.TOOLTIPS_FOOD_QUALITY_WILL = "%s will eat this."
L.TOOLTIPS_FOOD_QUALITY_LIKE = "%s likes to eat this."
L.TOOLTIPS_FOOD_QUALITY_LOVE = "%s loves to eat this."
L.TOOLTIPS_FOOD_MARKER_UNKNOWN = "can eat this, but hasn't tried it yet."
L.TOOLTIPS_FOOD_MARKER_UNDER = "doesn't like this anymore."
L.TOOLTIPS_FOOD_MARKER_MIGHT = "might eat this."
L.TOOLTIPS_FOOD_MARKER_WILL = "will eat this."
L.TOOLTIPS_FOOD_MARKER_LIKE = "likes to eat this."
L.TOOLTIPS_FOOD_MARKER_LOVE = "loves to eat this."
end
MTH.localization[MTH.currentLocale] = L
-- Get localized string
function MTH:GetLocalization(key, default)
if L[key] then
return L[key]
end
return default or key
end
-- Module localization helper
function MTH:RegisterModuleLocalization(module_name, localizationTable)
if not self.localization.modules then
self.localization.modules = {}
end
self.localization.modules[module_name] = localizationTable
end
function MTH:GetModuleLocalization(module_name, key, default)
if self.localization.modules and self.localization.modules[module_name] then
local L = self.localization.modules[module_name]
if L[key] then
return L[key]
end
end
return default or key
end
local MTH_SPELL_LOCALE = {
enUS = {
["Banish"] = "Banish",
["Bestial Wrath"] = "Bestial Wrath",
["Concussive Shot"] = "Concussive Shot",
["Counterattack"] = "Counterattack",
["Critical Mass"] = "Critical Mass",
["Clever Traps"] = "Clever Traps",
["Deterrence"] = "Deterrence",
["Explosive Trap"] = "Explosive Trap",
["Explosive Trap Effect"] = "Explosive Trap Effect",
["Feed Pet"] = "Feed Pet",
["Flare"] = "Flare",
["Freezing Trap"] = "Freezing Trap",
["Freezing Trap Effect"] = "Freezing Trap Effect",
["Frost Trap"] = "Frost Trap",
["Frost Trap Aura"] = "Frost Trap Aura",
["Hunter's Mark"] = "Hunter's Mark",
["Holy Strength"] = "Holy Strength",
["Immolation Trap"] = "Immolation Trap",
["Immolation Trap Effect"] = "Immolation Trap Effect",
["Improved Concussive Shot"] = "Improved Concussive Shot",
["Improved Wing Clip"] = "Improved Wing Clip",
["Perception"] = "Perception",
["Piercing Shot"] = "Piercing Shot",
["Piercing Shots"] = "Piercing Shots",
["Quick Shots"] = "Quick Shots",
["Rapid Fire"] = "Rapid Fire",
["Scare Beast"] = "Scare Beast",
["Scatter Shot"] = "Scatter Shot",
["Scorpid Poison"] = "Scorpid Poison",
["Scorpid Sting"] = "Scorpid Sting",
["Serpent Sting"] = "Serpent Sting",
["Stoneform"] = "Stoneform",
["Unstable Power"] = "Unstable Power",
["Ephemeral Power"] = "Ephemeral Power",
["Viper Sting"] = "Viper Sting",
["War Stomp"] = "War Stomp",
["Will of the Forsaken"] = "Will of the Forsaken",
["Wing Clip"] = "Wing Clip",
["Wyvern Sting"] = "Wyvern Sting",
["Blood Fury"] = "Blood Fury",
["Berserking"] = "Berserking",
},
}
MTH.Const = MTH.Const or {}
MTH.Const.SpellLocaleMap = MTH_SPELL_LOCALE
function MTH:LocalizeSpell(spellToken)
local key = tostring(spellToken or "")
if key == "" then
return key
end
local locale = tostring(self.currentLocale or GetLocale() or "enUS")
local localeMap = MTH_SPELL_LOCALE[locale] or MTH_SPELL_LOCALE.enUS
if localeMap and localeMap[key] then
return localeMap[key]
end
return key
end
+1262
View File
File diff suppressed because it is too large Load Diff
+171
View File
@@ -0,0 +1,171 @@
------------------------------------------------------
-- MetaHunt Minimap Button
------------------------------------------------------
if not MTH_MinimapButton then
MTH_MinimapButton = {}
end
local function MTH_MB_GetStore()
if not MTH_CharSavedVariables then
MTH_CharSavedVariables = {}
end
if type(MTH_CharSavedVariables.minimapButton) ~= "table" then
MTH_CharSavedVariables.minimapButton = {}
end
local store = MTH_CharSavedVariables.minimapButton
if store.angle == nil then
store.angle = 220
end
return store
end
local function MTH_MB_GetRadius()
return 80
end
local function MTH_MB_UpdatePosition()
if not MTH_MinimapButton.frame then
return
end
local store = MTH_MB_GetStore()
local angle = tonumber(store.angle) or 220
local rad = math.rad(angle)
local radius = MTH_MB_GetRadius()
local x = math.cos(rad) * radius
local y = math.sin(rad) * radius
MTH_MinimapButton.frame:ClearAllPoints()
MTH_MinimapButton.frame:SetPoint("CENTER", Minimap, "CENTER", x, y)
end
local function MTH_MB_UpdateDragPosition()
if not MTH_MinimapButton.frame then
return
end
local mx, my = Minimap:GetCenter()
local px, py = GetCursorPosition()
local scale = Minimap:GetEffectiveScale()
if scale and scale ~= 0 then
px = px / scale
py = py / scale
end
local dx = px - mx
local dy = py - my
local angle = math.deg(math.atan2(dy, dx))
local store = MTH_MB_GetStore()
store.angle = angle
MTH_MB_UpdatePosition()
end
local function MTH_MB_SetHunterIcon(texture)
if not texture then
return
end
texture:SetTexture("Interface\\GLUES\\CHARACTERCREATE\\UI-CHARACTERCREATE-CLASSES")
if CLASS_ICON_TCOORDS and CLASS_ICON_TCOORDS["HUNTER"] then
local coords = CLASS_ICON_TCOORDS["HUNTER"]
texture:SetTexCoord(coords[1], coords[2], coords[3], coords[4])
else
texture:SetTexCoord(0, 0.25, 0.25, 0.5)
end
end
function MTH_MinimapButton:Initialize()
if self.frame then
MTH_MB_UpdatePosition()
self.frame:Show()
return
end
local button = CreateFrame("Button", "MTH_MinimapButtonFrame", Minimap)
if not button then
return
end
button:SetWidth(31)
button:SetHeight(31)
button:SetFrameStrata("MEDIUM")
button:RegisterForClicks("LeftButtonUp", "RightButtonUp")
button:RegisterForDrag("LeftButton")
local border = button:CreateTexture(nil, "OVERLAY")
border:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder")
border:SetWidth(54)
border:SetHeight(54)
border:SetPoint("TOPLEFT", button, "TOPLEFT", 0, 0)
local icon = button:CreateTexture(nil, "BACKGROUND")
icon:SetWidth(20)
icon:SetHeight(20)
icon:SetPoint("CENTER", button, "CENTER", 0, 0)
MTH_MB_SetHunterIcon(icon)
local highlight = button:CreateTexture(nil, "HIGHLIGHT")
highlight:SetTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight")
highlight:SetBlendMode("ADD")
highlight:SetWidth(31)
highlight:SetHeight(31)
highlight:SetPoint("CENTER", button, "CENTER", 0, 0)
button:SetScript("OnEnter", function(self)
self = self or this
if not self then return end
GameTooltip:SetOwner(self, "ANCHOR_LEFT")
GameTooltip:SetText("MetaHunt", 1, 0.82, 0)
GameTooltip:AddLine("Left-Click: Open Hunter Book", 1, 1, 1)
GameTooltip:AddLine("Right-Click: Open MetaHunt Options", 1, 1, 1)
GameTooltip:AddLine("Drag: Move button", 0.8, 0.8, 0.8)
GameTooltip:Show()
end)
button:SetScript("OnLeave", function()
GameTooltip:Hide()
end)
button:SetScript("OnDragStart", function(self)
self = self or this
if not self then return end
self.dragging = 1
self:SetScript("OnUpdate", function()
MTH_MB_UpdateDragPosition()
end)
end)
button:SetScript("OnDragStop", function(self)
self = self or this
if not self then return end
self.dragging = nil
self:SetScript("OnUpdate", nil)
MTH_MB_UpdateDragPosition()
end)
button:SetScript("OnClick", function(_, mouseButton)
mouseButton = mouseButton or arg1
if mouseButton == "RightButton" then
if type(MTH_OpenOptions) == "function" then
MTH_OpenOptions("General")
end
return
end
if type(MTH_OpenHunterBook) == "function" then
MTH_OpenHunterBook()
elseif type(MTH_ToggleHunterBook) == "function" then
MTH_ToggleHunterBook()
end
end)
self.frame = button
self.icon = icon
MTH_MB_UpdatePosition()
end
local loader = CreateFrame("Frame", "MTH_MinimapButtonLoader")
loader:RegisterEvent("ADDON_LOADED")
loader:SetScript("OnEvent", function(_, eventName, addonName)
eventName = eventName or event
addonName = addonName or arg1
if eventName == "ADDON_LOADED" and addonName == "MetaHunt" then
MTH_MinimapButton:Initialize()
end
end)
+845
View File
@@ -0,0 +1,845 @@
local MTH_AB_READY = MTH_OptionsRequire and MTH_OptionsRequire("options-autobuy", {
"MTH_GetFrame",
"MTH_ClearContainer",
"MTH_CreateCheckbox",
})
local MTH_AB_LAYOUT = {
LEFT_SECTION_X = 0,
RIGHT_SECTION_X = 290,
ROW_CHECK_X = 0,
ROW_CHECK_LABEL_X = 27,
ROW_DROPDOWN_X = 33,
ROW_DROPDOWN_WIDTH = 100,
ROW_QTY_GAP = 6,
ROW_SUFFIX_GAP = 6,
ROW_STEP = 40,
SECTION_TITLE_Y = -110,
SECTION_ROWS_Y0 = -138,
}
local MTH_AB_STATE = {
container = nil,
built = false,
syncing = false,
controls = {
moduleEnabled = nil,
projectilesEnabled = nil,
petFoodEnabled = nil,
petFoodQty = nil,
petFoodQtyLabel = nil,
petFoodQtySuffix = nil,
petFoodScopeCurrent = nil,
petFoodScopeAll = nil,
petFoodTitle = nil,
petFoodPetLines = {},
title = nil,
body = nil,
sectionArrowTitle = nil,
sectionBulletTitle = nil,
rows = {
arrow = {},
bullet = {},
},
},
}
local function MTH_AB_SetCheckedNoClick(check, checked)
if not check then return end
MTH_AB_STATE.syncing = true
check:SetChecked(checked and true or false)
MTH_AB_STATE.syncing = false
end
local function MTH_AB_GetEngine()
return MTH_AutoBuyEngine
end
local function MTH_AB_GetStore()
local engine = MTH_AB_GetEngine()
if engine and engine.EnsureDefaults then
return engine:EnsureDefaults()
end
MTH_AutoBuy_Saved = MTH_AutoBuy_Saved or {}
return MTH_AutoBuy_Saved
end
local function MTH_AB_NormalizeRule(rule)
if type(rule) ~= "table" then
rule = {}
end
if rule.enabled == nil then
rule.enabled = false
end
rule.enabled = rule.enabled and true or false
rule.itemId = rule.itemId and (tonumber(rule.itemId) or rule.itemId) or nil
local stacks = tonumber(rule.stacks)
if not stacks then
stacks = rule.enabled and 1 or 0
end
stacks = math.floor(stacks)
if stacks < 0 then
stacks = 0
end
if rule.enabled and stacks < 1 then
stacks = 1
end
rule.stacks = stacks
return rule
end
local function MTH_AB_EnsureConfig()
local store = MTH_AB_GetStore()
if store.enabled == nil then store.enabled = true end
if type(store.projectiles) ~= "table" then store.projectiles = {} end
if type(store.petFood) ~= "table" then store.petFood = {} end
if store.projectiles.enabled == nil then store.projectiles.enabled = true end
if store.petFood.enabled == nil then store.petFood.enabled = false end
if store.petFood.stacks == nil then store.petFood.stacks = 1 end
if store.petFood.scope == nil then store.petFood.scope = "current" end
store.petFood.stacks = math.floor(tonumber(store.petFood.stacks) or 1)
if store.petFood.stacks < 1 then store.petFood.stacks = 1 end
if store.petFood.scope ~= "current" and store.petFood.scope ~= "all" then
store.petFood.scope = "current"
end
if type(store.projectiles.arrows) ~= "table" then store.projectiles.arrows = {} end
if type(store.projectiles.bullets) ~= "table" then store.projectiles.bullets = {} end
if type(store.projectiles.arrows.rules) ~= "table" then store.projectiles.arrows.rules = {} end
if type(store.projectiles.bullets.rules) ~= "table" then store.projectiles.bullets.rules = {} end
for i = 1, 3 do
store.projectiles.arrows.rules[i] = MTH_AB_NormalizeRule(store.projectiles.arrows.rules[i])
store.projectiles.bullets.rules[i] = MTH_AB_NormalizeRule(store.projectiles.bullets.rules[i])
end
return store
end
local function MTH_AB_SetPetFoodStacks(store, value)
if type(store) ~= "table" or type(store.petFood) ~= "table" then
return
end
local stacks = math.floor(tonumber(value) or 1)
if stacks < 1 then stacks = 1 end
store.petFood.stacks = stacks
end
local function MTH_AB_TitleCase(text)
local source = tostring(text or "")
if source == "" then return "" end
return string.upper(string.sub(source, 1, 1)) .. string.lower(string.sub(source, 2))
end
local function MTH_AB_NormalizeFamilyToken(text)
local value = string.lower(tostring(text or ""))
value = string.gsub(value, "%b()", " ")
value = string.gsub(value, "[^%a]", "")
return value
end
local function MTH_AB_SingularizeFamilyToken(token)
local value = tostring(token or "")
if string.len(value) <= 3 then
return value
end
if string.sub(value, -3) == "ves" and string.len(value) > 4 then
return string.sub(value, 1, -4) .. "f"
end
if string.sub(value, -3) == "ies" and string.len(value) > 4 then
return string.sub(value, 1, -4) .. "y"
end
if string.sub(value, -3) == "xes" and string.len(value) > 4 then
return string.sub(value, 1, -3)
end
if string.sub(value, -4) == "ches" and string.len(value) > 5 then
return string.sub(value, 1, -3)
end
if string.sub(value, -4) == "shes" and string.len(value) > 5 then
return string.sub(value, 1, -3)
end
if string.sub(value, -3) == "zes" and string.len(value) > 4 then
return string.sub(value, 1, -3)
end
if string.sub(value, -3) == "ses" then
return string.sub(value, 1, -3)
end
if string.sub(value, -1) == "s" then
return string.sub(value, 1, -2)
end
return value
end
local function MTH_AB_FindFamilyDietRow(familyName)
if type(MTH_DS_Families) ~= "table" then
return nil
end
local needle = MTH_AB_NormalizeFamilyToken(familyName)
if needle == "" then
return nil
end
local needleSingular = MTH_AB_SingularizeFamilyToken(needle)
for key, row in pairs(MTH_DS_Families) do
local candidate = MTH_AB_NormalizeFamilyToken(key)
if candidate ~= "" then
if candidate == needle or candidate == needleSingular then
return row
end
local candidateSingular = MTH_AB_SingularizeFamilyToken(candidate)
if candidateSingular == needle or candidateSingular == needleSingular then
return row
end
end
end
return nil
end
local function MTH_AB_GetPetDietText(familyName)
local family = tostring(familyName or "")
if family == "" then
return "unknown diet"
end
local row = MTH_AB_FindFamilyDietRow(family)
if type(row) ~= "table" or type(row.food) ~= "table" then
return "unknown diet"
end
local diets = {}
for i = 1, table.getn(row.food) do
local diet = tostring(row.food[i] or "")
if diet ~= "" then
table.insert(diets, MTH_AB_TitleCase(diet))
end
end
if table.getn(diets) == 0 then
return "unknown diet"
end
return table.concat(diets, ", ")
end
local function MTH_AB_GetPetFoodEntries()
if type(MTH_PETS_RefreshCurrentPet) == "function" then
pcall(MTH_PETS_RefreshCurrentPet)
end
local entries = {}
local defaultIcon = "Interface\\Icons\\INV_Misc_QuestionMark"
local function buildEntryFromRow(row)
if type(row) ~= "table" then
return nil
end
local name = tostring(row.name or "")
if name == "" then
return nil
end
local family = tostring(row.family or "")
local icon = tostring((type(row.stableInfo) == "table" and row.stableInfo.icon) or row.icon or "")
if icon == "" and type(row.stableRaw) == "table" then
icon = tostring(row.stableRaw[1] or "")
end
if icon == "" then icon = defaultIcon end
return {
icon = icon,
name = name,
family = family,
diets = MTH_AB_GetPetDietText(family),
}
end
local function setSlot(slot, data)
entries[slot + 1] = data
end
for slot = 0, 4 do
setSlot(slot, {
icon = defaultIcon,
name = "",
diets = "",
placeholder = (slot == 0) and "NO CURRENT PET !" or "Free slot",
})
end
local petsRoot = type(MTH_PETS_GetRootStore) == "function" and MTH_PETS_GetRootStore() or nil
local petStore = petsRoot and petsRoot.petStore or nil
local activeById = petStore and petStore.activeById or nil
local stableSlotIndex = petStore and petStore.stableSlotIndex or nil
local currentInfo = type(MTH_GetCurrentPetInfo) == "function" and MTH_GetCurrentPetInfo() or nil
if currentInfo and currentInfo.liveExists then
local currentName = tostring(currentInfo.name or "")
if currentName ~= "" then
local currentFamily = tostring(currentInfo.family or "")
local currentIcon = defaultIcon
local currentId = currentInfo.id
if currentId and type(activeById) == "table" and type(activeById[currentId]) == "table" then
local currentRow = activeById[currentId]
local iconFromRow = tostring((type(currentRow.stableInfo) == "table" and currentRow.stableInfo.icon) or currentRow.icon or "")
if iconFromRow == "" and type(currentRow.stableRaw) == "table" then
iconFromRow = tostring(currentRow.stableRaw[1] or "")
end
if iconFromRow ~= "" then
currentIcon = iconFromRow
end
end
setSlot(0, {
icon = currentIcon,
name = currentName,
family = currentFamily,
diets = MTH_AB_GetPetDietText(currentFamily),
})
end
end
for slot = 1, 4 do
local petId = nil
if type(stableSlotIndex) == "table" then
petId = stableSlotIndex[slot]
end
local row = (petId and type(activeById) == "table") and activeById[petId] or nil
if type(row) ~= "table" and type(activeById) == "table" then
for _, candidate in pairs(activeById) do
if type(candidate) == "table" and tonumber(candidate.stableSlot) == slot then
row = candidate
break
end
end
end
local entry = buildEntryFromRow(row)
if entry then
setSlot(slot, entry)
end
end
return entries
end
local function MTH_AB_RefreshPetFoodListUI()
local lines = MTH_AB_STATE.controls.petFoodPetLines or {}
if table.getn(lines) == 0 then
return
end
local entries = MTH_AB_GetPetFoodEntries()
for i = 1, table.getn(lines) do
local line = lines[i]
local entry = entries[i]
if line and line.text and line.icon then
if entry then
line.icon:Show()
line.icon:SetTexture(tostring(entry.icon or "Interface\\Icons\\INV_Misc_QuestionMark"))
line.text:Show()
if tostring(entry.name or "") ~= "" then
line.text:SetText(tostring(entry.name) .. " likes " .. tostring(entry.diets or "unknown diet"))
else
line.text:SetText(tostring(entry.placeholder or ""))
end
else
line.icon:Hide()
line.text:SetText("")
end
end
end
end
local function MTH_AB_GetBuyableItems(subtype)
if type(MTH_AutoBuy_GetProjectileItems) == "function" then
local result = MTH_AutoBuy_GetProjectileItems(subtype)
if type(result) == "table" then
return result
end
end
local wanted = string.lower(tostring(subtype or ""))
local result = {}
local source = MTH_DS_AmmoItems or {}
for itemId, row in pairs(source) do
if type(row) == "table" and string.lower(tostring(row.subtype or "")) == wanted and type(row.vendors) == "table" then
for _ in pairs(row.vendors) do
table.insert(result, {
itemId = tonumber(itemId) or itemId,
name = tostring(row.name or ("Item " .. tostring(itemId))),
level = tonumber(row.level) or 0,
reqlevel = tonumber(row.reqlevel) or 0,
})
break
end
end
end
table.sort(result, function(a, b)
if (a.level or 0) ~= (b.level or 0) then
return (a.level or 0) < (b.level or 0)
end
if tostring(a.name or "") ~= tostring(b.name or "") then
return tostring(a.name or "") < tostring(b.name or "")
end
return tonumber(a.itemId or 0) < tonumber(b.itemId or 0)
end)
return result
end
local function MTH_AB_GetItemLabel(item)
if type(item) ~= "table" then return "Unknown" end
return tostring(item.name or ("Item " .. tostring(item.itemId or "")))
end
local function MTH_AB_DropdownSetText(frame, text)
if UIDropDownMenu_SetText then
UIDropDownMenu_SetText(tostring(text or ""), frame)
return
end
if not frame then return end
local textRegion = getglobal(frame:GetName() .. "Text")
if textRegion and textRegion.SetText then
textRegion:SetText(tostring(text or ""))
end
end
local function MTH_AB_FindItemById(list, itemId)
itemId = tonumber(itemId)
if not itemId or type(list) ~= "table" then return nil end
for i = 1, table.getn(list) do
if tonumber(list[i].itemId) == itemId then
return list[i]
end
end
return nil
end
local function MTH_AB_GetRule(store, subtype, index)
local bucket = subtype == "arrow" and store.projectiles.arrows or store.projectiles.bullets
if type(bucket.rules[index]) ~= "table" then
bucket.rules[index] = MTH_AB_NormalizeRule(nil)
end
bucket.rules[index] = MTH_AB_NormalizeRule(bucket.rules[index])
return bucket.rules[index]
end
local function MTH_AB_SetRuleEnabled(store, subtype, index, enabled)
local rule = MTH_AB_GetRule(store, subtype, index)
rule.enabled = enabled and true or false
if not rule.enabled then
rule.itemId = nil
rule.stacks = 0
elseif rule.stacks < 1 then
rule.stacks = 1
end
end
local function MTH_AB_SetRuleItem(store, subtype, index, itemId)
local rule = MTH_AB_GetRule(store, subtype, index)
rule.itemId = itemId and (tonumber(itemId) or itemId) or nil
end
local function MTH_AB_SetRuleStacks(store, subtype, index, value)
local rule = MTH_AB_GetRule(store, subtype, index)
local stacks = math.floor(tonumber(value) or 0)
if stacks < 0 then stacks = 0 end
if rule.enabled and stacks < 1 then stacks = 1 end
rule.stacks = stacks
end
local function MTH_AB_RefreshRowUI(row, store, moduleEnabled, projectilesEnabled)
local rule = MTH_AB_GetRule(store, row.subtype, row.index)
local rowEnabled = moduleEnabled and projectilesEnabled and rule.enabled
local rowInteractive = moduleEnabled and projectilesEnabled
local muted = rowInteractive and 1 or 0.5
MTH_AB_SetCheckedNoClick(row.check, rule.enabled)
if rowInteractive then
if row.check.Enable then row.check:Enable() end
else
if row.check.Disable then row.check:Disable() end
end
if UIDropDownMenu_EnableDropDown and UIDropDownMenu_DisableDropDown then
if rowEnabled then
UIDropDownMenu_EnableDropDown(row.dropdown)
else
UIDropDownMenu_DisableDropDown(row.dropdown)
end
end
local selectedItem = MTH_AB_FindItemById(row.items, rule.itemId)
if selectedItem then
MTH_AB_DropdownSetText(row.dropdown, MTH_AB_GetItemLabel(selectedItem))
else
MTH_AB_DropdownSetText(row.dropdown, "Select item...")
end
if row.qty then
if rowEnabled then
if row.qty.Enable then row.qty:Enable() end
if row.qty.EnableMouse then row.qty:EnableMouse(true) end
if row.qty.SetTextColor then row.qty:SetTextColor(1, 1, 1) end
else
if row.qty.Disable then row.qty:Disable() end
if row.qty.EnableMouse then row.qty:EnableMouse(false) end
if row.qty.ClearFocus then row.qty:ClearFocus() end
if row.qty.SetTextColor then row.qty:SetTextColor(0.6, 0.6, 0.6) end
end
row.qty:SetText(tostring(rule.stacks or 0))
end
if row.label and row.label.SetTextColor then
row.label:SetTextColor(muted, muted, muted)
end
if row.suffix and row.suffix.SetTextColor then
row.suffix:SetTextColor(muted, muted, muted)
end
end
local function MTH_AB_RefreshOptionsUI()
if not MTH_AB_STATE.built then return end
local store = MTH_AB_EnsureConfig()
local moduleEnabled = true
if MTH and MTH.IsModuleEnabled then
moduleEnabled = MTH:IsModuleEnabled("autobuy", true) and true or false
end
MTH_AB_SetCheckedNoClick(MTH_AB_STATE.controls.moduleEnabled, moduleEnabled)
MTH_AB_SetCheckedNoClick(MTH_AB_STATE.controls.projectilesEnabled, store.projectiles.enabled and true or false)
if MTH_AB_STATE.controls.projectilesEnabled then
local proj = MTH_AB_STATE.controls.projectilesEnabled
if moduleEnabled then
if proj.Enable then
proj:Enable()
end
else
if proj.Disable then
proj:Disable()
end
end
end
if MTH_AB_STATE.controls.petFoodEnabled then
local pf = MTH_AB_STATE.controls.petFoodEnabled
MTH_AB_SetCheckedNoClick(pf, store.petFood.enabled and true or false)
if moduleEnabled then
if pf.Enable then pf:Enable() end
else
if pf.Disable then pf:Disable() end
end
end
local petFoodInteractive = moduleEnabled and store.petFood.enabled
if MTH_AB_STATE.controls.petFoodQty then
local qty = MTH_AB_STATE.controls.petFoodQty
qty:SetText(tostring(store.petFood.stacks or 1))
if petFoodInteractive then
if qty.Enable then qty:Enable() end
if qty.EnableMouse then qty:EnableMouse(true) end
if qty.SetTextColor then qty:SetTextColor(1, 1, 1) end
else
if qty.Disable then qty:Disable() end
if qty.EnableMouse then qty:EnableMouse(false) end
if qty.ClearFocus then qty:ClearFocus() end
if qty.SetTextColor then qty:SetTextColor(0.6, 0.6, 0.6) end
end
end
if MTH_AB_STATE.controls.petFoodScopeCurrent then
MTH_AB_SetCheckedNoClick(MTH_AB_STATE.controls.petFoodScopeCurrent, store.petFood.scope == "current")
if petFoodInteractive then
if MTH_AB_STATE.controls.petFoodScopeCurrent.Enable then MTH_AB_STATE.controls.petFoodScopeCurrent:Enable() end
else
if MTH_AB_STATE.controls.petFoodScopeCurrent.Disable then MTH_AB_STATE.controls.petFoodScopeCurrent:Disable() end
end
end
if MTH_AB_STATE.controls.petFoodScopeAll then
MTH_AB_SetCheckedNoClick(MTH_AB_STATE.controls.petFoodScopeAll, store.petFood.scope == "all")
if petFoodInteractive then
if MTH_AB_STATE.controls.petFoodScopeAll.Enable then MTH_AB_STATE.controls.petFoodScopeAll:Enable() end
else
if MTH_AB_STATE.controls.petFoodScopeAll.Disable then MTH_AB_STATE.controls.petFoodScopeAll:Disable() end
end
end
MTH_AB_RefreshPetFoodListUI()
for i = 1, 3 do
MTH_AB_RefreshRowUI(MTH_AB_STATE.controls.rows.arrow[i], store, moduleEnabled, store.projectiles.enabled)
MTH_AB_RefreshRowUI(MTH_AB_STATE.controls.rows.bullet[i], store, moduleEnabled, store.projectiles.enabled)
end
end
local function MTH_AB_CreateRow(container, subtype, index, xBase, yOffset)
local row = {
subtype = subtype,
index = index,
items = MTH_AB_GetBuyableItems(subtype),
}
local nameBase = "MetaHuntOptionsAutoBuy_" .. subtype .. "_" .. tostring(index)
row.check = MTH_CreateCheckbox(container, nameBase .. "Check", "", yOffset, xBase + MTH_AB_LAYOUT.ROW_CHECK_X)
row.label = container:CreateFontString(nameBase .. "Label", "ARTWORK", "GameFontNormalSmall")
row.label:SetPoint("TOPLEFT", container, "TOPLEFT", xBase + MTH_AB_LAYOUT.ROW_CHECK_LABEL_X, yOffset - 5)
row.label:SetText("On?")
row.dropdown = CreateFrame("Frame", nameBase .. "Drop", container, "UIDropDownMenuTemplate")
row.dropdown:ClearAllPoints()
row.dropdown:SetPoint("TOPLEFT", container, "TOPLEFT", xBase + MTH_AB_LAYOUT.ROW_DROPDOWN_X, yOffset + 3)
if UIDropDownMenu_SetWidth then
UIDropDownMenu_SetWidth(MTH_AB_LAYOUT.ROW_DROPDOWN_WIDTH, row.dropdown)
end
if UIDropDownMenu_JustifyText then
UIDropDownMenu_JustifyText("LEFT", row.dropdown)
end
if UIDropDownMenu_Initialize then
UIDropDownMenu_Initialize(row.dropdown, function()
local infoFactory = UIDropDownMenu_CreateInfo
local info = type(infoFactory) == "function" and infoFactory() or {}
info.text = "Select item..."
info.func = function()
local store = MTH_AB_EnsureConfig()
MTH_AB_SetRuleItem(store, subtype, index, nil)
MTH_AB_RefreshOptionsUI()
end
if UIDropDownMenu_AddButton then UIDropDownMenu_AddButton(info) end
for j = 1, table.getn(row.items) do
local item = row.items[j]
local label = MTH_AB_GetItemLabel(item)
local itemId = item.itemId
local entry = type(infoFactory) == "function" and infoFactory() or {}
entry.text = label
entry.func = function()
local store = MTH_AB_EnsureConfig()
MTH_AB_SetRuleItem(store, subtype, index, itemId)
MTH_AB_RefreshOptionsUI()
end
if UIDropDownMenu_AddButton then UIDropDownMenu_AddButton(entry) end
end
end)
end
row.qty = CreateFrame("EditBox", nameBase .. "Qty", container, "InputBoxTemplate")
local dropButton = getglobal(nameBase .. "DropButton")
if dropButton then
row.qty:SetPoint("LEFT", dropButton, "RIGHT", MTH_AB_LAYOUT.ROW_QTY_GAP, -1)
else
row.qty:SetPoint("LEFT", row.dropdown, "RIGHT", MTH_AB_LAYOUT.ROW_QTY_GAP + 6, -3)
end
row.qty:SetWidth(40)
row.qty:SetHeight(20)
row.qty:SetNumeric(true)
row.qty:SetAutoFocus(false)
row.suffix = container:CreateFontString(nameBase .. "Suffix", "ARTWORK", "GameFontNormalSmall")
row.suffix:SetPoint("LEFT", row.qty, "RIGHT", MTH_AB_LAYOUT.ROW_SUFFIX_GAP, 0)
row.suffix:SetText("Stacks")
if row.check then
row.check:SetScript("OnClick", function(self)
self = self or this
if not self or MTH_AB_STATE.syncing then return end
local store = MTH_AB_EnsureConfig()
MTH_AB_SetRuleEnabled(store, subtype, index, self:GetChecked() and true or false)
MTH_AB_RefreshOptionsUI()
end)
end
row.qty:SetScript("OnEnterPressed", function(self)
self = self or this
if not self then return end
local store = MTH_AB_EnsureConfig()
MTH_AB_SetRuleStacks(store, subtype, index, self:GetText())
MTH_AB_RefreshOptionsUI()
self:ClearFocus()
end)
row.qty:SetScript("OnEditFocusLost", function(self)
self = self or this
if not self then return end
local store = MTH_AB_EnsureConfig()
MTH_AB_SetRuleStacks(store, subtype, index, self:GetText())
MTH_AB_RefreshOptionsUI()
end)
return row
end
local function MTH_AB_BuildUI(container)
if MTH_AB_STATE.built and MTH_AB_STATE.container == container then
return
end
MTH_ClearContainer(container)
MTH_AB_STATE.container = container
MTH_AB_STATE.built = true
local controls = MTH_AB_STATE.controls
controls.title = container:CreateFontString("MetaHuntOptionsAutoBuyTitle", "ARTWORK", "GameFontHighlight")
if controls.title then
if controls.title.SetPoint then controls.title:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -10) end
if controls.title.SetText then controls.title:SetText("Auto Buy") end
end
controls.body = container:CreateFontString("MetaHuntOptionsAutoBuyBody", "ARTWORK", "GameFontNormalSmall")
if controls.body then
if controls.body.SetPoint then controls.body:SetPoint("TOPLEFT", container, "TOPLEFT", 176, -30) end
if controls.body.SetWidth then controls.body:SetWidth(400) end
if controls.body.SetJustifyH then controls.body:SetJustifyH("LEFT") end
if controls.body.SetTextColor then controls.body:SetTextColor(1, 1, 1) end
if controls.body.SetText then
controls.body:SetText("Configure automatic buying rules that triggers when you open a vendor.\nThe quantity refers to how many STACKS of the item you want to have in your bags after your purchase")
end
end
controls.moduleEnabled = MTH_CreateCheckbox(container, "MetaHuntOptionsAutoBuyModuleEnabled", "Enable Auto Buy module", -30)
if controls.moduleEnabled then
controls.moduleEnabled:SetScript("OnClick", function(self)
self = self or this
if not self or MTH_AB_STATE.syncing then return end
if MTH and MTH.SetModuleEnabled then
local ok, err = MTH:SetModuleEnabled("autobuy", self:GetChecked() and true or false)
if not ok and MTH and MTH.Print then
MTH:Print("Failed to change Auto Buy state: " .. tostring(err), "error")
end
end
MTH_AB_RefreshOptionsUI()
end)
end
controls.projectilesEnabled = MTH_CreateCheckbox(container, "MetaHuntOptionsAutoBuyProjectilesEnabled", "Enable for Projectiles", -74)
if controls.projectilesEnabled then
controls.projectilesEnabled:SetScript("OnClick", function(self)
self = self or this
if not self or MTH_AB_STATE.syncing then return end
local store = MTH_AB_EnsureConfig()
store.projectiles.enabled = self:GetChecked() and true or false
MTH_AB_RefreshOptionsUI()
end)
end
controls.sectionArrowTitle = container:CreateFontString("MetaHuntOptionsAutoBuyArrowsTitle", "ARTWORK", "GameFontHighlight")
if controls.sectionArrowTitle then
if controls.sectionArrowTitle.SetPoint then
controls.sectionArrowTitle:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_AB_LAYOUT.LEFT_SECTION_X, MTH_AB_LAYOUT.SECTION_TITLE_Y)
end
if controls.sectionArrowTitle.SetText then controls.sectionArrowTitle:SetText("Arrows") end
end
controls.sectionBulletTitle = container:CreateFontString("MetaHuntOptionsAutoBuyBulletsTitle", "ARTWORK", "GameFontHighlight")
if controls.sectionBulletTitle then
if controls.sectionBulletTitle.SetPoint then
controls.sectionBulletTitle:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_AB_LAYOUT.RIGHT_SECTION_X, MTH_AB_LAYOUT.SECTION_TITLE_Y)
end
if controls.sectionBulletTitle.SetText then controls.sectionBulletTitle:SetText("Bullets") end
end
for i = 1, 3 do
local rowY = MTH_AB_LAYOUT.SECTION_ROWS_Y0 - ((i - 1) * MTH_AB_LAYOUT.ROW_STEP)
controls.rows.arrow[i] = MTH_AB_CreateRow(container, "arrow", i, MTH_AB_LAYOUT.LEFT_SECTION_X, rowY)
controls.rows.bullet[i] = MTH_AB_CreateRow(container, "bullet", i, MTH_AB_LAYOUT.RIGHT_SECTION_X, rowY)
end
controls.petFoodTitle = container:CreateFontString("MetaHuntOptionsAutoBuyPetFoodTitle", "ARTWORK", "GameFontHighlight")
if controls.petFoodTitle then
controls.petFoodTitle:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_AB_LAYOUT.LEFT_SECTION_X, -300)
controls.petFoodTitle:SetText("Pet Food")
end
controls.petFoodEnabled = MTH_CreateCheckbox(container, "MetaHuntOptionsAutoBuyPetFoodEnabled", "Enable for Pet Food", -322, MTH_AB_LAYOUT.LEFT_SECTION_X)
if controls.petFoodEnabled then
controls.petFoodEnabled:SetScript("OnClick", function(self)
self = self or this
if not self or MTH_AB_STATE.syncing then return end
local store = MTH_AB_EnsureConfig()
store.petFood.enabled = self:GetChecked() and true or false
MTH_AB_RefreshOptionsUI()
end)
end
controls.petFoodQtyLabel = container:CreateFontString("MetaHuntOptionsAutoBuyPetFoodQtyLabel", "ARTWORK", "GameFontNormalSmall")
if controls.petFoodQtyLabel then
controls.petFoodQtyLabel:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_AB_LAYOUT.LEFT_SECTION_X + 28, -350)
controls.petFoodQtyLabel:SetText("Quantity :")
end
controls.petFoodQty = CreateFrame("EditBox", "MetaHuntOptionsAutoBuyPetFoodQty", container, "InputBoxTemplate")
if controls.petFoodQty then
controls.petFoodQty:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_AB_LAYOUT.LEFT_SECTION_X + 95, -347)
controls.petFoodQty:SetWidth(40)
controls.petFoodQty:SetHeight(20)
controls.petFoodQty:SetNumeric(true)
controls.petFoodQty:SetAutoFocus(false)
controls.petFoodQty:SetScript("OnEnterPressed", function(self)
self = self or this
if not self then return end
local store = MTH_AB_EnsureConfig()
MTH_AB_SetPetFoodStacks(store, self:GetText())
MTH_AB_RefreshOptionsUI()
self:ClearFocus()
end)
controls.petFoodQty:SetScript("OnEditFocusLost", function(self)
self = self or this
if not self then return end
local store = MTH_AB_EnsureConfig()
MTH_AB_SetPetFoodStacks(store, self:GetText())
MTH_AB_RefreshOptionsUI()
end)
end
controls.petFoodQtySuffix = container:CreateFontString("MetaHuntOptionsAutoBuyPetFoodQtySuffix", "ARTWORK", "GameFontNormalSmall")
if controls.petFoodQtySuffix then
controls.petFoodQtySuffix:SetPoint("LEFT", controls.petFoodQty, "RIGHT", 8, 0)
controls.petFoodQtySuffix:SetText("stacks")
end
controls.petFoodScopeCurrent = MTH_CreateCheckbox(container, "MetaHuntOptionsAutoBuyPetFoodScopeCurrent", "Buy only for my current pet", -376, MTH_AB_LAYOUT.LEFT_SECTION_X + 28)
if controls.petFoodScopeCurrent then
controls.petFoodScopeCurrent:SetScript("OnClick", function(self)
self = self or this
if not self or MTH_AB_STATE.syncing then return end
local store = MTH_AB_EnsureConfig()
store.petFood.scope = "current"
MTH_AB_RefreshOptionsUI()
end)
end
controls.petFoodScopeAll = MTH_CreateCheckbox(container, "MetaHuntOptionsAutoBuyPetFoodScopeAll", "Buy for all my pets", -402, MTH_AB_LAYOUT.LEFT_SECTION_X + 28)
if controls.petFoodScopeAll then
controls.petFoodScopeAll:SetScript("OnClick", function(self)
self = self or this
if not self or MTH_AB_STATE.syncing then return end
local store = MTH_AB_EnsureConfig()
store.petFood.scope = "all"
MTH_AB_RefreshOptionsUI()
end)
end
controls.petFoodPetLines = {}
for i = 1, 5 do
local rowY = -434 - ((i - 1) * 22)
local icon = container:CreateTexture("MetaHuntOptionsAutoBuyPetFoodPetIcon" .. tostring(i), "ARTWORK")
icon:SetWidth(16)
icon:SetHeight(16)
icon:SetPoint("TOPLEFT", container, "TOPLEFT", MTH_AB_LAYOUT.LEFT_SECTION_X + 28, rowY)
icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
local text = container:CreateFontString("MetaHuntOptionsAutoBuyPetFoodPetText" .. tostring(i), "ARTWORK", "GameFontNormalSmall")
text:SetPoint("LEFT", icon, "RIGHT", 6, 0)
text:SetJustifyH("LEFT")
text:SetTextColor(1, 1, 1)
text:SetText("")
controls.petFoodPetLines[i] = { icon = icon, text = text }
end
end
function MTH_SetupAutoBuyOptions()
if not MTH_AB_READY then return end
local container = MTH_GetFrame("MetaHuntOptionsAutoBuy")
if not container then return end
if CloseDropDownMenus then
CloseDropDownMenus()
end
MTH_AB_BuildUI(container)
MTH_AB_RefreshOptionsUI()
end
+597
View File
@@ -0,0 +1,597 @@
local function MTH_CHRON_GetGlobal(name)
if type(getglobal) == "function" then
return getglobal(name)
end
if _G then
return _G[name]
end
return nil
end
local function MTH_CHRON_IsReady()
local required = {
"MTH_GetFrame",
"MTH_ClearContainer",
"MTH_CreateCheckbox",
"MTH_CreateSlider",
"MTH_CreateActionButton",
}
for i = 1, table.getn(required) do
if MTH_CHRON_GetGlobal(required[i]) == nil then
return false
end
end
return true
end
local MTH_CHRON_STATE = {
controls = {},
}
local MTH_CHRON_DEFAULTS = {
growup = false,
reverse = false,
fadeonkill = true,
fadeonfade = true,
barposition = {},
ghost = 0,
selfbars = true,
barwidth = 220,
barheight = 16,
spacing = 0,
barscale = 1,
iconposition = "LEFT",
textsize = 10,
textcolor = "white",
bgcolor = "teal",
barcolor = "gray",
bordercolor = "black",
bordertex = "None",
bgalpha = 0.5,
text = "$t",
onlyself = false,
disabledSpells = {
COMMON = {
["Ephemeral Power"] = {},
["Mind Quickening"] = {},
["Unstable Power"] = {},
},
RACIAL = {},
HUNTER = {},
},
}
local function MTH_CHRON_SeedDefaultCommonDisabled(commonBucket)
if type(commonBucket) ~= "table" then
return
end
local names = {
"Ephemeral Power",
"Mind Quickening",
"Unstable Power",
}
for i = 1, table.getn(names) do
local baseName = names[i]
if commonBucket[baseName] == nil then
commonBucket[baseName] = {}
end
if MTH and MTH.LocalizeSpell then
local localized = MTH:LocalizeSpell(baseName)
if localized and localized ~= "" and commonBucket[localized] == nil then
commonBucket[localized] = {}
end
end
end
end
local function MTH_CHRON_CopyTable(source)
local target = {}
for key, value in pairs(source) do
if type(value) == "table" then
target[key] = MTH_CHRON_CopyTable(value)
else
target[key] = value
end
end
return target
end
local function MTH_CHRON_GetProfile()
if not (MTH and MTH.GetModuleSavedVariables) then
return MTH_CHRON_CopyTable(MTH_CHRON_DEFAULTS)
end
local store = MTH:GetModuleSavedVariables("chronometer")
if type(store.profile) ~= "table" then
store.profile = {}
end
local profile = store.profile
for key, value in pairs(MTH_CHRON_DEFAULTS) do
if profile[key] == nil then
if type(value) == "table" then
profile[key] = MTH_CHRON_CopyTable(value)
else
profile[key] = value
end
end
end
if type(profile.disabledSpells) ~= "table" then
profile.disabledSpells = {}
end
if type(profile.disabledSpells.COMMON) ~= "table" then
profile.disabledSpells.COMMON = {}
end
MTH_CHRON_SeedDefaultCommonDisabled(profile.disabledSpells.COMMON)
if type(profile.disabledSpells.RACIAL) ~= "table" then
profile.disabledSpells.RACIAL = {}
end
local _, class = UnitClass("player")
class = class or "HUNTER"
if type(profile.disabledSpells[class]) ~= "table" then
profile.disabledSpells[class] = {}
end
return profile
end
local function MTH_CHRON_GetTimerDefinitions()
local engine = MTH_ChronometerHunter
if engine and engine.timers and (engine.timers[engine.SPELL] or engine.timers[engine.EVENT]) then
return engine.timers, engine.SPELL, engine.EVENT
end
if not engine then
return nil, 1, 2
end
local fake = {
SPELL = 1,
EVENT = 2,
groups = {},
timers = {},
}
function fake:AddGroup(id, forall, color)
if color then
self.groups[id] = { fa = forall, cr = color }
else
self.groups[id] = { fa = forall }
end
end
function fake:AddTimer(kind, name, duration, targeted, isgain, selforselect, extra)
if type(self.timers[kind]) ~= "table" then
self.timers[kind] = {}
end
if type(extra) ~= "table" then
extra = {}
end
self.timers[kind][name] = {
d = duration,
k = { t = targeted, g = isgain, s = selforselect },
x = extra,
}
end
if type(engine.dataSetup) == "table" then
for _, setupFunc in pairs(engine.dataSetup) do
if type(setupFunc) == "function" then
setupFunc(fake)
end
end
end
return fake.timers, fake.SPELL, fake.EVENT
end
local function MTH_CHRON_SortKeys(list)
table.sort(list, function(a, b)
return tostring(a) < tostring(b)
end)
end
local function MTH_CHRON_BuildTimerLists()
local timers, spellKind, eventKind = MTH_CHRON_GetTimerDefinitions()
local classSpells, classEvents, racial = {}, {}, {}
if type(timers) ~= "table" then
return classSpells, classEvents, racial
end
local function classify(timerRecord)
if timerRecord and timerRecord.x and timerRecord.x.cl == "COMMON" then
return "COMMON"
elseif timerRecord and timerRecord.x and timerRecord.x.cl == "RACIAL" then
return "RACIAL"
end
local _, class = UnitClass("player")
return class or "HUNTER"
end
for timerName, timerRecord in pairs(timers[spellKind] or {}) do
local bucket = classify(timerRecord)
if bucket == "RACIAL" then
table.insert(racial, timerName)
elseif bucket ~= "COMMON" then
table.insert(classSpells, timerName)
end
end
for timerName, timerRecord in pairs(timers[eventKind] or {}) do
local bucket = classify(timerRecord)
if bucket == "RACIAL" then
table.insert(racial, timerName)
elseif bucket ~= "COMMON" then
table.insert(classEvents, timerName)
end
end
MTH_CHRON_SortKeys(classSpells)
MTH_CHRON_SortKeys(classEvents)
MTH_CHRON_SortKeys(racial)
return classSpells, classEvents, racial
end
local function MTH_CHRON_ApplyLiveProfile(profile)
if not profile then
return
end
local engine = MTH_ChronometerHunter
if not (engine and engine._mth_enabled) then
return
end
engine.profile = profile
if engine.SetCandyBarGroupGrowth then
engine:SetCandyBarGroupGrowth("MTHChronometer", profile.growup and true or false)
end
if engine.SetCandyBarGroupVerticalSpacing then
engine:SetCandyBarGroupVerticalSpacing("MTHChronometer", profile.spacing or 0)
end
if type(engine.bars) == "table" then
for i = 1, 20 do
local bar = engine.bars[i]
if bar and bar.id then
if profile.barscale and engine.SetCandyBarScale then
engine:SetCandyBarScale(bar.id, profile.barscale)
end
if profile.barwidth and engine.SetCandyBarWidth then
engine:SetCandyBarWidth(bar.id, profile.barwidth)
end
if profile.barheight and engine.SetCandyBarHeight then
engine:SetCandyBarHeight(bar.id, profile.barheight)
end
if profile.iconposition and engine.SetCandyBarIconPosition then
engine:SetCandyBarIconPosition(bar.id, profile.iconposition)
end
if profile.textsize and engine.SetCandyBarFontSize then
engine:SetCandyBarFontSize(bar.id, profile.textsize)
end
if profile.text and engine.SetCandyBarText then
local target = bar.target or "none"
local text = target == "none" and (bar.name or "") or profile.text
text = string.gsub(tostring(text or ""), "$t", tostring(target))
text = string.gsub(tostring(text or ""), "$s", tostring(bar.name or ""))
engine:SetCandyBarText(bar.id, text)
end
if engine.SetCandyBarReversed then
engine:SetCandyBarReversed(bar.id, profile.reverse and true or false)
end
end
end
end
end
local function MTH_CHRON_MakeTitle(parent, name, text, x, y)
local label = parent:CreateFontString(name, "ARTWORK", "GameFontHighlight")
label:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
label:SetText(text)
return label
end
local function MTH_CHRON_MakeSmall(parent, name, text, x, y)
local label = parent:CreateFontString(name, "ARTWORK", "GameFontNormalSmall")
label:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
label:SetText(text)
return label
end
local function MTH_CHRON_MakeInput(parent, name, x, y, width, height, value, onAccept)
local edit = CreateFrame("EditBox", name, parent, "InputBoxTemplate")
if not edit then
return nil
end
edit:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
edit:SetWidth(width or 200)
edit:SetHeight(height or 20)
edit:SetAutoFocus(false)
edit:SetText(tostring(value or ""))
edit:SetScript("OnEnterPressed", function(self)
self = self or this
if not self then return end
if type(onAccept) == "function" then
onAccept(self:GetText() or "")
end
self:ClearFocus()
end)
return edit
end
local function MTH_CHRON_NormalizeSection(tabKey)
if not tabKey and _G then
tabKey = _G.MTH_CHRON_OPTIONS_SECTION
end
if tabKey == "ChronometerBar" then
return "bar"
elseif tabKey == "ChronometerClassSpells" then
return "classspells"
elseif tabKey == "ChronometerClassEvents" then
return "classevents"
elseif tabKey == "ChronometerRacial" then
return "racial"
end
return "general"
end
local function MTH_CHRON_RenderTimerSection(container, profile, title, items, bucket)
local timerX = 20
local timerY = -84
MTH_CHRON_MakeTitle(container, "MetaHuntOptionsChronometerTimersHeader", title, timerX, timerY)
timerY = timerY - 24
if table.getn(items) == 0 then
MTH_CHRON_MakeSmall(container, "MetaHuntOptionsChronometerSectionEmpty", "(none)", timerX + 8, timerY)
return
end
for i = 1, table.getn(items) do
if timerY < -560 then
MTH_CHRON_MakeSmall(container, "MetaHuntOptionsChronometerOverflow", "...more", timerX + 8, timerY)
break
end
local timerName = items[i]
local controlName = "MetaHuntOptionsChronometerTimer" .. tostring(bucket) .. tostring(i)
local check = MTH_CreateCheckbox(container, controlName, tostring(timerName), timerY, timerX)
if check then
check:SetChecked(profile.disabledSpells[bucket][timerName] == nil and true or false)
check:SetScript("OnClick", function(self)
self = self or this
if not self then return end
if self:GetChecked() then
profile.disabledSpells[bucket][timerName] = nil
else
profile.disabledSpells[bucket][timerName] = {}
end
end)
end
timerY = timerY - 20
end
end
function MTH_RefreshChronometerOptions(tabKey)
if not MTH_CHRON_IsReady() then return end
MTH_SetupChronometerOptions(tabKey)
end
function MTH_SetupChronometerOptions(tabKey)
if not MTH_CHRON_IsReady() then
if MTH and MTH.Print then
MTH:Print("Chronometer options dependencies missing", "error")
end
return
end
local section = MTH_CHRON_NormalizeSection(tabKey)
local container = MTH_GetFrame("MetaHuntOptionsChronometer")
if not container then return end
MTH_ClearContainer(container)
MTH_CHRON_STATE.controls = {}
local profile = MTH_CHRON_GetProfile()
local _, class = UnitClass("player")
class = class or "HUNTER"
MTH_CHRON_MakeTitle(container, "MetaHuntOptionsChronometerTitle", "Chronometer", 20, -10)
local moduleCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerEnabled", "Enable Chronometer module", -54)
if moduleCheck then
local module = MTH and MTH:GetModule("chronometer")
moduleCheck:SetChecked(module and module.enabled and true or false)
moduleCheck:SetScript("OnClick", function(self)
self = self or this
if not self then return end
local enabled = self:GetChecked() and true or false
if MTH and MTH.SetModuleEnabled then
MTH:SetModuleEnabled("chronometer", enabled)
end
end)
end
if section == "general" then
MTH_CHRON_MakeTitle(container, "MetaHuntOptionsChronometerGeneralHeader", "General", 20, -84)
local ghostSlider = MTH_CreateSlider(container, "MetaHuntOptionsChronometerGhost", "Ghost Duration", 0, 30, 1, -112)
if ghostSlider then
ghostSlider:SetValue(tonumber(profile.ghost) or 0)
ghostSlider.onChange = function(value)
profile.ghost = tonumber(value) or 0
MTH_CHRON_ApplyLiveProfile(profile)
end
end
local killCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerFadeOnKill", "Fade on kill", -154)
if killCheck then
killCheck:SetChecked(profile.fadeonkill and true or false)
killCheck:SetScript("OnClick", function(self)
self = self or this
if not self then return end
profile.fadeonkill = self:GetChecked() and true or false
end)
end
local fadeCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerFadeOnFade", "Fade on aura fade", -179)
if fadeCheck then
fadeCheck:SetChecked(profile.fadeonfade and true or false)
fadeCheck:SetScript("OnClick", function(self)
self = self or this
if not self then return end
profile.fadeonfade = self:GetChecked() and true or false
end)
end
local selfCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerSelfBars", "Show self bars", -204)
if selfCheck then
selfCheck:SetChecked(profile.selfbars and true or false)
selfCheck:SetScript("OnClick", function(self)
self = self or this
if not self then return end
profile.selfbars = self:GetChecked() and true or false
end)
end
local onlySelfCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerOnlySelf", "Only self target", -229)
if onlySelfCheck then
onlySelfCheck:SetChecked(profile.onlyself and true or false)
onlySelfCheck:SetScript("OnClick", function(self)
self = self or this
if not self then return end
profile.onlyself = self:GetChecked() and true or false
end)
end
elseif section == "bar" then
MTH_CHRON_MakeTitle(container, "MetaHuntOptionsChronometerBarHeader", "Bars", 20, -84)
local runTestButton = MTH_CreateActionButton(container, "MetaHuntOptionsChronometerRunTest", "Run Test", 20, -112, 130, 22, function()
local engine = MTH_ChronometerHunter
if engine and engine.RunTest then
engine:RunTest()
end
end)
local anchorButton = MTH_CreateActionButton(container, "MetaHuntOptionsChronometerAnchor", "Toggle Anchor", 165, -112, 130, 22, function()
local engine = MTH_ChronometerHunter
if engine and engine.ToggleAnchor then
engine:ToggleAnchor()
end
end)
if runTestButton then runTestButton:Show() end
if anchorButton then anchorButton:Show() end
local anchorX = tonumber(profile.barposition and profile.barposition.x) or 0
local anchorY = tonumber(profile.barposition and profile.barposition.y) or 0
MTH_CHRON_MakeSmall(container, "MetaHuntOptionsChronometerAnchorPos", "Anchor: X " .. tostring(anchorX) .. " Y " .. tostring(anchorY), 20, -142)
local widthSlider = MTH_CreateSlider(container, "MetaHuntOptionsChronometerBarWidth", "Bar Width", 80, 320, 1, -180)
if widthSlider then
widthSlider:SetValue(tonumber(profile.barwidth) or 220)
widthSlider.onChange = function(value)
profile.barwidth = tonumber(value) or 220
MTH_CHRON_ApplyLiveProfile(profile)
end
end
local scaleSlider = MTH_CreateSlider(container, "MetaHuntOptionsChronometerBarScale", "Bar Scale", 0.5, 1.5, 0.1, -222)
if scaleSlider then
scaleSlider:SetValue(tonumber(profile.barscale) or 1)
scaleSlider.onChange = function(value)
profile.barscale = tonumber(value) or 1
MTH_CHRON_ApplyLiveProfile(profile)
end
end
local heightSlider = MTH_CreateSlider(container, "MetaHuntOptionsChronometerBarHeight", "Bar Height", 8, 30, 1, -264)
if heightSlider then
heightSlider:SetValue(tonumber(profile.barheight) or 16)
heightSlider.onChange = function(value)
profile.barheight = tonumber(value) or 16
MTH_CHRON_ApplyLiveProfile(profile)
end
end
local spacingSlider = MTH_CreateSlider(container, "MetaHuntOptionsChronometerBarSpacing", "Bar Spacing", 0, 15, 1, -306)
if spacingSlider then
spacingSlider:SetValue(tonumber(profile.spacing) or 0)
spacingSlider.onChange = function(value)
profile.spacing = tonumber(value) or 0
MTH_CHRON_ApplyLiveProfile(profile)
end
end
local textSizeSlider = MTH_CreateSlider(container, "MetaHuntOptionsChronometerTextSize", "Text Size", 8, 20, 1, -348)
if textSizeSlider then
textSizeSlider:SetValue(tonumber(profile.textsize) or 10)
textSizeSlider.onChange = function(value)
profile.textsize = tonumber(value) or 10
MTH_CHRON_ApplyLiveProfile(profile)
end
end
MTH_CHRON_MakeSmall(container, "MetaHuntOptionsChronometerTextPatternLabel", "Bar Text ($s spell, $t target)", 20, -390)
MTH_CHRON_MakeInput(container, "MetaHuntOptionsChronometerTextPattern", 20, -408, 220, 20, profile.text or "$t", function(newText)
if not newText or newText == "" then
newText = "$t"
end
profile.text = tostring(newText)
MTH_CHRON_ApplyLiveProfile(profile)
end)
local growCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerGrowUp", "Grow bars upward", -438)
if growCheck then
growCheck:SetChecked(profile.growup and true or false)
growCheck:SetScript("OnClick", function(self)
self = self or this
if not self then return end
profile.growup = self:GetChecked() and true or false
MTH_CHRON_ApplyLiveProfile(profile)
end)
end
local reverseCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerReverse", "Reverse bars", -463)
if reverseCheck then
reverseCheck:SetChecked(profile.reverse and true or false)
reverseCheck:SetScript("OnClick", function(self)
self = self or this
if not self then return end
profile.reverse = self:GetChecked() and true or false
MTH_CHRON_ApplyLiveProfile(profile)
end)
end
MTH_CHRON_MakeSmall(container, "MetaHuntOptionsChronometerIconPosLabel", "Icon Position", 20, -494)
local iconLeft = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerIconLeft", "Left", -514)
local iconRight = MTH_CreateCheckbox(container, "MetaHuntOptionsChronometerIconRight", "Right", -538)
if iconLeft and iconRight then
iconLeft:SetChecked(profile.iconposition ~= "RIGHT")
iconRight:SetChecked(profile.iconposition == "RIGHT")
iconLeft:SetScript("OnClick", function()
profile.iconposition = "LEFT"
iconLeft:SetChecked(true)
iconRight:SetChecked(nil)
MTH_CHRON_ApplyLiveProfile(profile)
end)
iconRight:SetScript("OnClick", function()
profile.iconposition = "RIGHT"
iconRight:SetChecked(true)
iconLeft:SetChecked(nil)
MTH_CHRON_ApplyLiveProfile(profile)
end)
end
else
local classSpells, classEvents, racial = MTH_CHRON_BuildTimerLists()
if section == "classspells" then
MTH_CHRON_RenderTimerSection(container, profile, "Hunter Spells", classSpells, class)
elseif section == "classevents" then
MTH_CHRON_RenderTimerSection(container, profile, "Hunter Events", classEvents, class)
elseif section == "racial" then
MTH_CHRON_RenderTimerSection(container, profile, "Racial", racial, "RACIAL")
end
end
end
if _G then
_G.MTH_SetupChronometerOptions = MTH_SetupChronometerOptions
_G.MTH_RefreshChronometerOptions = MTH_RefreshChronometerOptions
end
+193
View File
@@ -0,0 +1,193 @@
local MTH_CREDITS_READY = MTH_OptionsRequire and MTH_OptionsRequire("options-credits", {
"MTH_GetFrame",
"MTH_ClearContainer",
})
local MTH_CREDITS_FONT_DELTA = 2
local function MTH_CreditsBumpFont(fs)
if not fs or not fs.GetFont or not fs.SetFont then return end
local path, size, flags = fs:GetFont()
if path and size then
fs:SetFont(path, size + MTH_CREDITS_FONT_DELTA, flags)
end
end
local function MTH_CreditsInsertLink(url)
local link = tostring(url or "")
if link == "" then return end
if type(MTH_InsertLinkToChat) == "function" and MTH_InsertLinkToChat(link) then
if MTH and MTH.Print then
MTH:Print("Link inserted in chat: " .. link)
end
return
end
if MTH and MTH.Print then
MTH:Print("Link: " .. link)
end
end
local function MTH_CreditsClearFrame(frame)
if not frame then return end
local children = { frame:GetChildren() }
for i = 1, table.getn(children) do
if children[i] and children[i].Hide then
children[i]:Hide()
end
end
local regions = { frame:GetRegions() }
for i = 1, table.getn(regions) do
if regions[i] and regions[i].Hide then
regions[i]:Hide()
end
end
end
local function MTH_CreditsCreateLink(parent, label, url, anchorTo, y)
local button = CreateFrame("Button", nil, parent)
if not button then return nil end
button:SetPoint("TOPLEFT", anchorTo, "BOTTOMLEFT", 0, y or -4)
button:SetHeight(18)
button:SetWidth(500)
local text = button:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
text:SetPoint("LEFT", button, "LEFT", 0, 0)
text:SetJustifyH("LEFT")
text:SetText(label)
text:SetTextColor(0.40, 0.75, 1.00)
MTH_CreditsBumpFont(text)
button:SetScript("OnClick", function()
MTH_CreditsInsertLink(url)
end)
button:SetScript("OnEnter", function()
text:SetTextColor(0.60, 0.90, 1.00)
end)
button:SetScript("OnLeave", function()
text:SetTextColor(0.40, 0.75, 1.00)
end)
return button
end
local function MTH_CreditsCreateText(parent, anchorTo, text, font, y, r, g, b)
local fs = parent:CreateFontString(nil, "ARTWORK", font or "GameFontNormalSmall")
if anchorTo == parent then
fs:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, y or 0)
else
fs:SetPoint("TOPLEFT", anchorTo, "BOTTOMLEFT", 0, y or -8)
end
fs:SetWidth(500)
fs:SetJustifyH("LEFT")
fs:SetJustifyV("TOP")
fs:SetTextColor(r or 0.93, g or 0.93, b or 0.93)
fs:SetText(text)
MTH_CreditsBumpFont(fs)
return fs
end
function MTH_SetupCreditsOptions()
if not MTH_CREDITS_READY then return end
local container = MTH_GetFrame("MetaHuntOptionsCredits")
if not container then return end
MTH_ClearContainer(container)
local title = container:CreateFontString("MetaHuntOptionsCreditsTitle", "ARTWORK", "GameFontNormal")
title:SetPoint("TOPLEFT", container, "TOPLEFT", 10, -10)
title:SetText("Credits")
title:SetTextColor(1.00, 0.82, 0.00)
local scrollFrame = MTH_GetFrame("MetaHuntOptionsCreditsScroll")
if not scrollFrame then
scrollFrame = CreateFrame("ScrollFrame", "MetaHuntOptionsCreditsScroll", container, "UIPanelScrollFrameTemplate")
end
scrollFrame:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -10)
scrollFrame:SetPoint("BOTTOMRIGHT", container, "BOTTOMRIGHT", -30, 10)
scrollFrame:Show()
local content = MTH_GetFrame("MetaHuntOptionsCreditsContent")
if not content then
content = CreateFrame("Frame", "MetaHuntOptionsCreditsContent", scrollFrame)
end
content:SetWidth(520)
content:SetHeight(2600)
MTH_CreditsClearFrame(content)
scrollFrame:SetScrollChild(content)
local cursor = MTH_CreditsCreateText(content, content, "About the code", "GameFontNormal", -2, 1.00, 0.82, 0.00)
cursor = MTH_CreditsCreateText(content, cursor,
"This addon is, in others, a compilation of old vanilla addons that I loved and played with during years. But most of them had become very buggy with TwoW as new contain was added to the server. \n I fixed, reworked, enhanced, and melted them within a modern modular framework that I created.\n"
.. "I want to make it crystal clear about what work is mine, and what is not and respectfully credit the original authors for the great stuff I've taken from them:",
"GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateText(content, cursor,
"- zBars, Antidaze and Autostrip were core functionalities of Vanilla zHunterMod addon. And I kept the \"z\" naming of bars/buttons to always remember it. On top of this I have created myself zAmmo, zCompanions, zMount, zToys.\nAlso the SmartAmmo feature idea is coming from Zhuntermod: there was a file in it with that functionality, but was like a work-in-progress, totally unfunctional and even \"dangerous\" in its current state, and was not active in the addon. Since it was a fucking great idea, I recoded it mostly from scratch and made it work reliably and safely.",
"GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateText(content, cursor,
"- Feed-o-Matic was entirely the work of Fizzwidget. I made it work for Twow, by melting it as a module calling Metahunt core to get pet's state and up-to-date data for new pet families and their diet.",
"GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateLink(content,
"https://www.wowinterface.com/downloads/info4160-FizzwidgetFeed-O-Matic.html",
"https://www.wowinterface.com/downloads/info4160-FizzwidgetFeed-O-Matic.html",
cursor,
-4)
cursor = MTH_CreditsCreateText(content, cursor,
"- Tooltips module is based on Hunter Helper, another vanilla addon of Fizzwidget. I kept the general idea, but there's not much original code left from it in Metahunt as I refactored everything so it plugs on MetaHunt controlled event handlers, can work with Turtle wow data, and is now usable on other things than Beasts.",
"GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateLink(content,
"https://legacy-wow.com/vanilla-addons/fizzwidget-hunter-helper/",
"https://legacy-wow.com/vanilla-addons/fizzwidget-hunter-helper/",
cursor,
-4)
cursor = MTH_CreditsCreateText(content, cursor,
"- Chronometer was an old and very popular vanilla addon working with ACE2 libraries. It has known uncountable iterations by various people over the years, and I can't credit them all of them here. The current version used by Metahunt is based on this Twow conversion made by \"wigan91\". I have reworked it to have it Hunter-focused-only: removed the non-hunter/race stuff, improved the definition for spells/effects config that were not correct, added missing hunter spells (not many), and implemented my own bar color scheme fitting better the hunter spells.",
"GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateLink(content,
"https://github.com/wigan91/Chronometer-TWoW",
"https://github.com/wigan91/Chronometer-TWoW",
cursor,
-4)
cursor = MTH_CreditsCreateText(content, cursor,
"- The Map/Marker system is the one of pfQuest from Master Shagu. Unfortunately he is no more reachable for some months and I could not have some talk with him about this. I mostly let it untouched, I just made slight modifications so it can integrate better within Metahunt, and doesn't conflict with pfQuest.",
"GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateText(content, cursor,
"- The system allowing to fetch itemlinks in game (In the Weapon tab of the Hunter Book notably) is the one of Atlas Twow.",
"GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateText(content, cursor, "About data sources", "GameFontNormal", -14, 1.00, 0.82, 0.00)
cursor = MTH_CreditsCreateText(content, cursor,
"Metahunt ships with its own Turtle-WoW datastores, limited to hunter stuff only, and most of this data is up-to-date (Feb 2026).\n\n"
.. "Sources used to build the data include:",
"GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateText(content, cursor,
"- pfQuest for Beasts and Ammo vendors NPCs and their spawn points (and beasts respawn times if any). Thank you again Shagu.",
"GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateText(content, cursor,
"- This invaluable and very accurate spreadsheet for beasts having pet abilities. A big thanks to all twow players that crafted this! Now you dont need that sheet anymore, you have Metahunt! And the Great Book of Huntards is awesome and accurate also because of YOU.",
"GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateLink(content,
"https://docs.google.com/spreadsheets/d/1u33knqlYXvY-jR6pvTd58gyDg8OYEuwYsSoEnIjeA8k/edit?gid=4136205#gid=4136205",
"https://docs.google.com/spreadsheets/d/1u33knqlYXvY-jR6pvTd58gyDg8OYEuwYsSoEnIjeA8k/edit?gid=4136205#gid=4136205",
cursor,
-4)
cursor = MTH_CreditsCreateText(content, cursor,
"- Atlas twow for ranged weapon sources\n\n"
.. "- Twow wiki for pet diet (which was incomplete, missing Serpents and Foxes)",
"GameFontNormalSmall", -10)
cursor = MTH_CreditsCreateText(content, cursor,
"With that being said, go back hunting fellas!",
"GameFontNormalSmall", -10, 1.00, 0.82, 0.00)
cursor = MTH_CreditsCreateText(content, cursor,
"\n"
.. "Metasploit <Atlantis>, of Nordaanar.",
"GameFontNormalSmall", -10)
end
+373
View File
@@ -0,0 +1,373 @@
local MTH_FOM_READY = MTH_OptionsRequire and MTH_OptionsRequire("options-feedomatic", {
"MTH_GetFrame",
"MTH_ClearContainer",
"MTH_CreateCheckbox",
})
local MTH_FOM_STATE = {
enableCheck = nil,
keepOpenEdit = nil,
bindStatus = nil,
bindButton = nil,
bindClearButton = nil,
bindingCapture = false,
ctrl = {},
}
local function MTH_EnsureFOMConfig()
if FOM_Config then
return
end
if FOM_Config_Default then
FOM_Config = {}
for key, value in pairs(FOM_Config_Default) do
FOM_Config[key] = value
end
return
end
FOM_Config = {
Enabled = false,
Alert = "emote",
Level = "content",
KeepOpenSlots = 8,
AvoidUsefulFood = true,
AvoidQuestFood = true,
AvoidBonusFood = true,
Fallback = false,
SaveForCookingLevel = 1,
PreferHigherQuality = true,
AudioWarning = true,
TextWarning = true,
IconWarning = true,
}
end
local function MTH_FOM_SetAlert(mode)
MTH_EnsureFOMConfig()
if mode == "none" then
FOM_Config.Alert = nil
else
FOM_Config.Alert = mode
end
end
local function MTH_FOM_SetLevel(level)
MTH_EnsureFOMConfig()
if level == "off" then
FOM_Config.Level = nil
else
FOM_Config.Level = level
end
end
local function MTH_FOM_SetCookingLevel(level)
MTH_EnsureFOMConfig()
FOM_Config.SaveForCookingLevel = level
end
local function MTH_FOM_GetBindingDisplayText()
if not GetBindingKey then
return "Unavailable"
end
local key1, key2 = GetBindingKey("FEEDOMATIC")
if not key1 and not key2 then
return "Unbound"
end
if key1 and key2 then
return key1 .. " / " .. key2
end
return key1 or key2 or "Unbound"
end
local function MTH_FOM_UpdateBindingStatus()
local bindStatus = MTH_FOM_STATE.bindStatus
if not bindStatus or not bindStatus.SetText then
return
end
if MTH_FOM_STATE.bindingCapture then
bindStatus:SetText("Feed key: press a key... (ESC to clear)")
else
bindStatus:SetText("Feed key: " .. MTH_FOM_GetBindingDisplayText())
end
end
local function MTH_FOM_StopBindingCapture()
MTH_FOM_STATE.bindingCapture = false
if MTH_FOM_STATE.bindButton then
if MTH_FOM_STATE.bindButton.SetPropagateKeyboardInput then
MTH_FOM_STATE.bindButton:SetPropagateKeyboardInput(true)
end
MTH_FOM_STATE.bindButton:EnableKeyboard(false)
MTH_FOM_STATE.bindButton:SetText("Set Key")
end
MTH_FOM_UpdateBindingStatus()
end
local function MTH_FOM_StartBindingCapture()
MTH_FOM_STATE.bindingCapture = true
if MTH_FOM_STATE.bindButton then
MTH_FOM_STATE.bindButton:SetText("Press key...")
MTH_FOM_STATE.bindButton:EnableKeyboard(true)
if MTH_FOM_STATE.bindButton.SetPropagateKeyboardInput then
MTH_FOM_STATE.bindButton:SetPropagateKeyboardInput(false)
end
end
MTH_FOM_UpdateBindingStatus()
end
local function MTH_FOM_BuildBindingChord(key)
local chord = ""
if IsControlKeyDown and IsControlKeyDown() and key ~= "LCTRL" and key ~= "RCTRL" then chord = chord .. "CTRL-" end
if IsAltKeyDown and IsAltKeyDown() and key ~= "LALT" and key ~= "RALT" then chord = chord .. "ALT-" end
if IsShiftKeyDown and IsShiftKeyDown() and key ~= "LSHIFT" and key ~= "RSHIFT" then chord = chord .. "SHIFT-" end
return chord .. key
end
local function MTH_FOM_SaveBinding(key)
if not SetBinding or not SaveBindings or not GetBindingKey then
if MTH and MTH.Print then
MTH:Print("Keybinding APIs unavailable.")
end
return
end
if InCombatLockdown and InCombatLockdown() then
if MTH and MTH.Print then
MTH:Print("Cannot change keybindings during combat.")
end
MTH_FOM_StopBindingCapture()
return
end
local old1, old2 = GetBindingKey("FEEDOMATIC")
if old1 then SetBinding(old1) end
if old2 then SetBinding(old2) end
if key and key ~= "" and key ~= "ESCAPE" then
SetBinding(key, "FEEDOMATIC")
end
local bindingSet = 1
if GetCurrentBindingSet then
bindingSet = GetCurrentBindingSet() or 1
end
SaveBindings(bindingSet)
MTH_FOM_StopBindingCapture()
end
function MTH_RefreshFeedOMaticOptions()
if not MTH_FOM_READY then return end
MTH_EnsureFOMConfig()
if MTH_FOM_STATE.enableCheck then
local module = MTH and MTH:GetModule("feedomatic")
MTH_FOM_STATE.enableCheck:SetChecked(module and module.enabled and true or false)
end
if MTH_FOM_STATE.keepOpenEdit then
MTH_FOM_STATE.keepOpenEdit:SetText(tostring(FOM_Config.KeepOpenSlots or 0))
end
MTH_FOM_UpdateBindingStatus()
if MTH_FOM_STATE.ctrl.AvoidQuestFood then MTH_FOM_STATE.ctrl.AvoidQuestFood:SetChecked(FOM_Config.AvoidQuestFood and 1 or nil) end
if MTH_FOM_STATE.ctrl.AvoidBonusFood then MTH_FOM_STATE.ctrl.AvoidBonusFood:SetChecked(FOM_Config.AvoidBonusFood and 1 or nil) end
if MTH_FOM_STATE.ctrl.PreferHigherQuality then MTH_FOM_STATE.ctrl.PreferHigherQuality:SetChecked(FOM_Config.PreferHigherQuality and 1 or nil) end
if MTH_FOM_STATE.ctrl.Fallback then MTH_FOM_STATE.ctrl.Fallback:SetChecked(FOM_Config.Fallback and 1 or nil) end
if MTH_FOM_STATE.ctrl.AudioWarning then MTH_FOM_STATE.ctrl.AudioWarning:SetChecked(FOM_Config.AudioWarning and true or false) end
if MTH_FOM_STATE.ctrl.AudioWarningBell then MTH_FOM_STATE.ctrl.AudioWarningBell:SetChecked(FOM_Config.AudioWarning == "bell" and 1 or nil) end
if MTH_FOM_STATE.ctrl.TextWarning then MTH_FOM_STATE.ctrl.TextWarning:SetChecked(FOM_Config.TextWarning and 1 or nil) end
if MTH_FOM_STATE.ctrl.IconWarning then MTH_FOM_STATE.ctrl.IconWarning:SetChecked(FOM_Config.IconWarning and 1 or nil) end
if MTH_FOM_STATE.ctrl.AlertEmote then MTH_FOM_STATE.ctrl.AlertEmote:SetChecked(FOM_Config.Alert == "emote" and 1 or nil) end
if MTH_FOM_STATE.ctrl.AlertChat then MTH_FOM_STATE.ctrl.AlertChat:SetChecked(FOM_Config.Alert == "chat" and 1 or nil) end
if MTH_FOM_STATE.ctrl.AlertNone then MTH_FOM_STATE.ctrl.AlertNone:SetChecked((not FOM_Config.Alert) and 1 or nil) end
if MTH_FOM_STATE.ctrl.LevelContent then MTH_FOM_STATE.ctrl.LevelContent:SetChecked(FOM_Config.Level == "content" and 1 or nil) end
if MTH_FOM_STATE.ctrl.LevelUnhappy then MTH_FOM_STATE.ctrl.LevelUnhappy:SetChecked(FOM_Config.Level == "unhappy" and 1 or nil) end
if MTH_FOM_STATE.ctrl.LevelOff then MTH_FOM_STATE.ctrl.LevelOff:SetChecked((not FOM_Config.Level) and 1 or nil) end
if MTH_FOM_STATE.ctrl.SaveCookOrange then MTH_FOM_STATE.ctrl.SaveCookOrange:SetChecked(FOM_Config.SaveForCookingLevel == 3 and 1 or nil) end
if MTH_FOM_STATE.ctrl.SaveCookYellow then MTH_FOM_STATE.ctrl.SaveCookYellow:SetChecked(FOM_Config.SaveForCookingLevel == 2 and 1 or nil) end
if MTH_FOM_STATE.ctrl.SaveCookGreen then MTH_FOM_STATE.ctrl.SaveCookGreen:SetChecked(FOM_Config.SaveForCookingLevel == 1 and 1 or nil) end
if MTH_FOM_STATE.ctrl.SaveCookAll then MTH_FOM_STATE.ctrl.SaveCookAll:SetChecked(FOM_Config.SaveForCookingLevel == 0 and 1 or nil) end
if MTH_FOM_STATE.ctrl.SaveCookNone then MTH_FOM_STATE.ctrl.SaveCookNone:SetChecked(FOM_Config.SaveForCookingLevel and FOM_Config.SaveForCookingLevel >= 4 and 1 or nil) end
end
function MTH_SetupFeedOMaticOptions()
if not MTH_FOM_READY then return end
local container = MTH_GetFrame("MetaHuntOptionsFeedOMatic")
if not container then return end
local rightColumnX = 300
local yAdjust = -64
MTH_ClearContainer(container)
MTH_FOM_STATE.ctrl = {}
local title = container:CreateFontString("MetaHuntOptionsFeedOMaticTitle", "ARTWORK", "GameFontHighlight")
title:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -10)
title:SetText("Feed-O-Matic")
local statusNotice = container:CreateFontString("MetaHuntOptionsFeedOMaticStatusNotice", "ARTWORK", "GameFontNormalSmall")
statusNotice:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -34)
statusNotice:SetWidth(600)
statusNotice:SetJustifyH("LEFT")
statusNotice:SetJustifyV("TOP")
statusNotice:SetTextColor(0.35, 0.65, 1)
statusNotice:SetText("Feed-O-Matic, created by the great Fizzwidget, is not yet entirely ready for TurtleWoW because I am missing \na reliable list of all foods, mostly impossible to fetch from the DB.\n It still works much better in this version, but all stuff related to Food buff and Cooking isnt fully functional.")
local moduleCheck = MTH_CreateCheckbox(container, "MetaHuntOptionsFeedOMaticEnabled", "Enable FeedOMatic module", -32 + yAdjust)
if moduleCheck then
moduleCheck:SetScript("OnClick", function(self)
self = self or this
if not self then return end
local enabled = self:GetChecked() == 1
if MTH and MTH.SetModuleEnabled then
local ok, err = MTH:SetModuleEnabled("feedomatic", enabled)
if not ok and MTH and MTH.Print then
MTH:Print("Failed to change FeedOMatic state: " .. tostring(err))
end
end
MTH_RefreshFeedOMaticOptions()
end)
MTH_FOM_STATE.enableCheck = moduleCheck
end
local leftHeader = container:CreateFontString("MetaHuntOptionsFeedOMaticGeneralHeader", "ARTWORK", "GameFontHighlight")
leftHeader:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -62 + yAdjust)
leftHeader:SetText("General")
local bindStatus = container:CreateFontString("MetaHuntOptionsFOMBindStatus", "ARTWORK", "GameFontNormalSmall")
bindStatus:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -80 + yAdjust)
bindStatus:SetText("Feed key: " .. MTH_FOM_GetBindingDisplayText())
MTH_FOM_STATE.bindStatus = bindStatus
local bindButton = CreateFrame("Button", "MetaHuntOptionsFOMBindButton", container, "UIPanelButtonTemplate")
bindButton:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -100 + yAdjust)
bindButton:SetWidth(90)
bindButton:SetHeight(22)
bindButton:SetText("Set Key")
bindButton:EnableKeyboard(false)
bindButton:SetScript("OnClick", function()
if MTH_FOM_STATE.bindingCapture then
MTH_FOM_StopBindingCapture()
else
MTH_FOM_StartBindingCapture()
end
end)
bindButton:SetScript("OnKeyDown", function(_, key)
if not MTH_FOM_STATE.bindingCapture then
return
end
key = key or arg1
if not key or key == "" then
return
end
if key == "ESCAPE" then
MTH_FOM_SaveBinding(nil)
return
end
if key == "UNKNOWN" or key == "PRINTSCREEN" then
return
end
MTH_FOM_SaveBinding(MTH_FOM_BuildBindingChord(key))
end)
MTH_FOM_STATE.bindButton = bindButton
local bindClearButton = CreateFrame("Button", "MetaHuntOptionsFOMBindClearButton", container, "UIPanelButtonTemplate")
bindClearButton:SetPoint("LEFT", bindButton, "RIGHT", 8, 0)
bindClearButton:SetWidth(70)
bindClearButton:SetHeight(22)
bindClearButton:SetText("Clear")
bindClearButton:SetScript("OnClick", function()
MTH_FOM_SaveBinding(nil)
end)
MTH_FOM_STATE.bindClearButton = bindClearButton
local bindHint = container:CreateFontString("MetaHuntOptionsFOMBindHint", "ARTWORK", "GameFontNormalSmall")
bindHint:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -128 + yAdjust)
bindHint:SetWidth(360)
bindHint:SetJustifyH("LEFT")
bindHint:SetJustifyV("TOP")
bindHint:SetTextColor(0.35, 0.65, 1)
bindHint:SetText("You should assign a key bind for feed-o-matic,\nin order to feed your pet automatically.\nThe key \"P\" is an excellent candidate !")
MTH_FOM_STATE.ctrl.AvoidQuestFood = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMAvoidQuest", FOM_OptionsButtonText and FOM_OptionsButtonText["AvoidQuestFood"] or "Avoid quest foods", -170 + yAdjust)
MTH_FOM_STATE.ctrl.AvoidBonusFood = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMAvoidBonus", FOM_OptionsButtonText and FOM_OptionsButtonText["AvoidBonusFood"] or "Avoid bonus foods", -195 + yAdjust)
MTH_FOM_STATE.ctrl.PreferHigherQuality = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMPreferQuality", FOM_OptionsButtonText and FOM_OptionsButtonText["PreferHigherQuality"] or "Prefer higher quality food", -220 + yAdjust)
MTH_FOM_STATE.ctrl.Fallback = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMFallback", FOM_OptionsButtonText and FOM_OptionsButtonText["Fallback"] or "Fallback to avoided foods", -245 + yAdjust)
if MTH_FOM_STATE.ctrl.AvoidQuestFood then MTH_FOM_STATE.ctrl.AvoidQuestFood:SetScript("OnClick", function(self) self = self or this if not self then return end FOM_Config.AvoidQuestFood = self:GetChecked() == 1 end) end
if MTH_FOM_STATE.ctrl.AvoidBonusFood then MTH_FOM_STATE.ctrl.AvoidBonusFood:SetScript("OnClick", function(self) self = self or this if not self then return end FOM_Config.AvoidBonusFood = self:GetChecked() == 1 end) end
if MTH_FOM_STATE.ctrl.PreferHigherQuality then MTH_FOM_STATE.ctrl.PreferHigherQuality:SetScript("OnClick", function(self) self = self or this if not self then return end FOM_Config.PreferHigherQuality = self:GetChecked() == 1 end) end
if MTH_FOM_STATE.ctrl.Fallback then MTH_FOM_STATE.ctrl.Fallback:SetScript("OnClick", function(self) self = self or this if not self then return end FOM_Config.Fallback = self:GetChecked() == 1 end) end
local keepOpenLabel = container:CreateFontString("MetaHuntOptionsFOMKeepOpenLabel", "ARTWORK", "GameFontNormalSmall")
keepOpenLabel:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -302 + yAdjust)
keepOpenLabel:SetText("Keep open bag slots:")
local keepOpen = CreateFrame("EditBox", "MetaHuntOptionsFOMKeepOpen", container, "InputBoxTemplate")
keepOpen:SetPoint("LEFT", keepOpenLabel, "RIGHT", 8, 0)
keepOpen:SetWidth(40)
keepOpen:SetHeight(20)
keepOpen:SetNumeric(true)
keepOpen:SetAutoFocus(false)
keepOpen:SetScript("OnTextChanged", function(self)
self = self or this
if not self then return end
local value = tonumber(self:GetText()) or 0
FOM_Config.KeepOpenSlots = value
end)
keepOpen:SetScript("OnEnterPressed", function(self) self = self or this if self then self:ClearFocus() end end)
keepOpen:SetScript("OnEscapePressed", function(self) self = self or this if self then self:ClearFocus() end end)
MTH_FOM_STATE.keepOpenEdit = keepOpen
local notifyHeader = container:CreateFontString("MetaHuntOptionsFOMNotifyHeader", "ARTWORK", "GameFontHighlight")
notifyHeader:SetPoint("TOPLEFT", container, "TOPLEFT", 20, -336 + yAdjust)
notifyHeader:SetText("Notify when feeding")
MTH_FOM_STATE.ctrl.AlertEmote = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMAlertEmote", FOM_OptionsButtonText and FOM_OptionsButtonText["AlertEmote"] or "Via emote", -360 + yAdjust)
MTH_FOM_STATE.ctrl.AlertChat = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMAlertChat", FOM_OptionsButtonText and FOM_OptionsButtonText["AlertChat"] or "In chat", -385 + yAdjust)
MTH_FOM_STATE.ctrl.AlertNone = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMAlertNone", FOM_OptionsButtonText and FOM_OptionsButtonText["AlertNone"] or "Don't notify", -410 + yAdjust)
if MTH_FOM_STATE.ctrl.AlertEmote then MTH_FOM_STATE.ctrl.AlertEmote:SetScript("OnClick", function() MTH_FOM_SetAlert("emote"); MTH_RefreshFeedOMaticOptions() end) end
if MTH_FOM_STATE.ctrl.AlertChat then MTH_FOM_STATE.ctrl.AlertChat:SetScript("OnClick", function() MTH_FOM_SetAlert("chat"); MTH_RefreshFeedOMaticOptions() end) end
if MTH_FOM_STATE.ctrl.AlertNone then MTH_FOM_STATE.ctrl.AlertNone:SetScript("OnClick", function() MTH_FOM_SetAlert("none"); MTH_RefreshFeedOMaticOptions() end) end
local warnHeader = container:CreateFontString("MetaHuntOptionsFOMWarnHeader", "ARTWORK", "GameFontHighlight")
warnHeader:SetPoint("TOPLEFT", container, "TOPLEFT", rightColumnX, -276 + yAdjust)
warnHeader:SetText("Warn when pet needs feeding")
MTH_FOM_STATE.ctrl.LevelContent = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMLevelContent", FOM_OptionsButtonText and FOM_OptionsButtonText["LevelContent"] or "When content", -300 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.LevelUnhappy = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMLevelUnhappy", FOM_OptionsButtonText and FOM_OptionsButtonText["LevelUnhappy"] or "When unhappy", -325 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.LevelOff = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMLevelOff", FOM_OptionsButtonText and FOM_OptionsButtonText["LevelOff"] or "Don't warn", -350 + yAdjust, rightColumnX)
if MTH_FOM_STATE.ctrl.LevelContent then MTH_FOM_STATE.ctrl.LevelContent:SetScript("OnClick", function() MTH_FOM_SetLevel("content"); MTH_RefreshFeedOMaticOptions() end) end
if MTH_FOM_STATE.ctrl.LevelUnhappy then MTH_FOM_STATE.ctrl.LevelUnhappy:SetScript("OnClick", function() MTH_FOM_SetLevel("unhappy"); MTH_RefreshFeedOMaticOptions() end) end
if MTH_FOM_STATE.ctrl.LevelOff then MTH_FOM_STATE.ctrl.LevelOff:SetScript("OnClick", function() MTH_FOM_SetLevel("off"); MTH_RefreshFeedOMaticOptions() end) end
MTH_FOM_STATE.ctrl.AudioWarning = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMAudioWarning", FOM_OptionsButtonText and FOM_OptionsButtonText["AudioWarning"] or "Play sound", -378 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.AudioWarningBell = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMAudioWarningBell", FOM_OptionsButtonText and FOM_OptionsButtonText["AudioWarningBell"] or "Use bell sound", -403 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.TextWarning = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMTextWarning", FOM_OptionsButtonText and FOM_OptionsButtonText["TextWarning"] or "Show text", -428 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.IconWarning = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMIconWarning", FOM_OptionsButtonText and FOM_OptionsButtonText["IconWarning"] or "Flash icon", -453 + yAdjust, rightColumnX)
if MTH_FOM_STATE.ctrl.AudioWarning then MTH_FOM_STATE.ctrl.AudioWarning:SetScript("OnClick", function(self) self = self or this if not self then return end if self:GetChecked() == 1 then if FOM_Config.AudioWarning ~= "bell" then FOM_Config.AudioWarning = true end else FOM_Config.AudioWarning = nil end MTH_RefreshFeedOMaticOptions() end) end
if MTH_FOM_STATE.ctrl.AudioWarningBell then MTH_FOM_STATE.ctrl.AudioWarningBell:SetScript("OnClick", function(self) self = self or this if not self then return end if self:GetChecked() == 1 then FOM_Config.AudioWarning = "bell" else FOM_Config.AudioWarning = true end MTH_RefreshFeedOMaticOptions() end) end
if MTH_FOM_STATE.ctrl.TextWarning then MTH_FOM_STATE.ctrl.TextWarning:SetScript("OnClick", function(self) self = self or this if not self then return end FOM_Config.TextWarning = self:GetChecked() == 1 end) end
if MTH_FOM_STATE.ctrl.IconWarning then MTH_FOM_STATE.ctrl.IconWarning:SetScript("OnClick", function(self) self = self or this if not self then return end FOM_Config.IconWarning = self:GetChecked() == 1 end) end
local cookHeader = container:CreateFontString("MetaHuntOptionsFOMCookingHeader", "ARTWORK", "GameFontHighlight")
cookHeader:SetPoint("TOPLEFT", container, "TOPLEFT", rightColumnX, -62 + yAdjust)
cookHeader:SetText("Avoid foods used in cooking")
MTH_FOM_STATE.ctrl.SaveCookOrange = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMSaveCookOrange", FOM_OptionsButtonText and FOM_OptionsButtonText["SaveForCook_Orange"] or "Only difficult recipes", -86 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.SaveCookYellow = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMSaveCookYellow", FOM_OptionsButtonText and FOM_OptionsButtonText["SaveForCook_Yellow"] or "Medium or better", -111 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.SaveCookGreen = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMSaveCookGreen", FOM_OptionsButtonText and FOM_OptionsButtonText["SaveForCook_Green"] or "Easy or better", -136 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.SaveCookAll = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMSaveCookAll", FOM_OptionsButtonText and FOM_OptionsButtonText["SaveForCook_All"] or "All foods", -161 + yAdjust, rightColumnX)
MTH_FOM_STATE.ctrl.SaveCookNone = MTH_CreateCheckbox(container, "MetaHuntOptionsFOMSaveCookNone", FOM_OptionsButtonText and FOM_OptionsButtonText["SaveForCook_None"] or "Do not save cooking foods", -186 + yAdjust, rightColumnX)
if MTH_FOM_STATE.ctrl.SaveCookOrange then MTH_FOM_STATE.ctrl.SaveCookOrange:SetScript("OnClick", function() MTH_FOM_SetCookingLevel(3); MTH_RefreshFeedOMaticOptions() end) end
if MTH_FOM_STATE.ctrl.SaveCookYellow then MTH_FOM_STATE.ctrl.SaveCookYellow:SetScript("OnClick", function() MTH_FOM_SetCookingLevel(2); MTH_RefreshFeedOMaticOptions() end) end
if MTH_FOM_STATE.ctrl.SaveCookGreen then MTH_FOM_STATE.ctrl.SaveCookGreen:SetScript("OnClick", function() MTH_FOM_SetCookingLevel(1); MTH_RefreshFeedOMaticOptions() end) end
if MTH_FOM_STATE.ctrl.SaveCookAll then MTH_FOM_STATE.ctrl.SaveCookAll:SetScript("OnClick", function() MTH_FOM_SetCookingLevel(0); MTH_RefreshFeedOMaticOptions() end) end
if MTH_FOM_STATE.ctrl.SaveCookNone then MTH_FOM_STATE.ctrl.SaveCookNone:SetScript("OnClick", function() MTH_FOM_SetCookingLevel(4); MTH_RefreshFeedOMaticOptions() end) end
MTH_RefreshFeedOMaticOptions()
end
+220
View File
@@ -0,0 +1,220 @@
MetaHuntOptions = nil
local MTH_OPTIONS_SETUP = {} -- Track which frames have been set up
MTH_OPTIONS_CONST = MTH_OPTIONS_CONST or {}
MTH_OPTIONS_CONST.NAV_DEFAULT_WIDTH = MTH_OPTIONS_CONST.NAV_DEFAULT_WIDTH or 120
MTH_OPTIONS_CONST.WINDOW_PADDING = MTH_OPTIONS_CONST.WINDOW_PADDING or 12
MTH_OPTIONS_CONST.CONTENT_TOP_OFFSET = MTH_OPTIONS_CONST.CONTENT_TOP_OFFSET or -72
MTH_OPTIONS_CONST.CONTENT_HEIGHT = MTH_OPTIONS_CONST.CONTENT_HEIGHT or 540
MTH_OPTIONS_CONST.CLOSE_BTN_SIZE = MTH_OPTIONS_CONST.CLOSE_BTN_SIZE or 22
MTH_OPTIONS_CONST.CLOSE_BTN_OFFSET_X = MTH_OPTIONS_CONST.CLOSE_BTN_OFFSET_X or -8
MTH_OPTIONS_CONST.CLOSE_BTN_OFFSET_Y = MTH_OPTIONS_CONST.CLOSE_BTN_OFFSET_Y or -8
local MTH_OPTIONS_SHELL_READY = MTH_OptionsRequire and MTH_OptionsRequire("options-shell", {
"MTH_GetFrame",
"MTH_BuildOptionsTree",
})
local function MTH_RegisterEscClose(frameName)
if not (frameName and UISpecialFrames) then
return
end
for i = 1, table.getn(UISpecialFrames) do
if UISpecialFrames[i] == frameName then
return
end
end
table.insert(UISpecialFrames, frameName)
end
local function MTH_EnsureOptionsFrame()
if MetaHuntOptions then
return MetaHuntOptions
end
MetaHuntOptions = CreateFrame("Frame", "MetaHuntOptions", UIParent, "MetaHuntOptionsTemplate")
if not MetaHuntOptions then
if MTH and MTH.Print then
MTH:Print("[MTH OPTIONS] Failed to create MetaHuntOptions", "error")
end
return nil
end
MTH_RegisterEscClose("MetaHuntOptions")
local nav = MTH_GetFrame("MetaHuntOptionsNav")
if nav and nav.SetBackdropColor then
nav:SetBackdropColor(0.08, 0.08, 0.08, 0.95)
nav:SetBackdropBorderColor(0.25, 0.25, 0.25, 1)
end
local nav = MTH_GetFrame("MetaHuntOptionsNav")
local navWidth = nav and nav:GetWidth() or MTH_OPTIONS_CONST.NAV_DEFAULT_WIDTH
local contentLeft = MTH_OPTIONS_CONST.WINDOW_PADDING + navWidth + MTH_OPTIONS_CONST.WINDOW_PADDING
local contentWidth = (MetaHuntOptions:GetWidth() or 720) - contentLeft - MTH_OPTIONS_CONST.WINDOW_PADDING
for i = 1, table.getn(MTH_OPTIONS_TABS) do
local frame = MTH_GetFrame(MTH_OPTIONS_TABS[i].frame)
if frame then
frame:ClearAllPoints()
frame:SetPoint("TOPLEFT", MetaHuntOptions, "TOPLEFT", contentLeft, MTH_OPTIONS_CONST.CONTENT_TOP_OFFSET)
frame:SetWidth(contentWidth)
frame:SetHeight(MTH_OPTIONS_CONST.CONTENT_HEIGHT)
frame:SetFrameLevel(MetaHuntOptions:GetFrameLevel() + 1)
frame:EnableMouse(true)
frame:Hide()
end
end
local closeBtn = MTH_GetFrame("MetaHuntOptionsCloseButton")
if closeBtn then
closeBtn:ClearAllPoints()
closeBtn:SetPoint("TOPRIGHT", MetaHuntOptions, "TOPRIGHT", MTH_OPTIONS_CONST.CLOSE_BTN_OFFSET_X, MTH_OPTIONS_CONST.CLOSE_BTN_OFFSET_Y)
closeBtn:SetWidth(MTH_OPTIONS_CONST.CLOSE_BTN_SIZE)
closeBtn:SetHeight(MTH_OPTIONS_CONST.CLOSE_BTN_SIZE)
closeBtn:SetText("X")
closeBtn:SetScript("OnClick", function() MetaHuntOptions:Hide() end)
end
MTH_BuildOptionsTree()
MTH_SelectOptionsTab("General")
return MetaHuntOptions
end
function MTH_SelectOptionsTab(tabKey)
if not MTH_OPTIONS_SHELL_READY then return end
local optionsFrame = MTH_EnsureOptionsFrame()
if not optionsFrame then
return
end
MTH_OPTIONS_STATE = MTH_OPTIONS_STATE or {}
MTH_OPTIONS_STATE.activeTab = tabKey
MTH_OPTIONS_ACTIVE_TAB = tabKey
for i = 1, table.getn(MTH_OPTIONS_TABS) do
local frame = MTH_GetFrame(MTH_OPTIONS_TABS[i].frame)
if frame then
frame:Hide()
end
end
for i = 1, table.getn(MTH_OPTIONS_TABS) do
if MTH_OPTIONS_TABS[i].key == tabKey then
local frame = MTH_GetFrame(MTH_OPTIONS_TABS[i].frame)
if frame then
frame:Show()
end
end
end
MTH_BuildOptionsTree()
if tabKey == "General" then
if MTH_SetupGeneralOptions then
MTH_SetupGeneralOptions()
MTH_OPTIONS_SETUP["General"] = true
end
elseif tabKey == "Pet" then
if not MTH_OPTIONS_SETUP["Pet"] then
MTH_SetupPetOptions()
MTH_OPTIONS_SETUP["Pet"] = true
end
elseif tabKey == "Track" then
if not MTH_OPTIONS_SETUP["Track"] then
MTH_SetupTrackOptions()
MTH_OPTIONS_SETUP["Track"] = true
end
elseif tabKey == "Aspect" then
if not MTH_OPTIONS_SETUP["Aspect"] then
MTH_SetupAspectOptions()
MTH_OPTIONS_SETUP["Aspect"] = true
end
elseif tabKey == "Trap" then
if not MTH_OPTIONS_SETUP["Trap"] then
MTH_SetupTrapOptions()
MTH_OPTIONS_SETUP["Trap"] = true
end
elseif tabKey == "Ammo" then
if not MTH_OPTIONS_SETUP["Ammo"] then
MTH_SetupAmmoOptions()
MTH_OPTIONS_SETUP["Ammo"] = true
else
MTH_SetupAmmoOptions()
end
elseif tabKey == "Mounts" then
if not MTH_OPTIONS_SETUP["Mounts"] then
MTH_SetupMountsOptions()
MTH_OPTIONS_SETUP["Mounts"] = true
end
elseif tabKey == "Companions" then
if not MTH_OPTIONS_SETUP["Companions"] then
MTH_SetupCompanionsOptions()
MTH_OPTIONS_SETUP["Companions"] = true
end
elseif tabKey == "Toys" then
if not MTH_OPTIONS_SETUP["Toys"] then
MTH_SetupToysOptions()
MTH_OPTIONS_SETUP["Toys"] = true
end
elseif tabKey == "SmartAmmo" then
if not MTH_OPTIONS_SETUP["SmartAmmo"] then
MTH_SetupSmartAmmoOptions()
MTH_OPTIONS_SETUP["SmartAmmo"] = true
end
elseif tabKey == "FeedOMatic" then
if not MTH_OPTIONS_SETUP["FeedOMatic"] then
MTH_SetupFeedOMaticOptions()
MTH_OPTIONS_SETUP["FeedOMatic"] = true
end
MTH_RefreshFeedOMaticOptions()
elseif tabKey == "AutoBuy" then
if not MTH_OPTIONS_SETUP["AutoBuy"] then
MTH_SetupAutoBuyOptions()
MTH_OPTIONS_SETUP["AutoBuy"] = true
else
MTH_SetupAutoBuyOptions()
end
elseif tabKey == "Credits" then
local setupCredits = _G and _G["MTH_SetupCreditsOptions"]
if type(setupCredits) == "function" then
setupCredits()
end
MTH_OPTIONS_SETUP["Credits"] = true
elseif string.sub(tabKey or "", 1, 11) == "Chronometer" then
if not MTH_OPTIONS_SETUP[tabKey] then
if _G then
_G.MTH_CHRON_OPTIONS_SECTION = tabKey
end
if type(MTH_SetupChronometerOptions) == "function" then
MTH_SetupChronometerOptions()
MTH_OPTIONS_SETUP[tabKey] = true
elseif MTH and MTH.Print then
MTH:Print("Chronometer options module not loaded", "error")
end
else
if _G then
_G.MTH_CHRON_OPTIONS_SECTION = tabKey
end
if type(MTH_RefreshChronometerOptions) == "function" then
MTH_RefreshChronometerOptions()
elseif type(MTH_SetupChronometerOptions) == "function" then
MTH_SetupChronometerOptions()
end
end
end
end
function MTH_ResetAndSelectOptionsTab(tabKey)
if not MTH_OPTIONS_SHELL_READY then return end
if not tabKey then
return
end
MTH_OPTIONS_SETUP[tabKey] = nil
MTH_SelectOptionsTab(tabKey)
end
function MTH_OpenOptions(tabKey)
if not MTH_OPTIONS_SHELL_READY then return end
local optionsFrame = MTH_EnsureOptionsFrame()
if not optionsFrame then
return
end
optionsFrame:Show()
MTH_SelectOptionsTab(tabKey or "General")
end
+184
View File
@@ -0,0 +1,184 @@
MTH_OPTIONS_TREE_STATE = MTH_OPTIONS_TREE_STATE or { zButtons = true, chronometer = true }
MTH_OPTIONS_TREE_BUTTONS = MTH_OPTIONS_TREE_BUTTONS or {}
MTH_OPTIONS_STATE = MTH_OPTIONS_STATE or {}
if MTH_OPTIONS_STATE.activeTab == nil and MTH_OPTIONS_ACTIVE_TAB ~= nil then
MTH_OPTIONS_STATE.activeTab = MTH_OPTIONS_ACTIVE_TAB
end
MTH_OPTIONS_ACTIVE_TAB = MTH_OPTIONS_STATE.activeTab
MTH_OPTIONS_CONST = MTH_OPTIONS_CONST or {
NAV_DEFAULT_WIDTH = 120,
NAV_INNER_PADDING = 16,
TREE_START_Y = -10,
TREE_BUTTON_HEIGHT = 20,
TREE_ROW_STEP = 22,
TREE_INDENT_BASE = 8,
TREE_CHILD_INDENT = 14,
HIGHLIGHT_ALPHA = 0.25,
SELECTED_BG_R = 0.35,
SELECTED_BG_G = 0.25,
SELECTED_BG_B = 0.10,
SELECTED_BG_A = 0.55,
SELECTED_TEXT_R = 1.00,
SELECTED_TEXT_G = 0.82,
SELECTED_TEXT_B = 0.00,
GROUP_TEXT_R = 1.00,
GROUP_TEXT_G = 0.82,
GROUP_TEXT_B = 0.00,
}
MTH_OPTIONS_TABS = MTH_OPTIONS_TABS or {
{ key = "General", label = "General", frame = "MetaHuntOptionsGeneral" },
{ key = "Pet", label = "ZPet", frame = "MetaHuntOptionsPet" },
{ key = "Track", label = "ZTrack", frame = "MetaHuntOptionsTrack" },
{ key = "Aspect", label = "ZAspect", frame = "MetaHuntOptionsAspect" },
{ key = "Trap", label = "ZTrap", frame = "MetaHuntOptionsTrap" },
{ key = "Ammo", label = "ZAmmo", frame = "MetaHuntOptionsAmmo" },
{ key = "Mounts", label = "ZMounts", frame = "MetaHuntOptionsMounts" },
{ key = "Companions", label = "ZCompanions", frame = "MetaHuntOptionsCompanions" },
{ key = "Toys", label = "ZToys", frame = "MetaHuntOptionsToys" },
{ key = "SmartAmmo", label = "Smart Ammo", frame = "MetaHuntOptionsSmartAmmo" },
{ key = "FeedOMatic", label = "FeedOMatic", frame = "MetaHuntOptionsFeedOMatic" },
{ key = "AutoBuy", label = "Auto Buy", frame = "MetaHuntOptionsAutoBuy" },
{ key = "ChronometerGeneral", label = "General", frame = "MetaHuntOptionsChronometer" },
{ key = "ChronometerBar", label = "Bar", frame = "MetaHuntOptionsChronometer" },
{ key = "ChronometerClassSpells", label = "Hunter Spells", frame = "MetaHuntOptionsChronometer" },
{ key = "ChronometerClassEvents", label = "Hunter Events", frame = "MetaHuntOptionsChronometer" },
{ key = "ChronometerRacial", label = "Racial", frame = "MetaHuntOptionsChronometer" },
{ key = "Credits", label = "Credits", frame = "MetaHuntOptionsCredits" },
}
MTH_OPTIONS_TREE = MTH_OPTIONS_TREE or {
{ label = "General", key = "General" },
{ label = "Auto Buy", key = "AutoBuy" },
{ label = "Feed-O-Matic", key = "FeedOMatic" },
{
label = "zButtons",
node = "zButtons",
children = {
{ label = "zAmmo", key = "Ammo" },
{ label = "zPet", key = "Pet" },
{ label = "zAspect", key = "Aspect" },
{ label = "zTrack", key = "Track" },
{ label = "zTrap", key = "Trap" },
{ label = "zMounts", key = "Mounts" },
{ label = "zCompanions", key = "Companions" },
{ label = "zToys", key = "Toys" },
},
},
{
label = "Chronometer",
node = "chronometer",
children = {
{ label = "General", key = "ChronometerGeneral" },
{ label = "Bar", key = "ChronometerBar" },
{ label = "Hunter Spells", key = "ChronometerClassSpells" },
{ label = "Hunter Events", key = "ChronometerClassEvents" },
{ label = "Racial", key = "ChronometerRacial" },
},
},
{ label = "Credits", key = "Credits" },
}
function MTH_BuildOptionsTree()
local nav = MTH_GetFrame and MTH_GetFrame("MetaHuntOptionsNav") or nil
if not nav then
return
end
local navInnerWidth = (nav:GetWidth() or MTH_OPTIONS_CONST.NAV_DEFAULT_WIDTH) - MTH_OPTIONS_CONST.NAV_INNER_PADDING
local yOffset = MTH_OPTIONS_CONST.TREE_START_Y
local buttonIndex = 1
local function acquireButton()
local button = MTH_OPTIONS_TREE_BUTTONS[buttonIndex]
if not button then
button = CreateFrame("Button", "MetaHuntOptionsNavItem"..buttonIndex, nav)
button:SetHeight(MTH_OPTIONS_CONST.TREE_BUTTON_HEIGHT)
button:SetNormalTexture("")
button:SetPushedTexture("")
button:SetHighlightTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
button.highlight = button:GetHighlightTexture()
if button.highlight then
button.highlight:SetBlendMode("ADD")
button.highlight:SetVertexColor(1, 1, 1, MTH_OPTIONS_CONST.HIGHLIGHT_ALPHA)
end
button.selectedTexture = button:CreateTexture(nil, "BACKGROUND")
button.selectedTexture:SetTexture("Interface\\Buttons\\WHITE8X8")
button.selectedTexture:SetVertexColor(MTH_OPTIONS_CONST.SELECTED_BG_R, MTH_OPTIONS_CONST.SELECTED_BG_G, MTH_OPTIONS_CONST.SELECTED_BG_B, MTH_OPTIONS_CONST.SELECTED_BG_A)
button.selectedTexture:SetPoint("TOPLEFT", button, "TOPLEFT", 0, -1)
button.selectedTexture:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", 0, 1)
button.selectedTexture:Hide()
button.expander = button:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
button.expander:SetPoint("LEFT", button, "LEFT", 4, 0)
button.label = button:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
button.label:SetJustifyH("LEFT")
button.label:SetPoint("LEFT", button, "LEFT", 18, 0)
button.label:SetPoint("RIGHT", button, "RIGHT", -4, 0)
MTH_OPTIONS_TREE_BUTTONS[buttonIndex] = button
end
button:Show()
buttonIndex = buttonIndex + 1
return button
end
local function placeLeaf(node, indent)
local button = acquireButton()
local xOffset = MTH_OPTIONS_CONST.TREE_INDENT_BASE + (indent or 0)
local width = navInnerWidth - (indent or 0)
button:ClearAllPoints()
button:SetPoint("TOPLEFT", nav, "TOPLEFT", xOffset, yOffset)
button:SetWidth(width)
button.expander:SetText("")
button.label:SetPoint("LEFT", button, "LEFT", 4, 0)
button.label:SetText(node.label)
button:SetScript("OnClick", function() MTH_SelectOptionsTab(node.key) end)
if MTH_OPTIONS_STATE.activeTab == node.key then
button.selectedTexture:Show()
button.label:SetTextColor(MTH_OPTIONS_CONST.SELECTED_TEXT_R, MTH_OPTIONS_CONST.SELECTED_TEXT_G, MTH_OPTIONS_CONST.SELECTED_TEXT_B)
else
button.selectedTexture:Hide()
button.label:SetTextColor(1, 1, 1)
end
button.expander:SetTextColor(1, 1, 1)
yOffset = yOffset - MTH_OPTIONS_CONST.TREE_ROW_STEP
end
for i = 1, table.getn(MTH_OPTIONS_TREE) do
local node = MTH_OPTIONS_TREE[i]
if node.children then
local expanded = MTH_OPTIONS_TREE_STATE[node.node] and true or false
local button = acquireButton()
button:ClearAllPoints()
button:SetPoint("TOPLEFT", nav, "TOPLEFT", MTH_OPTIONS_CONST.TREE_INDENT_BASE, yOffset)
button:SetWidth(navInnerWidth)
button.expander:SetText(expanded and "-" or "+")
button.expander:SetTextColor(MTH_OPTIONS_CONST.GROUP_TEXT_R, MTH_OPTIONS_CONST.GROUP_TEXT_G, MTH_OPTIONS_CONST.GROUP_TEXT_B)
button.label:SetPoint("LEFT", button, "LEFT", 18, 0)
button.label:SetText(node.label)
button.label:SetTextColor(MTH_OPTIONS_CONST.GROUP_TEXT_R, MTH_OPTIONS_CONST.GROUP_TEXT_G, MTH_OPTIONS_CONST.GROUP_TEXT_B)
button.selectedTexture:Hide()
button:SetScript("OnClick", function()
MTH_OPTIONS_TREE_STATE[node.node] = not MTH_OPTIONS_TREE_STATE[node.node]
MTH_BuildOptionsTree()
end)
yOffset = yOffset - MTH_OPTIONS_CONST.TREE_ROW_STEP
if expanded then
for j = 1, table.getn(node.children) do
placeLeaf(node.children[j], MTH_OPTIONS_CONST.TREE_CHILD_INDENT)
end
end
else
placeLeaf(node, 0)
end
end
for i = buttonIndex, table.getn(MTH_OPTIONS_TREE_BUTTONS) do
if MTH_OPTIONS_TREE_BUTTONS[i] then
MTH_OPTIONS_TREE_BUTTONS[i]:Hide()
end
end
end
File diff suppressed because it is too large Load Diff
+125
View File
@@ -0,0 +1,125 @@
-- MetaHunt unified options window
function MTH_GetFrame(name)
return getglobal(name)
end
function MTH_OptionsRequire(moduleName, requiredGlobals)
local missing = {}
for i = 1, table.getn(requiredGlobals) do
local key = requiredGlobals[i]
if _G[key] == nil then
table.insert(missing, key)
end
end
if table.getn(missing) == 0 then
return true
end
local message = "[MTH OPTIONS] " .. tostring(moduleName) .. " missing dependencies: " .. table.concat(missing, ", ")
if MTH and MTH.Print then
MTH:Print(message, "error")
elseif type(MTH_Log) == "function" then
MTH_Log(message, "error")
elseif DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.AddMessage then
DEFAULT_CHAT_FRAME:AddMessage(message)
end
return false
end
function MTH_CreateActionButton(parent, name, text, x, y, width, height, onClick)
local button = CreateFrame("Button", name, parent, "UIPanelButtonTemplate")
if not button then return nil end
button:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
button:SetWidth(width or 120)
button:SetHeight(height or 24)
button:SetText(text)
if onClick then
button:SetScript("OnClick", onClick)
end
return button
end
-- Helper: Create a slider control
function MTH_CreateSlider(parent, name, label, minVal, maxVal, step, yOffset)
local slider = CreateFrame("Slider", name, parent, "OptionsSliderTemplate")
if not slider then return nil end
slider:SetPoint("TOPLEFT", parent, "TOPLEFT", 15, yOffset)
slider:SetWidth(280)
slider:SetMinMaxValues(minVal, maxVal)
slider:SetValueStep(step)
slider:SetValue(minVal)
-- Labels
local text = slider:CreateFontString(name.."Text", "ARTWORK", "GameFontNormalSmall")
text:SetPoint("BOTTOM", slider, "TOP", 0, 2)
text:SetText(label)
local value = slider:CreateFontString(name.."Value", "ARTWORK", "GameFontHighlight")
value:SetPoint("TOP", slider, "BOTTOM", 0, 2)
value:SetText(tostring(minVal))
-- Hide default low/high labels from OptionsSliderTemplate
local lowLabel = getglobal(name.."Low")
local highLabel = getglobal(name.."High")
if lowLabel then lowLabel:Hide() end
if highLabel then highLabel:Hide() end
slider:SetScript("OnValueChanged", function(self, newValue)
local activeSlider = self or this or slider
local current = tonumber(newValue)
if not current and activeSlider and activeSlider.GetValue then
current = tonumber(activeSlider:GetValue())
end
if not current then
current = minVal
end
value:SetText(tostring(math.floor(current + 0.5)))
if slider.onChange then slider.onChange(current, activeSlider) end
end)
return slider
end
-- Helper: Create a checkbox control
MTH_CreateCheckbox = function(parent, name, label, yOffset, xOffset)
local check = CreateFrame("CheckButton", name, parent, "UICheckButtonTemplate")
if not check then return nil end
check:SetPoint("TOPLEFT", parent, "TOPLEFT", xOffset or 15, yOffset)
local text = check:CreateFontString(name.."Text", "ARTWORK", "GameFontNormalSmall")
text:SetPoint("LEFT", check, "RIGHT", 5, 0)
text:SetText(label)
return check
end
-- Helper: Clear all children from a container
MTH_ClearContainer = function(container)
if not container then return end
-- Hide all child frames
local children = { container:GetChildren() }
for i = 1, table.getn(children) do
local child = children[i]
if child then
child:Hide()
child:ClearAllPoints()
end
end
-- Hide all font strings and textures
local regions = { container:GetRegions() }
for i = 1, table.getn(regions) do
local region = regions[i]
if region and region ~= container then
region:Hide()
if region.ClearAllPoints then
region:ClearAllPoints()
end
end
end
end
+117
View File
@@ -0,0 +1,117 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\FrameXML\UI.xsd">
<Frame name="MetaHuntOptionsTemplate" toplevel="true" frameStrata="DIALOG" movable="true" enableMouse="true" hidden="true" parent="UIParent" virtual="true">
<Size>
<AbsDimension x="720" y="650"/>
</Size>
<Anchors>
<Anchor point="CENTER"/>
</Anchors>
<Backdrop bgFile="Interface\DialogFrame\UI-DialogBox-Background" edgeFile="Interface\DialogFrame\UI-DialogBox-Border" tile="true">
<BackgroundInsets>
<AbsInset left="11" right="12" top="12" bottom="11"/>
</BackgroundInsets>
<TileSize>
<AbsValue val="32"/>
</TileSize>
<EdgeSize>
<AbsValue val="32"/>
</EdgeSize>
</Backdrop>
<Layers>
<Layer level="ARTWORK">
<Texture name="$parentHeader" file="Interface\DialogFrame\UI-DialogBox-Header">
<Size>
<AbsDimension x="256" y="64"/>
</Size>
<Anchors>
<Anchor point="TOP">
<Offset>
<AbsDimension x="0" y="12"/>
</Offset>
</Anchor>
</Anchors>
</Texture>
<FontString name="$parentHeaderText" inherits="GameFontNormal" text="MetaHunt Options">
<Anchors>
<Anchor point="TOP" relativeTo="$parentHeader">
<Offset>
<AbsDimension x="0" y="-14"/>
</Offset>
</Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<Frame name="$parentNav" enableMouse="true">
<Size>
<AbsDimension x="120" y="540"/>
</Size>
<Anchors>
<Anchor point="TOPLEFT">
<Offset>
<AbsDimension x="12" y="-72"/>
</Offset>
</Anchor>
</Anchors>
<Backdrop bgFile="Interface\Buttons\WHITE8X8" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
<BackgroundInsets>
<AbsInset left="3" right="3" top="3" bottom="3"/>
</BackgroundInsets>
<TileSize><AbsValue val="16"/></TileSize>
<EdgeSize><AbsValue val="16"/></EdgeSize>
</Backdrop>
</Frame>
<!-- Content Areas -->
<Frame name="$parentGeneral" hidden="true" enableMouse="true"/>
<Frame name="$parentPet" hidden="true" enableMouse="true"/>
<Frame name="$parentTrack" hidden="true" enableMouse="true"/>
<Frame name="$parentAspect" hidden="true" enableMouse="true"/>
<Frame name="$parentTrap" hidden="true" enableMouse="true"/>
<Frame name="$parentAmmo" hidden="true" enableMouse="true"/>
<Frame name="$parentMounts" hidden="true" enableMouse="true"/>
<Frame name="$parentCompanions" hidden="true" enableMouse="true"/>
<Frame name="$parentToys" hidden="true" enableMouse="true"/>
<Frame name="$parentSmartAmmo" hidden="true" enableMouse="true"/>
<Frame name="$parentFeedOMatic" hidden="true" enableMouse="true"/>
<Frame name="$parentAutoBuy" hidden="true" enableMouse="true"/>
<Frame name="$parentChronometer" hidden="true" enableMouse="true"/>
<Frame name="$parentCredits" hidden="true" enableMouse="true"/>
<!-- Top-right close button -->
<Button name="$parentCloseButton" inherits="UIPanelButtonTemplate" text="X">
<Size>
<AbsDimension x="22" y="22"/>
</Size>
<Anchors>
<Anchor point="TOPRIGHT">
<Offset>
<AbsDimension x="-8" y="-8"/>
</Offset>
</Anchor>
</Anchors>
</Button>
</Frames>
<Scripts>
<OnLoad>
this:RegisterForDrag("LeftButton")
</OnLoad>
<OnMouseUp>
if ( this.isMoving ) then
this:StopMovingOrSizing();
this.isMoving = false;
end
</OnMouseUp>
<OnDragStart>
this:StartMoving();
this.isMoving = true;
</OnDragStart>
<OnDragStop>
this:StopMovingOrSizing();
this.isMoving = false;
</OnDragStop>
</Scripts>
</Frame>
</Ui>
+125
View File
@@ -0,0 +1,125 @@
MTH_UI = MTH_UI or {}
MTH_UI.version = "0.1.0"
MTH_UI.pages = MTH_UI.pages or {}
MTH_UI.pageOrder = MTH_UI.pageOrder or {}
MTH_UI.callbacks = MTH_UI.callbacks or {}
local function MTH_UI_SafeCall(handler, a1, a2, a3)
if type(handler) ~= "function" then
return false, "handler is not a function"
end
local ok, resultOrError = pcall(handler, a1, a2, a3)
if not ok then
if MTH and MTH.Print then
MTH:Print("[MTH_UI] " .. tostring(resultOrError), "error")
end
return false, resultOrError
end
return true, resultOrError
end
local function MTH_UI_CopyTable(value)
if type(value) ~= "table" then return value end
local copy = {}
for key, nestedValue in pairs(value) do
copy[key] = MTH_UI_CopyTable(nestedValue)
end
return copy
end
local function MTH_UI_ApplyDefaults(target, defaults)
if type(target) ~= "table" or type(defaults) ~= "table" then return target end
for key, defaultValue in pairs(defaults) do
if target[key] == nil then
target[key] = MTH_UI_CopyTable(defaultValue)
elseif type(target[key]) == "table" and type(defaultValue) == "table" then
MTH_UI_ApplyDefaults(target[key], defaultValue)
end
end
return target
end
function MTH_UI.CloneTable(value)
return MTH_UI_CopyTable(value)
end
function MTH_UI.MergeDefaults(target, defaults)
return MTH_UI_ApplyDefaults(target, defaults)
end
function MTH_UI.ResolveValue(value, context)
if type(value) == "function" then
local ok, resolved = MTH_UI_SafeCall(value, context)
if ok then return resolved end
return nil
end
return value
end
function MTH_UI.RegisterCallback(eventName, callbackKey, handler)
if type(eventName) ~= "string" or eventName == "" then return false end
if type(callbackKey) ~= "string" or callbackKey == "" then return false end
if type(handler) ~= "function" then return false end
if not MTH_UI.callbacks[eventName] then
MTH_UI.callbacks[eventName] = {}
end
MTH_UI.callbacks[eventName][callbackKey] = handler
return true
end
function MTH_UI.UnregisterCallback(eventName, callbackKey)
if not MTH_UI.callbacks[eventName] then return false end
if not MTH_UI.callbacks[eventName][callbackKey] then return false end
MTH_UI.callbacks[eventName][callbackKey] = nil
return true
end
function MTH_UI.FireCallback(eventName, payload)
local listeners = MTH_UI.callbacks[eventName]
if not listeners then return end
for _, handler in pairs(listeners) do
MTH_UI_SafeCall(handler, payload)
end
end
function MTH_UI.RegisterPage(pageId, pageDefinition)
if type(pageId) ~= "string" or pageId == "" then return false, "invalid page id" end
if type(pageDefinition) ~= "table" then return false, "page definition must be a table" end
local rootType = pageDefinition.type or "group"
if rootType ~= "group" then
return false, "root page type must be group"
end
local existing = MTH_UI.pages[pageId]
MTH_UI.pages[pageId] = MTH_UI_CopyTable(pageDefinition)
MTH_UI.pages[pageId].id = pageId
if not existing then
table.insert(MTH_UI.pageOrder, pageId)
end
MTH_UI.FireCallback("PageRegistered", pageId)
return true
end
function MTH_UI.GetPage(pageId)
return MTH_UI.pages[pageId]
end
function MTH_UI.ListPages()
local pageList = {}
for _, pageId in ipairs(MTH_UI.pageOrder) do
if MTH_UI.pages[pageId] then
table.insert(pageList, pageId)
end
end
return pageList
end
function MTH_UI.NotifyPageChanged(pageId)
MTH_UI.FireCallback("PageChanged", pageId)
end
+190
View File
@@ -0,0 +1,190 @@
MTH_UIRenderer = MTH_UIRenderer or {}
MTH_UIRenderer.activeWidgets = MTH_UIRenderer.activeWidgets or {}
local function MTH_UIR_Clear(frame)
if not frame then return end
local widgets = frame._mthWidgets
if not widgets then return end
for _, widget in ipairs(widgets) do
if widget and widget.Hide then
widget:Hide()
end
end
frame._mthWidgets = {}
end
local function MTH_UIR_AddWidget(frame, widget)
if not frame._mthWidgets then
frame._mthWidgets = {}
end
table.insert(frame._mthWidgets, widget)
end
local function MTH_UIR_MakeLabel(frame, text, x, y, width, template)
local label = frame:CreateFontString(nil, "ARTWORK", template or "GameFontNormalSmall")
label:SetPoint("TOPLEFT", frame, "TOPLEFT", x, y)
label:SetWidth(width)
label:SetJustifyH("LEFT")
label:SetText(text or "")
MTH_UIR_AddWidget(frame, label)
return label
end
local function MTH_UIR_SetCheckboxLabel(checkButton, text)
local textRegion = nil
local checkName = checkButton and checkButton.GetName and checkButton:GetName()
if type(checkName) == "string" and checkName ~= "" then
textRegion = getglobal(checkName .. "Text")
end
if not textRegion and checkButton and checkButton.Text then
textRegion = checkButton.Text
end
if textRegion then
textRegion:SetText(text or "")
end
end
local function MTH_UIR_CreateControl(frame, key, option, context, x, y, width)
local optionType = option.type or "description"
local optionName = MTH_UI.ResolveValue(option.name, context)
if optionType == "description" then
local label = MTH_UIR_MakeLabel(frame, optionName or "", x, y, width, "GameFontHighlightSmall")
if label then label:SetTextColor(0.90, 0.90, 0.90) end
return 18
end
if optionType == "header" then
local label = MTH_UIR_MakeLabel(frame, optionName or "", x, y, width, "GameFontNormal")
if label then label:SetTextColor(1.00, 0.82, 0.00) end
return 24
end
if optionType == "toggle" then
local checkButton = CreateFrame("CheckButton", nil, frame, "UICheckButtonTemplate")
checkButton:SetPoint("TOPLEFT", frame, "TOPLEFT", x, y + 2)
checkButton:SetChecked(MTH_UI.ResolveValue(option.get, context) and 1 or nil)
MTH_UIR_SetCheckboxLabel(checkButton, optionName or "")
checkButton:SetScript("OnClick", function(self)
self = self or this
if not self then return end
if option.set then
option.set(context, self:GetChecked() == 1)
end
end)
if MTH_UI.ResolveValue(option.disabled, context) then
checkButton:Disable()
end
MTH_UIR_AddWidget(frame, checkButton)
return 24
end
if optionType == "execute" then
local button = CreateFrame("Button", nil, frame)
button:SetPoint("TOPLEFT", frame, "TOPLEFT", x, y)
button:SetHeight(20)
button:SetWidth(math.min(180, width))
button:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true,
tileSize = 16,
edgeSize = 16,
insets = { left = 3, right = 3, top = 3, bottom = 3 },
})
button:SetBackdropColor(0.1, 0.1, 0.1, 0.5)
button:SetBackdropBorderColor(0.4, 0.4, 0.4)
local text = button:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
text:SetPoint("CENTER", button, "CENTER", 0, 0)
text:SetText(optionName or "Run")
text:SetTextColor(1.00, 0.82, 0.00)
button:SetScript("OnEnter", function(self) self = self or this if self then self:SetBackdropBorderColor(0.6, 0.6, 0.6) end end)
button:SetScript("OnLeave", function(self) self = self or this if self then self:SetBackdropBorderColor(0.4, 0.4, 0.4) end end)
button:SetScript("OnClick", function()
if option.func then
option.func(context)
end
end)
if MTH_UI.ResolveValue(option.disabled, context) then
button:Disable()
end
MTH_UIR_AddWidget(frame, button)
return 24
end
if optionType == "select" then
local label = MTH_UIR_MakeLabel(frame, optionName or "Select", x, y, width, "GameFontNormalSmall")
if label then label:SetTextColor(1.00, 0.82, 0.00) end
local button = CreateFrame("Button", nil, frame)
button:SetPoint("TOPLEFT", frame, "TOPLEFT", x, y - 16)
button:SetHeight(20)
button:SetWidth(math.min(220, width))
button:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true,
tileSize = 16,
edgeSize = 16,
insets = { left = 3, right = 3, top = 3, bottom = 3 },
})
button:SetBackdropColor(0.1, 0.1, 0.1, 0.5)
button:SetBackdropBorderColor(0.4, 0.4, 0.4)
local currentValue = MTH_UI.ResolveValue(option.get, context)
local text = button:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
text:SetPoint("CENTER", button, "CENTER", 0, 0)
text:SetText(tostring(currentValue or "Choose"))
text:SetTextColor(0.90, 0.90, 0.90)
button:SetScript("OnEnter", function(self) self = self or this if self then self:SetBackdropBorderColor(0.6, 0.6, 0.6) end end)
button:SetScript("OnLeave", function(self) self = self or this if self then self:SetBackdropBorderColor(0.4, 0.4, 0.4) end end)
button:SetScript("OnClick", function()
if option.onClick then
option.onClick(context, option)
end
end)
if MTH_UI.ResolveValue(option.disabled, context) then
button:Disable()
end
MTH_UIR_AddWidget(frame, button)
return 42
end
MTH_UIR_MakeLabel(frame, optionName or ("Unsupported type: " .. tostring(optionType)), x, y, width, "GameFontDisableSmall")
return 18
end
function MTH_UIRenderer.RenderPage(frame, pageDefinition, context)
if not frame or not pageDefinition then return false, "invalid render target" end
MTH_UIR_Clear(frame)
local args = pageDefinition.args or {}
local orderedKeys = {}
for key in pairs(args) do
table.insert(orderedKeys, key)
end
table.sort(orderedKeys, function(leftKey, rightKey)
local leftOrder = (args[leftKey] and args[leftKey].order) or 9999
local rightOrder = (args[rightKey] and args[rightKey].order) or 9999
if leftOrder ~= rightOrder then return leftOrder < rightOrder end
return tostring(leftKey) < tostring(rightKey)
end)
local cursorY = -8
for _, key in ipairs(orderedKeys) do
local option = args[key]
if option then
local hidden = MTH_UI.ResolveValue(option.hidden, context)
if not hidden then
local consumedHeight = MTH_UIR_CreateControl(frame, key, option, context, 8, cursorY, frame:GetWidth() - 16)
cursorY = cursorY - consumedHeight
end
end
end
return true
end