Files
MetaHunt/api/hunterbook-tab-families.lua
DuvelCorp 51d4c785fa 1.5.0
2026-03-31 21:25:35 +02:00

589 lines
20 KiB
Lua

if type(MTH_HUNTERBOOK_TABS) ~= "table" then MTH_HUNTERBOOK_TABS = {} end
MTH_HUNTERBOOK_TABS.families = {
headerLabel = "Families",
columnLabels = { "Family", "Named", "Coords", "Abilities", "Diet", "HP", "dmg", "Arm" },
columnLayout = {
{ x = 10, width = 100, align = "LEFT" },
{ x = 114, width = 38, align = "LEFT" },
{ x = 154, width = 38, align = "LEFT" },
{ x = 196, width = 364, align = "LEFT" },
{ x = 496, width = 102, align = "LEFT" },
{ x = 602, width = 30, align = "CENTER" },
{ x = 636, width = 36, align = "CENTER" },
{ x = 674, width = 38, align = "CENTER" },
},
}
local function MTH_BOOKTAB_FamiliesTrace(message)
end
local function MTH_BOOKTAB_FamiliesSafeLower(value)
return string.lower(tostring(value or ""))
end
local function MTH_BOOKTAB_FamiliesTrim(value)
local text = tostring(value or "")
text = string.gsub(text, "^%s+", "")
text = string.gsub(text, "%s+$", "")
return text
end
local function MTH_BOOKTAB_FormatFamilyStat(value)
local numeric = tonumber(value)
if not numeric then
return "-"
end
local colorPrefix = "|cFF4DA6FF"
if numeric < 1 then
colorPrefix = "|cFFFF4040"
elseif numeric > 1 then
colorPrefix = "|cFF40FF40"
end
local text = nil
if MTH_BOOK_STATE and MTH_BOOK_STATE.familyStatsPercent == true then
text = tostring(math.floor((numeric * 100) + 0.5)) .. "%"
return colorPrefix .. text .. "|r"
end
text = string.format("%.3f", numeric)
text = string.gsub(text, "(%..-)0+$", "%1")
text = string.gsub(text, "%.$", "")
return colorPrefix .. text .. "|r"
end
local function MTH_BOOKTAB_ShouldDisplayAllDiet(familyName)
local token = MTH_BOOKTAB_FamiliesSafeLower(MTH_BOOKTAB_FamiliesTrim(familyName))
return token == "bears" or token == "boars"
end
local function MTH_BOOKTAB_FindAbilityBundle(abilityName)
if not (MTH_DS_PetSpells and type(MTH_DS_PetSpells.byAbility) == "table") then
return nil
end
local direct = MTH_DS_PetSpells.byAbility[abilityName]
if type(direct) == "table" then
return direct
end
local wanted = MTH_BOOKTAB_FamiliesSafeLower(abilityName)
for key, bundle in pairs(MTH_DS_PetSpells.byAbility) do
if MTH_BOOKTAB_FamiliesSafeLower(key) == wanted then
return bundle
end
end
return nil
end
function MTH_BOOKTAB_BuildFamiliesRows()
local results = {}
local families = MTH_DS_Families
local beasts = MTH_DS_Beasts
if type(families) ~= "table" then
return results
end
local abilityCounts = {}
local canonicalMap = {}
local familyCounts = {}
if type(beasts) == "table" then
for _, beastRow in pairs(beasts) do
if type(beastRow) == "table" then
local familyName = MTH_BOOKTAB_FamiliesTrim(beastRow.family)
if familyName ~= "" then
local counts = familyCounts[familyName]
if not counts then
counts = { named = 0, coords = 0 }
familyCounts[familyName] = counts
end
counts.named = counts.named + 1
if type(beastRow.coords) == "table" then
for coordIndex = 1, table.getn(beastRow.coords) do
local coord = beastRow.coords[coordIndex]
if type(coord) == "table" then
if coord[1] ~= nil and coord[2] ~= nil then
counts.coords = counts.coords + 1
end
end
end
end
end
end
end
end
for _, familyRow in pairs(families) do
if type(familyRow) == "table" and type(familyRow.abilities) == "table" then
local seenInFamily = {}
for i = 1, table.getn(familyRow.abilities) do
local rawName = MTH_BOOKTAB_FamiliesTrim(familyRow.abilities[i])
if rawName ~= "" then
local bundle = MTH_BOOKTAB_FindAbilityBundle(rawName)
local canonical = rawName
if type(bundle) == "table" and type(bundle.ability) == "string" and bundle.ability ~= "" then
canonical = bundle.ability
end
local token = MTH_BOOKTAB_FamiliesSafeLower(canonical)
if token ~= "" and not seenInFamily[token] then
seenInFamily[token] = true
abilityCounts[token] = (abilityCounts[token] or 0) + 1
canonicalMap[token] = canonical
end
end
end
end
end
for familyName, familyRow in pairs(families) do
if type(familyRow) == "table" then
local beastCounts = familyCounts[familyName] or nil
MTH_BOOKTAB_FamiliesTrace("BUILD " .. tostring(familyName) .. " icon=" .. tostring(familyRow.icon))
local abilities = {}
local seen = {}
if type(familyRow.abilities) == "table" then
for i = 1, table.getn(familyRow.abilities) do
local rawName = MTH_BOOKTAB_FamiliesTrim(familyRow.abilities[i])
if rawName ~= "" then
local bundle = MTH_BOOKTAB_FindAbilityBundle(rawName)
local canonical = rawName
if type(bundle) == "table" and type(bundle.ability) == "string" and bundle.ability ~= "" then
canonical = bundle.ability
end
local token = MTH_BOOKTAB_FamiliesSafeLower(canonical)
if token ~= "" and not seen[token] then
seen[token] = true
local spellRows = (type(bundle) == "table" and type(bundle.spells) == "table") and bundle.spells or {}
local firstAnySpell = nil
local firstBeastSpell = nil
local beastRankMin = nil
local beastRankMax = nil
local beastRankMap = {}
local anyRankMin = nil
local anyRankMax = nil
local anyRankMap = {}
for s = 1, table.getn(spellRows) do
local spell = spellRows[s]
if type(spell) == "table" then
if not firstAnySpell then
firstAnySpell = spell
end
local rank = tonumber(spell.rankNumber)
if rank and rank > 0 then
anyRankMap[rank] = true
if not anyRankMin or rank < anyRankMin then anyRankMin = rank end
if not anyRankMax or rank > anyRankMax then anyRankMax = rank end
end
if MTH_BOOKTAB_FamiliesSafeLower(spell.learnMethod or "beast") ~= "trainer" then
if not firstBeastSpell then
firstBeastSpell = spell
end
if rank and rank > 0 then
beastRankMap[rank] = true
if not beastRankMin or rank < beastRankMin then beastRankMin = rank end
if not beastRankMax or rank > beastRankMax then beastRankMax = rank end
end
end
end
end
local displaySpell = firstBeastSpell or firstAnySpell
local rankMap = beastRankMap
local rankMin = beastRankMin
local rankMax = beastRankMax
if not firstBeastSpell then
rankMap = anyRankMap
rankMin = anyRankMin
rankMax = anyRankMax
end
local rankCount = 0
for _ in pairs(rankMap) do
rankCount = rankCount + 1
end
table.insert(abilities, {
name = canonicalMap[token] or canonical,
token = token,
isUnique = (abilityCounts[token] or 0) == 1,
icon = displaySpell and displaySpell.icon or nil,
description = displaySpell and displaySpell.description or "",
rankMin = rankMin,
rankMax = rankMax,
rankCount = rankCount,
})
end
end
end
end
table.sort(abilities, function(a, b)
if a.isUnique ~= b.isUnique then
return a.isUnique and true or false
end
return MTH_BOOKTAB_FamiliesSafeLower(a.name) < MTH_BOOKTAB_FamiliesSafeLower(b.name)
end)
local dietText = ""
local stats = type(familyRow.stats) == "table" and familyRow.stats or nil
local healthStat = stats and tonumber(stats.health) or nil
local damageStat = stats and tonumber(stats.damage) or nil
local armorStat = stats and tonumber(stats.armor) or nil
if MTH_BOOKTAB_ShouldDisplayAllDiet(familyName) then
dietText = "ALL"
else
local dietItems = {}
if type(familyRow.food) == "table" then
for i = 1, table.getn(familyRow.food) do
local food = MTH_BOOKTAB_FamiliesTrim(familyRow.food[i])
local token = MTH_BOOKTAB_FamiliesSafeLower(food)
if food ~= "" and token ~= "raw fish" and token ~= "raw meat" then
table.insert(dietItems, string.upper(string.sub(food, 1, 1)) .. string.sub(food, 2))
end
end
table.sort(dietItems, function(a, b)
return MTH_BOOKTAB_FamiliesSafeLower(a) < MTH_BOOKTAB_FamiliesSafeLower(b)
end)
end
dietText = table.concat(dietItems, "|")
end
table.insert(results, {
family = tostring(familyName),
icon = familyRow.icon or nil,
named = beastCounts and (tonumber(beastCounts.named) or 0) or 0,
coords = beastCounts and (tonumber(beastCounts.coords) or 0) or 0,
abilities = abilities,
dietText = dietText,
healthStat = healthStat,
healthText = MTH_BOOKTAB_FormatFamilyStat(healthStat),
damageStat = damageStat,
damageText = MTH_BOOKTAB_FormatFamilyStat(damageStat),
armorStat = armorStat,
armorText = MTH_BOOKTAB_FormatFamilyStat(armorStat),
})
end
end
table.sort(results, function(a, b)
return MTH_BOOKTAB_FamiliesSafeLower(a.family) < MTH_BOOKTAB_FamiliesSafeLower(b.family)
end)
return results
end
function MTH_BOOKTAB_ShowFamilyAbilityTooltip(anchor, abilityEntry)
if not anchor or type(abilityEntry) ~= "table" then return end
local tooltip = MTH_BOOK_GetSpellTooltip and MTH_BOOK_GetSpellTooltip() or GameTooltip
if not tooltip then return end
tooltip:SetOwner(anchor, "ANCHOR_RIGHT")
tooltip:ClearLines()
if MTH_BOOK_SetSpellTooltipIcon then
MTH_BOOK_SetSpellTooltipIcon(tooltip, abilityEntry.icon)
end
tooltip:AddLine(tostring(abilityEntry.name or "Unknown"), 1.00, 0.82, 0.00)
tooltip:AddLine(" ")
tooltip:AddLine("Baseline", 1.00, 0.82, 0.00)
if abilityEntry.rankCount and abilityEntry.rankCount > 0 then
if abilityEntry.rankMin and abilityEntry.rankMax then
if abilityEntry.rankMin == abilityEntry.rankMax then
tooltip:AddDoubleLine("Ranks", tostring(abilityEntry.rankMin), 0.85, 0.85, 0.85, 1, 1, 1)
else
tooltip:AddDoubleLine("Ranks", tostring(abilityEntry.rankMin) .. "-" .. tostring(abilityEntry.rankMax), 0.85, 0.85, 0.85, 1, 1, 1)
end
end
else
tooltip:AddDoubleLine("Ranks", "Unranked", 0.85, 0.85, 0.85, 1, 1, 1)
end
tooltip:AddDoubleLine("Family-Unique", abilityEntry.isUnique and "Yes" or "No", 0.85, 0.85, 0.85, 1, 1, 1)
if abilityEntry.description and abilityEntry.description ~= "" then
tooltip:AddLine(" ")
tooltip:AddLine(tostring(abilityEntry.description), 0.90, 0.90, 0.90, true)
end
tooltip:Show()
end
function MTH_BOOKTAB_EnsureFamiliesUI()
if MTH_BOOK_STATE.familiesUI then return end
local listParent = getglobal("MTH_BOOK_ListBackdrop")
if not listParent then
MTH_BOOKTAB_FamiliesTrace("EnsureFamiliesUI failed: missing MTH_BOOK_ListBackdrop")
return
end
local ui = {}
ui.frame = CreateFrame("Frame", nil, listParent)
ui.frame:SetPoint("TOPLEFT", listParent, "TOPLEFT", 6, -6)
ui.frame:SetPoint("BOTTOMRIGHT", listParent, "BOTTOMRIGHT", -6, 6)
ui.frame:Hide()
local statsText = getglobal("MTH_BOOK_StatsText")
local statsParent = statsText and statsText:GetParent() or nil
if statsParent then
ui.percentCheckbox = CreateFrame("CheckButton", "MTH_BOOK_FamiliesPercentCheckbox", statsParent, "UICheckButtonTemplate")
ui.percentCheckbox:SetFrameStrata(statsParent:GetFrameStrata())
ui.percentCheckbox:SetFrameLevel((statsParent:GetFrameLevel() or 0) + 20)
ui.percentCheckbox:ClearAllPoints()
if statsText then
ui.percentCheckbox:SetPoint("LEFT", statsText, "RIGHT", 260, 0)
else
ui.percentCheckbox:SetPoint("TOPLEFT", statsParent, "TOPLEFT", 538, -526)
end
ui.percentCheckbox.label = ui.percentCheckbox:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.percentCheckbox.label:SetPoint("LEFT", ui.percentCheckbox, "RIGHT", 4, 0)
ui.percentCheckbox.label:SetTextColor(1.00, 0.82, 0.00)
ui.percentCheckbox.label:SetText("Show modifiers in %")
ui.percentCheckbox:SetScript("OnClick", function()
if not this then return end
MTH_BOOK_STATE.familyStatsPercent = this:GetChecked() == 1
MTH_BOOKTAB_RenderFamiliesList()
end)
ui.percentCheckbox:Hide()
end
ui.headerFamily = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerFamily:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 4, -2)
ui.headerFamily:SetTextColor(1.00, 0.82, 0.00)
ui.headerFamily:SetText("Family")
ui.headerNamed = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerNamed:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 114, -2)
ui.headerNamed:SetTextColor(1.00, 0.82, 0.00)
ui.headerNamed:SetText("Named")
ui.headerCoords = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerCoords:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 154, -2)
ui.headerCoords:SetTextColor(1.00, 0.82, 0.00)
ui.headerCoords:SetText("Coords")
ui.headerAbilities = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerAbilities:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 196, -2)
ui.headerAbilities:SetTextColor(1.00, 0.82, 0.00)
ui.headerAbilities:SetText("Abilities")
ui.headerDiet = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerDiet:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 496, -2)
ui.headerDiet:SetTextColor(1.00, 0.82, 0.00)
ui.headerDiet:SetText("Diet")
ui.headerHP = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerHP:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 602, -2)
ui.headerHP:SetWidth(30)
ui.headerHP:SetJustifyH("CENTER")
ui.headerHP:SetTextColor(1.00, 0.82, 0.00)
ui.headerHP:SetText("HP")
ui.headerDmg = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerDmg:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 636, -2)
ui.headerDmg:SetWidth(36)
ui.headerDmg:SetJustifyH("CENTER")
ui.headerDmg:SetTextColor(1.00, 0.82, 0.00)
ui.headerDmg:SetText("dmg")
ui.headerArm = ui.frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
ui.headerArm:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 674, -2)
ui.headerArm:SetWidth(38)
ui.headerArm:SetJustifyH("CENTER")
ui.headerArm:SetTextColor(1.00, 0.82, 0.00)
ui.headerArm:SetText("Arm")
ui.rows = {}
for i = 1, 28 do
local row = CreateFrame("Frame", nil, ui.frame)
row:SetHeight(18)
if i == 1 then
row:SetPoint("TOPLEFT", ui.frame, "TOPLEFT", 0, -18)
row:SetPoint("TOPRIGHT", ui.frame, "TOPRIGHT", 0, -18)
else
row:SetPoint("TOPLEFT", ui.rows[i - 1], "BOTTOMLEFT", 0, -1)
row:SetPoint("TOPRIGHT", ui.rows[i - 1], "BOTTOMRIGHT", 0, -1)
end
row.familyIcon = row:CreateTexture(nil, "ARTWORK")
row.familyIcon:SetPoint("LEFT", row, "LEFT", 4, 0)
row.familyIcon:SetWidth(16)
row.familyIcon:SetHeight(16)
row.familyIcon:Hide()
row.family = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.family:SetPoint("LEFT", row, "LEFT", 24, 0)
row.family:SetWidth(82)
row.family:SetJustifyH("LEFT")
row.named = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.named:SetPoint("LEFT", row, "LEFT", 114, 0)
row.named:SetWidth(38)
row.named:SetJustifyH("LEFT")
row.coords = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.coords:SetPoint("LEFT", row, "LEFT", 154, 0)
row.coords:SetWidth(38)
row.coords:SetJustifyH("LEFT")
row.diet = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.diet:SetPoint("LEFT", row, "LEFT", 496, 0)
row.diet:SetWidth(102)
row.diet:SetJustifyH("LEFT")
row.hp = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.hp:SetPoint("LEFT", row, "LEFT", 602, 0)
row.hp:SetWidth(30)
row.hp:SetJustifyH("CENTER")
row.dmg = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.dmg:SetPoint("LEFT", row, "LEFT", 636, 0)
row.dmg:SetWidth(36)
row.dmg:SetJustifyH("CENTER")
row.arm = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
row.arm:SetPoint("LEFT", row, "LEFT", 674, 0)
row.arm:SetWidth(38)
row.arm:SetJustifyH("CENTER")
row.abilityButtons = {}
for b = 1, 16 do
local button = CreateFrame("Button", nil, row)
button:SetHeight(14)
button.icon = button:CreateTexture(nil, "ARTWORK")
button.icon:SetPoint("LEFT", button, "LEFT", 0, 0)
button.icon:SetWidth(12)
button.icon:SetHeight(12)
button.text = button:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
button.text:SetPoint("LEFT", button.icon, "RIGHT", 2, 0)
button.text:SetJustifyH("LEFT")
button.text:SetTextColor(0.90, 0.90, 0.90)
button.entry = nil
button:SetScript("OnEnter", function()
if this and this.entry then
MTH_BOOKTAB_ShowFamilyAbilityTooltip(this, this.entry)
end
end)
button:SetScript("OnLeave", function()
if type(MTH_BOOK_HideSpellTooltip) == "function" then
MTH_BOOK_HideSpellTooltip()
end
end)
button:Hide()
row.abilityButtons[b] = button
end
row.divider = row:CreateTexture(nil, "BORDER")
row.divider:SetTexture("Interface\\Buttons\\WHITE8X8")
row.divider:SetVertexColor(0.28, 0.28, 0.28, 0.45)
row.divider:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT", 0, 0)
row.divider:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT", 0, 0)
row.divider:SetHeight(1)
ui.rows[i] = row
end
MTH_BOOK_STATE.familiesUI = ui
MTH_BOOKTAB_FamiliesTrace("EnsureFamiliesUI created rows=" .. tostring(table.getn(ui.rows or {})))
end
function MTH_BOOKTAB_SetFamiliesUIVisible(visible)
local ui = MTH_BOOK_STATE.familiesUI
if visible then
if not ui then
MTH_BOOKTAB_EnsureFamiliesUI()
ui = MTH_BOOK_STATE.familiesUI
end
if not ui then return end
else
if not ui then return end
end
local listParent = getglobal("MTH_BOOK_ListBackdrop")
local detailParent = getglobal("MTH_BOOK_DetailBackdrop")
local detailText = getglobal("MTH_BOOK_DetailBackdropDetailText")
local openMapButton = getglobal("MTH_BOOK_OpenMapButton")
if visible then
if listParent then listParent:Show() end
if detailParent then detailParent:Hide() end
if detailText then detailText:Hide() end
if openMapButton then openMapButton:Hide() end
if ui.percentCheckbox then
ui.percentCheckbox:SetChecked(MTH_BOOK_STATE and MTH_BOOK_STATE.familyStatsPercent == true and 1 or nil)
ui.percentCheckbox:Show()
end
ui.frame:Show()
else
ui.frame:Hide()
if ui.percentCheckbox then ui.percentCheckbox:Hide() end
if listParent then listParent:Show() end
if detailParent then detailParent:Show() end
if detailText then detailText:Show() end
end
end
function MTH_BOOKTAB_RenderFamiliesList()
local ui = MTH_BOOK_STATE.familiesUI
if not ui then return end
local rows = MTH_BOOK_STATE.results or {}
MTH_BOOKTAB_FamiliesTrace("RenderFamiliesList rows=" .. tostring(table.getn(rows)))
for i = 1, table.getn(ui.rows) do
local rowFrame = ui.rows[i]
local row = rows[i]
if row then
rowFrame:Show()
rowFrame.family:SetText(tostring(row.family or "-"))
local familyIconPath = row.icon and MTH_BOOK_ResolveIconPath and MTH_BOOK_ResolveIconPath(row.icon) or nil
MTH_BOOKTAB_FamiliesTrace("RENDER " .. tostring(row.family) .. " icon=" .. tostring(row.icon) .. " path=" .. tostring(familyIconPath))
if familyIconPath then
rowFrame.familyIcon:SetTexture(familyIconPath)
rowFrame.familyIcon:Show()
else
rowFrame.familyIcon:Hide()
end
rowFrame.named:SetText(tostring(row.named or 0))
rowFrame.coords:SetText(tostring(row.coords or 0))
rowFrame.diet:SetText(tostring(row.dietText or "-"))
rowFrame.hp:SetText(MTH_BOOKTAB_FormatFamilyStat(row.healthStat))
rowFrame.dmg:SetText(MTH_BOOKTAB_FormatFamilyStat(row.damageStat))
rowFrame.arm:SetText(MTH_BOOKTAB_FormatFamilyStat(row.armorStat))
local offsetX = 196
local abilityMaxRight = 490
for b = 1, table.getn(rowFrame.abilityButtons) do
local button = rowFrame.abilityButtons[b]
local ability = row.abilities and row.abilities[b] or nil
if ability then
local nameText = tostring(ability.name or "")
if b < table.getn(row.abilities) then
nameText = nameText .. ","
end
button.entry = ability
button.text:SetText(nameText)
local textW = button.text:GetStringWidth() or 40
if textW < 24 then textW = 24 end
local buttonWidth = 14 + textW
if (offsetX + buttonWidth) > abilityMaxRight then
button.entry = nil
button:Hide()
else
button:SetWidth(buttonWidth)
button:ClearAllPoints()
button:SetPoint("LEFT", rowFrame, "LEFT", offsetX, 0)
local iconPath = ability.icon and MTH_BOOK_ResolveIconPath and MTH_BOOK_ResolveIconPath(ability.icon) or nil
if iconPath then
button.icon:SetTexture(iconPath)
else
button.icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
end
button:Show()
offsetX = offsetX + button:GetWidth() + 6
end
else
button.entry = nil
button:Hide()
end
end
else
rowFrame:Hide()
end
end
end