Add trainer planning and level-up tracking

This commit is contained in:
DuvelCorp
2026-09-11 13:44:41 +02:00
parent aeaddd9558
commit 0fb52f67ff
15 changed files with 3416 additions and 70 deletions
+10
View File
@@ -9,6 +9,16 @@ Resurrecting on Octowow.
### Added
- **Hunter's Book - Hunter and Pet spells**: Added catalog pages for all Hunter and Pet Trainer spells, with spell icons, required level, base/Honored prices, branch, known status, search, level filters, sorting, and a scrollable inspector.
- **Trainer cost planning**: Hunter's Book now shows a training plan with the cost of spells available now, the next training level, and all remaining spells. Prices can be switched between standard and Honored costs.
- **Live trainer status tracking**: Visiting a Hunter or Pet Trainer now records available, unavailable, and already-used services without guessing from filtered trainer lists.
- **Level-up tracking**: Added Hunter, Pet, and Pet Loyalty level-up notifications. Hunter messages list catalog spells unlocked at that level with base and Honored costs; major Hunter milestones use a special celebration.
- **Leveling animation and message options**: Added separate Animation and Message controls for Hunter training levels, major milestones, pet training levels, and pet loyalty gains.
- **Pet Auto-Teach — Growl**: When you tame a new beast, MetaHunt now offers to teach it Growl at the best rank for the pet's level. Click **Yes** and it opens the Beast Training window, teaches the ability and closes the window automatically.
- **Animations**: MetaHunt now plays optional animations that make the interface feel alive. Powered by the PizzaSauce library of [Pizzahawaii](https://codeberg.org/Pizzahawaii/) !
+1
View File
@@ -36,6 +36,7 @@ data\ds-pet-spells.lua
data\ds-pet-spells-trainer.lua
data\ds-hunter-spells.lua
data\ds-hunter-spells-dbc.lua
data\ds-trainer-catalog.lua
data\ds-racial.lua
data\init.lua
+4 -4
View File
@@ -9,9 +9,9 @@ You play the best class, now you have the best companion for it (and its not you
### Important notes
- Altough Nampower isn't mandatory for most of the features, it is a must-have for some.
- If you were using old versions of Feed-O-Matic, ICU, zHunterMod and HunterHelper, you dont need those with MetaHunt and you should disable them to avoid conflicts.
- MetaHunt is fully compatible with QUIVER, and is not a replacement for it.
- Altough Nampower isn't mandatory for most of the features, it is a must-have for some, and certainly the MM Widget.
- If you were using old versions of Feed-O-Matic, ICU, zHunterMod and HunterHelper, you dont need those with MetaHunt and you SHOULD disable them to avoid conflicts.
- MetaHunt is fully compatible with QUIVER <3, and is not a replacement for it.
- The addon purposely disable itself on all Capycraft/Ravencraft Realms, following the DDOS attacks they conducted on other servers.
## Twow Data
@@ -36,7 +36,7 @@ Online bestiary is also available here https://DuvelCorp.github.io/MetaHunt-Web/
## MM Widget
1.18.1 weapon of mass-destruction. The widget is composed of 5 cells, tracking the 3-state of Experimental ammo cycle (Fire → Nature → Arcane), the Lock & Load procs, and a smart dynamic cell. The smart cell shows either Aimed Shot availability or the right shot to use following the current Experimental ammo proc if any, and it bypasses the proc spell if your current target is immune to the school. You can bind the smart cell to turn the widget into a very efficient one-button rotation. Lets make MM great again !
1.18.1 weapon of mass-destruction. NAMPOWER MANDATORY. The widget is composed of 5 cells, tracking the 3-state of Experimental ammo cycle (Fire → Nature → Arcane), the Lock & Load procs, and a smart dynamic cell. The smart cell shows either Aimed Shot availability or the right shot to use following the current Experimental ammo proc if any, and it bypasses the proc spell if your current target is immune to the school. You can bind the smart cell to turn the widget into a very efficient one-button rotation. Lets make MM great again !
## Tooltips
+10
View File
@@ -25,6 +25,11 @@ local MTH_MESSAGE_DEFAULTS = {
petHungry = false,
beastTrainingScan = true,
spellbookScan = false,
trainerScan = true,
hunterLevelUp = true,
hunterLevelQuiet = false,
petLevelUp = true,
petLoyaltyUp = true,
petRanAway = true,
mapMarkers = true,
stableScan = true,
@@ -216,6 +221,11 @@ function MTH:IsRealmGateBlocked()
end
function MTH:CheckRealmGate()
-- TEMP TESTING: allow Ravencraft client access while HunterBook layout is reviewed.
if true then
return false
end
-- Realm name is only reliable once logged in; an empty result never blocks.
local realm = self:GetCurrentRealmName()
if type(realm) == "string" and realm ~= "" then
+323
View File
@@ -0,0 +1,323 @@
-- MetaHunt level tracker. All training facts come from MTH_DS_TrainerCatalog
-- through trainer-spell-planner.lua; this module only detects progression and
-- presents the resulting facts through chat and animations.
local MTH_LT_Frame = nil
local MTH_LT_PendingPetCheck = false
local MTH_LT_HunterLines = {
[1] = "You can now track beasts. Revolutionary: look for the things you intend to shoot.",
[2] = "The wilderness remains deeply unimpressed.",
[3] = "Still mostly leather, arrows, and misplaced confidence.",
[4] = "You have actual trainer lessons now. Try not to spend all your coin on repairs.",
[5] = "Your pet is doing its share. Please consider doing yours.",
[6] = "A hunter's mark: because shouting at the target was not precise enough.",
[7] = "Every pull is a plan until it becomes cardio.",
[8] = "Concussive Shot. Distance is now a tactical concept, not an accident.",
[9] = "Your arrows have begun their long argument with gravity.",
[10] = "You have a hawk aspect. The bird remains unavailable for comment.",
[11] = "A fine age for discovering that pets also require paperwork.",
[12] = "Mend Pet unlocked. Your pet has noticed this was overdue.",
[13] = "The wilderness has upgraded its complaints department.",
[14] = "Eagle Eye. Finally, a way to inspect trouble before walking into it.",
[15] = "You have survived another level through skill, luck, or a pet-shaped shield.",
[16] = "Traps and sharp things. Subtlety continues to be optional.",
[17] = "Your pet is still carrying the emotional weight of this partnership.",
[18] = "Multi-Shot. One target was apparently an unacceptable limitation.",
[19] = "Soon you may run faster. The enemies will take this personally.",
[20] = "Congratulations: escape now has a speed setting.",
[21] = "Fast enough to arrive at the next questionable decision sooner.",
[22] = "Your trainer has new bills and somehow they are all addressed to you.",
[23] = "The pet remains employed despite the working conditions.",
[24] = "Beast Lore. At last: research before violence. Briefly.",
[25] = "Halfway to better excuses, nowhere near fewer mistakes.",
[26] = "Rapid Fire. Your bow has entered its impatient phase.",
[27] = "Things are going well enough to become dangerous.",
[28] = "Your arrows now contain a measurable amount of intent.",
[29] = "Prepare for the hunter's most respected tactical maneuver.",
[30] = "Feign Death. Why win a fight when you can become administrative debris?",
[31] = "You are alive, which validates absolutely nothing.",
[32] = "Flare. Stealth has been downgraded to a suggestion.",
[33] = "The next trap is definitely part of the plan.",
[34] = "Explosive Trap. Negotiation has left the chat.",
[35] = "Your pet thinks this is all normal. That is worrying.",
[36] = "More damage, more maintenance, same heroic disregard for repair bills.",
[37] = "Keep going. The next disaster is probably already pathing toward you.",
[38] = "Your toolkit is becoming impressively hard to explain to civilized people.",
[39] = "One level until armor finally admits you have been taking hits.",
[40] = "Mail training. Leather got you this far; enemies kept putting holes in it.",
[41] = "You are now visibly better protected from consequences.",
[42] = "More Multi-Shot: because collateral damage needed a sequel.",
[43] = "Your pet has grown used to the sound of avoidable danger.",
[44] = "The trainer is delighted. Your coin pouch is less enthusiastic.",
[45] = "You are a seasoned hunter now, which means the mistakes are expensive.",
[46] = "Nature resistance: proof that the outdoors have been escalating.",
[47] = "Still alive. Still pointing the dangerous end away from yourself. Usually.",
[48] = "Your damage has matured into a fully funded public nuisance.",
[49] = "One more level until the trainer starts charging executive prices.",
[50] = "Your arrows have tenure now.",
[51] = "The wilderness would like a word. It has a queue.",
[52] = "Aimed Shot hits harder, which is your preferred form of diplomacy.",
[53] = "Your pet remains your most reliable party member. Make of that what you will.",
[54] = "Explosive Trap again. The floor continues to be a valid target.",
[55] = "Almost there. Try not to die to something with a tutorial tooltip.",
[56] = "Aspect of the Viper. Mana problems now have a bird-shaped solution.",
[57] = "The finish line is visible. So are several elite mobs. Choose carefully.",
[58] = "The final trainer bills are arriving with alarming confidence.",
[59] = "One last level. Your pet has begun drafting a victory speech.",
[60] = "You made it. The wildlife will remember this unfavorably.",
}
local MTH_LT_Priority = {
["Feign Death"] = 100,
["Mail"] = 95,
["Aspect of the Cheetah"] = 90,
["Rapid Fire"] = 88,
["Aspect of the Pack"] = 86,
["Explosive Trap"] = 84,
["Freezing Trap"] = 82,
["Flare"] = 80,
["Eagle Eye"] = 78,
["Multi-Shot"] = 76,
["Aimed Shot"] = 74,
["Aspect of the Viper"] = 72,
["Trueshot Aura"] = 70,
["Volley"] = 68,
["Track Hidden"] = 66,
}
local MTH_LT_PetLines = {
"has leveled up. It is now statistically better than your last pull.",
"has leveled up. The food budget has noticed.",
"has leveled up. Please do not celebrate by pulling three extra mobs.",
"has leveled up. It remains committed to solving problems with teeth.",
"has leveled up. The wilderness has filed no objection.",
"has leveled up. Your dependable colleague is carrying this team again.",
}
local MTH_LT_LoyaltyLines = {
"has decided you are less terrible than expected.",
"has upgraded its opinion of you. Keep the food coming.",
"is learning to trust your decision-making. This may be a mistake.",
"has accepted the arrangement. The arrangement includes snacks.",
"now trusts you more. Do not immediately test this with a cliff.",
}
local function MTH_LT_Store()
if not MTH_CharSavedVariables then MTH_CharSavedVariables = {} end
if type(MTH_CharSavedVariables.levelTracker) ~= "table" then
MTH_CharSavedVariables.levelTracker = { petLine = 0, loyaltyLine = 0 }
end
return MTH_CharSavedVariables.levelTracker
end
local function MTH_LT_MessageEnabled(key, defaultValue)
return MTH and type(MTH.IsMessageEnabled) == "function" and MTH:IsMessageEnabled(key, defaultValue)
end
local function MTH_LT_FormatCost(copper)
local total = tonumber(copper) or 0
local gold = math.floor(total / 10000)
local silver = math.floor((total - gold * 10000) / 100)
local bronze = total - gold * 10000 - silver * 100
local parts = {}
if gold > 0 then table.insert(parts, "|cffffd100" .. gold .. "g|r") end
if silver > 0 or gold > 0 then table.insert(parts, "|cffc7c7cf" .. silver .. "s|r") end
table.insert(parts, "|cffeda55f" .. bronze .. "c|r")
return table.concat(parts, " ")
end
local function MTH_LT_NextLine(store, key, lines)
store[key] = (tonumber(store[key]) or 0) + 1
if store[key] > table.getn(lines) then store[key] = 1 end
return lines[store[key]]
end
local function MTH_LT_GetHeadline(plan)
local best = nil
local bestPriority = -1
for _, entry in ipairs(plan.rows or {}) do
local priority = MTH_LT_Priority[tostring(entry.row and entry.row.name or "")] or 0
if priority > bestPriority then best, bestPriority = entry, priority end
end
return best
end
local function MTH_LT_FormatRows(plan)
local labels = {}
for index, entry in ipairs(plan.rows or {}) do
local row = entry.row or {}
local label = tostring(row.name or "Unknown")
if row.rank and row.rank ~= "" then label = label .. " " .. tostring(row.rank) end
table.insert(labels, label)
end
return table.concat(labels, ", ")
end
local function MTH_LT_PrintSpellList(plan)
local line = ""
local isFirstLine = true
for _, entry in ipairs(plan.rows or {}) do
local row = entry.row or {}
local label = tostring(row.name or "Unknown")
if row.rank and row.rank ~= "" then label = label .. " " .. tostring(row.rank) end
if line == "" then
line = label
elseif string.len(line) + string.len(label) + 2 > 92 then
MTH:Print((isFirstLine and "New spells available: " or "And also: ") .. line)
isFirstLine = false
line = label
else
line = line .. ", " .. label
end
end
if line ~= "" then MTH:Print((isFirstLine and "New spells available: " or "And also: ") .. line) end
end
local function MTH_LT_AnnounceTraining(kind, level, eventKind, preview)
if type(MTH_TrainerPlan_BuildLevel) ~= "function" then return end
local base = MTH_TrainerPlan_BuildLevel(kind, level, { honored = false, includeKnown = preview })
local honored = MTH_TrainerPlan_BuildLevel(kind, level, { honored = true, includeKnown = preview })
if base.unlearned.count <= 0 then
if kind == "hunter" and MTH_LT_MessageEnabled("hunterLevelUp", true) then
MTH:Print("Congratz! You hit level " .. tostring(level) .. ".")
end
if preview then MTH:Print("Level test: no " .. kind .. " trainer spells in the 1.18.1 catalog at level " .. tostring(level) .. ".") end
return
end
local headline = MTH_LT_GetHeadline(base)
local headlineText = headline and headline.row and tostring(headline.row.name or "") or "New training"
if headline and headline.row and headline.row.rank and headline.row.rank ~= "" then headlineText = headlineText .. " " .. headline.row.rank end
local isLevelCap = kind == "hunter" and level == 60
if isLevelCap then headlineText = "The hunt is yours" end
if MTH_LT_MessageEnabled(eventKind == "hunter" and "hunterLevelUp" or "petLevelUp", true) then
if kind == "hunter" then
MTH:Print("Congratz! You hit level " .. tostring(level) .. ".")
MTH:Print(MTH_LT_HunterLines[level] or "New training is available.")
MTH:Print("The spells you can learn will cost you " .. MTH_LT_FormatCost(base.unlearned.cost)
.. ", but only " .. MTH_LT_FormatCost(honored.unlearned.cost) .. " if you are Honored with the master.")
MTH_LT_PrintSpellList(base)
else
MTH:Print("" .. MTH_LT_FormatRows(base))
MTH:Print("Trainer cost: Base " .. MTH_LT_FormatCost(base.unlearned.cost) .. " | Honored " .. MTH_LT_FormatCost(honored.unlearned.cost))
end
end
local isMilestone = kind == "hunter" and (level == 10 or level == 20 or level == 30 or level == 40 or level == 50 or level == 60)
local flag = isMilestone and "level.huntermilestone" or (kind == "hunter" and "level.hunter" or "level.pet")
if type(MTH_FX_IsEnabled) == "function" and MTH_FX_IsEnabled(flag) and type(MTH_Celebrate) == "function" then
local subtitle = kind == "hunter" and (MTH_LT_HunterLines[level] or "New training is available.") or "New pet training is available. The trainer wants coin."
if isMilestone and type(MTH_CelebrateBlackHole) == "function" then
MTH_CelebrateBlackHole("Level " .. tostring(level) .. " - " .. headlineText, subtitle)
else
MTH_Celebrate("Level " .. tostring(level) .. " - " .. headlineText, subtitle, "fanfare", 4.8)
end
end
end
local function MTH_LT_HandleHunterLevel(level)
local store = MTH_LT_Store()
local previous = tonumber(store.hunterLevel)
store.hunterLevel = level
if previous and level > previous then
MTH_LT_AnnounceTraining("hunter", level, "hunter")
end
end
local function MTH_LT_GetPetState()
if type(UnitExists) ~= "function" or not UnitExists("pet") then return nil end
local level = type(UnitLevel) == "function" and tonumber(UnitLevel("pet")) or nil
local name = type(UnitName) == "function" and UnitName("pet") or "Your pet"
if not level or level <= 0 then return nil end
local loyalty = nil
local getPetLoyalty = (type(getglobal) == "function" and getglobal("GetPetLoyalty")) or (_G and _G["GetPetLoyalty"])
if type(getPetLoyalty) == "function" then loyalty = tonumber(getPetLoyalty()) end
return { key = tostring(name or "pet"), name = tostring(name or "Your pet"), level = level, loyalty = loyalty }
end
local function MTH_LT_HandlePetState()
MTH_LT_PendingPetCheck = false
local state = MTH_LT_GetPetState()
if not state then return end
local store = MTH_LT_Store()
local pet = store.pet
if type(pet) ~= "table" or pet.key ~= state.key then
store.pet = state
return
end
if state.level > (tonumber(pet.level) or 0) then
local line = MTH_LT_NextLine(store, "petLine", MTH_LT_PetLines)
if MTH_LT_MessageEnabled("petLevelUp", true) then MTH:Print(state.name .. " reached level " .. state.level .. ". " .. line) end
MTH_LT_AnnounceTraining("pet", state.level, "pet")
end
if state.loyalty and state.loyalty > (tonumber(pet.loyalty) or 0) then
local line = MTH_LT_NextLine(store, "loyaltyLine", MTH_LT_LoyaltyLines)
if state.loyalty == 6 then line = "is your Best Friend. Against all evidence, it trusts you completely." end
if MTH_LT_MessageEnabled("petLoyaltyUp", true) then MTH:Print(state.name .. " reached Loyalty Level " .. state.loyalty .. ". " .. line) end
if type(MTH_FX_IsEnabled) == "function" and MTH_FX_IsEnabled("level.loyalty") and type(MTH_Celebrate) == "function" then
MTH_Celebrate(state.name .. " - Loyalty " .. state.loyalty, line, state.loyalty == 6 and "victory" or "fanfare")
end
end
store.pet = state
end
local function MTH_LT_RequestPetCheck()
if MTH_LT_PendingPetCheck or not MTH_LT_Frame then return end
MTH_LT_PendingPetCheck = true
MTH_LT_Frame.elapsed = 0
MTH_LT_Frame:SetScript("OnUpdate", function()
this.elapsed = (this.elapsed or 0) + (arg1 or 0)
if this.elapsed >= 0.35 then
this:SetScript("OnUpdate", nil)
MTH_LT_HandlePetState()
end
end)
end
function MTH_LevelTracker_Test(kind, level)
if kind == "hunter" or kind == "pet" then
local targetLevel = tonumber(level) or ((kind == "hunter") and (tonumber(UnitLevel("player")) or 1) or 1)
MTH:Print("Level test: previewing " .. kind .. " trainer spells at level " .. tostring(targetLevel) .. ".")
MTH_LT_AnnounceTraining(kind, targetLevel, kind, true)
return true
end
if kind == "loyalty" then
local state = MTH_LT_GetPetState()
local loyalty = tonumber(level) or (state and state.loyalty) or 1
local petName = state and state.name or "Your pet"
local line = loyalty == 6 and "is your Best Friend. Against all evidence, it trusts you completely."
or MTH_LT_LoyaltyLines[1]
MTH:Print(petName .. " reached Loyalty Level " .. tostring(loyalty) .. ". " .. line)
if type(MTH_Celebrate) == "function" then
MTH_Celebrate(petName .. " - Loyalty " .. tostring(loyalty), line, loyalty == 6 and "victory" or "fanfare")
end
return true
end
return false
end
function MTH_LevelTracker_Initialize()
if MTH_LT_Frame then return end
MTH_LT_Frame = CreateFrame("Frame", "MTH_LevelTracker")
MTH_LT_Frame:RegisterEvent("PLAYER_ENTERING_WORLD")
MTH_LT_Frame:RegisterEvent("PLAYER_LEVEL_UP")
MTH_LT_Frame:RegisterEvent("UNIT_LEVEL")
MTH_LT_Frame:RegisterEvent("UNIT_PET")
MTH_LT_Frame:RegisterEvent("PET_BAR_UPDATE")
MTH_LT_Frame:SetScript("OnEvent", function()
if event == "PLAYER_ENTERING_WORLD" then
local level = type(UnitLevel) == "function" and tonumber(UnitLevel("player")) or nil
if level then MTH_LT_Store().hunterLevel = level end
MTH_LT_RequestPetCheck()
elseif event == "PLAYER_LEVEL_UP" then
MTH_LT_HandleHunterLevel(tonumber(arg1) or tonumber(UnitLevel("player")) or 1)
elseif event == "UNIT_LEVEL" and arg1 == "pet" then
MTH_LT_RequestPetCheck()
elseif event == "UNIT_PET" and arg1 == "player" then
MTH_LT_RequestPetCheck()
elseif event == "PET_BAR_UPDATE" then
MTH_LT_RequestPetCheck()
end
end)
end
MTH_LevelTracker_Initialize()
+251
View File
@@ -0,0 +1,251 @@
-- 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()
+33 -8
View File
@@ -331,15 +331,40 @@ function SlashCmdList.MTH(msg, editbox)
else
MTH:Print("Train scan is not available.")
end
elseif lowerCmd == "trainerscan" then
if type(MTH_CommandTrainerScan) == "function" then
MTH_CommandTrainerScan()
else
MTH:Print("Trainer catalog scanner is not available.")
end
elseif lowerCmd == "spellscan" then
if type(MTH_HT_RescanSpellbook) ~= "function" then
MTH:Print("Hunter spellbook scanner is not available.")
return
end
MTH_HT_RescanSpellbook()
local mail = type(MTH_HT_GetSpellInfo) == "function" and MTH_HT_GetSpellInfo("Mail") or nil
if mail then
MTH:Print("Hunter spellbook scan: Mail found (rank=" .. tostring(mail.rank or "") .. ", slot=" .. tostring(mail.slot or "?") .. ").")
else
MTH:Print("Hunter spellbook scan: Mail was not returned by the client spellbook API.")
end
elseif lowerCmd == "leveltest" then
local _, _, kind, levelText = string.find(lowerArg or "", "^(%S+)%s*(%d*)")
if type(MTH_LevelTracker_Test) ~= "function" then
MTH:Print("Level tracker is not available.")
elseif not MTH_LevelTracker_Test(kind, tonumber(levelText)) then
MTH:Print("Use /mth leveltest hunter [level], pet [level], or loyalty [level]")
end
elseif lowerCmd == "fx" or lowerCmd == "fireworks" or lowerCmd == "celebrate" then
-- TEMP: preview the pet-ability celebration effect.
if type(MTH_Celebrate) == "function" then
local label = msg
local _, _, rest = string.find(msg, "^%S+%s+(.-)%s*$")
if rest and rest ~= "" then
MTH_Celebrate("New Pet Ability!", rest)
else
MTH_Celebrate("New Pet Ability!", "Growl (Rank 3)")
if type(MTH_CelebratePreview) == "function" then
local style = lowerArg or ""
if style == "" then style = "firework" end
if style == "help" or style == "list" then
MTH:Print("Celebrations: firework, blackhole, shockwave, shatter, all")
elseif not MTH_CelebratePreview(style) then
MTH:Print("Unknown celebration: " .. tostring(style))
MTH:Print("Use /mth fx help for: firework, blackhole, shockwave, shatter, all")
end
else
-- Self-diagnosing: report which piece failed to load so we can tell
+328 -15
View File
@@ -33,6 +33,7 @@ MTH_FX_DBG = {
-- Sections shown, in order, in the options panel. `titleKey` localizes `title`.
MTH_FX_ANIM_SECTIONS = {
{ id = "pet", title = "Pet", titleKey = "ANIM_SECTION_PET" },
{ id = "level", title = "Leveling", titleKey = "ANIM_SECTION_LEVEL" },
{ id = "combat", title = "Combat", titleKey = "ANIM_SECTION_COMBAT" },
{ id = "book", title = "Book", titleKey = "ANIM_SECTION_BOOK" },
{ id = "ui", title = "Interface", titleKey = "ANIM_SECTION_UI" },
@@ -43,6 +44,18 @@ MTH_FX_ANIM_SECTIONS = {
-- labelKey/tooltipKey localize label/tooltip (resolved at render time).
-- Adding a new animation = one entry here + one MTH_FX_IsEnabled("key") guard.
MTH_FX_ANIM_DEFS = {
{ key = "level.hunter", section = "level", default = true,
label = "Celebrate Hunter training levels",
tooltip = "Show a banner and burst when a level unlocks Hunter trainer spells." },
{ key = "level.huntermilestone", section = "level", default = true,
label = "Celebrate Hunter milestones",
tooltip = "Use a large celebration at levels 10, 20, 30, 40, 50, and 60." },
{ key = "level.pet", section = "level", default = true,
label = "Celebrate pet training levels",
tooltip = "Show a banner when your current pet gains a level with Pet Trainer spells." },
{ key = "level.loyalty", section = "level", default = true,
label = "Celebrate pet loyalty gains",
tooltip = "Show a golden banner when your current pet gains a loyalty level." },
{ key = "pet.celebrate", section = "pet", default = true,
label = "Celebrate new pet abilities", labelKey = "ANIM_PET_CELEBRATE",
tooltip = "Play a firework and a banner in the middle of the screen when your pet learns a new ability or a new rank.",
@@ -244,6 +257,27 @@ if Sauce and type(Sauce.RegisterType) == "function" and not Sauce.MTH_HasShake t
MTH_FX_DBG.shakeErr = err
end
if Sauce and type(Sauce.RegisterType) == "function" and not Sauce.MTH_HasSpiral then
local ok, err = pcall(function()
Sauce.MTH_HasSpiral = true
Sauce:RegisterType("mth_spiral", {
init = function(target, from)
return from or 0
end,
apply = function(target, from, to, t, tween)
local radius = (tween.radius or 0) * (1 - t)
local angle = (tween.startAngle or 0) + (tween.rotations or 1) * 2 * math.pi * t
target:ClearAllPoints()
target:SetPoint("CENTER", tween.anchor or UIParent, "CENTER",
(tween.cx or 0) + math.cos(angle) * radius,
(tween.cy or 0) + math.sin(angle) * radius)
end,
})
end)
MTH_FX_DBG.spiralOk = ok
MTH_FX_DBG.spiralErr = err
end
-- Reusable flash: overlays a short additive colour glow on any frame/button and
-- fades it out. Safe on any frame that supports CreateTexture; silent no-op
-- otherwise. The overlay texture is cached on the frame for reuse.
@@ -277,11 +311,15 @@ end
local MTH_FX_NUM_SPARKS = 22
local MTH_FX_NUM_CONFETTI = 48
local MTH_FX_NUM_BLACKHOLE = 40
local MTH_FX_Container
local MTH_FX_MsgFrame
local MTH_FX_TitleFS
local MTH_FX_SubFS
local MTH_FX_Sparks = {}
local MTH_FX_Confetti = {}
local MTH_FX_BlackHole = {}
-- Palette for the firework sparks (r, g, b), warm celebratory tones.
local MTH_FX_Colors = {
@@ -337,33 +375,75 @@ local function MTH_FX_EnsureFrames()
i = i + 1
end
i = 1
while i <= MTH_FX_NUM_CONFETTI do
local bit = MTH_FX_Container:CreateTexture(nil, "OVERLAY")
bit:SetTexture("Interface\\Buttons\\WHITE8X8")
bit:SetWidth(5)
bit:SetHeight(8)
bit:SetPoint("CENTER", MTH_FX_Container, "CENTER", 0, 0)
bit:Hide()
MTH_FX_Confetti[i] = bit
i = i + 1
end
i = 1
while i <= MTH_FX_NUM_BLACKHOLE do
local particle = MTH_FX_Container:CreateTexture(nil, "OVERLAY")
particle:SetTexture("Interface\\Cooldown\\star4")
particle:SetWidth(10)
particle:SetHeight(10)
particle:SetBlendMode("ADD")
particle:SetPoint("CENTER", MTH_FX_Container, "CENTER", 0, 0)
particle:Hide()
MTH_FX_BlackHole[i] = particle
i = i + 1
end
return true
end
-- Public entry point: play a celebratory message + firework burst in the middle
-- of the screen. `title` and `subtitle` are optional strings.
function MTH_Celebrate(title, subtitle)
-- Public entry point: play a full-screen celebration. `style` defaults to the
-- original firework burst and may be firework, victory, starfall, fanfare, or comet.
function MTH_Celebrate(title, subtitle, style, displaySeconds)
if not Sauce then
return
end
if not MTH_FX_EnsureFrames() then
return
end
style = string.lower(tostring(style or "firework"))
local fadeDelay = tonumber(displaySeconds) or 2.1
if fadeDelay < 0.5 then fadeDelay = 0.5 end
MTH_FX_TitleFS:SetTextColor(1, 0.82, 0)
if style == "starfall" then
MTH_FX_TitleFS:SetTextColor(0.45, 0.80, 1.00)
else
MTH_FX_TitleFS:SetTextColor(1, 0.82, 0)
end
MTH_FX_TitleFS:SetText(title or "")
MTH_FX_SubFS:SetText(subtitle or "")
Sauce:Stop(MTH_FX_MsgFrame)
MTH_FX_MsgFrame:SetAlpha(0)
MTH_FX_MsgFrame:Show()
Sauce:Sequence({
Sauce:Group({
Sauce:FadeTo(MTH_FX_MsgFrame, 1, 0.25),
Sauce:Pulse(MTH_FX_MsgFrame, 1.22, 0.5),
}),
Sauce:FadeOut(MTH_FX_MsgFrame, 0.6, "inQuad", { delay = 2.2 }),
})
if style == "victory" or style == "fanfare" then
Sauce:Sequence({
Sauce:Group({
Sauce:SlideIn(MTH_FX_MsgFrame, "UP", 80, 0.36, "outBack", { defer = true }),
Sauce:Pulse(MTH_FX_MsgFrame, style == "victory" and 1.32 or 1.18, 0.52, { defer = true }),
}),
Sauce:FadeOut(MTH_FX_MsgFrame, 0.6, "inQuad", { delay = fadeDelay }),
})
else
Sauce:Sequence({
Sauce:Group({
Sauce:FadeTo(MTH_FX_MsgFrame, 1, 0.25),
Sauce:Pulse(MTH_FX_MsgFrame, 1.22, 0.5),
}),
Sauce:FadeOut(MTH_FX_MsgFrame, 0.6, "inQuad", { delay = fadeDelay }),
})
end
MTH_FX_Container:Show()
local colorCount = table.getn(MTH_FX_Colors)
@@ -371,34 +451,70 @@ function MTH_Celebrate(title, subtitle)
local i = 1
while i <= MTH_FX_NUM_SPARKS do
local spark = MTH_FX_Sparks[i]
spark:SetTexture("Interface\\Cooldown\\star4")
spark:SetTexCoord(0, 1, 0, 1)
local startX, startY = 0, 0
local delay, gravity = 0, 300
local angle = (i / MTH_FX_NUM_SPARKS) * 2 * math.pi + (math.random() - 0.5) * 0.4
local speed = 150 + math.random() * 130
local vx = math.cos(angle) * speed
local vy = math.sin(angle) * speed + 70
local dur = 0.9 + math.random() * 0.4
if style == "victory" then
startX = ((i - math.floor(i / 2) * 2) == 0) and -130 or 130
speed = 100 + math.random() * 100
vx = math.cos(angle) * speed
vy = math.sin(angle) * speed + 105
elseif style == "starfall" then
startX = -230 + math.random() * 460
startY = 200 + math.random() * 100
vx = -35 + math.random() * 70
vy = -10 + math.random() * 25
gravity = 235
dur = 1.1 + math.random() * 0.5
elseif style == "fanfare" then
delay = ((i - 1) - math.floor((i - 1) / 3) * 3) * 0.18
speed = 110 + math.random() * 90
vx = math.cos(angle) * speed
vy = math.sin(angle) * speed + 90
elseif style == "comet" then
if i == 1 then
startY = -180
vx, vy, gravity, dur = 0, 430, 0, 0.52
else
delay = 0.5
startY = 45
speed = 130 + math.random() * 120
vx = math.cos(angle) * speed
vy = math.sin(angle) * speed + 70
end
end
colorIdx = colorIdx + 1
if colorIdx > colorCount then colorIdx = 1 end
local color = MTH_FX_Colors[colorIdx]
spark:SetVertexColor(color[1], color[2], color[3])
spark:SetAlpha(1)
spark:SetAlpha(delay > 0 and 0 or 1)
spark:Show()
Sauce:Stop(spark)
Sauce:Group({
Sauce:Tween(spark, {
type = "mth_ballistic",
from = { 0, 0 },
from = { startX, startY },
duration = dur,
easing = "linear",
delay = delay,
anchor = MTH_FX_Container,
dur = dur,
vx = vx,
vy = vy,
gravity = 300,
gravity = gravity,
}),
Sauce:FadeTo(spark, 1, 0.05, "outQuad", { delay = delay }),
Sauce:FadeTo(spark, 0, 0.45, "inQuad", {
delay = dur - 0.45,
delay = delay + dur - 0.45,
onFinish = function()
spark:Hide()
end,
@@ -408,6 +524,203 @@ function MTH_Celebrate(title, subtitle)
end
end
local function MTH_FX_ShowPreviewBanner(title, subtitle, r, g, b)
MTH_FX_TitleFS:SetTextColor(r, g, b)
MTH_FX_TitleFS:SetText(title)
MTH_FX_SubFS:SetText(subtitle)
Sauce:Stop(MTH_FX_MsgFrame)
MTH_FX_MsgFrame:SetAlpha(0)
MTH_FX_MsgFrame:Show()
Sauce:Sequence({
Sauce:Group({
Sauce:FadeTo(MTH_FX_MsgFrame, 1, 0.18),
Sauce:Pulse(MTH_FX_MsgFrame, 1.18, 0.45),
}),
Sauce:FadeOut(MTH_FX_MsgFrame, 0.55, "inQuad", { delay = 2.1 }),
})
end
function MTH_CelebrateConfetti(title, subtitle)
if not Sauce or not MTH_FX_EnsureFrames() then return end
MTH_FX_ShowPreviewBanner(title or "Celebration!", subtitle or "Confetti", 1, 0.82, 0.15)
MTH_FX_Container:Show()
local count = table.getn(MTH_FX_Colors)
local i = 1
while i <= MTH_FX_NUM_CONFETTI do
local bit = MTH_FX_Confetti[i]
local color = MTH_FX_Colors[((i - 1) - math.floor((i - 1) / count) * count) + 1]
local size = 3 + math.random() * 5
local angle = math.random() * 2 * math.pi
local speed = 90 + math.random() * 210
local dur = 1.25 + math.random() * 0.7
bit:SetWidth(size)
bit:SetHeight(size * 1.6)
bit:SetVertexColor(color[1], color[2], color[3])
bit:SetAlpha(1)
bit:Show()
Sauce:Stop(bit)
Sauce:Group({
Sauce:Tween(bit, {
type = "mth_ballistic", from = { 0, 0 }, duration = dur,
easing = "linear", anchor = MTH_FX_Container, dur = dur,
vx = math.cos(angle) * speed, vy = math.sin(angle) * speed + 100, gravity = 255,
}),
Sauce:FadeTo(bit, 0, 0.35, "inQuad", {
delay = dur - 0.35, onFinish = function() bit:Hide() end,
}),
})
i = i + 1
end
end
function MTH_CelebrateBlackHole(title, subtitle)
if not Sauce or not MTH_FX_EnsureFrames() then return end
MTH_FX_ShowPreviewBanner(title or "Gravity Well", subtitle or "Black Hole", 0.68, 0.45, 1.00)
MTH_FX_Container:Show()
local colors = { { 0.45, 0.25, 1 }, { 0.30, 0.50, 1 }, { 0.80, 0.55, 1 } }
local i = 1
while i <= MTH_FX_NUM_BLACKHOLE do
local particle = MTH_FX_BlackHole[i]
local color = colors[((i - 1) - math.floor((i - 1) / 3) * 3) + 1]
local startAngle = math.random() * 2 * math.pi
local radius = 95 + math.random() * 145
local duration = 1.5 + math.random() * 1.0
local size = 7 + math.random() * 8
particle:SetWidth(size)
particle:SetHeight(size)
particle:SetVertexColor(color[1], color[2], color[3])
particle:SetAlpha(1)
particle:Show()
Sauce:Stop(particle)
Sauce:Group({
Sauce:Tween(particle, {
type = "mth_spiral", from = 0, to = 1, duration = duration,
easing = "inQuad", anchor = MTH_FX_Container, cx = 0, cy = 0,
radius = radius, startAngle = startAngle, rotations = 1.5 + math.random() * 2.0,
}),
Sauce:Size(particle, { size, size }, { 2, 2 }, duration, "inQuad"),
Sauce:FadeTo(particle, 0, duration * 0.3, "inQuad", {
delay = duration * 0.7, onFinish = function() particle:Hide() end,
}),
})
i = i + 1
end
end
function MTH_CelebrateShockwave(title, subtitle)
if not Sauce or not MTH_FX_EnsureFrames() then return end
MTH_FX_ShowPreviewBanner(title or "Impact!", subtitle or "Shockwave", 1, 0.62, 0.15)
MTH_FX_Container:Show()
local dotsPerWave = 16
local i = 1
while i <= MTH_FX_NUM_CONFETTI do
local dot = MTH_FX_Confetti[i]
local wave = math.floor((i - 1) / dotsPerWave) + 1
local pos = (i - 1) - math.floor((i - 1) / dotsPerWave) * dotsPerWave
local angle = pos / dotsPerWave * 2 * math.pi
local duration = 1.1 + wave * 0.18
local radius = 115 + wave * 52
local delay = (wave - 1) * 0.18
local intensity = 1 - (wave - 1) * 0.20
dot:SetWidth(5)
dot:SetHeight(5)
dot:SetVertexColor(1 * intensity, 0.65 * intensity, 0.18 * intensity)
dot:SetAlpha(0)
dot:Show()
Sauce:Stop(dot)
Sauce:Group({
Sauce:Tween(dot, {
type = "mth_ballistic", from = { 0, 0 }, duration = duration,
easing = "outQuad", delay = delay, anchor = MTH_FX_Container, dur = duration,
vx = math.cos(angle) * radius / duration, vy = math.sin(angle) * radius / duration, gravity = 0,
}),
Sauce:FadeTo(dot, 1, 0.04, "outQuad", { delay = delay }),
Sauce:FadeTo(dot, 0, 0.4, "inQuad", {
delay = delay + duration - 0.4, onFinish = function() dot:Hide() end,
}),
})
i = i + 1
end
end
function MTH_CelebrateShatter(title, subtitle)
if not Sauce or not MTH_FX_EnsureFrames() then return end
MTH_FX_ShowPreviewBanner(title or "Shattered!", subtitle or "Shatter", 0.70, 0.85, 1.00)
MTH_FX_Container:Show()
local grid = 4
local iconSize = 80
local fragmentSize = iconSize / grid
local i = 1
while i <= MTH_FX_NUM_SPARKS do
local spark = MTH_FX_Sparks[i]
if i <= grid * grid then
local row = math.floor((i - 1) / grid)
local col = (i - 1) - row * grid
local x = (col - grid / 2 + 0.5) * fragmentSize
local y = ((grid - 1 - row) - grid / 2 + 0.5) * fragmentSize
local duration = 1.1 + math.random() * 0.45
spark:SetTexture("Interface\\Icons\\Ability_Hunter_SniperShot")
spark:SetTexCoord(col / grid, (col + 1) / grid, row / grid, (row + 1) / grid)
spark:SetWidth(fragmentSize)
spark:SetHeight(fragmentSize)
spark:SetVertexColor(1, 1, 1)
spark:SetAlpha(1)
spark:Show()
Sauce:Stop(spark)
Sauce:Group({
Sauce:Tween(spark, {
type = "mth_ballistic", from = { x, y }, duration = duration,
easing = "linear", anchor = MTH_FX_Container, dur = duration,
vx = x * 4 + (math.random() - 0.5) * 90,
vy = y * 4 + 95 + math.random() * 80, gravity = 265,
}),
Sauce:FadeTo(spark, 0, 0.35, "inQuad", {
delay = duration - 0.35, onFinish = function() spark:Hide() end,
}),
})
end
i = i + 1
end
end
function MTH_CelebratePreview(style)
style = string.lower(tostring(style or "firework"))
if style == "all" then
if not Sauce then return end
local styles = { "firework", "blackhole", "shockwave", "shatter" }
local steps = {}
local i = 1
while i <= table.getn(styles) do
local previewStyle = styles[i]
table.insert(steps, Sauce:Run(function()
MTH_CelebratePreview(previewStyle)
end, { defer = true }))
table.insert(steps, Sauce:Delay(3.7))
i = i + 1
end
Sauce:Sequence(steps)
return true
end
if style == "blackhole" then
MTH_CelebrateBlackHole("Celebration Preview", "Black Hole")
return true
elseif style == "shockwave" then
MTH_CelebrateShockwave("Celebration Preview", "Shockwave")
return true
elseif style == "shatter" then
MTH_CelebrateShatter("Celebration Preview", "Shatter")
return true
end
local labels = {
firework = "Firework Burst",
}
if not labels[style] then return false end
MTH_Celebrate("Celebration Preview", labels[style], style)
return true
end
-- Convenience wrapper used when the hunter's pet learns a new ability/rank.
function MTH_CelebratePetAbility(label)
if not MTH_FX_IsEnabled("pet.celebrate") then
+225
View File
@@ -0,0 +1,225 @@
if type(MTH_HUNTERBOOK_TABS) ~= "table" then MTH_HUNTERBOOK_TABS = {} end
local trainerColumns = {
{ x = 28, width = 150, align = "LEFT" },
{ x = 180, width = 62, align = "LEFT" },
{ x = 244, width = 43, align = "LEFT" },
{ x = 289, width = 80, align = "LEFT" },
{ x = 371, width = 105, align = "LEFT" },
{ x = 478, width = 74, align = "LEFT" },
}
MTH_HUNTERBOOK_TABS.hunterspells = {
headerLabel = "Hunter Spells",
columnLabels = { "Spell", "Rank", "Level", "Price", "Branch", "Status" },
columnLayout = trainerColumns,
}
MTH_HUNTERBOOK_TABS.petspells = {
headerLabel = "Pet Spells",
columnLabels = { "Spell", "Rank", "Level", "Price", "Branch", "Status" },
columnLayout = trainerColumns,
}
function MTH_BOOKTAB_IsTrainerSpellMode(mode)
return mode == "hunterspells" or mode == "petspells"
end
local function MTH_BOOKTAB_GetTrainerKind()
if MTH_BOOK_STATE and MTH_BOOK_STATE.mode == "petspells" then return "pet" end
return "hunter"
end
function MTH_BOOKTAB_FormatTrainerCost(copper)
local value = tonumber(copper) or 0
local gold = math.floor(value / 10000)
value = value - gold * 10000
local silver = math.floor(value / 100)
local bronze = value - silver * 100
local parts = {}
if gold > 0 then table.insert(parts, "|cffffd100" .. tostring(gold) .. "g|r") end
if silver > 0 or gold > 0 then table.insert(parts, "|cffc7c7cf" .. tostring(silver) .. "s|r") end
table.insert(parts, "|cffeda55f" .. tostring(bronze) .. "c|r")
return table.concat(parts, " ")
end
function MTH_BOOKTAB_GetTrainerDisplayCost(copper)
if type(MTH_TrainerPlan_GetPrice) == "function" then
return MTH_TrainerPlan_GetPrice({ trainerCost = copper }, MTH_BOOK_STATE and MTH_BOOK_STATE.trainerHonoredDiscount)
end
return tonumber(copper) or 0
end
function MTH_BOOKTAB_GetTrainerSpellStatus(row)
local kind = MTH_BOOKTAB_GetTrainerKind()
local playerLevel = MTH_BOOK_GetPlayerLevelValue and MTH_BOOK_GetPlayerLevelValue() or nil
local status = type(MTH_TrainerPlan_GetStatus) == "function" and MTH_TrainerPlan_GetStatus(kind, row, playerLevel) or "notyet"
if status == "known" then return "Known", "|cff40ff40" end
if status == "notlearned" then return "Not learned", "|cffffa500" end
return "Not yet learnable", "|cffff4040"
end
function MTH_BOOKTAB_BuildTrainerSpellResults()
local results = {}
local kind = MTH_BOOKTAB_GetTrainerKind()
local plan = type(MTH_TrainerPlan_Build) == "function" and MTH_TrainerPlan_Build(kind, {
playerLevel = MTH_BOOK_GetPlayerLevelValue and MTH_BOOK_GetPlayerLevelValue() or nil,
honored = MTH_BOOK_STATE and MTH_BOOK_STATE.trainerHonoredDiscount,
}) or nil
if type(plan) ~= "table" or type(plan.rows) ~= "table" then return results end
local search = MTH_BOOK_SafeLower(MTH_BOOK_STATE.search or "")
for _, planned in ipairs(plan.rows) do
local row = planned.row
local level = tonumber(row.requiredLevel) or 0
local haystack = tostring(row.name or "") .. " " .. tostring(row.rank or "") .. " " .. tostring(row.section or "") .. " " .. tostring(row.description or "")
if type(row.prerequisites) == "table" then
for _, prerequisite in ipairs(row.prerequisites) do
haystack = haystack .. " " .. tostring(prerequisite)
end
end
haystack = MTH_BOOK_SafeLower(haystack)
if (search == "" or string.find(haystack, search, 1, true) ~= nil)
and (not MTH_BOOK_STATE.minLevel or level >= MTH_BOOK_STATE.minLevel)
and (not MTH_BOOK_STATE.maxLevel or level <= MTH_BOOK_STATE.maxLevel)
and (type(MTH_TrainerPlan_MatchesQuick) ~= "function" or MTH_TrainerPlan_MatchesQuick(planned.status, MTH_BOOK_STATE.quick))
then
table.insert(results, row)
end
end
return results
end
function MTH_BOOKTAB_UpdateTrainerPlanPanel(detail)
local parent = detail and detail:GetParent()
if not parent then return end
if not parent.mthTrainerPlan then
local panel = CreateFrame("Frame", nil, parent)
panel:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, -238)
panel:SetWidth(136)
panel:SetHeight(128)
panel:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", tile = true, tileSize = 16, edgeSize = 12, insets = { left = 3, right = 3, top = 3, bottom = 3 } })
panel:SetBackdropColor(0.06, 0.06, 0.06, 1)
panel:SetBackdropBorderColor(0.28, 0.28, 0.28, 1)
panel.text = panel:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
panel.text:SetPoint("TOPLEFT", panel, "TOPLEFT", 7, -7)
panel.text:SetWidth(122)
panel.text:SetJustifyH("LEFT")
panel.text:SetJustifyV("TOP")
parent.mthTrainerPlan = panel
end
local plan = type(MTH_TrainerPlan_Build) == "function" and MTH_TrainerPlan_Build(MTH_BOOKTAB_GetTrainerKind(), {
playerLevel = MTH_BOOK_GetPlayerLevelValue and MTH_BOOK_GetPlayerLevelValue() or nil,
honored = MTH_BOOK_STATE.trainerHonoredDiscount,
}) or nil
if not plan then return end
local nextLevel = plan.nextLevel.level
local nextLabel = "Next"
if nextLevel then nextLabel = "At level " .. tostring(nextLevel) end
local function formatBlock(label, summary)
return "|cffffffff" .. label .. "|r\n"
.. "|cff4da6ff" .. tostring(summary.count) .. "|r |cff9a9a9aspells|r |cffffffff| |r"
.. MTH_BOOKTAB_FormatTrainerCost(summary.cost)
end
parent.mthTrainerPlan.text:SetText("|cffffd100Training Plan|r\n"
.. formatBlock("Now", plan.now)
.. "\n\n" .. formatBlock(nextLabel, plan.nextLevel)
.. "\n\n" .. formatBlock("Remaining", plan.remaining))
parent.mthTrainerPlan:Show()
end
function MTH_BOOKTAB_GetTrainerSpellTotal()
local catalog = MTH_DS_TrainerCatalog
local rows = catalog and catalog[MTH_BOOKTAB_GetTrainerKind()] or nil
return type(rows) == "table" and table.getn(rows) or 0
end
function MTH_BOOKTAB_TrainerSpellSort(a, b)
local al = tonumber(a and a.requiredLevel) or 0
local bl = tonumber(b and b.requiredLevel) or 0
if al ~= bl then return al < bl end
local an = MTH_BOOK_SafeLower(a and a.name)
local bn = MTH_BOOK_SafeLower(b and b.name)
if an ~= bn then return an < bn end
return MTH_BOOK_SafeLower(a and a.rank) < MTH_BOOK_SafeLower(b and b.rank)
end
function MTH_BOOKTAB_GetTrainerSpellSortKey(row, col)
if col == 1 then return MTH_BOOK_SafeLower(row and row.name) end
if col == 2 then return MTH_BOOK_SafeLower(row and row.rank) end
if col == 3 then return tonumber(row and row.requiredLevel) or 0 end
if col == 4 then return tonumber(row and row.trainerCost) or 0 end
if col == 5 then return MTH_BOOK_SafeLower(row and row.section) end
if col == 6 then
local status = MTH_BOOKTAB_GetTrainerSpellStatus(row)
return MTH_BOOK_SafeLower(status)
end
return nil
end
function MTH_BOOKTAB_GetTrainerSpellRowValues(row)
local requiredLevel = tonumber(row and row.requiredLevel) or 0
local playerLevel = MTH_BOOK_GetPlayerLevelValue and MTH_BOOK_GetPlayerLevelValue() or nil
local levelColor = (playerLevel and playerLevel >= requiredLevel) and "|cff40ff40" or "|cffff4040"
local status, statusColor = MTH_BOOKTAB_GetTrainerSpellStatus(row)
return {
tostring(row and row.name or "-"),
tostring(row and row.rank or "-"),
levelColor .. tostring(row and row.requiredLevel or "-") .. "|r",
MTH_BOOKTAB_FormatTrainerCost(MTH_BOOKTAB_GetTrainerDisplayCost(row and row.trainerCost)),
tostring(row and row.section or "-"),
statusColor .. status .. "|r",
}
end
function MTH_BOOKTAB_UpdateTrainerSpellDetail(detail, selected)
if not MTH_BOOKTAB_IsTrainerSpellMode(MTH_BOOK_STATE and MTH_BOOK_STATE.mode) then return false end
MTH_BOOKTAB_UpdateTrainerPlanPanel(detail)
if not selected then
MTH_BOOK_SetDetailText(detail, "|cFFFFD100Trainer Spells|r\n\nSelect a spell rank to inspect its trainer level, undiscounted cost, and prerequisites.")
return true
end
local lines = {}
table.insert(lines, "|cffffff00" .. tostring(selected.name or "Unknown") .. "|r")
if selected.rank and selected.rank ~= "" then table.insert(lines, "|cffaaaaaa" .. tostring(selected.rank) .. "|r") end
table.insert(lines, "")
table.insert(lines, "|cff9a9a9aBranch:|r |cffffffff" .. tostring(selected.section or "-") .. "|r")
local requiredLevel = tonumber(selected.requiredLevel) or 0
local playerLevel = MTH_BOOK_GetPlayerLevelValue and MTH_BOOK_GetPlayerLevelValue() or nil
local levelColor = (playerLevel and playerLevel >= requiredLevel) and "|cff40ff40" or "|cffff4040"
table.insert(lines, "|cff9a9a9aRequired Level:|r " .. levelColor .. tostring(selected.requiredLevel or "-") .. "|r")
local status, statusColor = MTH_BOOKTAB_GetTrainerSpellStatus(selected)
table.insert(lines, "|cff9a9a9aStatus:|r " .. statusColor .. status .. "|r")
table.insert(lines, "|cff9a9a9aBase Price:|r " .. MTH_BOOKTAB_FormatTrainerCost(selected.trainerCost))
table.insert(lines, "|cff9a9a9aHonored Price:|r " .. MTH_BOOKTAB_FormatTrainerCost(math.floor((tonumber(selected.trainerCost) or 0) * 0.90)))
if type(selected.prerequisites) == "table" and table.getn(selected.prerequisites) > 0 then
table.insert(lines, "")
table.insert(lines, "|cffffd100Prerequisites|r")
for _, prerequisite in ipairs(selected.prerequisites) do
table.insert(lines, " " .. tostring(prerequisite))
end
end
if selected.description and selected.description ~= "" then
table.insert(lines, "")
table.insert(lines, "|cffffd100Description|r")
table.insert(lines, "|cffffffff " .. tostring(selected.description) .. "|r")
end
local detailFrame = detail.mthTrainerDetailParent or detail:GetParent()
if not detail.mthTrainerIcon then
detail.mthTrainerIcon = detail:CreateTexture(nil, "ARTWORK")
detail.mthTrainerIcon:SetWidth(28)
detail.mthTrainerIcon:SetHeight(28)
end
detail.mthTrainerIcon:SetParent(detail)
detail.mthTrainerIcon:ClearAllPoints()
detail.mthTrainerIcon:SetPoint("TOPLEFT", detail, "TOPLEFT", 0, 0)
detail.mthTrainerIcon:SetTexture(MTH_BOOK_ResolveIconPath(selected.icon) or "Interface\\Icons\\INV_Misc_QuestionMark")
detail.mthTrainerIcon:Show()
if detail.SetTextInsets then detail:SetTextInsets(4, 4, 36, 4) end
if not detail.mthTrainerDetailScroll then
detail:ClearAllPoints()
detail:SetPoint("TOPLEFT", detailFrame, "TOPLEFT", 8, -42)
detail:SetWidth(detailFrame:GetWidth() - 16)
end
MTH_BOOK_SetDetailText(detail, table.concat(lines, "\n"))
return true
end
+356 -38
View File
@@ -147,6 +147,7 @@ local MTH_BOOK_STATE = {
familyStatsPercent = false,
petHideNoAbilities = true,
petHideUnknown = true,
trainerHonoredDiscount = false,
forcedBeastId = nil,
page = 1,
pageSize = 16,
@@ -170,6 +171,8 @@ local MTH_BOOK_STATE = {
npcs = { col = nil, asc = true },
stable = { col = nil, asc = true },
pethistory = { col = 5, asc = false },
hunterspells = { col = 3, asc = true },
petspells = { col = 3, asc = true },
},
sliderDragging = false,
}
@@ -308,6 +311,17 @@ local MTH_BOOK_CONTENT_LAYOUT_ITEMS = {
detailH = 366,
}
local MTH_BOOK_CONTENT_LAYOUT_TRAINERS = {
listX = 16,
listY = -146,
listW = 564,
listH = 374,
detailX = 608,
detailY = -146,
detailW = 136,
detailH = 230,
}
local MTH_BOOK_CONTENT_LAYOUT_PETABILITIES = {
listX = 16,
listY = -106,
@@ -363,7 +377,52 @@ local MTH_BOOK_CONTENT_LAYOUT_HISTORY = {
detailH = 446,
}
local function MTH_BOOK_ApplyContentLayoutForMode()
function MTH_BOOK_ConfigureTrainerDetailScroll(isTrainerMode, detailParent, detailText)
if not (detailParent and detailText) then return end
if isTrainerMode then
if not detailParent.mthTrainerDetailScroll then
local scroll = CreateFrame("ScrollFrame", "MTH_BOOK_TrainerDetailScroll", detailParent, "UIPanelScrollFrameTemplate")
scroll:SetPoint("TOPLEFT", detailParent, "TOPLEFT", 4, -4)
scroll:SetPoint("BOTTOMRIGHT", detailParent, "BOTTOMRIGHT", -24, 4)
detailParent.mthTrainerDetailScroll = scroll
detailText:EnableMouseWheel(true)
detailText:SetScript("OnMouseWheel", function()
local current = scroll:GetVerticalScroll()
if arg1 and arg1 > 0 then
scroll:SetVerticalScroll(current - 24)
else
scroll:SetVerticalScroll(current + 24)
end
end)
end
local scroll = detailParent.mthTrainerDetailScroll
detailText.mthTrainerDetailParent = detailParent
detailText.mthTrainerDetailScroll = scroll
if detailText:GetParent() ~= scroll then detailText:SetParent(scroll) end
detailText:ClearAllPoints()
detailText:SetPoint("TOPLEFT", scroll, "TOPLEFT", 0, 0)
scroll:SetScrollChild(detailText)
scroll:SetWidth(detailParent:GetWidth() - 28)
scroll:SetHeight(detailParent:GetHeight() - 8)
detailText:SetWidth(detailParent:GetWidth() - 28)
detailText:SetHeight(scroll:GetHeight())
scroll:Show()
else
local scroll = detailParent.mthTrainerDetailScroll
if scroll then scroll:Hide() end
if detailText.mthTrainerDetailParent then
detailText:SetParent(detailText.mthTrainerDetailParent)
detailText:ClearAllPoints()
detailText:SetPoint("TOPLEFT", detailParent, "TOPLEFT", 6, -6)
detailText:SetWidth(detailParent:GetWidth() - 12)
detailText:SetHeight(detailParent:GetHeight() - 12)
if detailText.SetTextInsets then detailText:SetTextInsets(4, 4, 4, 4) end
detailText.mthTrainerDetailScroll = nil
end
end
end
function MTH_BOOK_ApplyContentLayoutForMode()
local listParent = getglobal("MTH_BOOK_ListBackdrop")
local detailParent = getglobal("MTH_BOOK_DetailBackdrop")
local detailText = getglobal("MTH_BOOK_DetailBackdropDetailText")
@@ -374,6 +433,9 @@ local function MTH_BOOK_ApplyContentLayoutForMode()
local layout = MTH_BOOK_CONTENT_LAYOUT_DEFAULT
if MTH_BOOK_STATE.mode == "petabilities" then
layout = MTH_BOOK_CONTENT_LAYOUT_PETABILITIES
elseif type(MTH_BOOKTAB_IsTrainerSpellMode) == "function"
and MTH_BOOKTAB_IsTrainerSpellMode(MTH_BOOK_STATE.mode) then
layout = MTH_BOOK_CONTENT_LAYOUT_TRAINERS
elseif MTH_BOOK_IsItemMode() then
layout = MTH_BOOK_CONTENT_LAYOUT_ITEMS
elseif MTH_BOOK_STATE.mode == "npcs" then
@@ -411,6 +473,9 @@ local function MTH_BOOK_ApplyContentLayoutForMode()
if listSlider then
listSlider:SetHeight(sliderHeight)
end
local isTrainerMode = type(MTH_BOOKTAB_IsTrainerSpellMode) == "function"
and MTH_BOOKTAB_IsTrainerSpellMode(MTH_BOOK_STATE.mode)
MTH_BOOK_ConfigureTrainerDetailScroll(isTrainerMode, detailParent, detailText)
if MTH_BOOK_STATE.petUI then
local paneHeight = layout.listH - 8
@@ -477,6 +542,45 @@ local function MTH_BOOK_DropdownCreateInfo()
return {}
end
function MTH_BOOK_EnsureTrainerStatusDropdown()
local dropdown = getglobal("MTH_BOOK_TrainerStatusDropdown")
if not dropdown then
local filter = getglobal("MTH_BOOK_Filter1")
local parent = filter and filter:GetParent()
if not parent then return nil end
dropdown = CreateFrame("Frame", "MTH_BOOK_TrainerStatusDropdown", parent, "UIDropDownMenuTemplate")
dropdown:SetPoint("TOPLEFT", parent, "TOPLEFT", 150, -116)
UIDropDownMenu_Initialize(dropdown, function()
local options = {
{ value = "all", label = "All" },
{ value = "known", label = "Known" },
{ value = "notlearned", label = "Not learned" },
{ value = "notyet", label = "Not yet learnable" },
}
for i = 1, table.getn(options) do
local option = options[i]
local value = option.value
local label = option.label
local info = MTH_BOOK_DropdownCreateInfo()
info.text = label
info.checked = MTH_BOOK_STATE.quick == value
info.func = function()
MTH_BOOK_STATE.quick = value
MTH_BOOK_STATE.page = 1
MTH_BOOK_STATE.selectedEntry = nil
MTH_BOOK_DropdownSetText(dropdown, label)
MTH_BOOK_RefreshFilter()
end
UIDropDownMenu_AddButton(info)
end
end)
UIDropDownMenu_SetWidth(136, dropdown)
UIDropDownMenu_JustifyText("LEFT", dropdown)
dropdown:Hide()
end
return dropdown
end
local function MTH_BOOK_SetSliderFromCursor(slider)
if not slider then return end
local minValue, maxValue = slider:GetMinMaxValues()
@@ -1459,6 +1563,12 @@ local function MTH_BOOK_GetSortKey(entry, col)
if col == 6 then return MTH_BOOK_SafeLower(tostring(row.abandonReason or "")) end
end
if type(MTH_BOOKTAB_IsTrainerSpellMode) == "function"
and MTH_BOOKTAB_IsTrainerSpellMode(MTH_BOOK_STATE.mode) then
return type(MTH_BOOKTAB_GetTrainerSpellSortKey) == "function"
and MTH_BOOKTAB_GetTrainerSpellSortKey(entry, col) or nil
end
local items = MTH_BOOK_GetItemsTable()
local item = items and items[entry]
if not item then return nil end
@@ -1514,6 +1624,10 @@ local function MTH_BOOK_DefaultCompare(a, b)
if MTH_BOOK_STATE.mode == "npcs" then return MTH_BOOK_NPCSort(a, b) end
if MTH_BOOK_STATE.mode == "stable" then return MTH_BOOK_StableSort(a, b) end
if MTH_BOOK_STATE.mode == "pethistory" then return MTH_BOOK_PetHistorySort(a, b) end
if type(MTH_BOOKTAB_IsTrainerSpellMode) == "function"
and MTH_BOOKTAB_IsTrainerSpellMode(MTH_BOOK_STATE.mode) then
return type(MTH_BOOKTAB_TrainerSpellSort) == "function" and MTH_BOOKTAB_TrainerSpellSort(a, b) or false
end
return MTH_BOOK_ItemSort(a, b)
end
@@ -1782,6 +1896,15 @@ local function MTH_BOOK_BuildResults()
return results
end
if type(MTH_BOOKTAB_IsTrainerSpellMode) == "function"
and MTH_BOOKTAB_IsTrainerSpellMode(MTH_BOOK_STATE.mode) then
if type(MTH_BOOKTAB_BuildTrainerSpellResults) == "function" then
results = MTH_BOOKTAB_BuildTrainerSpellResults()
end
MTH_BOOK_ApplyActiveSort(results)
return results
end
local items = MTH_BOOK_GetItemsTable()
if not items then return results end
for itemId, item in pairs(items) do
@@ -1825,6 +1948,10 @@ local function MTH_BOOK_GetTotalCount()
local store = MTH_BOOK_GetPetDatastore()
return (type(store) == "table" and type(store.historyById) == "table") and MTH_BOOK_CountMap(store.historyById) or 0
end
if type(MTH_BOOKTAB_IsTrainerSpellMode) == "function"
and MTH_BOOKTAB_IsTrainerSpellMode(MTH_BOOK_STATE.mode) then
return type(MTH_BOOKTAB_GetTrainerSpellTotal) == "function" and MTH_BOOKTAB_GetTrainerSpellTotal() or 0
end
local items = MTH_BOOK_GetItemsTable()
return items and MTH_BOOK_CountMap(items) or 0
end
@@ -2328,7 +2455,9 @@ end
local function MTH_BOOK_UpdateQuickFilterControls()
MTH_BOOK_UpdateCheckboxLayoutByMode()
if MTH_BOOK_IsItemMode() then
if type(MTH_BOOKTAB_IsTrainerSpellMode) == "function" and MTH_BOOKTAB_IsTrainerSpellMode(MTH_BOOK_STATE.mode) then
MTH_BOOK_STATE.pageSize = 17
elseif MTH_BOOK_IsItemMode() then
MTH_BOOK_STATE.pageSize = 18
elseif MTH_BOOK_STATE.mode == "families" then
MTH_BOOK_STATE.pageSize = MTH_BOOK_MAX_ROWS
@@ -2350,6 +2479,7 @@ local function MTH_BOOK_UpdateQuickFilterControls()
local itemSubtypeDropdown = getglobal("MTH_BOOK_ItemSubtypeDropdown")
local npcFunctionDropdown = getglobal("MTH_BOOK_NPCFunctionDropdown")
local npcZoneDropdown = getglobal("MTH_BOOK_NPCZoneDropdown")
local trainerStatusDropdown = MTH_BOOK_EnsureTrainerStatusDropdown()
local searchLabel = getglobal("MTH_BOOK_SearchLabel")
local minLabel = getglobal("MTH_BOOK_MinLabel")
local maxLabel = getglobal("MTH_BOOK_MaxLabel")
@@ -2359,6 +2489,7 @@ local function MTH_BOOK_UpdateQuickFilterControls()
local hideNoAbilities = getglobal("MTH_BOOK_HideNoAbilities")
local hideUnknown = getglobal("MTH_BOOK_HideUnknown")
local petOnlyMyLevel = getglobal("MTH_BOOK_PetOnlyMyLevel")
local trainerHonoredDiscount = getglobal("MTH_BOOK_TrainerHonoredDiscount")
local petInZoneOnly = getglobal("MTH_BOOK_PetInZoneOnly")
local npcInZoneOnly = getglobal("MTH_BOOK_NPCInZoneOnly")
local showAllOnMapButton = getglobal("MTH_BOOK_ShowAllOnMapButton")
@@ -2369,6 +2500,7 @@ local function MTH_BOOK_UpdateQuickFilterControls()
local nextButton = getglobal("MTH_BOOK_NextButton")
local listSlider = getglobal("MTH_BOOK_ListSlider")
local sliderBackdrop = getglobal("MTH_BOOK_ListSliderBackdrop")
if trainerStatusDropdown then trainerStatusDropdown:Hide() end
if MTH_BOOK_STATE.mode == "petabilities" or MTH_BOOK_STATE.mode == "stable" then
if stats then stats:Hide() end
@@ -2543,6 +2675,59 @@ local function MTH_BOOK_UpdateQuickFilterControls()
resetButton:ClearAllPoints()
resetButton:SetPoint("TOPLEFT", resetButton:GetParent(), "TOPLEFT", 546, -80)
end
elseif type(MTH_BOOKTAB_IsTrainerSpellMode) == "function"
and MTH_BOOKTAB_IsTrainerSpellMode(MTH_BOOK_STATE.mode) then
if searchLabel then searchLabel:Show() end
if minLabel then minLabel:Show() end
if maxLabel then maxLabel:Show() end
if search then search:Show(); search:ClearAllPoints(); search:SetPoint("TOPLEFT", search:GetParent(), "TOPLEFT", 66, -80); search:SetWidth(160) end
if minLevel then minLevel:Show(); minLevel:ClearAllPoints(); minLevel:SetPoint("TOPLEFT", minLevel:GetParent(), "TOPLEFT", 302, -80) end
if maxLevel then maxLevel:Show(); maxLevel:ClearAllPoints(); maxLevel:SetPoint("TOPLEFT", maxLevel:GetParent(), "TOPLEFT", 414, -80) end
if applyButton then applyButton:Show(); applyButton:ClearAllPoints(); applyButton:SetPoint("TOPLEFT", applyButton:GetParent(), "TOPLEFT", 478, -80) end
local resetButton = getglobal("MTH_BOOK_ResetButton")
if resetButton then resetButton:Show(); resetButton:ClearAllPoints(); resetButton:SetPoint("TOPLEFT", resetButton:GetParent(), "TOPLEFT", 546, -80) end
if scanButton then scanButton:Hide() end
for i = 1, 6 do
local btn = getglobal("MTH_BOOK_Filter" .. i)
if btn then btn:Hide() end
end
if trainerStatusDropdown then
local labels = { all = "All", known = "Known", notlearned = "Not learned", notyet = "Not yet learnable" }
trainerStatusDropdown:ClearAllPoints()
trainerStatusDropdown:SetPoint("TOPLEFT", trainerStatusDropdown:GetParent(), "TOPLEFT", 150, -116)
MTH_BOOK_DropdownSetText(trainerStatusDropdown, labels[MTH_BOOK_STATE.quick] or "All")
trainerStatusDropdown:Show()
end
if familyDropdown then familyDropdown:Hide() end
if abilityDropdown then abilityDropdown:Hide() end
if rankDropdown then rankDropdown:Hide() end
if petLearnSourceDropdown then petLearnSourceDropdown:Hide() end
if beastZoneLabel then beastZoneLabel:Hide() end
if beastContinentDropdown then beastContinentDropdown:Hide() end
if beastZoneDropdown then beastZoneDropdown:Hide() end
if itemSubtypeDropdown then itemSubtypeDropdown:Hide() end
if npcFunctionDropdown then npcFunctionDropdown:Hide() end
if npcZoneDropdown then npcZoneDropdown:Hide() end
if hideNoAbilities then hideNoAbilities:Hide() end
if hideUnknown then hideUnknown:Hide() end
if petOnlyMyLevel then petOnlyMyLevel:Hide() end
if trainerHonoredDiscount then
trainerHonoredDiscount:ClearAllPoints()
trainerHonoredDiscount:SetPoint("TOPLEFT", trainerHonoredDiscount:GetParent(), "TOPLEFT", 18, -110)
trainerHonoredDiscount:SetChecked(MTH_BOOK_STATE.trainerHonoredDiscount and 1 or nil)
local trainerHonoredDiscountText = getglobal("MTH_BOOK_TrainerHonoredDiscountText")
if trainerHonoredDiscountText then trainerHonoredDiscountText:SetText("Honored prices") end
trainerHonoredDiscount:Show()
end
if petInZoneOnly then petInZoneOnly:Hide() end
if npcInZoneOnly then npcInZoneOnly:Hide() end
if showAllOnMapButton then showAllOnMapButton:Hide() end
local requireVendor = getglobal("MTH_BOOK_RequireVendor")
local requireDrop = getglobal("MTH_BOOK_RequireDrop")
local requireObject = getglobal("MTH_BOOK_RequireObject")
if requireVendor then requireVendor:Hide() end
if requireDrop then requireDrop:Hide() end
if requireObject then requireObject:Hide() end
elseif MTH_BOOK_STATE.mode == "npcs" then
if searchLabel then searchLabel:Show() end
if minLabel then minLabel:Hide() end
@@ -2768,6 +2953,9 @@ local function MTH_BOOK_UpdateQuickFilterControls()
if MTH_BOOK_STATE.mode ~= "petabilities" and not MTH_BOOK_IsItemMode() and petOnlyMyLevel then
petOnlyMyLevel:Hide()
end
if (type(MTH_BOOKTAB_IsTrainerSpellMode) ~= "function" or not MTH_BOOKTAB_IsTrainerSpellMode(MTH_BOOK_STATE.mode)) and trainerHonoredDiscount then
trainerHonoredDiscount:Hide()
end
if MTH_BOOK_STATE.mode ~= "pets" and petInZoneOnly then
petInZoneOnly:Hide()
end
@@ -3270,13 +3458,20 @@ end
function MTH_BOOK_SetDetailText(detail, text)
if not detail then return end
local wrapped = tostring(text or "")
if MTH_BOOK_STATE.mode ~= "pethistory" and MTH_BOOK_STATE.mode ~= "pets" and MTH_BOOK_STATE.mode ~= "abilities" then
if MTH_BOOK_STATE.mode ~= "pethistory" and MTH_BOOK_STATE.mode ~= "pets" and MTH_BOOK_STATE.mode ~= "abilities"
and (type(MTH_BOOKTAB_IsTrainerSpellMode) ~= "function" or not MTH_BOOKTAB_IsTrainerSpellMode(MTH_BOOK_STATE.mode)) then
wrapped = MTH_BOOK_WrapDetailText(wrapped)
end
detail._mthLocking = true
detail:SetText(wrapped)
detail._mthLockedText = wrapped
detail._mthLocking = false
local scroll = detail.mthTrainerDetailScroll
if scroll then
local textHeight = detail.GetStringHeight and detail:GetStringHeight() or 0
detail:SetHeight(math.max(scroll:GetHeight(), textHeight + 12))
scroll:SetVerticalScroll(0)
end
end
function MTH_BOOK_ConfigureReadOnlyEditBox(detail)
@@ -3323,6 +3518,9 @@ end
function MTH_BOOK_UpdateDetail()
local detail = getglobal("MTH_BOOK_DetailBackdropDetailText")
if not detail then return end
if detail.mthTrainerIcon then detail.mthTrainerIcon:Hide() end
local detailParent = detail:GetParent()
if detailParent and detailParent.mthTrainerPlan then detailParent.mthTrainerPlan:Hide() end
MTH_BOOK_HideDetailTitle()
-- Hide model skin preview when not in a beast-list mode
if MTH_BOOK_STATE.mode ~= "pets" and MTH_BOOK_STATE.mode ~= "abilities" then
@@ -3330,6 +3528,11 @@ function MTH_BOOK_UpdateDetail()
end
local petTop = MTH_BOOK_STATE.petUI and MTH_BOOK_STATE.petUI.detailTop
local petBottom = MTH_BOOK_STATE.petUI and MTH_BOOK_STATE.petUI.detailBottom
if type(MTH_BOOKTAB_UpdateTrainerSpellDetail) == "function"
and MTH_BOOKTAB_UpdateTrainerSpellDetail(detail, MTH_BOOK_STATE.selectedEntry) then
MTH_BOOK_UpdateOpenMapButton()
return
end
if not MTH_BOOK_STATE.selectedEntry then
if MTH_BOOK_ModelViewer_Clear then MTH_BOOK_ModelViewer_Clear() end
if MTH_BOOK_STATE.mode == "petabilities" then
@@ -4031,6 +4234,12 @@ local function MTH_BOOK_ApplyColumnLayout(parent)
end
local function MTH_BOOK_GetRowValues(entry)
if type(MTH_BOOKTAB_IsTrainerSpellMode) == "function"
and MTH_BOOKTAB_IsTrainerSpellMode(MTH_BOOK_STATE.mode) then
return type(MTH_BOOKTAB_GetTrainerSpellRowValues) == "function"
and MTH_BOOKTAB_GetTrainerSpellRowValues(entry) or { "", "", "", "", "" }
end
if MTH_BOOK_STATE.mode == "pets" then
-- MetaHunt DB entry
local beast = MTH_DS_Beasts and MTH_DS_Beasts[entry]
@@ -4317,6 +4526,9 @@ local function MTH_BOOK_AssignEntry(btn, entry)
btn.entry = entry
elseif MTH_BOOK_STATE.mode == "pethistory" then
btn.entry = { petId = entry }
elseif type(MTH_BOOKTAB_IsTrainerSpellMode) == "function"
and MTH_BOOKTAB_IsTrainerSpellMode(MTH_BOOK_STATE.mode) then
btn.entry = entry
else
btn.entry = { itemId = entry }
end
@@ -4482,11 +4694,18 @@ MTH_BOOK_UpdateResults = function()
btn.itemIcon:SetTexture(nil)
btn.itemIcon:Hide()
end
elseif type(MTH_BOOKTAB_IsTrainerSpellMode) == "function" and MTH_BOOKTAB_IsTrainerSpellMode(MTH_BOOK_STATE.mode) then
local iconPath = MTH_BOOK_ResolveIconPath(entry.icon)
btn.itemIcon:ClearAllPoints()
btn.itemIcon:SetPoint("LEFT", btn, "LEFT", 8, 0)
btn.itemIcon:SetTexture(iconPath or "Interface\\Icons\\INV_Misc_QuestionMark")
btn.itemIcon:Show()
else
btn.itemIcon:SetTexture(nil)
btn.itemIcon:Hide()
end
end
if btn.trainerCoinIcon then btn.trainerCoinIcon:Hide() end
if btn.familyAbilityButtons then
local abilityEntries = nil
local abilityColIndex = nil
@@ -4572,6 +4791,7 @@ MTH_BOOK_UpdateResults = function()
btn.itemIcon:SetTexture(nil)
btn.itemIcon:Hide()
end
if btn.trainerCoinIcon then btn.trainerCoinIcon:Hide() end
if btn.familyAbilityButtons then
for chipIndex = 1, table.getn(btn.familyAbilityButtons) do
local chip = btn.familyAbilityButtons[chipIndex]
@@ -4628,6 +4848,11 @@ local function MTH_BOOK_UpdateModeLabels()
check1 = ""
check2 = ""
check3 = ""
elseif type(MTH_BOOKTAB_IsTrainerSpellMode) == "function" and MTH_BOOKTAB_IsTrainerSpellMode(MTH_BOOK_STATE.mode) then
filterNames = { "All", "Known", "Not learned", "Not yet learnable", "", "" }
check1 = ""
check2 = ""
check3 = ""
elseif MTH_BOOK_STATE.mode == "npcs" then
filterNames = { "", "", "", "", "", "" }
check1 = "Alliance"
@@ -4769,6 +4994,9 @@ local function MTH_BOOK_SetQuickFilter(index)
MTH_BOOK_STATE.quick = "all"
elseif MTH_BOOK_STATE.mode == "petabilities" then
MTH_BOOK_STATE.quick = "all"
elseif type(MTH_BOOKTAB_IsTrainerSpellMode) == "function" and MTH_BOOKTAB_IsTrainerSpellMode(MTH_BOOK_STATE.mode) then
local map = { [1] = "all", [2] = "known", [3] = "notlearned", [4] = "notyet" }
MTH_BOOK_STATE.quick = map[index] or "all"
elseif MTH_BOOK_STATE.mode == "npcs" then
MTH_BOOK_STATE.quick = "all"
elseif MTH_BOOK_STATE.mode == "abilities" then
@@ -4786,10 +5014,94 @@ end
local function MTH_BOOK_StyleSectionTab(button)
if not button then return end
button:SetHeight(24)
button:SetHeight(26)
button:SetNormalTexture(nil)
button:SetPushedTexture(nil)
button:SetHighlightTexture(nil)
button:SetDisabledTexture(nil)
if not button.mthMenuBackground then
button.mthMenuBackground = button:CreateTexture(nil, "BACKGROUND")
button.mthMenuBackground:SetAllPoints(button)
button.mthMenuBackground:SetTexture("Interface\\Buttons\\WHITE8X8")
end
local regions = { button:GetRegions() }
for _, region in ipairs(regions) do
if region ~= button.mthMenuBackground and region.GetObjectType
and region:GetObjectType() == "Texture" then
region:Hide()
end
end
button:SetScript("OnEnter", function()
if not this.mthSelected then
this.mthMenuBackground:SetVertexColor(0.20, 0.17, 0.06, 1.00)
end
end)
button:SetScript("OnLeave", function()
if not this.mthSelected then
this.mthMenuBackground:SetVertexColor(0.10, 0.10, 0.10, 1.00)
end
end)
button:SetScript("OnShow", nil)
end
function MTH_BOOK_EnsureVerticalNavigation(frame)
if not frame then return end
local contentPane = getglobal("MTH_BOOK_ContentPane")
if not contentPane then
contentPane = CreateFrame("Frame", "MTH_BOOK_ContentPane", frame)
contentPane:SetPoint("TOPLEFT", frame, "TOPLEFT", 112, 0)
contentPane:SetWidth(760)
contentPane:SetHeight(560)
local navBackdrop = CreateFrame("Frame", "MTH_BOOK_NavBackdrop", frame)
navBackdrop:SetPoint("TOPLEFT", frame, "TOPLEFT", 16, -42)
navBackdrop:SetWidth(100)
navBackdrop:SetHeight(476)
navBackdrop:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true,
tileSize = 16,
edgeSize = 16,
insets = { left = 3, right = 3, top = 3, bottom = 3 },
})
navBackdrop:SetBackdropColor(0.07, 0.07, 0.07, 1.00)
navBackdrop:SetBackdropBorderColor(0.28, 0.28, 0.28, 1.00)
navBackdrop:SetFrameLevel(1)
local contentChildren = {
"StatsText", "SearchLabel", "MinLabel", "MaxLabel", "BeastZoneLabel",
"ListBackdrop", "DetailBackdrop", "OpenMapButton", "TopAreaBackdrop",
"Search", "MinLevel", "MaxLevel", "ApplyButton", "PetLearnSourceDropdown",
"ItemSubtypeDropdown", "PetOnlyMyLevel", "PetBookScanButton", "ResetButton", "TrainerHonoredDiscount",
"Filter1", "Filter2", "Filter3", "Filter4", "Filter5", "Filter6",
"FamilyDropdown", "AbilityDropdown", "RankDropdown", "PetInZoneOnly",
"ShowAllOnMapButton", "NPCFunctionDropdown", "NPCInZoneOnly", "RequireVendor",
"RequireDrop", "RequireObject", "HideNoAbilities", "HideUnknown",
"BeastContinentDropdown", "BeastZoneDropdown", "ListSliderBackdrop", "ListSlider",
"PrevButton", "NextButton",
}
for _, suffix in ipairs(contentChildren) do
local child = getglobal("MTH_BOOK_" .. suffix)
if child and child:GetParent() == frame then
local point, relativeTo, relativePoint, x, y = child:GetPoint(1)
child:SetParent(contentPane)
if relativeTo == frame then
child:ClearAllPoints()
child:SetPoint(point, contentPane, relativePoint, x, y)
end
end
end
end
local topAreaBackdrop = getglobal("MTH_BOOK_TopAreaBackdrop")
if topAreaBackdrop then
topAreaBackdrop:ClearAllPoints()
topAreaBackdrop:SetPoint("TOPLEFT", contentPane, "TOPLEFT", 0, -44)
topAreaBackdrop:SetWidth(760)
end
end
local function MTH_BOOK_UpdateSectionTabs()
local sectionPets = getglobal("MTH_BOOK_SectionPets")
local sectionFamilies = getglobal("MTH_BOOK_SectionFamilies")
@@ -4800,13 +5112,18 @@ local function MTH_BOOK_UpdateSectionTabs()
local sectionNPCs = getglobal("MTH_BOOK_SectionNPCs")
local sectionStable = getglobal("MTH_BOOK_SectionStable")
local sectionPetHistory = getglobal("MTH_BOOK_SectionPetHistory")
local sectionHunterSpells = getglobal("MTH_BOOK_SectionHunterSpells")
local sectionPetSpells = getglobal("MTH_BOOK_SectionPetSpells")
local function setState(button, selected)
if not button then return end
button.mthSelected = selected and true or false
if selected then
PanelTemplates_SelectTab(button)
button.mthMenuBackground:SetVertexColor(0.38, 0.28, 0.05, 1.00)
button:SetTextColor(1.00, 0.82, 0.00)
else
PanelTemplates_DeselectTab(button)
button.mthMenuBackground:SetVertexColor(0.10, 0.10, 0.10, 1.00)
button:SetTextColor(0.90, 0.90, 0.90)
end
end
@@ -4819,6 +5136,8 @@ local function MTH_BOOK_UpdateSectionTabs()
setState(sectionNPCs, MTH_BOOK_STATE.mode == "npcs")
setState(sectionStable, MTH_BOOK_STATE.mode == "stable")
setState(sectionPetHistory, MTH_BOOK_STATE.mode == "pethistory")
setState(sectionHunterSpells, MTH_BOOK_STATE.mode == "hunterspells")
setState(sectionPetSpells, MTH_BOOK_STATE.mode == "petspells")
end
local function MTH_BOOK_LayoutSectionTabs()
@@ -4832,40 +5151,23 @@ local function MTH_BOOK_LayoutSectionTabs()
local sectionNPCs = getglobal("MTH_BOOK_SectionNPCs")
local sectionStable = getglobal("MTH_BOOK_SectionStable")
local sectionPetHistory = getglobal("MTH_BOOK_SectionPetHistory")
local sectionHunterSpells = getglobal("MTH_BOOK_SectionHunterSpells")
local sectionPetSpells = getglobal("MTH_BOOK_SectionPetSpells")
if not (tabBar and sectionPets and sectionFamilies and sectionPetAbilities and sectionItems and sectionNPCs and sectionProjectiles and sectionAmmoBags) then return end
sectionPets:ClearAllPoints()
sectionPets:SetPoint("TOPLEFT", tabBar, "TOPLEFT", 0, 0)
sectionPetAbilities:ClearAllPoints()
sectionPetAbilities:SetPoint("LEFT", sectionPets, "RIGHT", 2, 0)
sectionFamilies:ClearAllPoints()
sectionFamilies:SetPoint("LEFT", sectionPetAbilities, "RIGHT", 2, 0)
sectionNPCs:ClearAllPoints()
sectionNPCs:SetPoint("LEFT", sectionFamilies, "RIGHT", 2, 0)
sectionItems:ClearAllPoints()
sectionItems:SetPoint("LEFT", sectionNPCs, "RIGHT", 2, 0)
sectionProjectiles:ClearAllPoints()
sectionProjectiles:SetPoint("LEFT", sectionItems, "RIGHT", 2, 0)
sectionAmmoBags:ClearAllPoints()
sectionAmmoBags:SetPoint("LEFT", sectionProjectiles, "RIGHT", 2, 0)
if sectionStable then
sectionStable:ClearAllPoints()
sectionStable:SetPoint("LEFT", sectionAmmoBags, "RIGHT", 2, 0)
end
if sectionPetHistory then
sectionPetHistory:ClearAllPoints()
if sectionStable then
sectionPetHistory:SetPoint("LEFT", sectionStable, "RIGHT", 2, 0)
else
sectionPetHistory:SetPoint("LEFT", sectionAmmoBags, "RIGHT", 2, 0)
local sections = {
sectionPets, sectionPetAbilities, sectionFamilies, sectionNPCs, sectionItems,
sectionProjectiles, sectionAmmoBags, sectionStable, sectionPetHistory,
sectionHunterSpells, sectionPetSpells,
}
local y = 0
for _, section in ipairs(sections) do
if section then
section:ClearAllPoints()
section:SetPoint("TOPLEFT", tabBar, "TOPLEFT", 0, y)
section:SetWidth(96)
section:SetHeight(26)
y = y - 28
end
end
end
@@ -5304,6 +5606,7 @@ local function MTH_BOOK_WireUI(frame)
frame:SetBackdropColor(0.05, 0.05, 0.05, 1.00)
frame:SetBackdropBorderColor(0.35, 0.35, 0.35, 1.00)
end
MTH_BOOK_EnsureVerticalNavigation(frame)
local closeButton = getglobal("MTH_BOOK_CloseButton")
local applyButton = getglobal("MTH_BOOK_ApplyButton")
@@ -5317,6 +5620,9 @@ local function MTH_BOOK_WireUI(frame)
local sectionNPCs = getglobal("MTH_BOOK_SectionNPCs")
local sectionStable = getglobal("MTH_BOOK_SectionStable")
local sectionPetHistory = getglobal("MTH_BOOK_SectionPetHistory")
local sectionHunterSpells = getglobal("MTH_BOOK_SectionHunterSpells")
local sectionPetSpells = getglobal("MTH_BOOK_SectionPetSpells")
local trainerHonoredDiscount = getglobal("MTH_BOOK_TrainerHonoredDiscount")
local requireVendor = getglobal("MTH_BOOK_RequireVendor")
local requireDrop = getglobal("MTH_BOOK_RequireDrop")
local requireObject = getglobal("MTH_BOOK_RequireObject")
@@ -5358,6 +5664,14 @@ local function MTH_BOOK_WireUI(frame)
if sectionNPCs then sectionNPCs:SetScript("OnClick", function() MTH_BOOK_SetMode("npcs") end) end
if sectionStable then sectionStable:SetScript("OnClick", function() MTH_BOOK_SetMode("stable") end) end
if sectionPetHistory then sectionPetHistory:SetScript("OnClick", function() MTH_BOOK_SetMode("pethistory") end) end
if sectionHunterSpells then sectionHunterSpells:SetScript("OnClick", function() MTH_BOOK_SetMode("hunterspells") end) end
if sectionPetSpells then sectionPetSpells:SetScript("OnClick", function() MTH_BOOK_SetMode("petspells") end) end
if trainerHonoredDiscount then
trainerHonoredDiscount:SetScript("OnClick", function()
MTH_BOOK_STATE.trainerHonoredDiscount = this:GetChecked() == 1
MTH_BOOK_UpdateResults()
end)
end
MTH_BOOK_StyleSectionTab(sectionPets)
MTH_BOOK_StyleSectionTab(sectionFamilies)
@@ -5368,7 +5682,10 @@ local function MTH_BOOK_WireUI(frame)
MTH_BOOK_StyleSectionTab(sectionNPCs)
MTH_BOOK_StyleSectionTab(sectionStable)
MTH_BOOK_StyleSectionTab(sectionPetHistory)
MTH_BOOK_StyleSectionTab(sectionHunterSpells)
MTH_BOOK_StyleSectionTab(sectionPetSpells)
MTH_BOOK_LayoutSectionTabs()
MTH_BOOK_ApplyContentLayoutForMode()
for i = 1, 6 do
local filterBtn = getglobal("MTH_BOOK_Filter" .. i)
@@ -5384,6 +5701,7 @@ local function MTH_BOOK_WireUI(frame)
MTH_BOOK_InitPetLearnSourceDropdown()
MTH_BOOK_InitItemSubtypeDropdown()
MTH_BOOK_InitNPCFunctionDropdown()
MTH_BOOK_EnsureTrainerStatusDropdown()
if petBookScanButton then
petBookScanButton:SetScript("OnClick", function()
+20 -2
View File
@@ -2,7 +2,7 @@
..\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"/>
<AbsDimension x="880" y="560"/>
</Size>
<Anchors>
<Anchor point="CENTER"/>
@@ -128,7 +128,7 @@
</Frame>
<Frame name="$parentTabBar">
<Size><AbsDimension x="728" y="24"/></Size>
<Size><AbsDimension x="96" y="472"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="18" y="-44"/></Offset></Anchor>
</Anchors>
@@ -188,6 +188,18 @@
<Anchor point="LEFT" relativeTo="$parentSectionAmmoBags" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
</Anchors>
</Button>
<Button name="$parentSectionHunterSpells" inherits="TabButtonTemplate" text="Hunter Spells">
<Size><AbsDimension x="82" y="24"/></Size>
<Anchors>
<Anchor point="LEFT" relativeTo="$parentSectionPetHistory" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
</Anchors>
</Button>
<Button name="$parentSectionPetSpells" inherits="TabButtonTemplate" text="Pet Spells">
<Size><AbsDimension x="82" y="24"/></Size>
<Anchors>
<Anchor point="LEFT" relativeTo="$parentSectionHunterSpells" relativePoint="RIGHT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
</Anchors>
</Button>
<EditBox name="$parentSearch" inherits="InputBoxTemplate">
<Size><AbsDimension x="180" y="20"/></Size>
@@ -235,6 +247,12 @@
</Anchors>
</CheckButton>
<CheckButton name="$parentTrainerHonoredDiscount" inherits="UICheckButtonTemplate" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="618" 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>
+46 -3
View File
@@ -1126,7 +1126,7 @@ function MTH_SetupMessagesOptions()
if not section then return end
section:SetPoint("TOPLEFT", container, "TOPLEFT", 8, -8)
section:SetPoint("TOPRIGHT", container, "TOPRIGHT", -8, -8)
section:SetHeight(268)
section:SetHeight(380)
local titleFrame = getglobal("MetaHuntMessagesSectionTitle")
if titleFrame then
@@ -1197,7 +1197,18 @@ function MTH_SetupMessagesOptions()
end)
end
local petRanAwayToggle = MTH_CreateCheckbox(section, "MetaHuntMessagesPetRanAway", "Pet ran away", -176)
local trainerScanToggle = MTH_CreateCheckbox(section, "MetaHuntMessagesTrainerScan", "Trainer scan", -176)
if trainerScanToggle then
trainerScanToggle:SetChecked(msgSettings.trainerScan ~= false)
trainerScanToggle:SetScript("OnClick", function()
if not this then return end
if MTH and MTH.SetMessageEnabled then
MTH:SetMessageEnabled("trainerScan", MTH_ZB_IsChecked(this))
end
end)
end
local petRanAwayToggle = MTH_CreateCheckbox(section, "MetaHuntMessagesPetRanAway", "Pet ran away", -204)
if petRanAwayToggle then
petRanAwayToggle:SetChecked(msgSettings.petRanAway ~= false)
petRanAwayToggle:SetScript("OnClick", function()
@@ -1208,7 +1219,7 @@ function MTH_SetupMessagesOptions()
end)
end
local mapMarkersToggle = MTH_CreateCheckbox(section, "MetaHuntMessagesMapMarkers", "Map markers", -204)
local mapMarkersToggle = MTH_CreateCheckbox(section, "MetaHuntMessagesMapMarkers", "Map markers", -232)
if mapMarkersToggle then
mapMarkersToggle:SetChecked(msgSettings.mapMarkers ~= false)
mapMarkersToggle:SetScript("OnClick", function()
@@ -1219,6 +1230,38 @@ function MTH_SetupMessagesOptions()
end)
end
local hunterLevelToggle = MTH_CreateCheckbox(section, "MetaHuntMessagesHunterLevel", "Hunter level training summary", -260)
if hunterLevelToggle then
hunterLevelToggle:SetChecked(msgSettings.hunterLevelUp ~= false)
hunterLevelToggle:SetScript("OnClick", function()
if MTH and MTH.SetMessageEnabled then MTH:SetMessageEnabled("hunterLevelUp", MTH_ZB_IsChecked(this)) end
end)
end
local hunterQuietLevelToggle = MTH_CreateCheckbox(section, "MetaHuntMessagesHunterQuietLevel", "Hunter quiet-level message", -288)
if hunterQuietLevelToggle then
hunterQuietLevelToggle:SetChecked(msgSettings.hunterLevelQuiet and true or false)
hunterQuietLevelToggle:SetScript("OnClick", function()
if MTH and MTH.SetMessageEnabled then MTH:SetMessageEnabled("hunterLevelQuiet", MTH_ZB_IsChecked(this)) end
end)
end
local petLevelToggle = MTH_CreateCheckbox(section, "MetaHuntMessagesPetLevel", "Pet level training summary", -316)
if petLevelToggle then
petLevelToggle:SetChecked(msgSettings.petLevelUp ~= false)
petLevelToggle:SetScript("OnClick", function()
if MTH and MTH.SetMessageEnabled then MTH:SetMessageEnabled("petLevelUp", MTH_ZB_IsChecked(this)) end
end)
end
local petLoyaltyToggle = MTH_CreateCheckbox(section, "MetaHuntMessagesPetLoyalty", "Pet loyalty gain", -344)
if petLoyaltyToggle then
petLoyaltyToggle:SetChecked(msgSettings.petLoyaltyUp ~= false)
petLoyaltyToggle:SetScript("OnClick", function()
if MTH and MTH.SetMessageEnabled then MTH:SetMessageEnabled("petLoyaltyUp", MTH_ZB_IsChecked(this)) end
end)
end
end
function MTH_SetupGeneralOptions()
+159
View File
@@ -0,0 +1,159 @@
-- MetaHunt shared trainer spell status and cost planner.
-- UI, chat notifications, and future level-up messages consume these functions.
local function MTH_TP_Lower(value)
return string.lower(tostring(value or ""))
end
local function MTH_TP_RankNumber(rank)
local _, _, value = string.find(tostring(rank or ""), "(%d+)")
return tonumber(value) or 0
end
function MTH_TrainerPlan_GetPrice(row, honored)
local base = tonumber(row and row.trainerCost) or 0
if honored then return math.floor(base * 0.90) end
return base
end
local function MTH_TP_IsKnownHunter(row)
if type(MTH_HT_GetSpellInfo) ~= "function" then return false end
local known = MTH_HT_GetSpellInfo(row and row.name)
if type(known) ~= "table" then return false end
local requiredRank = MTH_TP_RankNumber(row and row.rank)
return requiredRank == 0 or (tonumber(known.rankNum) or 0) >= requiredRank
end
local function MTH_TP_IsKnownPet(row)
if type(MTH_PETS_GetRootStore) ~= "function" then return false end
local pets = MTH_PETS_GetRootStore()
local store = pets and pets.petStore
local petId = store and store.activeCurrentId
local pet = store and store.activeById and petId and store.activeById[tostring(petId)]
local spells = pet and pet.petSpellbook and pet.petSpellbook.spells
if type(spells) ~= "table" then return false end
local requiredName = MTH_TP_Lower(row and row.name)
local requiredRank = MTH_TP_RankNumber(row and row.rank)
for _, spell in ipairs(spells) do
if MTH_TP_Lower(spell and spell.name) == requiredName then
local knownRank = tonumber(spell.rank) or 0
if requiredRank == 0 or knownRank >= requiredRank then return true end
end
end
return false
end
function MTH_TrainerPlan_IsKnown(kind, row)
if kind == "hunter" then return MTH_TP_IsKnownHunter(row) end
return MTH_TP_IsKnownPet(row)
end
local function MTH_TP_HasMissingPrerequisite(kind, row)
if type(row and row.prerequisites) ~= "table" then return false end
for _, prerequisite in ipairs(row.prerequisites) do
local _, _, name, rank = string.find(tostring(prerequisite), "^(.+)%s%([Rr]ank%s*(%d+)%)$")
if name and rank and not MTH_TrainerPlan_IsKnown(kind, { name = name, rank = "Rank " .. rank }) then
return true
end
end
return false
end
function MTH_TrainerPlan_GetStatus(kind, row, playerLevel)
if MTH_TrainerPlan_IsKnown(kind, row) then return "known" end
local observed = type(MTH_GetTrainerLiveStatus) == "function"
and MTH_GetTrainerLiveStatus(kind, row and row.name, row and row.rank) or nil
if observed and observed.status == "used" then return "known" end
if observed and observed.status == "available" then return "notlearned" end
if observed and observed.status == "unavailable" then return "notyet" end
local requiredLevel = tonumber(row and row.requiredLevel) or 0
if not playerLevel or playerLevel < requiredLevel or MTH_TP_HasMissingPrerequisite(kind, row) then
return "notyet"
end
return "notlearned"
end
function MTH_TrainerPlan_Build(kind, options)
options = options or {}
local catalog = MTH_DS_TrainerCatalog
local rows = catalog and catalog[kind] or nil
local playerLevel = tonumber(options.playerLevel)
if not playerLevel and type(UnitLevel) == "function" then playerLevel = tonumber(UnitLevel("player")) end
local honored = options.honored and true or false
local plan = {
kind = kind,
playerLevel = playerLevel,
honored = honored,
rows = {},
now = { count = 0, cost = 0 },
nextLevel = { level = nil, count = 0, cost = 0 },
remaining = { count = 0, cost = 0 },
}
if type(rows) ~= "table" then return plan end
for _, row in ipairs(rows) do
local status = MTH_TrainerPlan_GetStatus(kind, row, playerLevel)
local price = MTH_TrainerPlan_GetPrice(row, honored)
local entry = { row = row, status = status, price = price }
table.insert(plan.rows, entry)
if status ~= "known" then
plan.remaining.count = plan.remaining.count + 1
plan.remaining.cost = plan.remaining.cost + price
if status == "notlearned" then
plan.now.count = plan.now.count + 1
plan.now.cost = plan.now.cost + price
end
local requiredLevel = tonumber(row.requiredLevel) or 0
if playerLevel and requiredLevel > playerLevel then
if not plan.nextLevel.level or requiredLevel < plan.nextLevel.level then
plan.nextLevel.level = requiredLevel
plan.nextLevel.count = 0
plan.nextLevel.cost = 0
end
if requiredLevel == plan.nextLevel.level then
plan.nextLevel.count = plan.nextLevel.count + 1
plan.nextLevel.cost = plan.nextLevel.cost + price
end
end
end
end
return plan
end
function MTH_TrainerPlan_BuildLevel(kind, targetLevel, options)
options = options or {}
local level = tonumber(targetLevel)
local catalog = MTH_DS_TrainerCatalog
local rows = catalog and catalog[kind] or nil
local honored = options.honored and true or false
local plan = {
kind = kind,
level = level,
honored = honored,
rows = {},
available = { count = 0, cost = 0 },
unlearned = { count = 0, cost = 0 },
}
if not level or type(rows) ~= "table" then return plan end
for _, row in ipairs(rows) do
if tonumber(row.requiredLevel) == level and (options.includeKnown or not MTH_TrainerPlan_IsKnown(kind, row)) then
local status = MTH_TrainerPlan_GetStatus(kind, row, level)
local price = MTH_TrainerPlan_GetPrice(row, honored)
local entry = { row = row, status = status, price = price }
table.insert(plan.rows, entry)
plan.unlearned.count = plan.unlearned.count + 1
plan.unlearned.cost = plan.unlearned.cost + price
if status == "notlearned" then
plan.available.count = plan.available.count + 1
plan.available.cost = plan.available.cost + price
end
end
end
return plan
end
function MTH_TrainerPlan_MatchesQuick(status, quick)
if quick == "known" then return status == "known" end
if quick == "notlearned" then return status == "notlearned" end
if quick == "notyet" then return status == "notyet" end
return true
end
File diff suppressed because it is too large Load Diff
+4
View File
@@ -37,6 +37,7 @@
<Include file="..\api\hunterbook-tab-npcfinder.lua"/>
<Include file="..\api\hunterbook-tab-stable.lua"/>
<Include file="..\api\hunterbook-tab-pethistory.lua"/>
<Include file="..\api\hunterbook-tab-trainers.lua"/>
<Include file="..\api\hunterbook-model-viewer.lua"/>
<Include file="..\api\map-markers.lua"/>
<Include file="..\api\debug-frame.lua"/>
@@ -48,8 +49,11 @@
<Include file="..\api\pets-service-stablescan.lua"/>
<Include file="..\api\pet-growl-teach.lua"/>
<Include file="..\api\core-scan-beasttraining.lua"/>
<Include file="..\api\core-scan-trainer.lua"/>
<Include file="..\api\core-scan-spellbookpet.lua"/>
<Include file="..\api\core-scan-huntertalents.lua"/>
<Include file="..\api\trainer-spell-planner.lua"/>
<Include file="..\api\core-level-tracking.lua"/>
<Include file="..\api\core.lua"/>
<Include file="..\api\version-check.lua"/>
<Include file="..\api\localization.lua"/>