Files
MetaHunt/api/core-scan-trainer.lua
2026-09-11 13:45:58 +02:00

251 lines
9.1 KiB
Lua

-- MetaHunt: one-time live trainer catalog collector.
-- Developer workflow: /mth trainerscan, then open a Hunter or Pet Trainer.
local MTH_TS_Frame
local MTH_TS_Armed = false
local MTH_TS_AutoArmed = false
local function MTH_TS_Log(text)
if MTH_DebugFrame and type(MTH_DebugFrame.AddInfo) == "function" then
MTH_DebugFrame:AddInfo("[TrainerScan] " .. tostring(text))
end
end
local function MTH_TS_Call(name, ...)
local fn = getglobal and getglobal(name) or (_G and _G[name])
if type(fn) ~= "function" then return nil, "missing" end
local ok, a, b, c, d, e, f, g = pcall(fn, unpack(arg))
if not ok then return nil, "error=" .. tostring(a) end
return { a, b, c, d, e, f, g }, nil
end
local function MTH_TS_Values(values)
if not values then return "" end
local parts = {}
local i = 1
while i <= table.getn(values) do
if values[i] ~= nil then
table.insert(parts, tostring(values[i]))
end
i = i + 1
end
return table.concat(parts, " | ")
end
local function MTH_TS_TargetName()
if type(UnitName) == "function" then
local name = UnitName("target")
if name and name ~= "" then return name end
end
local title = getglobal and getglobal("TrainerFrameTitleText") or nil
if title and title.GetText then return title:GetText() or "?" end
return "?"
end
local function MTH_TS_GetLiveStatusStore()
if type(MTH_CharSavedVariables) ~= "table" then MTH_CharSavedVariables = {} end
if type(MTH_CharSavedVariables.trainerLiveStatus) ~= "table" then
MTH_CharSavedVariables.trainerLiveStatus = {}
end
return MTH_CharSavedVariables.trainerLiveStatus
end
local function MTH_TS_GetCatalogKind(name, rank)
local catalog = MTH_DS_TrainerCatalog
if type(catalog) ~= "table" or type(catalog.byNameRank) ~= "table" then return nil end
local keySuffix = "\031" .. tostring(name or "") .. "\031" .. tostring(rank or "")
if catalog.byNameRank["hunter" .. keySuffix] then return "hunter" end
if catalog.byNameRank["pet" .. keySuffix] then return "pet" end
return nil
end
function MTH_GetTrainerLiveStatus(kind, name, rank)
local store = type(MTH_CharSavedVariables) == "table" and MTH_CharSavedVariables.trainerLiveStatus or nil
if type(store) ~= "table" then return nil end
return store[tostring(kind or "") .. "\031" .. tostring(name or "") .. "\031" .. tostring(rank or "")]
end
local function MTH_TS_CollectLiveStatuses()
local countValues = MTH_TS_Call("GetNumTrainerServices")
local total = countValues and tonumber(countValues[1]) or 0
if total <= 0 then return false end
local trainerName = MTH_TS_TargetName()
local store = MTH_TS_GetLiveStatusStore()
local observed = 0
for index = 1, total do
local info = MTH_TS_Call("GetTrainerServiceInfo", index)
local name = info and info[1] or nil
local rank = info and info[2] or ""
local status = info and info[3] or nil
local kind = MTH_TS_GetCatalogKind(name, rank)
if kind and (status == "available" or status == "unavailable" or status == "used") then
local key = kind .. "\031" .. tostring(name) .. "\031" .. tostring(rank)
store[key] = {
status = status,
observedAt = time(),
trainer = trainerName,
}
observed = observed + 1
end
end
if observed > 0 then
MTH_TS_Log("Live trainer overlay: " .. tostring(observed) .. " observed catalog row(s) from " .. tostring(trainerName) .. ".")
if MTH and MTH.Print and (not MTH.IsMessageEnabled or MTH:IsMessageEnabled("trainerScan", true)) then
MTH:Print("Trainer scan: " .. tostring(observed) .. " Hunter/Pet spell status" .. (observed == 1 and "" or "es") .. " observed.")
end
return true
end
return false
end
local function MTH_TS_Collect()
local countValues, countError = MTH_TS_Call("GetNumTrainerServices")
local total = countValues and tonumber(countValues[1]) or 0
if not total or total <= 0 then
MTH_TS_Log("No trainer services yet. GetNumTrainerServices=" .. tostring(MTH_TS_Values(countValues))
.. " " .. tostring(countError or ""))
return false
end
local trainerName = MTH_TS_TargetName()
local capture = {
trainer = trainerName,
capturedAt = time(),
services = {},
}
MTH_TRAINER_SCAN_LAST = capture
if type(MTH_CharSavedVariables) == "table" then
if type(MTH_CharSavedVariables.trainerCatalogScans) ~= "table" then
MTH_CharSavedVariables.trainerCatalogScans = {}
end
MTH_CharSavedVariables.trainerCatalogScans[trainerName] = capture
end
MTH_TS_Log("=== " .. tostring(capture.trainer) .. " | " .. tostring(total) .. " trainer services ===")
local index = 1
while index <= total do
local info, infoError = MTH_TS_Call("GetTrainerServiceInfo", index)
local icon, iconError = MTH_TS_Call("GetTrainerServiceIcon", index)
local cost, costError = MTH_TS_Call("GetTrainerServiceCost", index)
local level, levelError = MTH_TS_Call("GetTrainerServiceLevelReq", index)
local skill, skillError = MTH_TS_Call("GetTrainerServiceSkillReq", index)
local description, descriptionError = MTH_TS_Call("GetTrainerServiceDescription", index)
local reqCountValues = MTH_TS_Call("GetTrainerServiceNumAbilityReq", index)
local reqCount = reqCountValues and tonumber(reqCountValues[1]) or 0
local requirements = {}
local reqIndex = 1
while reqIndex <= reqCount do
local requirement, reqError = MTH_TS_Call("GetTrainerServiceAbilityReq", index, reqIndex)
table.insert(requirements, {
name = requirement and requirement[1] or nil,
flag = requirement and requirement[2] or nil,
error = reqError,
})
reqIndex = reqIndex + 1
end
local row = {
index = index,
name = info and info[1] or nil,
rank = info and info[2] or nil,
status = info and info[3] or nil,
expanded = info and info[4] or nil,
icon = icon and icon[1] or nil,
cost = cost and cost[1] or nil,
requiredLevel = level and level[1] or nil,
skillRequirement = skill,
description = description and description[1] or nil,
abilityRequirements = requirements,
}
table.insert(capture.services, row)
MTH_TS_Log(string.format("%02d name=%s | rank=%s | status=%s | expanded=%s | level=%s | cost=%s | icon=%s",
index, tostring(row.name), tostring(row.rank), tostring(row.status), tostring(row.expanded),
tostring(row.requiredLevel), tostring(row.cost), tostring(row.icon)))
if row.description and row.description ~= "" then
MTH_TS_Log(" description=" .. tostring(row.description))
end
if table.getn(requirements) > 0 then
local requirementText = {}
local requirementIndex = 1
while requirementIndex <= table.getn(requirements) do
local requirement = requirements[requirementIndex]
table.insert(requirementText, tostring(requirement.name)
.. " [flag=" .. tostring(requirement.flag) .. "]")
requirementIndex = requirementIndex + 1
end
MTH_TS_Log(" abilityRequirements=" .. table.concat(requirementText, ", "))
end
if skill and table.getn(skill) > 0 then
MTH_TS_Log(" skillRequirement=" .. MTH_TS_Values(skill))
end
if infoError or iconError or costError or levelError or skillError or descriptionError then
MTH_TS_Log(" API=" .. tostring(infoError or "ok") .. ", " .. tostring(iconError or "ok")
.. ", " .. tostring(costError or "ok") .. ", " .. tostring(levelError or "ok")
.. ", " .. tostring(skillError or "ok") .. ", " .. tostring(descriptionError or "ok"))
end
index = index + 1
end
MTH_TS_Log("=== Capture complete. " .. tostring(total)
.. " rows retained in MTH_TRAINER_SCAN_LAST and trainerCatalogScans. ===")
return true
end
local function MTH_TS_EnsureFrame()
if not MTH_TS_Frame then
MTH_TS_Frame = CreateFrame("Frame", "MTH_TrainerCatalogScanner")
MTH_TS_Frame:SetScript("OnEvent", function()
if event == "TRAINER_SHOW" then
MTH_TS_AutoArmed = true
this.elapsed = 0
this:SetScript("OnUpdate", function()
this.elapsed = (this.elapsed or 0) + (arg1 or 0)
if this.elapsed < 0.25 then return end
this:SetScript("OnUpdate", nil)
MTH_TS_CollectLiveStatuses()
end)
return
end
if event ~= "TRAINER_LIST_UPDATE" or not (MTH_TS_Armed or MTH_TS_AutoArmed) then return end
this.elapsed = 0
this:SetScript("OnUpdate", function()
this.elapsed = (this.elapsed or 0) + (arg1 or 0)
if this.elapsed < 0.25 then return end
this:SetScript("OnUpdate", nil)
local manualArmed = MTH_TS_Armed
MTH_TS_Armed = false
MTH_TS_AutoArmed = false
MTH_TS_CollectLiveStatuses()
if manualArmed and not MTH_TS_Collect() and MTH and MTH.Print then
MTH:Print("Trainer list was empty. Open the trainer again and run /mth trainerscan.")
end
end)
end)
MTH_TS_Frame:RegisterEvent("TRAINER_SHOW")
MTH_TS_Frame:RegisterEvent("TRAINER_LIST_UPDATE")
end
end
function MTH_CommandTrainerScan()
if MTH_DebugFrame and type(MTH_DebugFrame.Show) == "function" then MTH_DebugFrame:Show() end
local currentRows = MTH_TS_Call("GetNumTrainerServices")
if currentRows and (tonumber(currentRows[1]) or 0) > 0 then
MTH_TS_Collect()
if MTH and MTH.Print then
MTH:Print("Trainer catalog captured from the open trainer window.")
end
return
end
MTH_TS_EnsureFrame()
MTH_TS_Armed = true
if MTH and MTH.Print then
MTH:Print("Trainer catalog scan armed. Open a Hunter Trainer or Pet Trainer; results will appear in the debug window.")
end
end
MTH_TS_EnsureFrame()