mirror of
https://codeberg.org/Duvelcorp/MetaHunt.git
synced 2026-09-22 07:36:58 +00:00
71c84e1704
- Add MTH_Growl_IsTeaching(); the beast-training scanner skips its chat feedback while auto-teach is driving the trainer window (no more 'scan did not run yet' spam on tame) - Confirm the taught rank in chat: 'Auto-taught Growl (Rank N) to <pet>.'
331 lines
11 KiB
Lua
331 lines
11 KiB
Lua
-- MetaHunt: auto-train Growl to a freshly tamed pet.
|
|
--
|
|
|
|
local MTH_GROWL_MAX_RANK = 7
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Saved-variable backed toggle (per character), default ON.
|
|
-- ---------------------------------------------------------------------------
|
|
local function MTH_Growl_GetStore()
|
|
if type(MTH_CharSavedVariables) ~= "table" then
|
|
MTH_CharSavedVariables = {}
|
|
end
|
|
if type(MTH_CharSavedVariables.growlTeach) ~= "table" then
|
|
MTH_CharSavedVariables.growlTeach = {}
|
|
end
|
|
local store = MTH_CharSavedVariables.growlTeach
|
|
if store.enabled == nil then
|
|
store.enabled = true
|
|
end
|
|
return store
|
|
end
|
|
|
|
function MTH_Growl_IsEnabled()
|
|
return MTH_Growl_GetStore().enabled and true or false
|
|
end
|
|
|
|
function MTH_Growl_SetEnabled(enabled)
|
|
MTH_Growl_GetStore().enabled = enabled and true or false
|
|
end
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Helpers
|
|
-- ---------------------------------------------------------------------------
|
|
local function MTH_Growl_DebugLog(line)
|
|
if type(MTH_DebugFrame) == "table" and type(MTH_DebugFrame.AddInfo) == "function" then
|
|
MTH_DebugFrame:AddInfo("[Growl] " .. tostring(line))
|
|
end
|
|
end
|
|
|
|
local function MTH_Growl_GetGlobal(name)
|
|
if type(getglobal) == "function" then
|
|
return getglobal(name)
|
|
end
|
|
if _G then
|
|
return _G[name]
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-- Rank appropriate for a pet of the given level.
|
|
local function MTH_Growl_RankForLevel(level)
|
|
local lvl = tonumber(level) or 0
|
|
if lvl < 1 then lvl = 1 end
|
|
local rank = math.floor(lvl / 10) + 1
|
|
if rank < 1 then rank = 1 end
|
|
if rank > MTH_GROWL_MAX_RANK then rank = MTH_GROWL_MAX_RANK end
|
|
return rank
|
|
end
|
|
|
|
-- Parse a rank number out of a craft row's name/subtext (e.g. "Growl", "Rank 5").
|
|
local function MTH_Growl_ParseRank(name, subText)
|
|
local sub = tostring(subText or "")
|
|
local _, _, subRank = string.find(sub, "(%d+)")
|
|
if subRank then return tonumber(subRank) end
|
|
local nm = tostring(name or "")
|
|
local _, _, nameRank = string.find(nm, "(%d+)")
|
|
if nameRank then return tonumber(nameRank) end
|
|
return nil
|
|
end
|
|
|
|
-- Find the hunter spellbook slot for a spell by name (e.g. "Beast Training").
|
|
local function MTH_Growl_FindSpellSlot(spellName)
|
|
if not spellName or spellName == "" then return nil end
|
|
local getSpellName = MTH_Growl_GetGlobal("GetSpellName")
|
|
if type(getSpellName) ~= "function" then return nil end
|
|
|
|
local getNumSpellTabs = MTH_Growl_GetGlobal("GetNumSpellTabs")
|
|
local getSpellTabInfo = MTH_Growl_GetGlobal("GetSpellTabInfo")
|
|
if type(getNumSpellTabs) == "function" and type(getSpellTabInfo) == "function" then
|
|
local numTabs = getNumSpellTabs() or 0
|
|
for tabIndex = 1, numTabs do
|
|
local _, _, offset, numSpells = getSpellTabInfo(tabIndex)
|
|
offset = tonumber(offset) or 0
|
|
numSpells = tonumber(numSpells) or 0
|
|
for spellIndex = (offset + 1), (offset + numSpells) do
|
|
if getSpellName(spellIndex, "spell") == spellName then
|
|
return spellIndex
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Fallback: linear scan of the spellbook.
|
|
local nilStreak = 0
|
|
for spellIndex = 1, 1024 do
|
|
local nameAtIndex = getSpellName(spellIndex, "spell")
|
|
if nameAtIndex then
|
|
nilStreak = 0
|
|
if nameAtIndex == spellName then
|
|
return spellIndex
|
|
end
|
|
else
|
|
nilStreak = nilStreak + 1
|
|
if nilStreak >= 30 then break end
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-- Open the Beast Training window by casting the hunter "Beast Training" spell.
|
|
local function MTH_Growl_OpenTrainingWindow()
|
|
local spellName = MTH_Growl_GetGlobal("ZHUNTER_PET_TRAINING") or "Beast Training"
|
|
local slot = MTH_Growl_FindSpellSlot(spellName)
|
|
local castSpell = MTH_Growl_GetGlobal("CastSpell")
|
|
if slot and type(castSpell) == "function" then
|
|
local getSpellName = MTH_Growl_GetGlobal("GetSpellName")
|
|
local atSlot = (type(getSpellName) == "function") and getSpellName(slot, "spell") or "?"
|
|
castSpell(slot, "spell")
|
|
MTH_Growl_DebugLog("Opened Beast Training via CastSpell(slot=" .. tostring(slot)
|
|
.. " name='" .. tostring(atSlot) .. "')")
|
|
return true
|
|
end
|
|
local castSpellByName = MTH_Growl_GetGlobal("CastSpellByName")
|
|
if type(castSpellByName) == "function" then
|
|
castSpellByName(spellName)
|
|
MTH_Growl_DebugLog("Opened Beast Training via CastSpellByName('" .. tostring(spellName) .. "') (slot not found)")
|
|
return true
|
|
end
|
|
MTH_Growl_DebugLog("Could not open Beast Training (no spell slot, no CastSpellByName)")
|
|
return false
|
|
end
|
|
|
|
-- Is the Beast Training / craft window currently visible?
|
|
local function MTH_Growl_IsTrainingWindowVisible()
|
|
local frameNames = { "BeastTrainingFrame", "PetTrainingFrame", "CraftFrame" }
|
|
for i = 1, table.getn(frameNames) do
|
|
local frame = MTH_Growl_GetGlobal(frameNames[i])
|
|
if frame then
|
|
if frame.IsVisible and frame:IsVisible() then return true end
|
|
if frame.IsShown and frame:IsShown() then return true end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Pending auto-train request. Set when a fresh tame is confirmed; cleared once
|
|
-- we train (or time out). { rank, name, elapsed, closeAfter, done }
|
|
-- ---------------------------------------------------------------------------
|
|
local MTH_Growl_Pending = nil
|
|
|
|
-- The offer currently awaiting the player's Yes/No answer. { rank, name, level }
|
|
local MTH_Growl_Offer = nil
|
|
|
|
-- While we auto-open the trainer to teach Growl, silence the Beast Training
|
|
-- scanner's chat feedback (set on confirm; expires after the teach window).
|
|
local MTH_Growl_SuppressScanUntil = 0
|
|
function MTH_Growl_IsTeaching()
|
|
local getTime = MTH_Growl_GetGlobal("GetTime")
|
|
local now = (type(getTime) == "function" and getTime()) or 0
|
|
return now < (MTH_Growl_SuppressScanUntil or 0)
|
|
end
|
|
|
|
local function MTH_Growl_TryTrainNow()
|
|
if not MTH_Growl_Pending or MTH_Growl_Pending.done then
|
|
return false
|
|
end
|
|
|
|
local getNumCrafts = MTH_Growl_GetGlobal("GetNumCrafts")
|
|
local getCraftInfo = MTH_Growl_GetGlobal("GetCraftInfo")
|
|
local doCraft = MTH_Growl_GetGlobal("DoCraft")
|
|
if type(getNumCrafts) ~= "function" or type(getCraftInfo) ~= "function" or type(doCraft) ~= "function" then
|
|
return false
|
|
end
|
|
|
|
local total = tonumber(getNumCrafts()) or 0
|
|
if total <= 0 then
|
|
return false -- window not populated yet
|
|
end
|
|
|
|
local target = MTH_Growl_Pending.rank
|
|
local bestIndex, bestRank = nil, 0
|
|
for i = 1, total do
|
|
local craftName, craftSub, craftType = getCraftInfo(i)
|
|
local lname = string.lower(tostring(craftName or ""))
|
|
if string.find(lname, "growl", 1, true) and tostring(craftType) ~= "header" then
|
|
local rank = MTH_Growl_ParseRank(craftName, craftSub)
|
|
if rank then
|
|
if rank == target then
|
|
bestIndex, bestRank = i, rank
|
|
break
|
|
elseif rank < target and rank > bestRank then
|
|
-- Highest rank we could give if the exact target isn't offered.
|
|
bestIndex, bestRank = i, rank
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if not bestIndex then
|
|
return false -- no Growl row found yet
|
|
end
|
|
|
|
doCraft(bestIndex)
|
|
MTH_Growl_DebugLog("Trained Growl Rank " .. tostring(bestRank)
|
|
.. " (target " .. tostring(target) .. ") for '" .. tostring(MTH_Growl_Pending.name)
|
|
.. "' via DoCraft(" .. tostring(bestIndex) .. ")")
|
|
if MTH and type(MTH.Print) == "function" then
|
|
MTH:Print("Auto-taught Growl (Rank " .. tostring(bestRank) .. ") to " .. tostring(MTH_Growl_Pending.name) .. ".")
|
|
end
|
|
|
|
MTH_Growl_Pending.done = true
|
|
if MTH_Growl_Pending.closeAfter then
|
|
local closeCraft = MTH_Growl_GetGlobal("CloseCraft")
|
|
if type(closeCraft) == "function" then
|
|
closeCraft()
|
|
end
|
|
end
|
|
MTH_Growl_Pending = nil
|
|
return true
|
|
end
|
|
|
|
-- Driver frame: react to the craft window opening/updating, with an OnUpdate
|
|
-- poll as backup and a timeout so we never leave a stale pending request.
|
|
local MTH_Growl_Frame = CreateFrame("Frame", "MTH_GrowlTeachFrame")
|
|
MTH_Growl_Frame:RegisterEvent("CRAFT_SHOW")
|
|
MTH_Growl_Frame:RegisterEvent("CRAFT_UPDATE")
|
|
MTH_Growl_Frame:RegisterEvent("PET_TRAINING_SHOW")
|
|
MTH_Growl_Frame:SetScript("OnEvent", function()
|
|
MTH_Growl_TryTrainNow()
|
|
end)
|
|
MTH_Growl_Frame:SetScript("OnUpdate", function()
|
|
if not MTH_Growl_Pending then return end
|
|
local p = MTH_Growl_Pending
|
|
p.elapsed = (p.elapsed or 0) + (arg1 or 0)
|
|
|
|
-- Train as soon as the window is populated.
|
|
if MTH_Growl_TryTrainNow() then return end
|
|
|
|
if p.elapsed > 8 then
|
|
local getNumCrafts = MTH_Growl_GetGlobal("GetNumCrafts")
|
|
local crafts = (type(getNumCrafts) == "function" and tonumber(getNumCrafts())) or -1
|
|
MTH_Growl_DebugLog("Gave up training Growl Rank " .. tostring(p.rank)
|
|
.. " for '" .. tostring(p.name) .. "' (timeout: windowVisible="
|
|
.. tostring(MTH_Growl_IsTrainingWindowVisible()) .. " numCrafts=" .. tostring(crafts)
|
|
.. " openAttempts=" .. tostring(p.openAttempts) .. ")")
|
|
MTH_Growl_Pending = nil
|
|
return
|
|
end
|
|
|
|
-- Window not up yet: re-attempt opening every second (the pet may still be
|
|
-- settling right after the tame, so the first cast can be ignored).
|
|
if not MTH_Growl_IsTrainingWindowVisible() then
|
|
p.sinceOpen = (p.sinceOpen or 0) + (arg1 or 0)
|
|
if p.sinceOpen >= 1.0 then
|
|
p.sinceOpen = 0
|
|
p.openAttempts = (p.openAttempts or 0) + 1
|
|
if p.openAttempts <= 6 then
|
|
MTH_Growl_OpenTrainingWindow()
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Start training the confirmed offer. Called from the popup's Yes button (a
|
|
-- hardware event), which makes opening the Beast Training window reliable.
|
|
-- ---------------------------------------------------------------------------
|
|
local function MTH_Growl_StartTraining(rank, petName)
|
|
local alreadyOpen = MTH_Growl_IsTrainingWindowVisible()
|
|
local getTime = MTH_Growl_GetGlobal("GetTime")
|
|
MTH_Growl_SuppressScanUntil = ((type(getTime) == "function" and getTime()) or 0) + 10
|
|
MTH_Growl_Pending = {
|
|
rank = rank,
|
|
name = petName,
|
|
elapsed = 0,
|
|
sinceOpen = 0,
|
|
openAttempts = 0,
|
|
closeAfter = not alreadyOpen,
|
|
done = false,
|
|
}
|
|
MTH_Growl_DebugLog("Confirmed: train Growl Rank " .. tostring(rank) .. " for '"
|
|
.. tostring(petName) .. "' (windowOpen=" .. tostring(alreadyOpen) .. ")")
|
|
if alreadyOpen then
|
|
MTH_Growl_TryTrainNow()
|
|
else
|
|
MTH_Growl_Pending.openAttempts = 1
|
|
MTH_Growl_OpenTrainingWindow()
|
|
end
|
|
end
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Confirmation popup. Yes = hardware event -> open Beast Training + DoCraft.
|
|
-- ---------------------------------------------------------------------------
|
|
StaticPopupDialogs["MTH_GROWL_TEACH"] = {
|
|
text = "Teach %s to your newly tamed %s?",
|
|
button1 = YES or "Yes",
|
|
button2 = NO or "No",
|
|
OnAccept = function()
|
|
local offer = MTH_Growl_Offer
|
|
MTH_Growl_Offer = nil
|
|
if not offer then return end
|
|
MTH_Growl_StartTraining(offer.rank, offer.name)
|
|
end,
|
|
OnCancel = function()
|
|
MTH_Growl_Offer = nil
|
|
end,
|
|
timeout = 0,
|
|
whileDead = 1,
|
|
hideOnEscape = 1,
|
|
}
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Public entry point: called when a fresh tame is confirmed.
|
|
-- ---------------------------------------------------------------------------
|
|
function MTH_Growl_OfferForTamedPet(petName, petLevel)
|
|
if not MTH_Growl_IsEnabled() then
|
|
return
|
|
end
|
|
if type(StaticPopup_Show) ~= "function" then
|
|
return
|
|
end
|
|
|
|
local rank = MTH_Growl_RankForLevel(petLevel)
|
|
MTH_Growl_Offer = { rank = rank, name = petName, level = petLevel }
|
|
local spellLabel = "Growl (Rank " .. tostring(rank) .. ")"
|
|
local name = tostring(petName or "your pet")
|
|
MTH_Growl_DebugLog("Offer pet='" .. name .. "' level=" .. tostring(petLevel) .. " -> " .. spellLabel)
|
|
StaticPopup_Show("MTH_GROWL_TEACH", spellLabel, name)
|
|
end
|