1180 lines
48 KiB
Lua
1180 lines
48 KiB
Lua
--------------------------------------------------------------------------------
|
|
-- Nanami-UI: Beast Training UI (BeastTrainingUI.lua)
|
|
-- Dedicated panel for Hunter pet beast training (训练野兽)
|
|
-- Separate from TradeSkillUI; shows training points, skill ranks, etc.
|
|
-- NOTE: Lua 5.0 upvalue limit = 32 per closure; all state packed into tables.
|
|
--------------------------------------------------------------------------------
|
|
|
|
SFrames = SFrames or {}
|
|
SFrames.BeastTrainingUI = {}
|
|
local BTUI = SFrames.BeastTrainingUI
|
|
SFramesDB = SFramesDB or {}
|
|
|
|
local BEAST_NAMES = {
|
|
["训练野兽"] = true,
|
|
["Beast Training"] = true,
|
|
}
|
|
|
|
function BTUI.IsBeastTraining()
|
|
local name = GetCraftName and GetCraftName() or ""
|
|
return BEAST_NAMES[name] == true
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Theme
|
|
--------------------------------------------------------------------------------
|
|
local T = SFrames.Theme:Extend({
|
|
tpGood = { 0.40, 0.90, 0.40 },
|
|
tpLow = { 0.90, 0.60, 0.20 },
|
|
tpNone = { 0.90, 0.30, 0.30 },
|
|
rankText = { 0.80, 0.70, 1.00 },
|
|
available = { 0.25, 1.00, 0.25 },
|
|
unavailable = { 0.80, 0.20, 0.20 },
|
|
learned = { 0.50, 0.50, 0.50 },
|
|
})
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Layout (packed to save upvalues)
|
|
--------------------------------------------------------------------------------
|
|
local L = {
|
|
FRAME_W = 620, FRAME_H = 470,
|
|
HEADER_H = 52, SIDE_PAD = 10,
|
|
FILTER_H = 26, LIST_ROW_H = 28,
|
|
CAT_ROW_H = 20, BOTTOM_H = 44,
|
|
SCROLL_STEP = 36, SCROLLBAR_W = 12,
|
|
MAX_ROWS = 60, LEFT_W = 280,
|
|
}
|
|
L.RIGHT_W = L.FRAME_W - L.LEFT_W
|
|
L.CONTENT_W = L.RIGHT_W - L.SIDE_PAD * 2
|
|
L.LIST_ROW_W = L.LEFT_W - L.SIDE_PAD * 2 - L.SCROLLBAR_W - 4
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- State (packed to save upvalues)
|
|
--------------------------------------------------------------------------------
|
|
local S = {
|
|
MainFrame = nil,
|
|
selectedIndex = nil,
|
|
currentFilter = "available",
|
|
displayList = {},
|
|
rowButtons = {},
|
|
collapsedCats = {},
|
|
}
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Tooltip scanner & extended craft info
|
|
--------------------------------------------------------------------------------
|
|
local scanTip = nil
|
|
|
|
function BTUI.GetCraftExtendedInfo(index)
|
|
local name, rank, skillType, v4, _, tpCost, reqLevel = GetCraftInfo(index)
|
|
tpCost = tonumber(tpCost) or 0
|
|
local canLearn = false
|
|
local isLearned = false
|
|
if skillType ~= "header" then
|
|
if skillType == "used" then
|
|
isLearned = true
|
|
else
|
|
canLearn = (tonumber(reqLevel) or 0) > 0
|
|
end
|
|
end
|
|
return name, rank, skillType, canLearn, tpCost, isLearned
|
|
end
|
|
|
|
function BTUI.GetSkillTooltipLines(index)
|
|
if not scanTip then
|
|
scanTip = CreateFrame("GameTooltip", "SFramesBTScanTip", nil, "GameTooltipTemplate")
|
|
end
|
|
scanTip:SetOwner(WorldFrame, "ANCHOR_NONE")
|
|
scanTip:ClearLines()
|
|
local ok = pcall(scanTip.SetCraftSpell, scanTip, index)
|
|
if not ok then scanTip:Hide(); return {} end
|
|
|
|
local lines = {}
|
|
local numLines = scanTip:NumLines()
|
|
for i = 2, numLines do
|
|
local leftFS = _G["SFramesBTScanTipTextLeft" .. i]
|
|
local rightFS = _G["SFramesBTScanTipTextRight" .. i]
|
|
if leftFS then
|
|
local lt = leftFS:GetText() or ""
|
|
local rt = rightFS and rightFS:GetText() or ""
|
|
local r, g, b = leftFS:GetTextColor()
|
|
if lt ~= "" or rt ~= "" then
|
|
table.insert(lines, {
|
|
left = lt, right = rt,
|
|
r = r, g = g, b = b,
|
|
})
|
|
end
|
|
end
|
|
end
|
|
scanTip:Hide()
|
|
return lines
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Helpers
|
|
--------------------------------------------------------------------------------
|
|
function BTUI.GetFont()
|
|
if SFrames and SFrames.GetFont then return SFrames:GetFont() end
|
|
return "Fonts\\ARIALN.TTF"
|
|
end
|
|
|
|
function BTUI.SetRoundBackdrop(frame)
|
|
frame:SetBackdrop({
|
|
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
|
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
|
tile = true, tileSize = 16, edgeSize = 14,
|
|
insets = { left = 3, right = 3, top = 3, bottom = 3 },
|
|
})
|
|
frame:SetBackdropColor(T.panelBg[1], T.panelBg[2], T.panelBg[3], T.panelBg[4])
|
|
frame:SetBackdropBorderColor(T.panelBorder[1], T.panelBorder[2], T.panelBorder[3], T.panelBorder[4])
|
|
end
|
|
|
|
function BTUI.CreateShadow(parent)
|
|
local s = CreateFrame("Frame", nil, parent)
|
|
s:SetPoint("TOPLEFT", parent, "TOPLEFT", -4, 4)
|
|
s:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", 4, -4)
|
|
s:SetBackdrop({
|
|
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
|
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
|
tile = true, tileSize = 16, edgeSize = 16,
|
|
insets = { left = 4, right = 4, top = 4, bottom = 4 },
|
|
})
|
|
s:SetBackdropColor(0, 0, 0, 0.45)
|
|
s:SetBackdropBorderColor(0, 0, 0, 0.6)
|
|
s:SetFrameLevel(math.max(0, parent:GetFrameLevel() - 1))
|
|
return s
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Widget Factories
|
|
--------------------------------------------------------------------------------
|
|
function BTUI.CreateFilterBtn(parent, text, w)
|
|
local btn = CreateFrame("Button", nil, parent)
|
|
btn:SetWidth(w or 60); btn:SetHeight(20)
|
|
btn:SetBackdrop({
|
|
bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8",
|
|
tile = false, tileSize = 0, edgeSize = 1,
|
|
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
|
})
|
|
btn:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
|
|
btn:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], 0.5)
|
|
local fs = btn:CreateFontString(nil, "OVERLAY")
|
|
fs:SetFont(BTUI.GetFont(), 11, "OUTLINE"); fs:SetPoint("CENTER", 0, 0)
|
|
fs:SetText(text); fs:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
|
|
btn.label = fs
|
|
btn:SetScript("OnEnter", function()
|
|
this:SetBackdropColor(T.btnHoverBg[1], T.btnHoverBg[2], T.btnHoverBg[3], T.btnHoverBg[4])
|
|
this:SetBackdropBorderColor(T.btnHoverBd[1], T.btnHoverBd[2], T.btnHoverBd[3], T.btnHoverBd[4])
|
|
end)
|
|
btn:SetScript("OnLeave", function()
|
|
if this.active then
|
|
this:SetBackdropColor(T.btnHoverBg[1], T.btnHoverBg[2], T.btnHoverBg[3], T.btnHoverBg[4])
|
|
this:SetBackdropBorderColor(T.slotSelected[1], T.slotSelected[2], T.slotSelected[3], 1)
|
|
else
|
|
this:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
|
|
this:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], 0.5)
|
|
end
|
|
end)
|
|
function btn:SetActive(flag)
|
|
self.active = flag
|
|
if flag then
|
|
self:SetBackdropColor(T.btnHoverBg[1], T.btnHoverBg[2], T.btnHoverBg[3], T.btnHoverBg[4])
|
|
self:SetBackdropBorderColor(T.slotSelected[1], T.slotSelected[2], T.slotSelected[3], 1)
|
|
self.label:SetTextColor(T.btnActiveText[1], T.btnActiveText[2], T.btnActiveText[3])
|
|
else
|
|
self:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
|
|
self:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], 0.5)
|
|
self.label:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
|
|
end
|
|
end
|
|
return btn
|
|
end
|
|
|
|
function BTUI.CreateActionBtn(parent, text, w)
|
|
local btn = CreateFrame("Button", nil, parent)
|
|
btn:SetWidth(w or 100); btn:SetHeight(28)
|
|
btn:SetBackdrop({
|
|
bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8",
|
|
tile = false, tileSize = 0, edgeSize = 1,
|
|
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
|
})
|
|
btn:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
|
|
btn:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], T.btnBorder[4])
|
|
local fs = btn:CreateFontString(nil, "OVERLAY")
|
|
fs:SetFont(BTUI.GetFont(), 12, "OUTLINE"); fs:SetPoint("CENTER", 0, 0)
|
|
fs:SetText(text); fs:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
|
|
btn.label = fs; btn.disabled = false
|
|
function btn:SetDisabled(flag)
|
|
self.disabled = flag
|
|
if flag then
|
|
self.label:SetTextColor(T.btnDisabledText[1], T.btnDisabledText[2], T.btnDisabledText[3])
|
|
self:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], 0.5)
|
|
else
|
|
self.label:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
|
|
self:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
|
|
end
|
|
end
|
|
btn:SetScript("OnEnter", function()
|
|
if not this.disabled then
|
|
this:SetBackdropColor(T.btnHoverBg[1], T.btnHoverBg[2], T.btnHoverBg[3], T.btnHoverBg[4])
|
|
this:SetBackdropBorderColor(T.btnHoverBd[1], T.btnHoverBd[2], T.btnHoverBd[3], T.btnHoverBd[4])
|
|
this.label:SetTextColor(T.btnActiveText[1], T.btnActiveText[2], T.btnActiveText[3])
|
|
end
|
|
end)
|
|
btn:SetScript("OnLeave", function()
|
|
if not this.disabled then
|
|
this:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
|
|
this:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], T.btnBorder[4])
|
|
this.label:SetTextColor(T.btnText[1], T.btnText[2], T.btnText[3])
|
|
end
|
|
end)
|
|
return btn
|
|
end
|
|
|
|
function BTUI.CreateListRow(parent, idx)
|
|
local row = CreateFrame("Button", nil, parent)
|
|
row:SetWidth(L.LIST_ROW_W); row:SetHeight(L.LIST_ROW_H)
|
|
|
|
local iconFrame = CreateFrame("Frame", nil, row)
|
|
iconFrame:SetWidth(L.LIST_ROW_H - 4); iconFrame:SetHeight(L.LIST_ROW_H - 4)
|
|
iconFrame:SetPoint("LEFT", row, "LEFT", 0, 0)
|
|
iconFrame:SetBackdrop({
|
|
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
|
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
|
tile = true, tileSize = 16, edgeSize = 12,
|
|
insets = { left = 2, right = 2, top = 2, bottom = 2 },
|
|
})
|
|
iconFrame:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4])
|
|
iconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
|
|
row.iconFrame = iconFrame
|
|
|
|
local icon = iconFrame:CreateTexture(nil, "ARTWORK")
|
|
icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
|
|
icon:SetPoint("TOPLEFT", iconFrame, "TOPLEFT", 3, -3)
|
|
icon:SetPoint("BOTTOMRIGHT", iconFrame, "BOTTOMRIGHT", -3, 3)
|
|
row.icon = icon
|
|
|
|
local font = BTUI.GetFont()
|
|
|
|
local tpFS = row:CreateFontString(nil, "OVERLAY")
|
|
tpFS:SetFont(font, 10, "OUTLINE")
|
|
tpFS:SetPoint("RIGHT", row, "RIGHT", -4, 0)
|
|
tpFS:SetJustifyH("RIGHT")
|
|
tpFS:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
|
|
tpFS:Hide()
|
|
row.tpFS = tpFS
|
|
|
|
local nameFS = row:CreateFontString(nil, "OVERLAY")
|
|
nameFS:SetFont(font, 11, "OUTLINE")
|
|
nameFS:SetPoint("LEFT", iconFrame, "RIGHT", 5, 2)
|
|
nameFS:SetPoint("RIGHT", tpFS, "LEFT", -4, 0)
|
|
nameFS:SetJustifyH("LEFT")
|
|
nameFS:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
|
|
row.nameFS = nameFS
|
|
|
|
local rankFS = row:CreateFontString(nil, "OVERLAY")
|
|
rankFS:SetFont(font, 9, "OUTLINE")
|
|
rankFS:SetPoint("TOPLEFT", nameFS, "BOTTOMLEFT", 0, 0)
|
|
rankFS:SetJustifyH("LEFT")
|
|
rankFS:SetTextColor(T.rankText[1], T.rankText[2], T.rankText[3])
|
|
row.rankFS = rankFS
|
|
|
|
local catFS = row:CreateFontString(nil, "OVERLAY")
|
|
catFS:SetFont(font, 11, "OUTLINE")
|
|
catFS:SetPoint("LEFT", row, "LEFT", 4, 0)
|
|
catFS:SetJustifyH("LEFT")
|
|
catFS:SetTextColor(T.catHeader[1], T.catHeader[2], T.catHeader[3])
|
|
catFS:Hide()
|
|
row.catFS = catFS
|
|
|
|
local catSep = row:CreateTexture(nil, "ARTWORK")
|
|
catSep:SetTexture("Interface\\Buttons\\WHITE8X8"); catSep:SetHeight(1)
|
|
catSep:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT", 0, 0)
|
|
catSep:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT", 0, 0)
|
|
catSep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], 0.3)
|
|
catSep:Hide()
|
|
row.catSep = catSep
|
|
|
|
local selBg = row:CreateTexture(nil, "ARTWORK")
|
|
selBg:SetTexture("Interface\\Buttons\\WHITE8X8"); selBg:SetAllPoints(row)
|
|
selBg:SetVertexColor(T.slotSelected[1], T.slotSelected[2], T.slotSelected[3], 0.40)
|
|
selBg:Hide(); row.selBg = selBg
|
|
|
|
local selGlow = row:CreateTexture(nil, "ARTWORK")
|
|
selGlow:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
selGlow:SetWidth(4); selGlow:SetHeight(L.LIST_ROW_H)
|
|
selGlow:SetPoint("LEFT", row, "LEFT", 0, 0)
|
|
selGlow:SetVertexColor(1, 0.65, 0.85, 1)
|
|
selGlow:Hide(); row.selGlow = selGlow
|
|
|
|
local selTop = row:CreateTexture(nil, "OVERLAY")
|
|
selTop:SetTexture("Interface\\Buttons\\WHITE8X8"); selTop:SetHeight(1)
|
|
selTop:SetPoint("TOPLEFT", row, "TOPLEFT", 0, 0)
|
|
selTop:SetPoint("TOPRIGHT", row, "TOPRIGHT", 0, 0)
|
|
selTop:SetVertexColor(T.slotSelected[1], T.slotSelected[2], T.slotSelected[3], 0.8)
|
|
selTop:Hide(); row.selTop = selTop
|
|
|
|
local selBot = row:CreateTexture(nil, "OVERLAY")
|
|
selBot:SetTexture("Interface\\Buttons\\WHITE8X8"); selBot:SetHeight(1)
|
|
selBot:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT", 0, 0)
|
|
selBot:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT", 0, 0)
|
|
selBot:SetVertexColor(T.slotSelected[1], T.slotSelected[2], T.slotSelected[3], 0.8)
|
|
selBot:Hide(); row.selBot = selBot
|
|
|
|
local hl = row:CreateTexture(nil, "HIGHLIGHT")
|
|
hl:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
|
|
hl:SetBlendMode("ADD"); hl:SetAllPoints(row); hl:SetAlpha(0.3)
|
|
row.highlight = hl
|
|
|
|
row.craftIndex = nil; row.isHeader = false
|
|
|
|
row:SetScript("OnEnter", function()
|
|
if this.craftIndex and not this.isHeader then
|
|
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
|
|
local ok2 = pcall(GameTooltip.SetCraftSpell, GameTooltip, this.craftIndex)
|
|
if ok2 then GameTooltip:Show() else GameTooltip:Hide() end
|
|
end
|
|
end)
|
|
row:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
|
|
|
function row:SetAsHeader(name, collapsed)
|
|
self.isHeader = true; self:SetHeight(L.CAT_ROW_H)
|
|
self.iconFrame:Hide(); self.nameFS:Hide(); self.rankFS:Hide(); self.tpFS:Hide()
|
|
self.highlight:SetAlpha(0.15)
|
|
self.selBg:Hide(); self.selGlow:Hide(); self.selTop:Hide(); self.selBot:Hide()
|
|
self.catFS:SetText((collapsed and "+" or "-") .. " " .. (name or ""))
|
|
self.catFS:Show(); self.catSep:Show()
|
|
end
|
|
|
|
function row:SetAsSkill(skill)
|
|
self.isHeader = false; self.craftIndex = skill.index
|
|
self:SetHeight(L.LIST_ROW_H)
|
|
self.iconFrame:Show(); self.nameFS:Show()
|
|
self.catFS:Hide(); self.catSep:Hide(); self.highlight:SetAlpha(0.3)
|
|
|
|
local iconTex = GetCraftIcon and GetCraftIcon(skill.index)
|
|
self.icon:SetTexture(iconTex)
|
|
self.nameFS:SetText(skill.name)
|
|
|
|
if skill.rank and skill.rank ~= "" then
|
|
self.rankFS:SetText(skill.rank)
|
|
self.rankFS:Show()
|
|
else
|
|
self.rankFS:SetText("")
|
|
self.rankFS:Hide()
|
|
end
|
|
|
|
if skill.isLearned then
|
|
self.nameFS:SetTextColor(T.learned[1], T.learned[2], T.learned[3])
|
|
self.icon:SetVertexColor(0.5, 0.5, 0.5)
|
|
self.tpFS:Hide()
|
|
elseif skill.canLearn then
|
|
self.nameFS:SetTextColor(T.available[1], T.available[2], T.available[3])
|
|
self.icon:SetVertexColor(1, 1, 1)
|
|
local tp = skill.tpCost or 0
|
|
if tp > 0 then
|
|
local remaining = BTUI.GetRemainingTP()
|
|
if remaining >= tp then
|
|
self.tpFS:SetTextColor(T.tpGood[1], T.tpGood[2], T.tpGood[3])
|
|
else
|
|
self.tpFS:SetTextColor(T.tpNone[1], T.tpNone[2], T.tpNone[3])
|
|
end
|
|
self.tpFS:SetText(tp .. " TP")
|
|
self.tpFS:Show()
|
|
else
|
|
self.tpFS:Hide()
|
|
end
|
|
else
|
|
self.nameFS:SetTextColor(T.unavailable[1], T.unavailable[2], T.unavailable[3])
|
|
self.icon:SetVertexColor(0.6, 0.3, 0.3)
|
|
self.tpFS:Hide()
|
|
end
|
|
end
|
|
|
|
function row:Clear()
|
|
self.craftIndex = nil; self.isHeader = false
|
|
self.selBg:Hide(); self.selGlow:Hide(); self.selTop:Hide(); self.selBot:Hide()
|
|
self.tpFS:Hide()
|
|
self:Hide()
|
|
end
|
|
|
|
return row
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Logic
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- Strip rank suffix to get base skill name (e.g. "自然抗性 2" -> "自然抗性")
|
|
function BTUI.GetBaseSkillName(name)
|
|
if not name then return "" end
|
|
-- Remove trailing " Rank X" / " X" patterns (both EN and CN)
|
|
local base = string.gsub(name, "%s+%d+$", "")
|
|
base = string.gsub(base, "%s+[Rr]ank%s+%d+$", "")
|
|
return base
|
|
end
|
|
|
|
-- Build a lookup: baseName -> highest tpCost among learned ranks
|
|
function BTUI.BuildLearnedCostMap()
|
|
local map = {} -- baseName -> highest learned tpCost
|
|
local numCrafts = GetNumCrafts and GetNumCrafts() or 0
|
|
for i = 1, numCrafts do
|
|
local name, rank, skillType, canLearn, tpCost, isLearned = BTUI.GetCraftExtendedInfo(i)
|
|
if name and skillType ~= "header" and isLearned then
|
|
local base = BTUI.GetBaseSkillName(name)
|
|
local cost = tpCost or 0
|
|
if not map[base] or cost > map[base] then
|
|
map[base] = cost
|
|
end
|
|
end
|
|
end
|
|
return map
|
|
end
|
|
|
|
function BTUI.BuildDisplayList()
|
|
S.displayList = {}
|
|
local numCrafts = GetNumCrafts and GetNumCrafts() or 0
|
|
if numCrafts == 0 then return end
|
|
|
|
-- Pre-compute learned cost for each base skill name
|
|
local learnedCostMap = BTUI.BuildLearnedCostMap()
|
|
|
|
local currentCat = nil
|
|
local catSkills = {}
|
|
local catOrder = {}
|
|
|
|
for i = 1, numCrafts do
|
|
local name, rank, skillType, canLearn, tpCost, isLearned = BTUI.GetCraftExtendedInfo(i)
|
|
if name then
|
|
if skillType == "header" then
|
|
currentCat = name
|
|
if not catSkills[name] then
|
|
catSkills[name] = {}
|
|
table.insert(catOrder, name)
|
|
end
|
|
else
|
|
if not currentCat then
|
|
currentCat = "技能"
|
|
if not catSkills[currentCat] then
|
|
catSkills[currentCat] = {}
|
|
table.insert(catOrder, currentCat)
|
|
end
|
|
end
|
|
-- Calculate effective (incremental) TP cost
|
|
local effectiveCost = tpCost or 0
|
|
if not isLearned and effectiveCost > 0 then
|
|
local base = BTUI.GetBaseSkillName(name)
|
|
local prevCost = learnedCostMap[base] or 0
|
|
effectiveCost = math.max(0, effectiveCost - prevCost)
|
|
end
|
|
local show = true
|
|
if S.currentFilter == "available" then
|
|
show = canLearn
|
|
elseif S.currentFilter == "used" then
|
|
show = isLearned
|
|
end
|
|
if show then
|
|
table.insert(catSkills[currentCat], {
|
|
index = i,
|
|
name = name,
|
|
rank = rank or "",
|
|
canLearn = canLearn,
|
|
tpCost = effectiveCost,
|
|
isLearned = isLearned,
|
|
})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local hasCats = table.getn(catOrder) > 1
|
|
for _, catName in ipairs(catOrder) do
|
|
local skills = catSkills[catName]
|
|
if skills and table.getn(skills) > 0 then
|
|
if hasCats then
|
|
table.insert(S.displayList, {
|
|
type = "header", name = catName,
|
|
collapsed = S.collapsedCats[catName],
|
|
})
|
|
end
|
|
if not S.collapsedCats[catName] then
|
|
for _, skill in ipairs(skills) do
|
|
table.insert(S.displayList, { type = "skill", data = skill })
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function BTUI.GetRemainingTP()
|
|
if not GetPetTrainingPoints then return 0, 0 end
|
|
local ok, total, spent = pcall(GetPetTrainingPoints)
|
|
if not ok then return 0, 0 end
|
|
total = total or 0
|
|
spent = spent or 0
|
|
return total - spent, total
|
|
end
|
|
|
|
function BTUI.UpdateTrainingPoints()
|
|
if not S.MainFrame then return end
|
|
local remaining, total = BTUI.GetRemainingTP()
|
|
local color = T.tpGood
|
|
if remaining <= 0 then color = T.tpNone
|
|
elseif remaining < 10 then color = T.tpLow end
|
|
S.MainFrame.tpValueFS:SetText(tostring(remaining))
|
|
S.MainFrame.tpValueFS:SetTextColor(color[1], color[2], color[3])
|
|
end
|
|
|
|
function BTUI.UpdateList()
|
|
if not S.MainFrame or not S.MainFrame:IsVisible() then return end
|
|
BTUI.BuildDisplayList()
|
|
local content = S.MainFrame.listScroll.content
|
|
local count = table.getn(S.displayList)
|
|
local y = 0
|
|
for i = 1, L.MAX_ROWS do
|
|
local row = S.rowButtons[i]
|
|
if i <= count then
|
|
local entry = S.displayList[i]
|
|
row:ClearAllPoints()
|
|
if entry.type == "header" then
|
|
row:SetAsHeader(entry.name, entry.collapsed)
|
|
row:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -y)
|
|
row.catName = entry.name
|
|
row:Show(); y = y + L.CAT_ROW_H
|
|
else
|
|
row:SetAsSkill(entry.data)
|
|
row:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -y)
|
|
row.catName = nil
|
|
row:Show(); y = y + L.LIST_ROW_H
|
|
if S.selectedIndex == entry.data.index then
|
|
row.iconFrame:SetBackdropBorderColor(1, 0.65, 0.85, 1)
|
|
row.iconFrame:SetBackdropColor(T.slotSelected[1], T.slotSelected[2], T.slotSelected[3], 0.5)
|
|
row.selBg:Show(); row.selGlow:Show(); row.selTop:Show(); row.selBot:Show()
|
|
row.nameFS:SetTextColor(1, 1, 1)
|
|
else
|
|
row.iconFrame:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
|
|
row.iconFrame:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4])
|
|
row.selBg:Hide(); row.selGlow:Hide(); row.selTop:Hide(); row.selBot:Hide()
|
|
end
|
|
end
|
|
else row:Clear() end
|
|
end
|
|
content:SetHeight(math.max(1, y))
|
|
end
|
|
|
|
function BTUI.UpdateDetail()
|
|
if not S.MainFrame then return end
|
|
local detail = S.MainFrame.detail
|
|
if not S.selectedIndex then
|
|
detail.iconFrame:Hide()
|
|
detail.nameFS:SetText(""); detail.rankFS:SetText("")
|
|
detail.descFS:SetText(""); detail.costFS:SetText("")
|
|
detail.reqFS:SetText("")
|
|
S.MainFrame.trainBtn:SetDisabled(true)
|
|
return
|
|
end
|
|
|
|
local name, rank, skillType, canLearn, tpCost, isLearned = BTUI.GetCraftExtendedInfo(S.selectedIndex)
|
|
local iconTex = GetCraftIcon and GetCraftIcon(S.selectedIndex)
|
|
|
|
detail.icon:SetTexture(iconTex); detail.iconFrame:Show()
|
|
detail.nameFS:SetText(name or "")
|
|
|
|
if isLearned then
|
|
detail.nameFS:SetTextColor(T.learned[1], T.learned[2], T.learned[3])
|
|
elseif canLearn then
|
|
detail.nameFS:SetTextColor(T.available[1], T.available[2], T.available[3])
|
|
else
|
|
detail.nameFS:SetTextColor(T.unavailable[1], T.unavailable[2], T.unavailable[3])
|
|
end
|
|
|
|
if rank and rank ~= "" then
|
|
detail.rankFS:SetText(rank)
|
|
detail.rankFS:SetTextColor(T.rankText[1], T.rankText[2], T.rankText[3])
|
|
else
|
|
detail.rankFS:SetText("")
|
|
end
|
|
|
|
local desc = ""
|
|
if GetCraftDescription then
|
|
local ok, d = pcall(GetCraftDescription, S.selectedIndex)
|
|
if ok and d then desc = d end
|
|
end
|
|
detail.descFS:SetText(desc)
|
|
|
|
local descH = detail.descFS:GetHeight() or 40
|
|
detail.descScroll:GetScrollChild():SetHeight(math.max(1, descH))
|
|
detail.descScroll:SetVerticalScroll(0)
|
|
|
|
if isLearned then
|
|
detail.costFS:SetText("|cff808080已学会|r")
|
|
elseif tpCost > 0 then
|
|
-- Calculate effective (incremental) cost
|
|
local effectiveCost = tpCost
|
|
local base = BTUI.GetBaseSkillName(name)
|
|
local learnedMap = BTUI.BuildLearnedCostMap()
|
|
local prevCost = learnedMap[base] or 0
|
|
effectiveCost = math.max(0, tpCost - prevCost)
|
|
|
|
local remaining = BTUI.GetRemainingTP()
|
|
local costColor = remaining >= effectiveCost and "|cff40ff40" or "|cffff4040"
|
|
if prevCost > 0 then
|
|
detail.costFS:SetText("训练点数: " .. costColor .. effectiveCost .. "|r (总" .. tpCost .. " - 已学" .. prevCost .. ") 剩余: " .. remaining)
|
|
else
|
|
detail.costFS:SetText("训练点数: " .. costColor .. effectiveCost .. "|r (剩余: " .. remaining .. ")")
|
|
end
|
|
elseif canLearn then
|
|
detail.costFS:SetText("训练点数: |cff40ff40免费|r")
|
|
else
|
|
detail.costFS:SetText("")
|
|
end
|
|
|
|
local lines = BTUI.GetSkillTooltipLines(S.selectedIndex)
|
|
local reqParts = {}
|
|
for _, line in ipairs(lines) do
|
|
if line.r and line.r > 0.85 and line.g < 0.35 and line.b < 0.35 then
|
|
table.insert(reqParts, "|cffff4040" .. line.left .. "|r")
|
|
end
|
|
end
|
|
detail.reqFS:SetText(table.concat(reqParts, "\n"))
|
|
|
|
S.MainFrame.trainBtn:SetDisabled(not canLearn)
|
|
end
|
|
|
|
function BTUI.UpdateFilters()
|
|
if not S.MainFrame then return end
|
|
S.MainFrame.filterAll:SetActive(S.currentFilter == "all")
|
|
S.MainFrame.filterAvail:SetActive(S.currentFilter == "available")
|
|
S.MainFrame.filterUsed:SetActive(S.currentFilter == "used")
|
|
end
|
|
|
|
function BTUI.FullUpdate()
|
|
BTUI.UpdateTrainingPoints()
|
|
BTUI.UpdateFilters()
|
|
BTUI.UpdateList()
|
|
BTUI.UpdateDetail()
|
|
if BTUI.UpdateScrollbar then BTUI.UpdateScrollbar() end
|
|
end
|
|
|
|
function BTUI.SelectSkill(index)
|
|
S.selectedIndex = index
|
|
if SelectCraft then pcall(SelectCraft, index) end
|
|
BTUI.FullUpdate()
|
|
end
|
|
|
|
function BTUI.ToggleCategory(catName)
|
|
if S.collapsedCats[catName] then S.collapsedCats[catName] = nil
|
|
else S.collapsedCats[catName] = true end
|
|
BTUI.FullUpdate()
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Hide Blizzard Craft Frame
|
|
--------------------------------------------------------------------------------
|
|
function BTUI.CleanupBlizzardCraft()
|
|
if not CraftFrame then return end
|
|
CraftFrame:SetScript("OnHide", function() end)
|
|
if HideUIPanel then pcall(HideUIPanel, CraftFrame) end
|
|
if CraftFrame:IsVisible() then CraftFrame:Hide() end
|
|
CraftFrame:SetAlpha(0); CraftFrame:EnableMouse(false)
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Initialize
|
|
--------------------------------------------------------------------------------
|
|
function BTUI:Initialize()
|
|
if S.MainFrame then return end
|
|
local MF = CreateFrame("Frame", "SFramesBeastTrainingFrame", UIParent)
|
|
S.MainFrame = MF
|
|
MF:SetWidth(L.FRAME_W); MF:SetHeight(L.FRAME_H)
|
|
MF:SetPoint("LEFT", UIParent, "LEFT", 36, 0)
|
|
MF:SetFrameStrata("HIGH"); MF:SetToplevel(true); MF:EnableMouse(true)
|
|
MF:SetMovable(true); MF:RegisterForDrag("LeftButton")
|
|
MF:SetScript("OnDragStart", function() this:StartMoving() end)
|
|
MF:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
|
|
BTUI.SetRoundBackdrop(MF); BTUI.CreateShadow(MF)
|
|
|
|
local font = BTUI.GetFont()
|
|
|
|
-- ═══ Header ═════════════════════════════════════════════════════════
|
|
local header = CreateFrame("Frame", nil, MF)
|
|
header:SetPoint("TOPLEFT", 0, 0); header:SetPoint("TOPRIGHT", 0, 0)
|
|
header:SetHeight(L.HEADER_H)
|
|
header:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8" })
|
|
header:SetBackdropColor(T.headerBg[1], T.headerBg[2], T.headerBg[3], T.headerBg[4])
|
|
|
|
local titleIco = SFrames:CreateIcon(header, "dragon", 16)
|
|
titleIco:SetDrawLayer("OVERLAY")
|
|
titleIco:SetPoint("TOPLEFT", header, "TOPLEFT", L.SIDE_PAD, -6)
|
|
titleIco:SetVertexColor(T.gold[1], T.gold[2], T.gold[3])
|
|
|
|
local titleFS = header:CreateFontString(nil, "OVERLAY")
|
|
titleFS:SetFont(font, 14, "OUTLINE")
|
|
titleFS:SetPoint("LEFT", titleIco, "RIGHT", 5, 0)
|
|
titleFS:SetJustifyH("LEFT")
|
|
titleFS:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
|
|
MF.titleFS = titleFS
|
|
|
|
-- Training points in header
|
|
local tpIco = SFrames:CreateIcon(header, "star", 12)
|
|
tpIco:SetDrawLayer("OVERLAY")
|
|
tpIco:SetPoint("BOTTOMLEFT", header, "BOTTOMLEFT", L.SIDE_PAD, 8)
|
|
tpIco:SetVertexColor(0.55, 0.85, 0.4)
|
|
|
|
local tpLabelFS = header:CreateFontString(nil, "OVERLAY")
|
|
tpLabelFS:SetFont(font, 11, "OUTLINE")
|
|
tpLabelFS:SetPoint("LEFT", tpIco, "RIGHT", 4, 0)
|
|
tpLabelFS:SetText("训练点数:")
|
|
tpLabelFS:SetTextColor(T.bodyText[1], T.bodyText[2], T.bodyText[3])
|
|
|
|
local tpValueFS = header:CreateFontString(nil, "OVERLAY")
|
|
tpValueFS:SetFont(font, 13, "OUTLINE")
|
|
tpValueFS:SetPoint("LEFT", tpLabelFS, "RIGHT", 6, 0)
|
|
tpValueFS:SetText("0")
|
|
tpValueFS:SetTextColor(T.tpGood[1], T.tpGood[2], T.tpGood[3])
|
|
MF.tpValueFS = tpValueFS
|
|
|
|
-- Pet level in header
|
|
local petLvlFS = header:CreateFontString(nil, "OVERLAY")
|
|
petLvlFS:SetFont(font, 10, "OUTLINE")
|
|
petLvlFS:SetPoint("LEFT", tpValueFS, "RIGHT", 16, 0)
|
|
petLvlFS:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
|
|
MF.petLvlFS = petLvlFS
|
|
|
|
-- Close button
|
|
local closeBtn = CreateFrame("Button", nil, header)
|
|
closeBtn:SetWidth(20); closeBtn:SetHeight(20)
|
|
closeBtn:SetPoint("TOPRIGHT", header, "TOPRIGHT", -8, -6)
|
|
local closeTex = closeBtn:CreateTexture(nil, "ARTWORK")
|
|
closeTex:SetTexture("Interface\\AddOns\\Nanami-UI\\img\\icon")
|
|
closeTex:SetTexCoord(0.25, 0.375, 0, 0.125); closeTex:SetAllPoints()
|
|
closeTex:SetVertexColor(T.dimText[1], T.dimText[2], T.dimText[3])
|
|
closeBtn:SetScript("OnClick", function() S.MainFrame:Hide() end)
|
|
closeBtn:SetScript("OnEnter", function() closeTex:SetVertexColor(1, 0.6, 0.7) end)
|
|
closeBtn:SetScript("OnLeave", function() closeTex:SetVertexColor(T.dimText[1], T.dimText[2], T.dimText[3]) end)
|
|
|
|
-- Separators
|
|
local hsep = MF:CreateTexture(nil, "ARTWORK")
|
|
hsep:SetTexture("Interface\\Buttons\\WHITE8X8"); hsep:SetHeight(1)
|
|
hsep:SetPoint("TOPLEFT", MF, "TOPLEFT", 4, -L.HEADER_H)
|
|
hsep:SetPoint("TOPRIGHT", MF, "TOPRIGHT", -4, -L.HEADER_H)
|
|
hsep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])
|
|
|
|
local vdiv = MF:CreateTexture(nil, "ARTWORK")
|
|
vdiv:SetTexture("Interface\\Buttons\\WHITE8X8"); vdiv:SetWidth(1)
|
|
vdiv:SetPoint("TOP", MF, "TOPLEFT", L.LEFT_W, -(L.HEADER_H + 2))
|
|
vdiv:SetPoint("BOTTOM", MF, "BOTTOMLEFT", L.LEFT_W, 4)
|
|
vdiv:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])
|
|
|
|
-- ═══ LEFT: Filters + Skill List ═════════════════════════════════════
|
|
local fb = CreateFrame("Frame", nil, MF)
|
|
fb:SetPoint("TOPLEFT", MF, "TOPLEFT", L.SIDE_PAD, -(L.HEADER_H + 4))
|
|
fb:SetWidth(L.LEFT_W - L.SIDE_PAD * 2); fb:SetHeight(22)
|
|
|
|
local fAvail = BTUI.CreateFilterBtn(fb, "可学", 52)
|
|
fAvail:SetPoint("LEFT", fb, "LEFT", 0, 0)
|
|
fAvail:SetScript("OnClick", function() S.currentFilter = "available"; BTUI.FullUpdate() end)
|
|
MF.filterAvail = fAvail
|
|
|
|
local fUsed = BTUI.CreateFilterBtn(fb, "已学", 52)
|
|
fUsed:SetPoint("LEFT", fAvail, "RIGHT", 3, 0)
|
|
fUsed:SetScript("OnClick", function() S.currentFilter = "used"; BTUI.FullUpdate() end)
|
|
MF.filterUsed = fUsed
|
|
|
|
local fAll = BTUI.CreateFilterBtn(fb, "全部", 52)
|
|
fAll:SetPoint("LEFT", fUsed, "RIGHT", 3, 0)
|
|
fAll:SetScript("OnClick", function() S.currentFilter = "all"; BTUI.FullUpdate() end)
|
|
MF.filterAll = fAll
|
|
|
|
-- List scroll area
|
|
local listTop = L.HEADER_H + L.FILTER_H + 8
|
|
local ls = CreateFrame("ScrollFrame", "SFramesBTListScroll", MF)
|
|
ls:SetPoint("TOPLEFT", MF, "TOPLEFT", L.SIDE_PAD, -listTop)
|
|
ls:SetPoint("BOTTOMRIGHT", MF, "BOTTOMLEFT", L.SIDE_PAD + L.LIST_ROW_W, L.BOTTOM_H + 4)
|
|
|
|
local lc = CreateFrame("Frame", "SFramesBTListContent", ls)
|
|
lc:SetWidth(L.LIST_ROW_W); lc:SetHeight(1)
|
|
ls:SetScrollChild(lc)
|
|
ls:EnableMouseWheel(true)
|
|
|
|
-- Scrollbar
|
|
local sbTrack = CreateFrame("Frame", nil, MF)
|
|
sbTrack:SetWidth(L.SCROLLBAR_W)
|
|
sbTrack:SetPoint("TOPLEFT", ls, "TOPRIGHT", 2, 0)
|
|
sbTrack:SetPoint("BOTTOMLEFT", ls, "BOTTOMRIGHT", 2, 0)
|
|
sbTrack:SetBackdrop({
|
|
bgFile = "Interface\\Buttons\\WHITE8X8",
|
|
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
|
tile = false, tileSize = 0, edgeSize = 1,
|
|
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
|
})
|
|
sbTrack:SetBackdropColor(T.progressBg[1], T.progressBg[2], T.progressBg[3], 0.6)
|
|
sbTrack:SetBackdropBorderColor(T.panelBorder[1], T.panelBorder[2], T.panelBorder[3], 0.3)
|
|
|
|
local sbThumb = CreateFrame("Button", nil, sbTrack)
|
|
sbThumb:SetWidth(L.SCROLLBAR_W - 2); sbThumb:SetHeight(30)
|
|
sbThumb:SetPoint("TOP", sbTrack, "TOP", 0, -1)
|
|
sbThumb:SetBackdrop({
|
|
bgFile = "Interface\\Buttons\\WHITE8X8",
|
|
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
|
tile = false, tileSize = 0, edgeSize = 1,
|
|
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
|
})
|
|
sbThumb:SetBackdropColor(T.progressFill[1], T.progressFill[2], T.progressFill[3], 0.7)
|
|
sbThumb:SetBackdropBorderColor(T.panelBorder[1], T.panelBorder[2], T.panelBorder[3], 0.5)
|
|
sbThumb:EnableMouse(true); sbThumb:SetMovable(true)
|
|
sbThumb:RegisterForDrag("LeftButton")
|
|
sbThumb._dragging = false
|
|
|
|
sbThumb:SetScript("OnEnter", function()
|
|
this:SetBackdropColor(T.progressFill[1], T.progressFill[2], T.progressFill[3], 1)
|
|
end)
|
|
sbThumb:SetScript("OnLeave", function()
|
|
this:SetBackdropColor(T.progressFill[1], T.progressFill[2], T.progressFill[3], 0.7)
|
|
end)
|
|
sbThumb:SetScript("OnDragStart", function()
|
|
this._dragging = true
|
|
this._startY = select(2, GetCursorPosition()) / (this:GetEffectiveScale())
|
|
this._startScroll = ls:GetVerticalScroll()
|
|
end)
|
|
sbThumb:SetScript("OnDragStop", function() this._dragging = false end)
|
|
sbThumb:SetScript("OnUpdate", function()
|
|
if not this._dragging then return end
|
|
local cursorY = select(2, GetCursorPosition()) / (this:GetEffectiveScale())
|
|
local delta = this._startY - cursorY
|
|
local trackH = sbTrack:GetHeight() - this:GetHeight()
|
|
if trackH <= 0 then return end
|
|
local scrollMax = BTUI.GetScrollMax()
|
|
local newScroll = this._startScroll + (delta / trackH) * scrollMax
|
|
newScroll = math.max(0, math.min(scrollMax, newScroll))
|
|
ls:SetVerticalScroll(newScroll)
|
|
BTUI.UpdateScrollbar()
|
|
end)
|
|
|
|
sbTrack:EnableMouse(true)
|
|
sbTrack:SetScript("OnMouseDown", function()
|
|
local trackTop = sbTrack:GetTop()
|
|
local cursorY = select(2, GetCursorPosition()) / (sbTrack:GetEffectiveScale())
|
|
local clickRatio = (trackTop - cursorY) / sbTrack:GetHeight()
|
|
clickRatio = math.max(0, math.min(1, clickRatio))
|
|
ls:SetVerticalScroll(clickRatio * BTUI.GetScrollMax())
|
|
BTUI.UpdateScrollbar()
|
|
end)
|
|
|
|
MF.sbTrack = sbTrack; MF.sbThumb = sbThumb
|
|
|
|
function BTUI.GetScrollMax()
|
|
local contentH = ls.content and ls.content:GetHeight() or 0
|
|
local viewH = ls:GetHeight() or 0
|
|
return math.max(0, contentH - viewH)
|
|
end
|
|
|
|
function BTUI.UpdateScrollbar()
|
|
if not MF.sbThumb or not MF.sbTrack then return end
|
|
local scrollMax = BTUI.GetScrollMax()
|
|
if scrollMax <= 0 then
|
|
MF.sbThumb:Hide()
|
|
if ls:GetVerticalScroll() > 0 then ls:SetVerticalScroll(0) end
|
|
return
|
|
end
|
|
MF.sbThumb:Show()
|
|
local trackH = MF.sbTrack:GetHeight()
|
|
local curScroll = ls:GetVerticalScroll()
|
|
if curScroll > scrollMax then
|
|
curScroll = scrollMax
|
|
ls:SetVerticalScroll(scrollMax)
|
|
end
|
|
local ratio = curScroll / scrollMax
|
|
ratio = math.max(0, math.min(1, ratio))
|
|
local thumbH = math.max(20, trackH * (trackH / (trackH + scrollMax)))
|
|
MF.sbThumb:SetHeight(thumbH)
|
|
local maxOffset = trackH - thumbH - 2
|
|
MF.sbThumb:ClearAllPoints()
|
|
MF.sbThumb:SetPoint("TOP", MF.sbTrack, "TOP", 0, -(1 + ratio * maxOffset))
|
|
end
|
|
|
|
ls:SetScript("OnMouseWheel", function()
|
|
local cur = this:GetVerticalScroll()
|
|
local mx = BTUI.GetScrollMax()
|
|
if arg1 > 0 then this:SetVerticalScroll(math.max(0, cur - L.SCROLL_STEP))
|
|
else this:SetVerticalScroll(math.min(mx, cur + L.SCROLL_STEP)) end
|
|
BTUI.UpdateScrollbar()
|
|
end)
|
|
ls:SetScript("OnScrollRangeChanged", function() BTUI.UpdateScrollbar() end)
|
|
ls.content = lc; MF.listScroll = ls
|
|
|
|
for i = 1, L.MAX_ROWS do
|
|
local row = BTUI.CreateListRow(lc, i)
|
|
row:SetWidth(L.LIST_ROW_W)
|
|
row:EnableMouseWheel(true)
|
|
row:SetScript("OnMouseWheel", function()
|
|
local sf = S.MainFrame.listScroll
|
|
local cur = sf:GetVerticalScroll()
|
|
local mx = BTUI.GetScrollMax()
|
|
if arg1 > 0 then sf:SetVerticalScroll(math.max(0, cur - L.SCROLL_STEP))
|
|
else sf:SetVerticalScroll(math.min(mx, cur + L.SCROLL_STEP)) end
|
|
BTUI.UpdateScrollbar()
|
|
end)
|
|
row:SetScript("OnClick", function()
|
|
if this.isHeader and this.catName then
|
|
BTUI.ToggleCategory(this.catName)
|
|
elseif this.craftIndex then
|
|
BTUI.SelectSkill(this.craftIndex)
|
|
end
|
|
end)
|
|
S.rowButtons[i] = row
|
|
end
|
|
|
|
-- ═══ RIGHT: Detail ══════════════════════════════════════════════════
|
|
local rightX = L.LEFT_W + L.SIDE_PAD
|
|
local det = CreateFrame("Frame", nil, MF)
|
|
det:SetPoint("TOPLEFT", MF, "TOPLEFT", rightX, -(L.HEADER_H + 6))
|
|
det:SetPoint("BOTTOMRIGHT", MF, "BOTTOMRIGHT", -L.SIDE_PAD, L.BOTTOM_H + 2)
|
|
MF.detail = det
|
|
|
|
-- Icon
|
|
local dIF = CreateFrame("Frame", nil, det)
|
|
dIF:SetWidth(44); dIF:SetHeight(44); dIF:SetPoint("TOPLEFT", det, "TOPLEFT", 0, 0)
|
|
dIF:SetBackdrop({
|
|
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
|
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
|
tile = true, tileSize = 16, edgeSize = 14,
|
|
insets = { left = 2, right = 2, top = 2, bottom = 2 },
|
|
})
|
|
dIF:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4])
|
|
dIF:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
|
|
dIF:Hide(); det.iconFrame = dIF
|
|
|
|
local dIcon = dIF:CreateTexture(nil, "ARTWORK")
|
|
dIcon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
|
|
dIcon:SetPoint("TOPLEFT", dIF, "TOPLEFT", 3, -3)
|
|
dIcon:SetPoint("BOTTOMRIGHT", dIF, "BOTTOMRIGHT", -3, 3)
|
|
det.icon = dIcon
|
|
|
|
dIF:EnableMouse(true)
|
|
dIF:SetScript("OnEnter", function()
|
|
if S.selectedIndex then
|
|
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
|
|
local ok2 = pcall(GameTooltip.SetCraftSpell, GameTooltip, S.selectedIndex)
|
|
if ok2 then GameTooltip:Show() else GameTooltip:Hide() end
|
|
end
|
|
end)
|
|
dIF:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
|
|
|
-- Name
|
|
local dName = det:CreateFontString(nil, "OVERLAY")
|
|
dName:SetFont(font, 14, "OUTLINE")
|
|
dName:SetPoint("TOPLEFT", dIF, "TOPRIGHT", 8, -2)
|
|
dName:SetPoint("RIGHT", det, "RIGHT", -4, 0)
|
|
dName:SetJustifyH("LEFT")
|
|
dName:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
|
|
det.nameFS = dName
|
|
|
|
-- Rank
|
|
local dRank = det:CreateFontString(nil, "OVERLAY")
|
|
dRank:SetFont(font, 12, "OUTLINE")
|
|
dRank:SetPoint("TOPLEFT", dName, "BOTTOMLEFT", 0, -2)
|
|
dRank:SetJustifyH("LEFT")
|
|
dRank:SetTextColor(T.rankText[1], T.rankText[2], T.rankText[3])
|
|
det.rankFS = dRank
|
|
|
|
-- Separator
|
|
local sep1 = det:CreateTexture(nil, "ARTWORK")
|
|
sep1:SetTexture("Interface\\Buttons\\WHITE8X8"); sep1:SetHeight(1)
|
|
sep1:SetPoint("TOPLEFT", det, "TOPLEFT", 0, -54)
|
|
sep1:SetPoint("RIGHT", det, "RIGHT", 0, 0)
|
|
sep1:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], 0.3)
|
|
|
|
-- Description (scrollable)
|
|
local descScroll = CreateFrame("ScrollFrame", nil, det)
|
|
descScroll:SetPoint("TOPLEFT", det, "TOPLEFT", 0, -60)
|
|
descScroll:SetPoint("RIGHT", det, "RIGHT", -4, 0)
|
|
descScroll:SetHeight(120)
|
|
descScroll:EnableMouseWheel(true)
|
|
descScroll:SetScript("OnMouseWheel", function()
|
|
local cur = this:GetVerticalScroll()
|
|
local maxVal = this:GetVerticalScrollRange()
|
|
if arg1 > 0 then this:SetVerticalScroll(math.max(0, cur - 14))
|
|
else this:SetVerticalScroll(math.min(maxVal, cur + 14)) end
|
|
end)
|
|
det.descScroll = descScroll
|
|
|
|
local descContent = CreateFrame("Frame", nil, descScroll)
|
|
descContent:SetWidth(L.CONTENT_W - 8); descContent:SetHeight(1)
|
|
descScroll:SetScrollChild(descContent)
|
|
|
|
local dDesc = descContent:CreateFontString(nil, "OVERLAY")
|
|
dDesc:SetFont(font, 11)
|
|
dDesc:SetPoint("TOPLEFT", descContent, "TOPLEFT", 0, 0)
|
|
dDesc:SetWidth(L.CONTENT_W - 8)
|
|
dDesc:SetJustifyH("LEFT")
|
|
dDesc:SetTextColor(T.bodyText[1], T.bodyText[2], T.bodyText[3])
|
|
det.descFS = dDesc
|
|
|
|
-- Separator 2
|
|
local sep2 = det:CreateTexture(nil, "ARTWORK")
|
|
sep2:SetTexture("Interface\\Buttons\\WHITE8X8"); sep2:SetHeight(1)
|
|
sep2:SetPoint("TOPLEFT", det, "TOPLEFT", 0, -186)
|
|
sep2:SetPoint("RIGHT", det, "RIGHT", 0, 0)
|
|
sep2:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], 0.3)
|
|
|
|
-- TP Cost
|
|
local dCost = det:CreateFontString(nil, "OVERLAY")
|
|
dCost:SetFont(font, 12, "OUTLINE")
|
|
dCost:SetPoint("TOPLEFT", det, "TOPLEFT", 2, -194)
|
|
dCost:SetPoint("RIGHT", det, "RIGHT", -4, 0)
|
|
dCost:SetJustifyH("LEFT")
|
|
dCost:SetTextColor(T.bodyText[1], T.bodyText[2], T.bodyText[3])
|
|
det.costFS = dCost
|
|
|
|
-- Requirements
|
|
local dReq = det:CreateFontString(nil, "OVERLAY")
|
|
dReq:SetFont(font, 11, "OUTLINE")
|
|
dReq:SetPoint("TOPLEFT", dCost, "BOTTOMLEFT", 0, -4)
|
|
dReq:SetPoint("RIGHT", det, "RIGHT", -4, 0)
|
|
dReq:SetJustifyH("LEFT")
|
|
det.reqFS = dReq
|
|
|
|
-- ═══ Bottom Bar ═════════════════════════════════════════════════════
|
|
local bsep = MF:CreateTexture(nil, "ARTWORK")
|
|
bsep:SetTexture("Interface\\Buttons\\WHITE8X8"); bsep:SetHeight(1)
|
|
bsep:SetPoint("BOTTOMLEFT", MF, "BOTTOMLEFT", 4, L.BOTTOM_H)
|
|
bsep:SetPoint("BOTTOMRIGHT", MF, "BOTTOMRIGHT", -4, L.BOTTOM_H)
|
|
bsep:SetVertexColor(T.divider[1], T.divider[2], T.divider[3], T.divider[4])
|
|
|
|
local trainBtn = BTUI.CreateActionBtn(MF, "训练", 100)
|
|
trainBtn:SetPoint("BOTTOMRIGHT", MF, "BOTTOMRIGHT", -L.SIDE_PAD, 8)
|
|
trainBtn:SetScript("OnClick", function()
|
|
if this.disabled then return end
|
|
if S.selectedIndex and DoCraft then
|
|
DoCraft(S.selectedIndex)
|
|
BTUI.FullUpdate()
|
|
if not MF._refreshFrame then
|
|
MF._refreshFrame = CreateFrame("Frame")
|
|
end
|
|
MF._refreshElapsed = 0
|
|
MF._refreshCount = 0
|
|
MF._refreshFrame:SetScript("OnUpdate", function()
|
|
MF._refreshElapsed = (MF._refreshElapsed or 0) + arg1
|
|
if MF._refreshElapsed >= 0.2 then
|
|
MF._refreshElapsed = 0
|
|
MF._refreshCount = (MF._refreshCount or 0) + 1
|
|
BTUI.FullUpdate()
|
|
if MF._refreshCount >= 3 then
|
|
MF._refreshFrame:SetScript("OnUpdate", nil)
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
end)
|
|
MF.trainBtn = trainBtn
|
|
|
|
local closeB = BTUI.CreateActionBtn(MF, "关闭", 80)
|
|
closeB:SetPoint("BOTTOMRIGHT", trainBtn, "BOTTOMLEFT", -6, 0)
|
|
closeB:SetScript("OnClick", function() S.MainFrame:Hide() end)
|
|
|
|
-- ═══ Events ═════════════════════════════════════════════════════════
|
|
MF:SetScript("OnHide", function()
|
|
if CloseCraft then pcall(CloseCraft) end
|
|
BTUI.CleanupBlizzardCraft()
|
|
end)
|
|
|
|
MF:RegisterEvent("CRAFT_SHOW")
|
|
MF:RegisterEvent("CRAFT_UPDATE")
|
|
MF:RegisterEvent("CRAFT_CLOSE")
|
|
MF:SetScript("OnEvent", function()
|
|
if event == "CRAFT_SHOW" then
|
|
if not BTUI.IsBeastTraining() then return end
|
|
if CraftFrame then
|
|
CraftFrame:SetScript("OnHide", function() end)
|
|
CraftFrame:SetAlpha(0); CraftFrame:EnableMouse(false)
|
|
end
|
|
S.selectedIndex = nil; S.currentFilter = "available"; S.collapsedCats = {}
|
|
local petName = UnitName("pet") or ""
|
|
if petName ~= "" then
|
|
MF.titleFS:SetText("训练野兽 - " .. petName)
|
|
else
|
|
MF.titleFS:SetText("训练野兽")
|
|
end
|
|
local petLvl = UnitLevel("pet")
|
|
if petLvl and petLvl > 0 then
|
|
MF.petLvlFS:SetText("宠物等级: " .. petLvl)
|
|
else
|
|
MF.petLvlFS:SetText("")
|
|
end
|
|
MF:Show()
|
|
BTUI.BuildDisplayList()
|
|
for _, entry in ipairs(S.displayList) do
|
|
if entry.type == "skill" then
|
|
BTUI.SelectSkill(entry.data.index); break
|
|
end
|
|
end
|
|
BTUI.FullUpdate()
|
|
MF._hideBlizzTimer = 0
|
|
MF:SetScript("OnUpdate", function()
|
|
if not this._hideBlizzTimer then return end
|
|
this._hideBlizzTimer = this._hideBlizzTimer + arg1
|
|
if this._hideBlizzTimer > 0.05 then
|
|
this._hideBlizzTimer = nil; this:SetScript("OnUpdate", nil)
|
|
BTUI.CleanupBlizzardCraft()
|
|
end
|
|
end)
|
|
elseif event == "CRAFT_UPDATE" then
|
|
if S.MainFrame:IsVisible() then
|
|
BTUI.FullUpdate()
|
|
end
|
|
elseif event == "CRAFT_CLOSE" then
|
|
if S.MainFrame:IsVisible() then
|
|
BTUI.CleanupBlizzardCraft()
|
|
S.MainFrame._hideBlizzTimer = nil
|
|
S.MainFrame:SetScript("OnUpdate", nil)
|
|
S.MainFrame:Hide()
|
|
end
|
|
end
|
|
end)
|
|
|
|
MF:Hide()
|
|
tinsert(UISpecialFrames, "SFramesBeastTrainingFrame")
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Bootstrap
|
|
--------------------------------------------------------------------------------
|
|
local bootstrap = CreateFrame("Frame")
|
|
bootstrap:RegisterEvent("PLAYER_LOGIN")
|
|
bootstrap:SetScript("OnEvent", function()
|
|
if event == "PLAYER_LOGIN" then
|
|
if SFramesDB.enableTradeSkill == nil then SFramesDB.enableTradeSkill = true end
|
|
if SFramesDB.enableTradeSkill ~= false then BTUI:Initialize() end
|
|
end
|
|
end)
|
|
|
|
SLASH_BTDEBUG1 = "/btdebug"
|
|
SlashCmdList["BTDEBUG"] = function()
|
|
local p = "|cffff80ff[BT-Debug]|r "
|
|
local numCrafts = GetNumCrafts and GetNumCrafts() or 0
|
|
DEFAULT_CHAT_FRAME:AddMessage(p .. "Total crafts: " .. numCrafts .. " TP: " .. BTUI.GetRemainingTP())
|
|
local shown = 0
|
|
for i = 1, numCrafts do
|
|
local v1,v2,v3,v4,v5,v6,v7 = GetCraftInfo(i)
|
|
if v1 and v3 ~= "header" then
|
|
shown = shown + 1
|
|
if shown <= 8 then
|
|
if SelectCraft then pcall(SelectCraft, i) end
|
|
local nr = GetCraftNumReagents and GetCraftNumReagents(i) or 0
|
|
local reagentCost = ""
|
|
if nr and nr > 0 then
|
|
for r = 1, nr do
|
|
local rn, rt, rc, pc = GetCraftReagentInfo(i, r)
|
|
reagentCost = reagentCost .. " [" .. tostring(rn) .. "x" .. tostring(rc) .. "]"
|
|
end
|
|
end
|
|
DEFAULT_CHAT_FRAME:AddMessage(p .. i .. ": " .. tostring(v1)
|
|
.. " " .. tostring(v2) .. " v7=" .. tostring(v7)
|
|
.. " reagents=" .. tostring(nr) .. reagentCost)
|
|
end
|
|
end
|
|
end
|
|
DEFAULT_CHAT_FRAME:AddMessage(p .. "Total skills: " .. shown)
|
|
end
|