2069 lines
73 KiB
Lua
2069 lines
73 KiB
Lua
SFrames.Player = {}
|
||
local _A = SFrames.ActiveTheme
|
||
|
||
local CLASS_NAME_ZH = {
|
||
WARRIOR = "\230\136\152\229\163\171",
|
||
MAGE = "\230\179\149\229\184\136",
|
||
ROGUE = "\230\189\156\232\161\140\232\128\133",
|
||
DRUID = "\229\190\183\233\178\129\228\188\138",
|
||
HUNTER = "\231\140\142\228\186\186",
|
||
SHAMAN = "\232\144\168\230\187\161\231\165\173\229\143\184",
|
||
PRIEST = "\231\137\167\229\184\136",
|
||
WARLOCK = "\230\156\175\229\163\171",
|
||
PALADIN = "\229\156\163\233\170\145\229\163\171",
|
||
}
|
||
|
||
local function GetChineseClassName(classToken, localizedClass)
|
||
if classToken and CLASS_NAME_ZH[classToken] then
|
||
return CLASS_NAME_ZH[classToken]
|
||
end
|
||
return localizedClass or ""
|
||
end
|
||
|
||
local function GetIncomingHeals(unit)
|
||
return SFrames:GetIncomingHeals(unit)
|
||
end
|
||
|
||
local function Clamp(value, minValue, maxValue)
|
||
if value < minValue then
|
||
return minValue
|
||
end
|
||
if value > maxValue then
|
||
return maxValue
|
||
end
|
||
return value
|
||
end
|
||
|
||
function SFrames.Player:GetConfig()
|
||
local db = SFramesDB or {}
|
||
|
||
local width = tonumber(db.playerFrameWidth) or SFrames.Config.width or 220
|
||
width = Clamp(math.floor(width + 0.5), 170, 420)
|
||
|
||
local portraitWidth = tonumber(db.playerPortraitWidth) or SFrames.Config.portraitWidth or 50
|
||
portraitWidth = Clamp(math.floor(portraitWidth + 0.5), 32, 95)
|
||
if portraitWidth > width - 90 then
|
||
portraitWidth = width - 90
|
||
end
|
||
|
||
local healthHeight = tonumber(db.playerHealthHeight) or 38
|
||
healthHeight = Clamp(math.floor(healthHeight + 0.5), 14, 80)
|
||
|
||
local powerHeight = tonumber(db.playerPowerHeight) or 9
|
||
powerHeight = Clamp(math.floor(powerHeight + 0.5), 6, 40)
|
||
|
||
local height = healthHeight + powerHeight + 4
|
||
height = Clamp(height, 30, 140)
|
||
|
||
local nameFont = tonumber(db.playerNameFontSize) or 10
|
||
nameFont = Clamp(math.floor(nameFont + 0.5), 8, 18)
|
||
|
||
local valueFont = tonumber(db.playerValueFontSize) or 10
|
||
valueFont = Clamp(math.floor(valueFont + 0.5), 8, 18)
|
||
|
||
local frameScale = tonumber(db.playerFrameScale) or 1
|
||
frameScale = Clamp(frameScale, 0.7, 1.8)
|
||
|
||
return {
|
||
width = width,
|
||
height = height,
|
||
portraitWidth = portraitWidth,
|
||
healthHeight = healthHeight,
|
||
powerHeight = powerHeight,
|
||
nameFont = nameFont,
|
||
valueFont = valueFont,
|
||
scale = frameScale,
|
||
}
|
||
end
|
||
|
||
function SFrames.Player:ApplyConfig()
|
||
if not self.frame then return end
|
||
|
||
local cfg = self:GetConfig()
|
||
local f = self.frame
|
||
local db = SFramesDB or {}
|
||
|
||
local showPortrait = db.playerShowPortrait ~= false
|
||
local frameAlpha = tonumber(db.playerFrameAlpha) or 1
|
||
frameAlpha = Clamp(frameAlpha, 0.1, 1.0)
|
||
|
||
f:SetScale(cfg.scale)
|
||
f:SetWidth(cfg.width)
|
||
f:SetHeight(cfg.height)
|
||
f:SetAlpha(frameAlpha)
|
||
|
||
local bgA = tonumber(db.playerBgAlpha) or 0.9
|
||
local _A = SFrames.ActiveTheme
|
||
if _A and _A.panelBg and bgA < 0.89 then
|
||
if f.SetBackdropColor then f:SetBackdropColor(_A.panelBg[1], _A.panelBg[2], _A.panelBg[3], bgA) end
|
||
if f.healthBGFrame and f.healthBGFrame.SetBackdropColor then f.healthBGFrame:SetBackdropColor(_A.panelBg[1], _A.panelBg[2], _A.panelBg[3], bgA) end
|
||
if f.powerBGFrame and f.powerBGFrame.SetBackdropColor then f.powerBGFrame:SetBackdropColor(_A.panelBg[1], _A.panelBg[2], _A.panelBg[3], bgA) end
|
||
if f.portraitBG and f.portraitBG.SetBackdropColor then f.portraitBG:SetBackdropColor(_A.panelBg[1], _A.panelBg[2], _A.panelBg[3], bgA) end
|
||
end
|
||
|
||
if showPortrait then
|
||
if f.portrait then
|
||
f.portrait:SetWidth(cfg.portraitWidth)
|
||
f.portrait:SetHeight(cfg.height - 2)
|
||
f.portrait:Show()
|
||
end
|
||
if f.portraitBG then
|
||
f.portraitBG:ClearAllPoints()
|
||
f.portraitBG:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0)
|
||
f.portraitBG:SetPoint("BOTTOMRIGHT", f.portrait, "BOTTOMRIGHT", 1, -1)
|
||
f.portraitBG:Show()
|
||
end
|
||
if f.health then
|
||
f.health:ClearAllPoints()
|
||
f.health:SetPoint("TOPLEFT", f.portrait, "TOPRIGHT", 1, 0)
|
||
f.health:SetPoint("TOPRIGHT", f, "TOPRIGHT", -1, -1)
|
||
f.health:SetHeight(cfg.healthHeight)
|
||
end
|
||
if f.classIcon and f.classIcon.overlay then
|
||
f.classIcon.overlay:ClearAllPoints()
|
||
f.classIcon.overlay:SetPoint("CENTER", f.portrait, "TOPRIGHT", 0, 0)
|
||
end
|
||
else
|
||
if f.portrait then f.portrait:Hide() end
|
||
if f.portraitBG then f.portraitBG:Hide() end
|
||
if f.health then
|
||
f.health:ClearAllPoints()
|
||
f.health:SetPoint("TOPLEFT", f, "TOPLEFT", 1, -1)
|
||
f.health:SetPoint("TOPRIGHT", f, "TOPRIGHT", -1, -1)
|
||
f.health:SetHeight(cfg.healthHeight)
|
||
end
|
||
if f.classIcon and f.classIcon.overlay then
|
||
f.classIcon.overlay:ClearAllPoints()
|
||
f.classIcon.overlay:SetPoint("CENTER", f, "TOPLEFT", 8, 0)
|
||
end
|
||
end
|
||
|
||
if f.healthBGFrame then
|
||
f.healthBGFrame:ClearAllPoints()
|
||
f.healthBGFrame:SetPoint("TOPLEFT", f.health, "TOPLEFT", -1, 1)
|
||
f.healthBGFrame:SetPoint("BOTTOMRIGHT", f.health, "BOTTOMRIGHT", 1, -1)
|
||
end
|
||
|
||
if f.power then
|
||
f.power:ClearAllPoints()
|
||
f.power:SetPoint("TOPLEFT", f.health, "BOTTOMLEFT", 0, -1)
|
||
f.power:SetPoint("TOPRIGHT", f.health, "BOTTOMRIGHT", 0, 0)
|
||
f.power:SetHeight(cfg.powerHeight)
|
||
end
|
||
|
||
if f.powerBGFrame then
|
||
f.powerBGFrame:ClearAllPoints()
|
||
f.powerBGFrame:SetPoint("TOPLEFT", f.power, "TOPLEFT", -1, 1)
|
||
f.powerBGFrame:SetPoint("BOTTOMRIGHT", f.power, "BOTTOMRIGHT", 1, -1)
|
||
end
|
||
|
||
if f.restOverlay then
|
||
if showPortrait then f.restOverlay:SetAlpha(1) else f.restOverlay:SetAlpha(0) end
|
||
end
|
||
|
||
if f.castbar then
|
||
f.castbar:ClearAllPoints()
|
||
if showPortrait then
|
||
f.castbar:SetPoint("BOTTOMRIGHT", f, "TOPRIGHT", 0, 6)
|
||
f.castbar:SetPoint("BOTTOMLEFT", f.portrait, "TOPLEFT", SFrames.Config.castbarHeight + 6, 6)
|
||
else
|
||
f.castbar:SetPoint("BOTTOMRIGHT", f, "TOPRIGHT", 0, 6)
|
||
f.castbar:SetPoint("BOTTOMLEFT", f, "TOPLEFT", SFrames.Config.castbarHeight + 6, 6)
|
||
end
|
||
end
|
||
|
||
local outline = (SFrames and SFrames.Media and SFrames.Media.fontOutline) or "OUTLINE"
|
||
local fontPath = SFrames:GetFont()
|
||
|
||
if f.nameText then
|
||
f.nameText:SetFont(fontPath, cfg.nameFont, outline)
|
||
end
|
||
if f.healthText then
|
||
f.healthText:SetFont(fontPath, cfg.valueFont, outline)
|
||
end
|
||
if f.powerText then
|
||
f.powerText:SetFont(fontPath, cfg.valueFont, outline)
|
||
end
|
||
if f.manaText then
|
||
local manaFont = cfg.valueFont - 1
|
||
if manaFont < 8 then manaFont = 8 end
|
||
f.manaText:SetFont(fontPath, manaFont, outline)
|
||
end
|
||
|
||
if f.zLetters then
|
||
for i = 1, 3 do
|
||
if f.zLetters[i] and f.zLetters[i].text then
|
||
f.zLetters[i].text:SetFont(fontPath, 8 + (i - 1) * 3, "OUTLINE")
|
||
end
|
||
end
|
||
end
|
||
|
||
self:UpdateAll()
|
||
end
|
||
|
||
function SFrames.Player:Initialize()
|
||
local f = CreateFrame("Button", "SFramesPlayerFrame", UIParent)
|
||
f:SetWidth(SFrames.Config.width)
|
||
f:SetHeight(SFrames.Config.height)
|
||
if SFramesDB and SFramesDB.Positions and SFramesDB.Positions["PlayerFrame"] then
|
||
local pos = SFramesDB.Positions["PlayerFrame"]
|
||
f:SetPoint(pos.point, UIParent, pos.relativePoint, pos.xOfs, pos.yOfs)
|
||
else
|
||
f:SetPoint("CENTER", UIParent, "CENTER", -200, -100)
|
||
end
|
||
local frameScale = (SFramesDB and type(SFramesDB.playerFrameScale) == "number") and SFramesDB.playerFrameScale or 1
|
||
f:SetScale(frameScale)
|
||
|
||
-- Make it movable
|
||
f:SetMovable(true)
|
||
f:EnableMouse(true)
|
||
f:RegisterForDrag("LeftButton")
|
||
f:SetScript("OnDragStart", function() if IsAltKeyDown() or SFrames.isUnlocked then f:StartMoving() end end)
|
||
f:SetScript("OnDragStop", function()
|
||
f:StopMovingOrSizing()
|
||
if not SFramesDB then SFramesDB = {} end
|
||
if not SFramesDB.Positions then SFramesDB.Positions = {} end
|
||
local point, relativeTo, relativePoint, xOfs, yOfs = f:GetPoint()
|
||
SFramesDB.Positions["PlayerFrame"] = { point = point, relativePoint = relativePoint, xOfs = xOfs, yOfs = yOfs }
|
||
end)
|
||
|
||
-- Register clicks for targeting
|
||
f:RegisterForClicks("LeftButtonUp", "RightButtonUp")
|
||
f:SetScript("OnClick", function()
|
||
if arg1 == "LeftButton" then
|
||
TargetUnit("player")
|
||
else
|
||
ToggleDropDownMenu(1, nil, PlayerFrameDropDown, this:GetName(), 106, 27)
|
||
end
|
||
end)
|
||
|
||
-- Base Backdrop
|
||
SFrames:CreateUnitBackdrop(f)
|
||
|
||
-- 3D Portrait
|
||
local pWidth = SFrames.Config.portraitWidth
|
||
f.portrait = CreateFrame("PlayerModel", nil, f)
|
||
f.portrait:SetWidth(pWidth)
|
||
f.portrait:SetHeight(SFrames.Config.height - 2)
|
||
f.portrait:SetPoint("LEFT", f, "LEFT", 1, 0)
|
||
f.portrait:SetUnit("player")
|
||
f.portrait:SetCamera(0)
|
||
f.portrait:SetPosition(-1.0, 0, 0)
|
||
|
||
-- We need a backdrop for the portrait to separate it from health bar
|
||
local pbg = CreateFrame("Frame", nil, f)
|
||
pbg:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0)
|
||
pbg:SetPoint("BOTTOMRIGHT", f.portrait, "BOTTOMRIGHT", 1, -1)
|
||
pbg:SetFrameLevel(f:GetFrameLevel())
|
||
SFrames:CreateUnitBackdrop(pbg)
|
||
f.portraitBG = pbg
|
||
|
||
-- Health Bar
|
||
f.health = SFrames:CreateStatusBar(f, "SFramesPlayerHealth")
|
||
f.health:SetPoint("TOPLEFT", f.portrait, "TOPRIGHT", 1, 0)
|
||
f.health:SetPoint("TOPRIGHT", f, "TOPRIGHT", -1, -1)
|
||
f.health:SetHeight((SFrames.Config.height - 2) * 0.82 - 1) -- 82% height, minus 1px gap
|
||
f.health:SetMinMaxValues(0, 100)
|
||
|
||
-- Health Backdrop
|
||
local hbg = CreateFrame("Frame", nil, f)
|
||
hbg:SetPoint("TOPLEFT", f.health, "TOPLEFT", -1, 1)
|
||
hbg:SetPoint("BOTTOMRIGHT", f.health, "BOTTOMRIGHT", 1, -1)
|
||
hbg:SetFrameLevel(f:GetFrameLevel() - 1)
|
||
SFrames:CreateUnitBackdrop(hbg)
|
||
f.healthBGFrame = hbg
|
||
|
||
-- Add a dark backdrop behind the health texture
|
||
f.health.bg = f.health:CreateTexture(nil, "BACKGROUND")
|
||
f.health.bg:SetAllPoints()
|
||
f.health.bg:SetTexture(SFrames:GetTexture())
|
||
f.health.bg:SetVertexColor(_A.slotBg[1], _A.slotBg[2], _A.slotBg[3], _A.slotBg[4] or 1)
|
||
|
||
-- Heal prediction overlay (incoming heals)
|
||
f.health.healPredMine = f.health:CreateTexture(nil, "ARTWORK")
|
||
f.health.healPredMine:SetTexture(SFrames:GetTexture())
|
||
f.health.healPredMine:SetVertexColor(0.4, 1.0, 0.55, 0.78)
|
||
f.health.healPredMine:SetDrawLayer("ARTWORK", 2)
|
||
f.health.healPredMine:Hide()
|
||
|
||
f.health.healPredOther = f.health:CreateTexture(nil, "ARTWORK")
|
||
f.health.healPredOther:SetTexture(SFrames:GetTexture())
|
||
f.health.healPredOther:SetVertexColor(0.2, 0.9, 0.35, 0.5)
|
||
f.health.healPredOther:SetDrawLayer("ARTWORK", 2)
|
||
f.health.healPredOther:Hide()
|
||
|
||
f.health.healPredOver = f.health:CreateTexture(nil, "OVERLAY")
|
||
f.health.healPredOver:SetTexture(SFrames:GetTexture())
|
||
f.health.healPredOver:SetVertexColor(1.0, 0.3, 0.3, 0.6)
|
||
f.health.healPredOver:SetDrawLayer("OVERLAY", 7)
|
||
f.health.healPredOver:Hide()
|
||
|
||
-- Power Bar
|
||
f.power = SFrames:CreateStatusBar(f, "SFramesPlayerPower")
|
||
f.power:SetPoint("TOPLEFT", f.health, "BOTTOMLEFT", 0, -1)
|
||
f.power:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -1, 1)
|
||
f.power:SetMinMaxValues(0, 100)
|
||
|
||
-- Power Backdrop
|
||
local powerbg = CreateFrame("Frame", nil, f)
|
||
powerbg:SetPoint("TOPLEFT", f.power, "TOPLEFT", -1, 1)
|
||
powerbg:SetPoint("BOTTOMRIGHT", f.power, "BOTTOMRIGHT", 1, -1)
|
||
powerbg:SetFrameLevel(f:GetFrameLevel() - 1)
|
||
SFrames:CreateUnitBackdrop(powerbg)
|
||
f.powerBGFrame = powerbg
|
||
|
||
-- Add a dark backdrop behind the power texture
|
||
f.power.bg = f.power:CreateTexture(nil, "BACKGROUND")
|
||
f.power.bg:SetAllPoints()
|
||
f.power.bg:SetTexture(SFrames:GetTexture())
|
||
f.power.bg:SetVertexColor(_A.slotBg[1], _A.slotBg[2], _A.slotBg[3], _A.slotBg[4] or 1)
|
||
|
||
-- Five-second rule ticker (mana regen delay indicator)
|
||
f.power.fsrGlow = f.power:CreateTexture(nil, "OVERLAY")
|
||
f.power.fsrGlow:SetTexture("Interface\\CastingBar\\UI-CastingBar-Spark")
|
||
f.power.fsrGlow:SetVertexColor(0.62, 0.90, 1.0, 0.52)
|
||
f.power.fsrGlow:SetBlendMode("ADD")
|
||
pcall(function() f.power.fsrGlow:SetDrawLayer("OVERLAY", 5) end)
|
||
f.power.fsrGlow:Hide()
|
||
|
||
-- Texts
|
||
f.nameText = SFrames:CreateFontString(f.health, 10, "LEFT")
|
||
f.nameText:SetPoint("LEFT", f.health, "LEFT", 6, 0)
|
||
|
||
f.healthText = SFrames:CreateFontString(f.health, 10, "RIGHT")
|
||
f.healthText:SetPoint("RIGHT", f.health, "RIGHT", -6, 0)
|
||
|
||
f.powerText = SFrames:CreateFontString(f.power, 10, "RIGHT")
|
||
f.powerText:SetPoint("RIGHT", f.power, "RIGHT", -6, 0)
|
||
|
||
-- Extra mana text for shapeshift druids (show blue mana while rage/energy is active)
|
||
f.manaText = SFrames:CreateFontString(f.power, 9, "LEFT")
|
||
f.manaText:SetPoint("LEFT", f.power, "LEFT", 6, 0)
|
||
f.manaText:SetTextColor(0.30, 0.65, 1.0)
|
||
f.manaText:Hide()
|
||
|
||
f.manaBar = CreateFrame("StatusBar", nil, f)
|
||
f.manaBar:SetHeight(4)
|
||
f.manaBar:SetPoint("TOPLEFT", f.power, "BOTTOMLEFT", 0, -1)
|
||
f.manaBar:SetPoint("TOPRIGHT", f.power, "BOTTOMRIGHT", 0, -1)
|
||
f.manaBar:SetStatusBarTexture(SFrames:GetTexture())
|
||
f.manaBar:SetStatusBarColor(0.30, 0.65, 1.0, 0.90)
|
||
f.manaBar:SetMinMaxValues(0, 100)
|
||
f.manaBar:SetValue(0)
|
||
f.manaBar:SetFrameLevel(f:GetFrameLevel() + 1)
|
||
f.manaBar.bg = f.manaBar:CreateTexture(nil, "BACKGROUND")
|
||
f.manaBar.bg:SetAllPoints()
|
||
f.manaBar.bg:SetTexture(SFrames:GetTexture())
|
||
f.manaBar.bg:SetVertexColor(0.05, 0.10, 0.20, 0.7)
|
||
f.manaBar:Hide()
|
||
|
||
-- Outline/shadow setup for text to make it pop
|
||
f.nameText:SetShadowColor(0, 0, 0, 1)
|
||
f.nameText:SetShadowOffset(1, -1)
|
||
f.healthText:SetShadowColor(0, 0, 0, 1)
|
||
f.healthText:SetShadowOffset(1, -1)
|
||
f.powerText:SetShadowColor(0, 0, 0, 1)
|
||
f.powerText:SetShadowOffset(1, -1)
|
||
f.manaText:SetShadowColor(0, 0, 0, 1)
|
||
f.manaText:SetShadowOffset(1, -1)
|
||
|
||
-- Resting Indicator (animated zzz on portrait)
|
||
local restOverlay = CreateFrame("Frame", nil, f)
|
||
restOverlay:SetFrameLevel((f:GetFrameLevel() or 0) + 6)
|
||
restOverlay:SetWidth(pWidth)
|
||
restOverlay:SetHeight(SFrames.Config.height)
|
||
restOverlay:SetPoint("CENTER", f.portrait, "CENTER", 0, 0)
|
||
f.restOverlay = restOverlay
|
||
|
||
local zLetters = {}
|
||
for i = 1, 3 do
|
||
local zf = CreateFrame("Frame", nil, restOverlay)
|
||
zf:SetWidth(16)
|
||
zf:SetHeight(16)
|
||
local zt = zf:CreateFontString(nil, "OVERLAY")
|
||
zt:SetFont(SFrames:GetFont(), 8 + (i - 1) * 3, "OUTLINE")
|
||
zt:SetText("z")
|
||
zt:SetTextColor(0.85, 0.85, 1.0)
|
||
zt:SetShadowColor(0, 0, 0, 0.8)
|
||
zt:SetShadowOffset(1, -1)
|
||
zt:SetAllPoints(zf)
|
||
zf.text = zt
|
||
zf.phase = (i - 1) * 1.2
|
||
zf.baseX = (i - 1) * 6 - 2
|
||
zf.baseY = (i - 1) * 5
|
||
zf:SetPoint("BOTTOMLEFT", restOverlay, "CENTER", zf.baseX, zf.baseY - 4)
|
||
zLetters[i] = zf
|
||
end
|
||
f.zLetters = zLetters
|
||
|
||
local restElapsed = 0
|
||
restOverlay:SetScript("OnUpdate", function()
|
||
restElapsed = restElapsed + arg1
|
||
for idx = 1, 3 do
|
||
local zf = zLetters[idx]
|
||
local t = math.mod(restElapsed + zf.phase, 3.6)
|
||
local ratio = t / 3.6
|
||
local floatY = ratio * 14
|
||
local alpha
|
||
if ratio < 0.15 then
|
||
alpha = ratio / 0.15
|
||
elseif ratio < 0.7 then
|
||
alpha = 1.0
|
||
else
|
||
alpha = 1.0 - (ratio - 0.7) / 0.3
|
||
end
|
||
if alpha < 0 then alpha = 0 end
|
||
if alpha > 1 then alpha = 1 end
|
||
zf.text:SetAlpha(alpha)
|
||
zf:ClearAllPoints()
|
||
zf:SetPoint("BOTTOMLEFT", restOverlay, "CENTER", zf.baseX, zf.baseY - 4 + floatY)
|
||
end
|
||
end)
|
||
restOverlay:Hide()
|
||
|
||
-- Class Icon Badge (overlaid on portrait, top-right corner with 1/3 outside)
|
||
f.classIcon = SFrames:CreateClassIcon(f, 16)
|
||
f.classIcon.overlay:SetPoint("CENTER", f.portrait, "TOPRIGHT", 0, 0)
|
||
|
||
-- Party Leader Icon
|
||
local leaderOvr = CreateFrame("Frame", nil, f)
|
||
leaderOvr:SetFrameLevel((f:GetFrameLevel() or 0) + 4)
|
||
leaderOvr:SetAllPoints(f)
|
||
f.leaderIcon = leaderOvr:CreateTexture(nil, "OVERLAY")
|
||
f.leaderIcon:SetTexture("Interface\\GroupFrame\\UI-Group-LeaderIcon")
|
||
f.leaderIcon:SetWidth(16)
|
||
f.leaderIcon:SetHeight(16)
|
||
f.leaderIcon:SetPoint("TOPLEFT", f.portrait, "TOPLEFT", -4, 4)
|
||
f.leaderIcon:Hide()
|
||
|
||
-- Raid Target Icon (top center of health bar, half outside frame)
|
||
local raidIconSize = 22
|
||
local raidIconOvr = CreateFrame("Frame", nil, f)
|
||
raidIconOvr:SetFrameLevel((f:GetFrameLevel() or 0) + 5)
|
||
raidIconOvr:SetWidth(raidIconSize)
|
||
raidIconOvr:SetHeight(raidIconSize)
|
||
raidIconOvr:SetPoint("CENTER", f.health, "TOP", 0, 0)
|
||
f.raidIcon = raidIconOvr:CreateTexture(nil, "OVERLAY")
|
||
f.raidIcon:SetTexture("Interface\\TargetingFrame\\UI-RaidTargetingIcons")
|
||
f.raidIcon:SetAllPoints(raidIconOvr)
|
||
f.raidIcon:Hide()
|
||
f.raidIconOverlay = raidIconOvr
|
||
|
||
self.frame = f
|
||
self:ApplyConfig()
|
||
self.frame:Show() -- Ensure it's explicitly shown
|
||
self:UpdateAll()
|
||
|
||
-- Events
|
||
SFrames:RegisterEvent("UNIT_HEALTH", function() if arg1 == "player" then self:UpdateHealth() end end)
|
||
SFrames:RegisterEvent("UNIT_MAXHEALTH", function() if arg1 == "player" then self:UpdateHealth() end end)
|
||
SFrames:RegisterEvent("UNIT_MANA", function() if arg1 == "player" then self:UpdatePower() end end)
|
||
SFrames:RegisterEvent("UNIT_MAXMANA", function() if arg1 == "player" then self:UpdatePower() end end)
|
||
SFrames:RegisterEvent("UNIT_ENERGY", function() if arg1 == "player" then self:UpdatePower() end end)
|
||
SFrames:RegisterEvent("UNIT_MAXENERGY", function() if arg1 == "player" then self:UpdatePower() end end)
|
||
SFrames:RegisterEvent("UNIT_RAGE", function() if arg1 == "player" then self:UpdatePower() end end)
|
||
SFrames:RegisterEvent("UNIT_MAXRAGE", function() if arg1 == "player" then self:UpdatePower() end end)
|
||
SFrames:RegisterEvent("PLAYER_ENTERING_WORLD", function() self:UpdateAll() end)
|
||
SFrames:RegisterEvent("PLAYER_LEVEL_UP", function()
|
||
if arg1 then self.currentLevel = arg1 end
|
||
self:UpdateAll()
|
||
if arg1 and mod(arg1, 2) == 0 and (not SFramesDB or SFramesDB.trainerReminder ~= false) then
|
||
self:ShowTrainerReminder(arg1)
|
||
end
|
||
end)
|
||
SFrames:RegisterEvent("TRAINER_SHOW", function()
|
||
SFrames.Player.trainerScannedThisVisit = nil
|
||
SFrames.Player.trainerShowPending = true
|
||
SFrames.Player.trainerRetryCount = 0
|
||
if not SFrames.Player.trainerRetryFrame then
|
||
SFrames.Player.trainerRetryFrame = CreateFrame("Frame")
|
||
end
|
||
SFrames.Player.trainerRetryFrame:SetScript("OnUpdate", function()
|
||
if not this.elapsed then this.elapsed = 0 end
|
||
this.elapsed = this.elapsed + arg1
|
||
if this.elapsed < 0.3 then return end
|
||
this.elapsed = 0
|
||
if SFrames.Player.trainerScannedThisVisit then
|
||
this:SetScript("OnUpdate", nil)
|
||
return
|
||
end
|
||
SFrames.Player.trainerRetryCount = (SFrames.Player.trainerRetryCount or 0) + 1
|
||
if SFrames.Player.trainerRetryCount > 10 then
|
||
SFrames.Player:ScanTrainer()
|
||
this:SetScript("OnUpdate", nil)
|
||
return
|
||
end
|
||
SFrames.Player:ScanTrainer()
|
||
end)
|
||
end)
|
||
SFrames:RegisterEvent("TRAINER_UPDATE", function()
|
||
if not SFrames.Player.trainerScannedThisVisit then
|
||
SFrames.Player:ScanTrainer()
|
||
end
|
||
end)
|
||
SFrames:RegisterEvent("PARTY_MEMBERS_CHANGED", function() self:UpdateLeaderIcon() end)
|
||
SFrames:RegisterEvent("PARTY_LEADER_CHANGED", function() self:UpdateLeaderIcon() end)
|
||
SFrames:RegisterEvent("RAID_TARGET_UPDATE", function() self:UpdateRaidIcon() end)
|
||
SFrames:RegisterEvent("UNIT_PORTRAIT_UPDATE", function()
|
||
if arg1 == "player" and self.frame.portrait and not (SFramesDB and SFramesDB.playerShowPortrait == false) then
|
||
self.frame.portrait:SetUnit("player")
|
||
self.frame.portrait:SetCamera(0)
|
||
self.frame.portrait:SetPosition(-1.0, 0, 0)
|
||
end
|
||
end)
|
||
SFrames:RegisterEvent("UNIT_DISPLAYPOWER", function() if arg1 == "player" then self:UpdatePowerType(); self:UpdatePower() end end)
|
||
SFrames:RegisterEvent("UPDATE_SHAPESHIFT_FORM", function() self:UpdatePowerType(); self:UpdatePower() end)
|
||
SFrames:RegisterEvent("PLAYER_UPDATE_RESTING", function() self:UpdateRestingStatus() end)
|
||
|
||
f.unit = "player"
|
||
f:SetScript("OnEnter", function()
|
||
if SetMouseoverUnit then SetMouseoverUnit(this.unit) end
|
||
GameTooltip_SetDefaultAnchor(GameTooltip, this)
|
||
GameTooltip:SetUnit(this.unit)
|
||
GameTooltip:Show()
|
||
end)
|
||
f:SetScript("OnLeave", function()
|
||
if SetMouseoverUnit then SetMouseoverUnit() end
|
||
GameTooltip:Hide()
|
||
end)
|
||
end
|
||
|
||
function SFrames.Player:HasSpellInBook(spellName)
|
||
local baseName = string.gsub(spellName, " 等级 %d+$", "")
|
||
baseName = string.gsub(baseName, " %d+级$", "")
|
||
local i = 1
|
||
while true do
|
||
local name = GetSpellName(i, BOOKTYPE_SPELL)
|
||
if not name then return false end
|
||
if name == spellName or name == baseName then return true end
|
||
i = i + 1
|
||
end
|
||
end
|
||
|
||
function SFrames.Player:GetSpellIcon(skillDisplayName)
|
||
local baseName = string.gsub(skillDisplayName, " %d+级$", "")
|
||
local altName1 = string.gsub(baseName, ":", ":")
|
||
local altName2 = string.gsub(baseName, ":", ":")
|
||
local i = 1
|
||
while true do
|
||
local name = GetSpellName(i, BOOKTYPE_SPELL)
|
||
if not name then break end
|
||
if name == baseName or name == altName1 or name == altName2 then
|
||
return GetSpellTexture(i, BOOKTYPE_SPELL)
|
||
end
|
||
i = i + 1
|
||
end
|
||
return nil
|
||
end
|
||
|
||
function SFrames.Player:ParseTrainerTooltipLevel(serviceIndex)
|
||
if not self.trainerScanTip then
|
||
local tt = CreateFrame("GameTooltip", "NanamiTrainerScanTip", nil, "GameTooltipTemplate")
|
||
tt:SetOwner(UIParent, "ANCHOR_NONE")
|
||
self.trainerScanTip = tt
|
||
end
|
||
local tt = self.trainerScanTip
|
||
tt:ClearLines()
|
||
if not tt.SetTrainerService then return nil end
|
||
tt:SetTrainerService(serviceIndex)
|
||
for j = 2, tt:NumLines() do
|
||
local textObj = getglobal("NanamiTrainerScanTipTextLeft" .. j)
|
||
if textObj then
|
||
local text = textObj:GetText()
|
||
if text then
|
||
local _, _, lvl = string.find(text, "需要等级%s*(%d+)")
|
||
if not lvl then
|
||
_, _, lvl = string.find(text, "Requires Level (%d+)")
|
||
end
|
||
if lvl then return tonumber(lvl) end
|
||
end
|
||
end
|
||
end
|
||
return nil
|
||
end
|
||
|
||
function SFrames.Player:FindSkillLevelInStaticData(classEn, skillName)
|
||
local staticData = SFrames.ClassSkillData and SFrames.ClassSkillData[classEn]
|
||
if not staticData then return nil end
|
||
local baseName = string.gsub(skillName, " %d+级$", "")
|
||
baseName = string.gsub(baseName, "(等级 %d+)$", "")
|
||
baseName = string.gsub(baseName, "%s+$", "")
|
||
for level, skills in pairs(staticData) do
|
||
for _, s in ipairs(skills) do
|
||
local sBase = string.gsub(s, " %d+级$", "")
|
||
sBase = string.gsub(sBase, "(等级 %d+)$", "")
|
||
sBase = string.gsub(sBase, "%s+$", "")
|
||
if baseName == sBase or skillName == s then
|
||
return level
|
||
end
|
||
end
|
||
end
|
||
local talentData = SFrames.TalentTrainerSkills and SFrames.TalentTrainerSkills[classEn]
|
||
if talentData then
|
||
for level, entries in pairs(talentData) do
|
||
for _, entry in ipairs(entries) do
|
||
local tBase = string.gsub(entry[1], " %d+级$", "")
|
||
if baseName == tBase or skillName == entry[1] then
|
||
return level
|
||
end
|
||
end
|
||
end
|
||
end
|
||
return nil
|
||
end
|
||
|
||
function SFrames.Player:ScanTrainer()
|
||
if self.scanningTrainer then return end
|
||
self.scanningTrainer = true
|
||
|
||
local hex = SFrames.Theme and SFrames.Theme:GetAccentHex() or "ffffb3d9"
|
||
|
||
if not GetNumTrainerServices then
|
||
DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r |cffff6666训练师扫描失败: GetNumTrainerServices 不存在|r")
|
||
self.scanningTrainer = nil
|
||
return
|
||
end
|
||
if IsTradeskillTrainer and IsTradeskillTrainer() then
|
||
self.scanningTrainer = nil
|
||
return
|
||
end
|
||
|
||
if SetTrainerServiceTypeFilter then
|
||
SetTrainerServiceTypeFilter("available", 1)
|
||
SetTrainerServiceTypeFilter("unavailable", 1)
|
||
SetTrainerServiceTypeFilter("used", 1)
|
||
end
|
||
|
||
local _, classEn = UnitClass("player")
|
||
if not classEn or not SFramesDB then self.scanningTrainer = nil return end
|
||
|
||
local cache = {}
|
||
local numServices = GetNumTrainerServices()
|
||
if not numServices or numServices == 0 then
|
||
DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r |cffff6666训练师扫描: 未检测到技能列表 (0项),将在数据加载后重试|r")
|
||
self.scanningTrainer = nil
|
||
return
|
||
end
|
||
|
||
local hasLevelAPI = (GetTrainerServiceLevelReq ~= nil)
|
||
local noLevelCount = 0
|
||
local totalAdded = 0
|
||
|
||
local iconMissCount = 0
|
||
|
||
for i = 1, numServices do
|
||
local name, subText, serviceType = GetTrainerServiceInfo(i)
|
||
|
||
if name and subText and subText ~= "" then
|
||
local icon = GetTrainerServiceIcon and GetTrainerServiceIcon(i)
|
||
|
||
if (not icon or icon == "") and ClassTrainerFrame then
|
||
local skillButton = getglobal("ClassTrainerSkill" .. i)
|
||
if skillButton then
|
||
local iconTex = getglobal("ClassTrainerSkill" .. i .. "Icon")
|
||
if iconTex and iconTex.GetTexture then
|
||
icon = iconTex:GetTexture()
|
||
end
|
||
end
|
||
end
|
||
|
||
if not icon or icon == "" then
|
||
iconMissCount = iconMissCount + 1
|
||
end
|
||
|
||
local reqLevel
|
||
if hasLevelAPI then
|
||
reqLevel = GetTrainerServiceLevelReq(i)
|
||
end
|
||
if not reqLevel or reqLevel == 0 then
|
||
reqLevel = self:ParseTrainerTooltipLevel(i)
|
||
end
|
||
if not reqLevel or reqLevel == 0 then
|
||
local lookupName = name .. " " .. subText
|
||
reqLevel = self:FindSkillLevelInStaticData(classEn, lookupName)
|
||
if not reqLevel then
|
||
reqLevel = self:FindSkillLevelInStaticData(classEn, name)
|
||
end
|
||
end
|
||
if not reqLevel or reqLevel == 0 then
|
||
noLevelCount = noLevelCount + 1
|
||
end
|
||
|
||
if reqLevel and reqLevel > 0 then
|
||
local displayName = name .. " " .. subText
|
||
if not cache[reqLevel] then
|
||
cache[reqLevel] = {}
|
||
end
|
||
table.insert(cache[reqLevel], {
|
||
name = displayName,
|
||
icon = icon or "",
|
||
})
|
||
totalAdded = totalAdded + 1
|
||
end
|
||
end
|
||
end
|
||
|
||
if not SFramesDB.trainerCache then
|
||
SFramesDB.trainerCache = {}
|
||
end
|
||
SFramesDB.trainerCache[classEn] = cache
|
||
self.trainerScannedThisVisit = true
|
||
|
||
local levelCount = 0
|
||
for _ in pairs(cache) do levelCount = levelCount + 1 end
|
||
local iconOk = totalAdded - iconMissCount
|
||
DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r 已从训练师更新技能数据(" .. levelCount .. " 个等级," .. totalAdded .. " 项技能," .. iconOk .. " 个图标)")
|
||
if noLevelCount > 0 then
|
||
DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r |cffff9900有 " .. noLevelCount .. " 项技能无法确定等级要求,已跳过|r")
|
||
end
|
||
if iconMissCount > 0 then
|
||
DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r |cffff9900有 " .. iconMissCount .. " 项技能未获取到图标|r")
|
||
end
|
||
|
||
self.scanningTrainer = nil
|
||
end
|
||
|
||
function SFrames.Player:ShowTrainerReminder(newLevel)
|
||
local _, classEn = UnitClass("player")
|
||
local classNames = {
|
||
WARRIOR = "战士", PALADIN = "圣骑士", HUNTER = "猎人",
|
||
ROGUE = "盗贼", PRIEST = "牧师", SHAMAN = "萨满祭司",
|
||
MAGE = "法师", WARLOCK = "术士", DRUID = "德鲁伊",
|
||
}
|
||
local className = classNames[classEn] or UnitClass("player")
|
||
|
||
local classFallbackIcons = {
|
||
WARRIOR = "Interface\\Icons\\Ability_Warrior_OffensiveStance",
|
||
PALADIN = "Interface\\Icons\\Spell_Holy_HolyBolt",
|
||
HUNTER = "Interface\\Icons\\Ability_Marksmanship",
|
||
ROGUE = "Interface\\Icons\\Ability_BackStab",
|
||
PRIEST = "Interface\\Icons\\Spell_Holy_HolyBolt",
|
||
SHAMAN = "Interface\\Icons\\Spell_Nature_Lightning",
|
||
MAGE = "Interface\\Icons\\Spell_Frost_IceStorm",
|
||
WARLOCK = "Interface\\Icons\\Spell_Shadow_DeathCoil",
|
||
DRUID = "Interface\\Icons\\Spell_Nature_Regeneration",
|
||
}
|
||
local fallbackIcon = classFallbackIcons[classEn] or "Interface\\Icons\\Trade_Engraving"
|
||
|
||
local allSkills = {}
|
||
local allIcons = {}
|
||
local talentSkills = {}
|
||
local usedCache = false
|
||
|
||
local classCache = SFramesDB and SFramesDB.trainerCache and SFramesDB.trainerCache[classEn]
|
||
if classCache then
|
||
local lowLevel = newLevel - 1
|
||
if lowLevel < 1 then lowLevel = 1 end
|
||
for lv = lowLevel, newLevel do
|
||
if classCache[lv] then
|
||
usedCache = true
|
||
for _, entry in ipairs(classCache[lv]) do
|
||
if not self:HasSpellInBook(entry.name) then
|
||
table.insert(allSkills, entry.name)
|
||
local ico = entry.icon
|
||
if not ico or ico == "" then
|
||
ico = self:GetSpellIcon(entry.name) or fallbackIcon
|
||
end
|
||
table.insert(allIcons, ico)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
if not usedCache then
|
||
local baseSkills = SFrames.ClassSkillData and SFrames.ClassSkillData[classEn] and SFrames.ClassSkillData[classEn][newLevel]
|
||
if baseSkills then
|
||
for _, s in ipairs(baseSkills) do
|
||
table.insert(allSkills, s)
|
||
table.insert(allIcons, self:GetSpellIcon(s) or fallbackIcon)
|
||
end
|
||
end
|
||
|
||
local talentData = SFrames.TalentTrainerSkills and SFrames.TalentTrainerSkills[classEn] and SFrames.TalentTrainerSkills[classEn][newLevel]
|
||
if talentData then
|
||
for _, entry in ipairs(talentData) do
|
||
local displayName = entry[1]
|
||
local requiredSpell = entry[2]
|
||
if self:HasSpellInBook(requiredSpell) then
|
||
table.insert(allSkills, displayName)
|
||
table.insert(allIcons, self:GetSpellIcon(displayName) or fallbackIcon)
|
||
table.insert(talentSkills, displayName)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
local mountQuest = SFrames.ClassMountQuests and SFrames.ClassMountQuests[classEn] and SFrames.ClassMountQuests[classEn][newLevel]
|
||
|
||
local skillCount = table.getn(allSkills)
|
||
|
||
if skillCount == 0 and not mountQuest then
|
||
return
|
||
end
|
||
|
||
local hex = SFrames.Theme and SFrames.Theme:GetAccentHex() or "ffffb3d9"
|
||
|
||
SFrames:Print(string.format("已达到 %d 级!你的%s训练师有新技能可以学习。", newLevel, className))
|
||
if skillCount > 0 then
|
||
DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r 可学习的技能(共 " .. skillCount .. " 项):")
|
||
local line = " "
|
||
local lineCount = 0
|
||
for i = 1, skillCount do
|
||
if lineCount > 0 then line = line .. ", " end
|
||
line = line .. "|cffffd100" .. allSkills[i] .. "|r"
|
||
lineCount = lineCount + 1
|
||
if i == skillCount or lineCount == 4 then
|
||
DEFAULT_CHAT_FRAME:AddMessage(line)
|
||
line = " "
|
||
lineCount = 0
|
||
end
|
||
end
|
||
end
|
||
if not usedCache and table.getn(talentSkills) > 0 then
|
||
DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r |cff00ff00(天赋)|r " .. table.concat(talentSkills, ", "))
|
||
end
|
||
if mountQuest then
|
||
DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r |cffffff00★|r " .. mountQuest)
|
||
end
|
||
|
||
local bannerMsg = string.format("Lv.%d - %s训练师有 %d 项新技能可学习", newLevel, className, skillCount)
|
||
if mountQuest then
|
||
bannerMsg = bannerMsg .. " + " .. mountQuest
|
||
end
|
||
if skillCount == 0 and not mountQuest then
|
||
bannerMsg = string.format("Lv.%d - 前往%s训练师查看可学技能", newLevel, className)
|
||
elseif skillCount == 0 and mountQuest then
|
||
bannerMsg = string.format("Lv.%d - %s", newLevel, mountQuest)
|
||
end
|
||
UIErrorsFrame:AddMessage(bannerMsg, 1.0, 0.82, 0.0, 1, 5)
|
||
PlaySound("LEVELUP")
|
||
|
||
if not self.trainerReminderFrame then
|
||
local fr = CreateFrame("Frame", "NanamiTrainerReminder", UIParent)
|
||
fr:SetWidth(440)
|
||
fr:SetHeight(106)
|
||
fr:SetPoint("TOP", UIParent, "TOP", 0, -120)
|
||
fr:SetFrameStrata("DIALOG")
|
||
|
||
local bg = fr:CreateTexture(nil, "BACKGROUND")
|
||
bg:SetAllPoints(fr)
|
||
bg:SetTexture(0, 0, 0, 0.78)
|
||
|
||
local border = fr:CreateTexture(nil, "BORDER")
|
||
border:SetPoint("TOPLEFT", fr, "TOPLEFT", -1, 1)
|
||
border:SetPoint("BOTTOMRIGHT", fr, "BOTTOMRIGHT", 1, -1)
|
||
border:SetTexture(1, 0.82, 0, 0.3)
|
||
|
||
local icon = fr:CreateTexture(nil, "ARTWORK")
|
||
icon:SetWidth(36)
|
||
icon:SetHeight(36)
|
||
icon:SetPoint("TOPLEFT", fr, "TOPLEFT", 10, -6)
|
||
icon:SetTexture("Interface\\Icons\\INV_Misc_Book_11")
|
||
fr.icon = icon
|
||
|
||
local title = fr:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||
title:SetPoint("TOPLEFT", icon, "TOPRIGHT", 10, -2)
|
||
title:SetPoint("RIGHT", fr, "RIGHT", -10, 0)
|
||
title:SetJustifyH("LEFT")
|
||
title:SetTextColor(1, 0.82, 0)
|
||
fr.title = title
|
||
|
||
local subtitle = fr:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
|
||
subtitle:SetPoint("TOPLEFT", icon, "TOPRIGHT", 10, -18)
|
||
subtitle:SetPoint("RIGHT", fr, "RIGHT", -10, 0)
|
||
subtitle:SetJustifyH("LEFT")
|
||
subtitle:SetTextColor(0.75, 0.75, 0.75)
|
||
fr.subtitle = subtitle
|
||
|
||
fr.skillIcons = {}
|
||
fr.skillBorders = {}
|
||
local maxIcons = 13
|
||
for idx = 1, maxIcons do
|
||
local bdr = fr:CreateTexture(nil, "BORDER")
|
||
bdr:SetWidth(30)
|
||
bdr:SetHeight(30)
|
||
bdr:SetPoint("TOPLEFT", fr, "TOPLEFT", 9 + (idx - 1) * 32, -45)
|
||
bdr:SetTexture(1, 0.82, 0, 0.25)
|
||
bdr:Hide()
|
||
fr.skillBorders[idx] = bdr
|
||
|
||
local si = fr:CreateTexture(nil, "ARTWORK")
|
||
si:SetWidth(28)
|
||
si:SetHeight(28)
|
||
si:SetPoint("CENTER", bdr, "CENTER", 0, 0)
|
||
si:SetTexCoord(0.07, 0.93, 0.07, 0.93)
|
||
si:Hide()
|
||
fr.skillIcons[idx] = si
|
||
end
|
||
|
||
local detail = fr:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
|
||
detail:SetPoint("BOTTOMLEFT", fr, "BOTTOMLEFT", 10, 8)
|
||
detail:SetPoint("RIGHT", fr, "RIGHT", -10, 0)
|
||
detail:SetJustifyH("LEFT")
|
||
detail:SetTextColor(0.85, 0.85, 0.85)
|
||
fr.detail = detail
|
||
|
||
fr:SetAlpha(0)
|
||
fr:Hide()
|
||
self.trainerReminderFrame = fr
|
||
end
|
||
|
||
local fr = self.trainerReminderFrame
|
||
|
||
for idx = 1, 13 do
|
||
fr.skillIcons[idx]:Hide()
|
||
fr.skillBorders[idx]:Hide()
|
||
end
|
||
|
||
local iconCount = 0
|
||
|
||
for i = 1, skillCount do
|
||
if iconCount >= 13 then break end
|
||
iconCount = iconCount + 1
|
||
fr.skillIcons[iconCount]:SetTexture(allIcons[i])
|
||
fr.skillIcons[iconCount]:Show()
|
||
fr.skillBorders[iconCount]:Show()
|
||
end
|
||
if mountQuest and iconCount < 13 then
|
||
iconCount = iconCount + 1
|
||
fr.skillIcons[iconCount]:SetTexture("Interface\\Icons\\Spell_Nature_Swiftness")
|
||
fr.skillIcons[iconCount]:Show()
|
||
fr.skillBorders[iconCount]:Show()
|
||
end
|
||
|
||
fr.title:SetText(string.format("已达到 |cffffffff%d|r 级 — %s训练师有新技能", newLevel, className))
|
||
|
||
if skillCount > 0 or mountQuest then
|
||
local preview = ""
|
||
if skillCount > 0 then
|
||
preview = "|cffffd100" .. allSkills[1] .. "|r"
|
||
if skillCount > 1 then preview = preview .. ", |cffffd100" .. allSkills[2] .. "|r" end
|
||
if skillCount > 2 then preview = preview .. ", |cffffd100" .. allSkills[3] .. "|r" end
|
||
if skillCount > 3 then preview = preview .. " 等 " .. skillCount .. " 项" end
|
||
end
|
||
if mountQuest then
|
||
if preview ~= "" then preview = preview .. " | " end
|
||
preview = preview .. "|cffffff00" .. mountQuest .. "|r"
|
||
end
|
||
fr.subtitle:SetText(preview)
|
||
fr.detail:SetText("详见聊天窗口")
|
||
else
|
||
fr.subtitle:SetText("")
|
||
fr.detail:SetText("前往职业训练师查看可学习的技能")
|
||
end
|
||
|
||
if iconCount > 0 then
|
||
fr:SetHeight(106)
|
||
else
|
||
fr:SetHeight(80)
|
||
end
|
||
fr:Show()
|
||
fr:SetAlpha(0)
|
||
|
||
fr.fadeState = "in"
|
||
fr.fadeTimer = 0
|
||
fr.holdTimer = 0
|
||
fr:SetScript("OnUpdate", function()
|
||
local dt = arg1
|
||
if fr.fadeState == "in" then
|
||
fr.fadeTimer = fr.fadeTimer + dt
|
||
local a = fr.fadeTimer / 0.5
|
||
if a >= 1 then
|
||
a = 1
|
||
fr.fadeState = "hold"
|
||
end
|
||
fr:SetAlpha(a)
|
||
elseif fr.fadeState == "hold" then
|
||
fr.holdTimer = fr.holdTimer + dt
|
||
if fr.holdTimer >= 8 then
|
||
fr.fadeState = "out"
|
||
fr.fadeTimer = 0
|
||
end
|
||
elseif fr.fadeState == "out" then
|
||
fr.fadeTimer = fr.fadeTimer + dt
|
||
local a = 1 - fr.fadeTimer / 1.0
|
||
if a <= 0 then
|
||
a = 0
|
||
fr:Hide()
|
||
fr:SetScript("OnUpdate", nil)
|
||
end
|
||
fr:SetAlpha(a)
|
||
end
|
||
end)
|
||
end
|
||
|
||
function SFrames.Player:UpdateAll()
|
||
if not self.frame then return end
|
||
self:UpdateHealth()
|
||
self:UpdatePowerType()
|
||
self:UpdatePower()
|
||
self:UpdateLeaderIcon()
|
||
self:UpdateRaidIcon()
|
||
self:UpdateRestingStatus()
|
||
|
||
local name = UnitName("player") or ""
|
||
-- Use the stored level from PLAYER_LEVEL_UP if it exists, since API might lag slightly
|
||
local level = self.currentLevel or UnitLevel("player")
|
||
local formattedLevel = string.format("|cffffff00%d|r", level)
|
||
|
||
if SFramesDB and SFramesDB.showLevel == false then
|
||
formattedLevel = ""
|
||
else
|
||
formattedLevel = formattedLevel .. " "
|
||
end
|
||
|
||
local showPortrait = not (SFramesDB and SFramesDB.playerShowPortrait == false)
|
||
if showPortrait and self.frame.portrait then
|
||
self.frame.portrait:SetUnit("player")
|
||
self.frame.portrait:SetCamera(0)
|
||
self.frame.portrait:SetPosition(-1.0, 0, 0)
|
||
end
|
||
|
||
-- Class Color for Health
|
||
local localizedClass, class = UnitClass("player")
|
||
local className = GetChineseClassName(class, localizedClass)
|
||
local nameLine = formattedLevel .. name
|
||
local showClassText = not (SFramesDB and SFramesDB.playerShowClass == false)
|
||
if showClassText and className and className ~= "" then
|
||
nameLine = nameLine .. " " .. className
|
||
end
|
||
|
||
if not (SFramesDB and SFramesDB.playerShowClassIcon == false) then
|
||
SFrames:SetClassIcon(self.frame.classIcon, class)
|
||
else
|
||
self.frame.classIcon:Hide()
|
||
if self.frame.classIcon.overlay then self.frame.classIcon.overlay:Hide() end
|
||
end
|
||
|
||
local useClassColor = not (SFramesDB and SFramesDB.classColorHealth == false)
|
||
|
||
if useClassColor and class and SFrames.Config.colors.class[class] then
|
||
local color = SFrames.Config.colors.class[class]
|
||
self.frame.health:SetStatusBarColor(color.r, color.g, color.b)
|
||
|
||
-- Apply Class Color to Name
|
||
self.frame.nameText:SetText(nameLine)
|
||
self.frame.nameText:SetTextColor(color.r, color.g, color.b)
|
||
else
|
||
self.frame.health:SetStatusBarColor(0, 1, 0)
|
||
self.frame.nameText:SetText(nameLine)
|
||
self.frame.nameText:SetTextColor(1, 1, 1)
|
||
end
|
||
end
|
||
|
||
function SFrames.Player:UpdateRestingStatus()
|
||
if not self.frame or not self.frame.restOverlay then return end
|
||
if IsResting() then
|
||
self.frame.restOverlay:Show()
|
||
else
|
||
self.frame.restOverlay:Hide()
|
||
end
|
||
end
|
||
|
||
function SFrames.Player:UpdateLeaderIcon()
|
||
if IsPartyLeader() then
|
||
self.frame.leaderIcon:Show()
|
||
else
|
||
self.frame.leaderIcon:Hide()
|
||
end
|
||
end
|
||
|
||
function SFrames.Player:UpdateRaidIcon()
|
||
if not (self.frame and self.frame.raidIcon) then return end
|
||
if not GetRaidTargetIndex then
|
||
self.frame.raidIcon:Hide()
|
||
return
|
||
end
|
||
local index = GetRaidTargetIndex("player")
|
||
if index and index > 0 and index <= 8 then
|
||
local col = math.mod(index - 1, 4)
|
||
local row = math.floor((index - 1) / 4)
|
||
self.frame.raidIcon:SetTexCoord(col * 0.25, (col + 1) * 0.25, row * 0.25, (row + 1) * 0.25)
|
||
self.frame.raidIcon:Show()
|
||
else
|
||
self.frame.raidIcon:Hide()
|
||
end
|
||
end
|
||
|
||
function SFrames.Player:UpdateHealth()
|
||
local hp = UnitHealth("player")
|
||
local maxHp = UnitHealthMax("player")
|
||
|
||
if CheckSuperWow then
|
||
local ok, hasSW = pcall(CheckSuperWow)
|
||
if ok and hasSW then
|
||
local ok2, realHp = pcall(UnitHealth, "player")
|
||
if ok2 then
|
||
hp = realHp or hp
|
||
end
|
||
local ok3, realMaxHp = pcall(UnitHealthMax, "player")
|
||
if ok3 then
|
||
maxHp = realMaxHp or maxHp
|
||
end
|
||
end
|
||
end
|
||
|
||
self.frame.health:SetMinMaxValues(0, maxHp)
|
||
self.frame.health:SetValue(hp)
|
||
|
||
if maxHp > 0 then
|
||
self.frame.healthText:SetText(hp .. " / " .. maxHp)
|
||
else
|
||
self.frame.healthText:SetText(hp)
|
||
end
|
||
|
||
self:UpdateHealPrediction()
|
||
end
|
||
|
||
function SFrames.Player:UpdateHealPrediction()
|
||
if not (self.frame and self.frame.health and self.frame.health.healPredMine and self.frame.health.healPredOther and self.frame.health.healPredOver) then return end
|
||
local predMine = self.frame.health.healPredMine
|
||
local predOther = self.frame.health.healPredOther
|
||
local predOver = self.frame.health.healPredOver
|
||
|
||
local function HidePredictions()
|
||
predMine:Hide()
|
||
predOther:Hide()
|
||
predOver:Hide()
|
||
end
|
||
|
||
local hp = UnitHealth("player") or 0
|
||
local maxHp = UnitHealthMax("player") or 0
|
||
|
||
if CheckSuperWow then
|
||
local ok, hasSW = pcall(CheckSuperWow)
|
||
if ok and hasSW then
|
||
local ok2, realHp = pcall(UnitHealth, "player")
|
||
if ok2 then
|
||
hp = realHp or hp
|
||
end
|
||
local ok3, realMaxHp = pcall(UnitHealthMax, "player")
|
||
if ok3 then
|
||
maxHp = realMaxHp or maxHp
|
||
end
|
||
end
|
||
end
|
||
|
||
if maxHp <= 0 or UnitIsDeadOrGhost("player") then
|
||
HidePredictions()
|
||
return
|
||
end
|
||
|
||
local totalIncoming, mineIncoming, othersIncoming = 0, 0, 0
|
||
|
||
local ok, t, m, o = pcall(function() return GetIncomingHeals("player") end)
|
||
if ok then
|
||
totalIncoming, mineIncoming, othersIncoming = t or 0, m or 0, o or 0
|
||
end
|
||
local missing = maxHp - hp
|
||
if missing <= 0 and (mineIncoming <= 0 and othersIncoming <= 0) then
|
||
HidePredictions()
|
||
return
|
||
end
|
||
|
||
local mineShown = math.min(math.max(0, mineIncoming), missing)
|
||
local remaining = missing - mineShown
|
||
local otherShown = math.min(math.max(0, othersIncoming), remaining)
|
||
if mineShown <= 0 and otherShown <= 0 and (mineIncoming <= 0 and othersIncoming <= 0) then
|
||
HidePredictions()
|
||
return
|
||
end
|
||
|
||
local showPortrait = SFramesDB and SFramesDB.playerShowPortrait ~= false
|
||
local barWidth = self.frame:GetWidth() - (showPortrait and (self.frame.portrait:GetWidth() + 2) or 2)
|
||
if barWidth <= 0 then
|
||
HidePredictions()
|
||
return
|
||
end
|
||
|
||
local currentWidth = (hp / maxHp) * barWidth
|
||
if currentWidth < 0 then currentWidth = 0 end
|
||
if currentWidth > barWidth then currentWidth = barWidth end
|
||
|
||
local availableWidth = barWidth - currentWidth
|
||
if availableWidth <= 0 and (mineIncoming <= 0 and othersIncoming <= 0) then
|
||
HidePredictions()
|
||
return
|
||
end
|
||
|
||
local mineWidth = 0
|
||
local otherWidth = 0
|
||
if missing > 0 then
|
||
mineWidth = (mineShown / missing) * availableWidth
|
||
otherWidth = (otherShown / missing) * availableWidth
|
||
if mineWidth < 0 then mineWidth = 0 end
|
||
if otherWidth < 0 then otherWidth = 0 end
|
||
if mineWidth > availableWidth then mineWidth = availableWidth end
|
||
if otherWidth > (availableWidth - mineWidth) then
|
||
otherWidth = availableWidth - mineWidth
|
||
end
|
||
end
|
||
|
||
if mineWidth > 0 then
|
||
predMine:ClearAllPoints()
|
||
predMine:SetPoint("TOPLEFT", self.frame.health, "TOPLEFT", currentWidth, 0)
|
||
predMine:SetPoint("BOTTOMLEFT", self.frame.health, "BOTTOMLEFT", currentWidth, 0)
|
||
predMine:SetWidth(mineWidth)
|
||
predMine:SetHeight(self.frame.health:GetHeight())
|
||
predMine:Show()
|
||
else
|
||
predMine:Hide()
|
||
end
|
||
|
||
if otherWidth > 0 then
|
||
predOther:ClearAllPoints()
|
||
predOther:SetPoint("TOPLEFT", self.frame.health, "TOPLEFT", currentWidth + mineWidth, 0)
|
||
predOther:SetPoint("BOTTOMLEFT", self.frame.health, "BOTTOMLEFT", currentWidth + mineWidth, 0)
|
||
predOther:SetWidth(otherWidth)
|
||
predOther:SetHeight(self.frame.health:GetHeight())
|
||
predOther:Show()
|
||
else
|
||
predOther:Hide()
|
||
end
|
||
|
||
local totalIncomingValue = mineIncoming + othersIncoming
|
||
local overHeal = totalIncomingValue - missing
|
||
if overHeal > 0 then
|
||
local overWidth = math.floor((overHeal / maxHp) * barWidth + 0.5)
|
||
if overWidth > 0 then
|
||
predOver:ClearAllPoints()
|
||
predOver:SetPoint("TOPLEFT", self.frame.health, "TOPRIGHT", 0, 0)
|
||
predOver:SetPoint("BOTTOMLEFT", self.frame.health, "BOTTOMRIGHT", 0, 0)
|
||
predOver:SetWidth(overWidth)
|
||
predOver:SetHeight(self.frame.health:GetHeight())
|
||
predOver:Show()
|
||
else
|
||
predOver:Hide()
|
||
end
|
||
else
|
||
predOver:Hide()
|
||
end
|
||
end
|
||
|
||
function SFrames.Player:UpdatePowerType()
|
||
local powerType = UnitPowerType("player")
|
||
local color = SFrames.Config.colors.power[powerType]
|
||
if color then
|
||
self.frame.power:SetStatusBarColor(color.r, color.g, color.b)
|
||
else
|
||
self.frame.power:SetStatusBarColor(0, 0, 1)
|
||
end
|
||
end
|
||
|
||
function SFrames.Player:GetDruidAltMana(currentPower, currentMaxPower)
|
||
-- Method 1: DruidManaLib (tracks regen ticks, MP5, talents, shapeshift cost)
|
||
if AceLibrary and AceLibrary.HasInstance and AceLibrary:HasInstance("DruidManaLib-1.0") then
|
||
local ok, lib = pcall(function() return AceLibrary("DruidManaLib-1.0") end)
|
||
if ok and lib and lib.GetMana then
|
||
local mana, maxMana = lib:GetMana()
|
||
if type(mana) == "number" and type(maxMana) == "number" and maxMana > 0 then
|
||
return math.floor(mana + 0.5), math.floor(maxMana + 0.5)
|
||
end
|
||
end
|
||
end
|
||
|
||
-- Method 2: SuperWow returns real mana as second value of UnitMana
|
||
if CheckSuperWow then
|
||
local ok, hasSW = pcall(CheckSuperWow)
|
||
if ok and hasSW then
|
||
local ok2, _, realMana = pcall(UnitMana, "player")
|
||
local ok3, _, realMax = pcall(UnitManaMax, "player")
|
||
if ok2 and ok3 and type(realMana) == "number" and type(realMax) == "number" and realMax > 0 then
|
||
return realMana, realMax
|
||
end
|
||
end
|
||
end
|
||
|
||
-- Method 3: TBC-style UnitPower API
|
||
if UnitPower and UnitPowerMax then
|
||
local okMana, pMana = pcall(function() return UnitPower("player", 0) end)
|
||
local okMax, pMax = pcall(function() return UnitPowerMax("player", 0) end)
|
||
if okMana and okMax and type(pMana) == "number" and type(pMax) == "number" and pMax > 0 then
|
||
if not (pMax == currentMaxPower and pMana == currentPower) and pMax > 100 then
|
||
return pMana, pMax
|
||
end
|
||
end
|
||
end
|
||
|
||
return nil, nil
|
||
end
|
||
|
||
function SFrames.Player:UpdateFiveSecondRule()
|
||
if not (self.frame and self.frame.power and self.frame.power.fsrGlow) then return end
|
||
|
||
local powerBar = self.frame.power
|
||
local glow = powerBar.fsrGlow
|
||
|
||
local function HideTicker()
|
||
glow:Hide()
|
||
end
|
||
|
||
local powerType = UnitPowerType("player")
|
||
if powerType ~= 0 then
|
||
self.fiveSecondStart = nil
|
||
self.fiveSecondPendingStart = nil
|
||
self.fiveSecondLastMana = nil
|
||
HideTicker()
|
||
return
|
||
end
|
||
|
||
local now = GetTime()
|
||
|
||
-- Continuous monitoring: whenever mana drops, (re)start 5-second rule.
|
||
-- If mana drops while hard-casting, delay start until cast end.
|
||
local currentMana = UnitMana("player") or 0
|
||
if self.fiveSecondLastMana and currentMana < self.fiveSecondLastMana then
|
||
local delay = 0
|
||
local cb = self.frame and self.frame.castbar
|
||
if cb and cb.casting and cb.startTime and cb.maxValue then
|
||
local castEnd = cb.startTime + cb.maxValue
|
||
if castEnd > now then
|
||
delay = castEnd - now
|
||
end
|
||
end
|
||
|
||
local startAt = now + delay + 0.08
|
||
if delay > 0 then
|
||
self.fiveSecondPendingStart = startAt
|
||
else
|
||
self.fiveSecondStart = startAt
|
||
self.fiveSecondPendingStart = nil
|
||
end
|
||
end
|
||
self.fiveSecondLastMana = currentMana
|
||
|
||
if self.fiveSecondPendingStart then
|
||
if now >= self.fiveSecondPendingStart then
|
||
self.fiveSecondStart = self.fiveSecondPendingStart
|
||
self.fiveSecondPendingStart = nil
|
||
else
|
||
HideTicker()
|
||
return
|
||
end
|
||
end
|
||
|
||
local startTime = self.fiveSecondStart
|
||
if not startTime then
|
||
HideTicker()
|
||
return
|
||
end
|
||
|
||
local elapsed = now - startTime
|
||
if elapsed < 0 then
|
||
HideTicker()
|
||
return
|
||
end
|
||
if elapsed >= 5 then
|
||
self.fiveSecondStart = nil
|
||
HideTicker()
|
||
return
|
||
end
|
||
|
||
local barWidth = powerBar:GetWidth() or 0
|
||
if barWidth <= 0 then
|
||
HideTicker()
|
||
return
|
||
end
|
||
|
||
local progress = elapsed / 5
|
||
if progress < 0 then progress = 0 end
|
||
if progress > 1 then progress = 1 end
|
||
|
||
local glowWidth = 40
|
||
if glowWidth > barWidth then glowWidth = barWidth end
|
||
|
||
local maxGlowX = barWidth - glowWidth
|
||
|
||
-- Move strictly from left edge to right edge across full 5 seconds.
|
||
local glowX = math.floor(progress * maxGlowX + 0.5)
|
||
|
||
local pulse = 0.82 + 0.18 * math.sin(GetTime() * 10)
|
||
|
||
glow:ClearAllPoints()
|
||
glow:SetPoint("TOPLEFT", powerBar, "TOPLEFT", glowX, 0)
|
||
glow:SetPoint("BOTTOMLEFT", powerBar, "BOTTOMLEFT", glowX, 0)
|
||
glow:SetWidth(glowWidth)
|
||
glow:SetAlpha(0.62 * pulse)
|
||
glow:Show()
|
||
end
|
||
|
||
function SFrames.Player:UpdatePower()
|
||
local power = UnitMana("player")
|
||
local maxPower = UnitManaMax("player")
|
||
self.frame.power:SetMinMaxValues(0, maxPower)
|
||
self.frame.power:SetValue(power)
|
||
self.frame.powerText:SetText(power .. " / " .. maxPower)
|
||
|
||
local _, class = UnitClass("player")
|
||
local powerType = UnitPowerType("player")
|
||
|
||
self:UpdateFiveSecondRule()
|
||
|
||
if class ~= "DRUID" then
|
||
if self.frame.manaText then self.frame.manaText:Hide() end
|
||
if self.frame.manaBar then self.frame.manaBar:Hide() end
|
||
return
|
||
end
|
||
|
||
if not self.druidManaCache then
|
||
self.druidManaCache = { value = 0, max = 0, ts = GetTime() }
|
||
end
|
||
|
||
if powerType == 0 then
|
||
self.druidManaCache.value = power or 0
|
||
self.druidManaCache.max = maxPower or 0
|
||
self.druidManaCache.ts = GetTime()
|
||
if self.frame.manaText then self.frame.manaText:Hide() end
|
||
if self.frame.manaBar then self.frame.manaBar:Hide() end
|
||
return
|
||
end
|
||
|
||
if not self.frame.manaText then
|
||
return
|
||
end
|
||
|
||
local mana, maxMana = self:GetDruidAltMana(power, maxPower)
|
||
if mana and maxMana and maxMana > 0 then
|
||
self.druidManaCache.value = mana
|
||
self.druidManaCache.max = maxMana
|
||
self.druidManaCache.ts = GetTime()
|
||
else
|
||
local cache = self.druidManaCache
|
||
if cache and cache.max and cache.max > 0 then
|
||
local now = GetTime()
|
||
local dt = now - (cache.ts or now)
|
||
if dt < 0 then dt = 0 end
|
||
|
||
local regen = 0
|
||
if GetManaRegen then
|
||
local base, casting = GetManaRegen()
|
||
if type(base) == "number" and base > 0 then
|
||
regen = base
|
||
elseif type(casting) == "number" and casting > 0 then
|
||
regen = casting
|
||
end
|
||
end
|
||
if (not regen or regen <= 0) and UnitStat then
|
||
local ok, _, spi = pcall(UnitStat, "player", 5)
|
||
if not (ok and type(spi) == "number" and spi > 0) then
|
||
ok, spi = pcall(function() return UnitStat("player", 5) end)
|
||
end
|
||
if ok and type(spi) == "number" and spi > 0 then
|
||
regen = (math.ceil(spi / 5) + 15) / 2
|
||
end
|
||
end
|
||
if regen and regen > 0 and dt > 0 then
|
||
cache.value = math.min(cache.max, (cache.value or 0) + regen * dt)
|
||
end
|
||
cache.ts = now
|
||
mana = math.floor((cache.value or 0) + 0.5)
|
||
maxMana = cache.max
|
||
end
|
||
end
|
||
|
||
if maxMana and maxMana > 0 then
|
||
local pct = math.floor(mana / maxMana * 100 + 0.5)
|
||
self.frame.manaText:SetText(pct .. "% " .. mana)
|
||
self.frame.manaText:Show()
|
||
if self.frame.manaBar then
|
||
self.frame.manaBar:SetMinMaxValues(0, maxMana)
|
||
self.frame.manaBar:SetValue(mana)
|
||
self.frame.manaBar:Show()
|
||
end
|
||
else
|
||
self.frame.manaText:SetText("--")
|
||
self.frame.manaText:Show()
|
||
if self.frame.manaBar then self.frame.manaBar:Hide() end
|
||
end
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Player Auras (Buffs / Debuffs)
|
||
--------------------------------------------------------------------------------
|
||
|
||
function SFrames.Player:CreateAuras()
|
||
-- Create 32 Buff Slots
|
||
self.frame.buffs = {}
|
||
self.frame.debuffs = {}
|
||
local size = 24
|
||
local spacing = 2
|
||
local rowSpacing = 1
|
||
local buffsPerRow = 9
|
||
|
||
for i = 1, 32 do
|
||
local b = CreateFrame("Button", "SFramesPlayerBuff"..i, self.frame)
|
||
b:SetWidth(size)
|
||
b:SetHeight(size)
|
||
SFrames:CreateUnitBackdrop(b)
|
||
|
||
b.icon = b:CreateTexture(nil, "ARTWORK")
|
||
b.icon:SetPoint("TOPLEFT", b, "TOPLEFT", 1, -1)
|
||
b.icon:SetPoint("BOTTOMRIGHT", b, "BOTTOMRIGHT", -1, 1)
|
||
b.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
|
||
|
||
b.cdText = SFrames:CreateFontString(b, 9, "CENTER")
|
||
b.cdText:SetPoint("BOTTOM", b, "BOTTOM", 0, 1)
|
||
b.cdText:SetTextColor(1, 0.82, 0)
|
||
b.cdText:SetShadowColor(0, 0, 0, 1)
|
||
b.cdText:SetShadowOffset(1, -1)
|
||
|
||
-- Tooltip support for precise buff checking
|
||
b:SetScript("OnEnter", function()
|
||
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT")
|
||
GameTooltip:SetPlayerBuff(this.buffIndex)
|
||
end)
|
||
b:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
||
|
||
-- Default row anchor
|
||
if i == 1 then
|
||
b:SetPoint("TOPLEFT", self.frame, "BOTTOMLEFT", 0, -1)
|
||
elseif math.mod(i - 1, buffsPerRow) == 0 then
|
||
b:SetPoint("TOP", self.frame.buffs[i-buffsPerRow], "BOTTOM", 0, -rowSpacing)
|
||
else
|
||
b:SetPoint("LEFT", self.frame.buffs[i-1], "RIGHT", spacing, 0)
|
||
end
|
||
|
||
b:Hide()
|
||
self.frame.buffs[i] = b
|
||
end
|
||
end
|
||
|
||
function SFrames.Player:UpdateAuras()
|
||
local slotIdx = 0
|
||
local hasGetPlayerBuffID = SFrames.superwow_active and type(GetPlayerBuffID) == "function"
|
||
for i = 0, 31 do
|
||
local buffIndex, untilCancelled = GetPlayerBuff(i, "HELPFUL")
|
||
if buffIndex and buffIndex >= 0 then
|
||
if not SFrames:IsBuffHidden(buffIndex) then
|
||
slotIdx = slotIdx + 1
|
||
if slotIdx > 32 then break end
|
||
local b = self.frame.buffs[slotIdx]
|
||
local texture = GetPlayerBuffTexture(buffIndex)
|
||
if texture then
|
||
b.icon:SetTexture(texture)
|
||
b.buffIndex = buffIndex
|
||
-- Store aura ID when SuperWoW is available
|
||
if hasGetPlayerBuffID then
|
||
local ok, auraID = pcall(GetPlayerBuffID, buffIndex)
|
||
b.auraID = ok and auraID or nil
|
||
else
|
||
b.auraID = nil
|
||
end
|
||
b:Show()
|
||
|
||
local timeLeft = GetPlayerBuffTimeLeft(buffIndex)
|
||
if timeLeft and timeLeft > 0 and timeLeft < 9999 then
|
||
b.cdText:SetText(SFrames:FormatTime(timeLeft))
|
||
else
|
||
b.cdText:SetText("")
|
||
end
|
||
else
|
||
b:Hide()
|
||
end
|
||
end
|
||
end
|
||
end
|
||
for j = slotIdx + 1, 32 do
|
||
self.frame.buffs[j]:Hide()
|
||
self.frame.buffs[j].auraID = nil
|
||
end
|
||
end
|
||
|
||
-- Initialization Hook for Auras and Castbar
|
||
local origInit = SFrames.Player.Initialize
|
||
function SFrames.Player:Initialize()
|
||
origInit(self)
|
||
|
||
-- Setup Auras
|
||
self:CreateAuras()
|
||
self.auraUpdater = CreateFrame("Frame")
|
||
self.auraUpdater.timer = 0
|
||
self.auraUpdater:SetScript("OnUpdate", function()
|
||
this.timer = this.timer + arg1
|
||
if this.timer >= 0.2 then
|
||
SFrames.Player:UpdateFiveSecondRule()
|
||
SFrames.Player:UpdateAuras()
|
||
SFrames.Player:UpdatePower()
|
||
SFrames.Player:UpdateHealPrediction()
|
||
this.timer = 0
|
||
end
|
||
end)
|
||
|
||
-- Setup Castbar
|
||
self:CreateCastbar()
|
||
|
||
-- Hide default castbar
|
||
if CastingBarFrame then
|
||
CastingBarFrame:UnregisterAllEvents()
|
||
CastingBarFrame:Hide()
|
||
end
|
||
|
||
-- Register mover
|
||
if SFrames.Movers and SFrames.Movers.RegisterMover and self.frame then
|
||
SFrames.Movers:RegisterMover("PlayerFrame", self.frame, "玩家",
|
||
"CENTER", "UIParent", "CENTER", -200, -100)
|
||
end
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Player Castbar
|
||
--------------------------------------------------------------------------------
|
||
|
||
local function GetLatencySeconds()
|
||
if GetNetStats then
|
||
local _, _, latency = GetNetStats()
|
||
if latency and latency > 0 then return latency / 1000 end
|
||
end
|
||
return 0
|
||
end
|
||
|
||
local function HSVtoRGB(h, s, v)
|
||
local i = math.floor(h * 6)
|
||
local f = h * 6 - i
|
||
local p = v * (1 - s)
|
||
local q = v * (1 - f * s)
|
||
local t = v * (1 - (1 - f) * s)
|
||
local m = math.mod(i, 6)
|
||
if m == 0 then return v, t, p
|
||
elseif m == 1 then return q, v, p
|
||
elseif m == 2 then return p, v, t
|
||
elseif m == 3 then return p, q, v
|
||
elseif m == 4 then return t, p, v
|
||
else return v, p, q end
|
||
end
|
||
|
||
local RAINBOW_TEX_PATH = "Interface\\AddOns\\Nanami-UI\\img\\progress"
|
||
local RAINBOW_SEG_COORDS = {
|
||
{40/512, 473/512, 45/512, 169/512},
|
||
{40/512, 473/512, 194/512, 318/512},
|
||
{40/512, 473/512, 343/512, 467/512},
|
||
}
|
||
|
||
local function UpdateRainbowProgress(cb, progress)
|
||
if not cb.rainbowSegs then return end
|
||
local barWidth = cb:GetWidth()
|
||
if barWidth <= 0 then return end
|
||
local fillW = progress * barWidth
|
||
for i = 1, 3 do
|
||
local seg = cb.rainbowSegs[i]
|
||
local segL = barWidth * (i - 1) / 3
|
||
local segR = barWidth * i / 3
|
||
if fillW <= segL then
|
||
seg:Hide()
|
||
else
|
||
local visR = math.min(fillW, segR)
|
||
local frac = (visR - segL) / (segR - segL)
|
||
if visR >= segR and i < 3 then
|
||
visR = segR + 2
|
||
end
|
||
seg:ClearAllPoints()
|
||
seg:SetPoint("TOPLEFT", cb, "TOPLEFT", segL, 0)
|
||
seg:SetPoint("BOTTOMRIGHT", cb, "BOTTOMLEFT", visR, 0)
|
||
local c = seg.fullCoords
|
||
seg:SetTexCoord(c[1], c[1] + (c[2] - c[1]) * frac, c[3], c[4])
|
||
seg:Show()
|
||
end
|
||
end
|
||
end
|
||
|
||
function SFrames.Player:CreateCastbar()
|
||
local cb = SFrames:CreateStatusBar(self.frame, "SFramesPlayerCastbar")
|
||
cb:SetHeight(SFrames.Config.castbarHeight)
|
||
cb:SetPoint("BOTTOMRIGHT", self.frame, "TOPRIGHT", 0, 6)
|
||
cb:SetPoint("BOTTOMLEFT", self.frame.portrait, "TOPLEFT", SFrames.Config.castbarHeight + 6, 6)
|
||
|
||
local cbbg = CreateFrame("Frame", nil, self.frame)
|
||
cbbg:SetPoint("TOPLEFT", cb, "TOPLEFT", -1, 1)
|
||
cbbg:SetPoint("BOTTOMRIGHT", cb, "BOTTOMRIGHT", 1, -1)
|
||
cbbg:SetFrameLevel(cb:GetFrameLevel() - 1)
|
||
SFrames:CreateUnitBackdrop(cbbg)
|
||
|
||
cb.bg = cb:CreateTexture(nil, "BACKGROUND")
|
||
cb.bg:SetAllPoints()
|
||
cb.bg:SetTexture(SFrames:GetTexture())
|
||
cb.bg:SetVertexColor(0.2, 0.2, 0.2, 1)
|
||
cb:SetStatusBarColor(1, 0.7, 0)
|
||
|
||
cb.text = SFrames:CreateFontString(cb, 10, "LEFT")
|
||
cb.text:SetPoint("LEFT", cb, "LEFT", 4, 0)
|
||
|
||
cb.time = SFrames:CreateFontString(cb, 10, "RIGHT")
|
||
cb.time:SetPoint("RIGHT", cb, "RIGHT", -4, 0)
|
||
|
||
cb.icon = cb:CreateTexture(nil, "ARTWORK")
|
||
cb.icon:SetWidth(SFrames.Config.castbarHeight + 2)
|
||
cb.icon:SetHeight(SFrames.Config.castbarHeight + 2)
|
||
cb.icon:SetPoint("RIGHT", cb, "LEFT", -4, 0)
|
||
cb.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
|
||
|
||
-- Icon Backdrop
|
||
local ibg = CreateFrame("Frame", nil, self.frame)
|
||
ibg:SetPoint("TOPLEFT", cb.icon, "TOPLEFT", -1, 1)
|
||
ibg:SetPoint("BOTTOMRIGHT", cb.icon, "BOTTOMRIGHT", 1, -1)
|
||
ibg:SetFrameLevel(cb:GetFrameLevel() - 1)
|
||
SFrames:CreateUnitBackdrop(ibg)
|
||
|
||
local lagTex = cb:CreateTexture(nil, "OVERLAY")
|
||
lagTex:SetTexture(SFrames:GetTexture())
|
||
lagTex:SetVertexColor(1, 0.2, 0.2, 0.5)
|
||
lagTex:Hide()
|
||
cb.lagTex = lagTex
|
||
|
||
cb.rainbowSegs = {}
|
||
for i = 1, 3 do
|
||
local tex = cb:CreateTexture(nil, "ARTWORK")
|
||
tex:SetTexture(RAINBOW_TEX_PATH)
|
||
local c = RAINBOW_SEG_COORDS[i]
|
||
tex:SetTexCoord(c[1], c[2], c[3], c[4])
|
||
tex.fullCoords = c
|
||
tex:Hide()
|
||
cb.rainbowSegs[i] = tex
|
||
end
|
||
|
||
cb:Hide()
|
||
cbbg:Hide()
|
||
cb.icon:Hide()
|
||
ibg:Hide()
|
||
|
||
self.frame.castbar = cb
|
||
self.frame.castbar.cbbg = cbbg
|
||
self.frame.castbar.ibg = ibg
|
||
|
||
cb:SetScript("OnUpdate", function() SFrames.Player:CastbarOnUpdate() end)
|
||
|
||
self:ApplyCastbarPosition()
|
||
|
||
-- Hook events
|
||
SFrames:RegisterEvent("SPELLCAST_START", function() self:CastbarStart(arg1, arg2) end)
|
||
SFrames:RegisterEvent("SPELLCAST_STOP", function() self:CastbarStop() end)
|
||
SFrames:RegisterEvent("SPELLCAST_FAILED", function() self:CastbarStop() end)
|
||
SFrames:RegisterEvent("SPELLCAST_INTERRUPTED", function() self:CastbarStop() end)
|
||
SFrames:RegisterEvent("SPELLCAST_DELAYED", function() self:CastbarDelayed(arg1) end)
|
||
SFrames:RegisterEvent("SPELLCAST_CHANNEL_START", function() self:CastbarChannelStart(arg1, arg2) end)
|
||
SFrames:RegisterEvent("SPELLCAST_CHANNEL_UPDATE", function() self:CastbarChannelUpdate(arg1) end)
|
||
SFrames:RegisterEvent("SPELLCAST_CHANNEL_STOP", function() self:CastbarStop() end)
|
||
end
|
||
|
||
function SFrames.Player:ApplyCastbarPosition()
|
||
local cb = self.frame.castbar
|
||
if not cb then return end
|
||
local db = SFramesDB or {}
|
||
|
||
cb:ClearAllPoints()
|
||
cb.cbbg:ClearAllPoints()
|
||
cb.icon:ClearAllPoints()
|
||
cb.ibg:ClearAllPoints()
|
||
|
||
if db.castbarStandalone then
|
||
cb:SetParent(UIParent)
|
||
cb.cbbg:SetParent(UIParent)
|
||
cb.ibg:SetParent(UIParent)
|
||
cb:SetWidth(280)
|
||
cb:SetHeight(20)
|
||
cb:SetPoint("BOTTOM", UIParent, "BOTTOM", 0, 120)
|
||
cb:SetFrameStrata("HIGH")
|
||
cb.cbbg:SetPoint("TOPLEFT", cb, "TOPLEFT", -1, 1)
|
||
cb.cbbg:SetPoint("BOTTOMRIGHT", cb, "BOTTOMRIGHT", 1, -1)
|
||
cb.cbbg:SetFrameLevel(math.max(1, cb:GetFrameLevel() - 1))
|
||
cb.icon:SetWidth(22)
|
||
cb.icon:SetHeight(22)
|
||
cb.icon:SetPoint("RIGHT", cb, "LEFT", -4, 0)
|
||
cb.ibg:SetPoint("TOPLEFT", cb.icon, "TOPLEFT", -1, 1)
|
||
cb.ibg:SetPoint("BOTTOMRIGHT", cb.icon, "BOTTOMRIGHT", 1, -1)
|
||
cb.ibg:SetFrameLevel(math.max(1, cb:GetFrameLevel() - 1))
|
||
|
||
if SFrames.Movers and SFrames.Movers.RegisterMover then
|
||
SFrames.Movers:RegisterMover("PlayerCastbar", cb, "施法条",
|
||
"BOTTOM", "UIParent", "BOTTOM", 0, 120)
|
||
end
|
||
else
|
||
cb:SetParent(self.frame)
|
||
cb.cbbg:SetParent(self.frame)
|
||
cb.ibg:SetParent(self.frame)
|
||
local cbH = SFrames.Config.castbarHeight
|
||
cb:SetWidth(0) -- 清除独立模式的显式宽度,让双锚点自动计算
|
||
cb:SetHeight(cbH)
|
||
cb:SetPoint("BOTTOMRIGHT", self.frame, "TOPRIGHT", 0, 6)
|
||
cb:SetPoint("BOTTOMLEFT", self.frame.portrait, "TOPLEFT", cbH + 6, 6)
|
||
cb:SetFrameStrata("MEDIUM")
|
||
cb.cbbg:SetPoint("TOPLEFT", cb, "TOPLEFT", -1, 1)
|
||
cb.cbbg:SetPoint("BOTTOMRIGHT", cb, "BOTTOMRIGHT", 1, -1)
|
||
cb.cbbg:SetFrameLevel(math.max(1, cb:GetFrameLevel() - 1))
|
||
cb.icon:SetWidth(cbH + 2)
|
||
cb.icon:SetHeight(cbH + 2)
|
||
cb.icon:SetPoint("RIGHT", cb, "LEFT", -4, 0)
|
||
cb.ibg:SetPoint("TOPLEFT", cb.icon, "TOPLEFT", -1, 1)
|
||
cb.ibg:SetPoint("BOTTOMRIGHT", cb.icon, "BOTTOMRIGHT", 1, -1)
|
||
cb.ibg:SetFrameLevel(math.max(1, cb:GetFrameLevel() - 1))
|
||
end
|
||
end
|
||
|
||
function SFrames.Player:CastbarStart(spellName, duration)
|
||
local cb = self.frame.castbar
|
||
cb.casting = true
|
||
cb.channeling = nil
|
||
cb.fadeOut = nil
|
||
cb.startTime = GetTime()
|
||
cb.maxValue = duration / 1000
|
||
cb:SetMinMaxValues(0, cb.maxValue)
|
||
cb:SetValue(0)
|
||
cb.text:SetText(spellName)
|
||
|
||
local texture
|
||
local _UnitCastingInfo = UnitCastingInfo or (ShaguTweaks and ShaguTweaks.UnitCastingInfo)
|
||
if _UnitCastingInfo then
|
||
local _, _, _, tex = _UnitCastingInfo("player")
|
||
texture = tex
|
||
end
|
||
if not texture and SFrames.castdb and UnitGUID then
|
||
local guid = UnitGUID("player")
|
||
if guid and SFrames.castdb[guid] and SFrames.castdb[guid].icon then
|
||
texture = SFrames.castdb[guid].icon
|
||
end
|
||
end
|
||
if (not texture or texture == "Interface\\Icons\\INV_Misc_QuestionMark")
|
||
and SFrames.GetSpellIcon then
|
||
texture = SFrames.GetSpellIcon(spellName) or texture
|
||
end
|
||
|
||
if texture then
|
||
cb.icon:SetTexture(texture)
|
||
cb.icon:Show()
|
||
cb.ibg:Show()
|
||
else
|
||
cb.icon:Hide()
|
||
cb.ibg:Hide()
|
||
end
|
||
|
||
cb:SetAlpha(1)
|
||
cb.cbbg:SetAlpha(1)
|
||
if texture then
|
||
cb.icon:SetAlpha(1)
|
||
cb.ibg:SetAlpha(1)
|
||
end
|
||
cb:Show()
|
||
cb.cbbg:Show()
|
||
|
||
local lag = GetLatencySeconds()
|
||
if lag > 0 and cb.maxValue > 0 then
|
||
local barW = cb:GetWidth()
|
||
local lagW = math.min((lag / cb.maxValue) * barW, barW * 0.5)
|
||
if lagW >= 1 then
|
||
cb.lagTex:ClearAllPoints()
|
||
cb.lagTex:SetPoint("TOPRIGHT", cb, "TOPRIGHT", 0, 0)
|
||
cb.lagTex:SetPoint("BOTTOMRIGHT", cb, "BOTTOMRIGHT", 0, 0)
|
||
cb.lagTex:SetWidth(lagW)
|
||
cb.lagTex:Show()
|
||
else
|
||
cb.lagTex:Hide()
|
||
end
|
||
else
|
||
cb.lagTex:Hide()
|
||
end
|
||
end
|
||
|
||
function SFrames.Player:CastbarChannelStart(duration, spellName)
|
||
local cb = self.frame.castbar
|
||
cb.casting = nil
|
||
cb.channeling = true
|
||
cb.fadeOut = nil
|
||
cb.startTime = GetTime()
|
||
cb.maxValue = duration / 1000
|
||
cb.endTime = cb.startTime + cb.maxValue
|
||
cb:SetMinMaxValues(0, cb.maxValue)
|
||
cb:SetValue(cb.maxValue)
|
||
cb.text:SetText(spellName)
|
||
|
||
local texture
|
||
local _UnitChannelInfo = UnitChannelInfo or (ShaguTweaks and ShaguTweaks.UnitChannelInfo)
|
||
if _UnitChannelInfo then
|
||
local _, _, _, tex = _UnitChannelInfo("player")
|
||
texture = tex
|
||
end
|
||
if not texture and SFrames.castdb and UnitGUID then
|
||
local guid = UnitGUID("player")
|
||
if guid and SFrames.castdb[guid] and SFrames.castdb[guid].icon then
|
||
texture = SFrames.castdb[guid].icon
|
||
end
|
||
end
|
||
if (not texture or texture == "Interface\\Icons\\INV_Misc_QuestionMark")
|
||
and SFrames.GetSpellIcon then
|
||
texture = SFrames.GetSpellIcon(spellName) or texture
|
||
end
|
||
|
||
if texture then
|
||
cb.icon:SetTexture(texture)
|
||
cb.icon:Show()
|
||
cb.ibg:Show()
|
||
else
|
||
cb.icon:Hide()
|
||
cb.ibg:Hide()
|
||
end
|
||
|
||
cb:SetAlpha(1)
|
||
cb.cbbg:SetAlpha(1)
|
||
if texture then
|
||
cb.icon:SetAlpha(1)
|
||
cb.ibg:SetAlpha(1)
|
||
end
|
||
cb:Show()
|
||
cb.cbbg:Show()
|
||
|
||
local lag = GetLatencySeconds()
|
||
if lag > 0 and cb.maxValue > 0 then
|
||
local barW = cb:GetWidth()
|
||
local lagW = math.min((lag / cb.maxValue) * barW, barW * 0.5)
|
||
if lagW >= 1 then
|
||
cb.lagTex:ClearAllPoints()
|
||
cb.lagTex:SetPoint("TOPLEFT", cb, "TOPLEFT", 0, 0)
|
||
cb.lagTex:SetPoint("BOTTOMLEFT", cb, "BOTTOMLEFT", 0, 0)
|
||
cb.lagTex:SetWidth(lagW)
|
||
cb.lagTex:Show()
|
||
else
|
||
cb.lagTex:Hide()
|
||
end
|
||
else
|
||
cb.lagTex:Hide()
|
||
end
|
||
end
|
||
|
||
function SFrames.Player:CastbarStop()
|
||
local cb = self.frame.castbar
|
||
cb.casting = nil
|
||
cb.channeling = nil
|
||
cb.fadeOut = true
|
||
if cb.lagTex then cb.lagTex:Hide() end
|
||
end
|
||
|
||
function SFrames.Player:CastbarDelayed(delay)
|
||
local cb = self.frame.castbar
|
||
if cb.casting then
|
||
cb.maxValue = cb.maxValue + (delay / 1000)
|
||
cb:SetMinMaxValues(0, cb.maxValue)
|
||
end
|
||
end
|
||
|
||
function SFrames.Player:CastbarChannelUpdate(remainingMs)
|
||
local cb = self.frame.castbar
|
||
if cb.channeling then
|
||
cb.endTime = GetTime() + remainingMs / 1000
|
||
end
|
||
end
|
||
|
||
function SFrames.Player:CastbarOnUpdate()
|
||
local cb = self.frame.castbar
|
||
local db = SFramesDB or {}
|
||
|
||
if cb.casting then
|
||
local elapsed = GetTime() - cb.startTime
|
||
if elapsed >= cb.maxValue then
|
||
cb.casting = nil
|
||
cb.fadeOut = true
|
||
cb:SetValue(cb.maxValue)
|
||
return
|
||
end
|
||
cb:SetValue(elapsed)
|
||
cb.time:SetText(string.format("%.1f", math.max(cb.maxValue - elapsed, 0)))
|
||
if db.castbarRainbow then
|
||
if not cb.rainbowActive then
|
||
cb:SetStatusBarColor(0, 0, 0, 0)
|
||
cb.rainbowActive = true
|
||
end
|
||
UpdateRainbowProgress(cb, elapsed / cb.maxValue)
|
||
elseif cb.rainbowActive then
|
||
cb:SetStatusBarColor(1, 0.7, 0)
|
||
if cb.rainbowSegs then
|
||
for i = 1, 3 do cb.rainbowSegs[i]:Hide() end
|
||
end
|
||
cb.rainbowActive = nil
|
||
end
|
||
if not cb.icon:IsShown() then
|
||
self:CastbarTryResolveIcon()
|
||
end
|
||
elseif cb.channeling then
|
||
local timeRemaining = cb.endTime - GetTime()
|
||
if timeRemaining <= 0 then
|
||
cb.channeling = nil
|
||
cb.fadeOut = true
|
||
cb:SetValue(0)
|
||
return
|
||
end
|
||
cb:SetValue(timeRemaining)
|
||
cb.time:SetText(string.format("%.1f", timeRemaining))
|
||
if db.castbarRainbow then
|
||
if not cb.rainbowActive then
|
||
cb:SetStatusBarColor(0, 0, 0, 0)
|
||
cb.rainbowActive = true
|
||
end
|
||
UpdateRainbowProgress(cb, timeRemaining / cb.maxValue)
|
||
elseif cb.rainbowActive then
|
||
cb:SetStatusBarColor(1, 0.7, 0)
|
||
if cb.rainbowSegs then
|
||
for i = 1, 3 do cb.rainbowSegs[i]:Hide() end
|
||
end
|
||
cb.rainbowActive = nil
|
||
end
|
||
if not cb.icon:IsShown() then
|
||
self:CastbarTryResolveIcon()
|
||
end
|
||
elseif cb.fadeOut then
|
||
if cb.rainbowActive then
|
||
for i = 1, 3 do cb.rainbowSegs[i]:Hide() end
|
||
cb:SetStatusBarColor(1, 0.7, 0)
|
||
cb.rainbowActive = nil
|
||
end
|
||
local alpha = cb:GetAlpha() - 0.05
|
||
if alpha > 0 then
|
||
cb:SetAlpha(alpha)
|
||
cb.cbbg:SetAlpha(alpha)
|
||
cb.icon:SetAlpha(alpha)
|
||
cb.ibg:SetAlpha(alpha)
|
||
else
|
||
cb.fadeOut = nil
|
||
cb:Hide()
|
||
cb.cbbg:Hide()
|
||
cb.icon:Hide()
|
||
cb.ibg:Hide()
|
||
end
|
||
end
|
||
end
|
||
|
||
function SFrames.Player:CastbarTryResolveIcon()
|
||
local cb = self.frame.castbar
|
||
local spellName = cb.text:GetText()
|
||
local texture
|
||
|
||
if SFrames.castdb and UnitGUID then
|
||
local guid = UnitGUID("player")
|
||
if guid and SFrames.castdb[guid] then
|
||
local entry = SFrames.castdb[guid]
|
||
if entry.icon and entry.icon ~= "Interface\\Icons\\INV_Misc_QuestionMark" then
|
||
texture = entry.icon
|
||
end
|
||
end
|
||
end
|
||
|
||
if not texture and NanamiPlates and NanamiPlates.castDB and UnitGUID then
|
||
local guid = UnitGUID("player")
|
||
if guid and NanamiPlates.castDB[guid] then
|
||
local entry = NanamiPlates.castDB[guid]
|
||
if entry.icon and entry.icon ~= "Interface\\Icons\\INV_Misc_QuestionMark" then
|
||
texture = entry.icon
|
||
end
|
||
end
|
||
end
|
||
|
||
if not texture and SFrames.GetSpellIcon then
|
||
texture = SFrames.GetSpellIcon(spellName)
|
||
end
|
||
|
||
if texture then
|
||
cb.icon:SetTexture(texture)
|
||
cb.icon:SetAlpha(1)
|
||
cb.ibg:SetAlpha(1)
|
||
cb.icon:Show()
|
||
cb.ibg:Show()
|
||
end
|
||
end
|