Files
Nanami-UI/Units/Party.lua
2026-04-09 09:46:47 +08:00

1508 lines
55 KiB
Lua

SFrames.Party = {}
local _A = SFrames.ActiveTheme
local PARTY_FRAME_WIDTH = 150
local PARTY_FRAME_HEIGHT = 35
local PARTY_VERTICAL_GAP = 30
local PARTY_HORIZONTAL_GAP = 8
local PARTY_UNIT_LOOKUP = { party1 = true, party2 = true, party3 = true, party4 = true }
local PARTYPET_UNIT_LOOKUP = { partypet1 = true, partypet2 = true, partypet3 = true, partypet4 = true }
-- Pre-allocated table reused every UpdateAuras call
local _partyDebuffColor = { r = 0, g = 0, b = 0 }
local function GetIncomingHeals(unit)
return SFrames:GetIncomingHeals(unit)
end
local function TryDropCursorOnUnit(unit)
if not unit or not UnitExists(unit) then return false end
if not CursorHasItem or not CursorHasItem() then return false end
if not DropItemOnUnit then return false end
local ok = pcall(DropItemOnUnit, unit)
if not ok then
return false
end
return not CursorHasItem()
end
local function Clamp(value, minValue, maxValue)
if value < minValue then
return minValue
end
if value > maxValue then
return maxValue
end
return value
end
local function SetTextureIfPresent(region, texturePath)
if region and region.SetTexture and texturePath then
region:SetTexture(texturePath)
end
end
function SFrames.Party:GetMetrics()
local db = SFramesDB or {}
local width = tonumber(db.partyFrameWidth) or PARTY_FRAME_WIDTH
width = Clamp(math.floor(width + 0.5), 120, 320)
local height = tonumber(db.partyFrameHeight) or PARTY_FRAME_HEIGHT
height = Clamp(math.floor(height + 0.5), 28, 80)
local portraitWidth = tonumber(db.partyPortraitWidth) or math.min(33, height - 2)
portraitWidth = Clamp(math.floor(portraitWidth + 0.5), 24, 64)
if portraitWidth > width - 70 then
portraitWidth = width - 70
end
local healthHeight = tonumber(db.partyHealthHeight) or math.floor((height - 3) * 0.7)
healthHeight = Clamp(math.floor(healthHeight + 0.5), 10, height - 8)
local powerHeight = tonumber(db.partyPowerHeight) or (height - healthHeight - 3)
powerHeight = Clamp(math.floor(powerHeight + 0.5), 6, height - 6)
local gradientStyle = SFrames:IsGradientStyle()
local availablePowerWidth = width - portraitWidth - 5
if availablePowerWidth < 40 then
availablePowerWidth = 40
end
local rawPowerWidth = tonumber(db.partyPowerWidth)
local legacyFullWidth = tonumber(db.partyFrameWidth) or width
local legacyPowerWidth = width - portraitWidth - 3
local defaultPowerWidth = gradientStyle and width or availablePowerWidth
local maxPowerWidth = gradientStyle and width or availablePowerWidth
local powerWidth
if gradientStyle then
-- 渐变风格:能量条始终与血条等宽(全宽)
powerWidth = width
elseif not rawPowerWidth
or math.abs(rawPowerWidth - legacyFullWidth) < 0.5
or math.abs(rawPowerWidth - legacyPowerWidth) < 0.5
or math.abs(rawPowerWidth - availablePowerWidth) < 0.5 then
powerWidth = defaultPowerWidth
else
powerWidth = rawPowerWidth
end
powerWidth = Clamp(math.floor(powerWidth + 0.5), 40, maxPowerWidth)
local powerOffsetX = Clamp(math.floor((tonumber(db.partyPowerOffsetX) or 0) + 0.5), -120, 120)
local powerOffsetY = Clamp(math.floor((tonumber(db.partyPowerOffsetY) or 0) + 0.5), -80, 80)
if healthHeight + powerHeight + 3 > height then
powerHeight = height - healthHeight - 3
if powerHeight < 6 then
powerHeight = 6
healthHeight = height - powerHeight - 3
if healthHeight < 10 then
healthHeight = 10
powerHeight = height - healthHeight - 3
end
end
end
local hgap = tonumber(db.partyHorizontalGap) or PARTY_HORIZONTAL_GAP
hgap = Clamp(math.floor(hgap + 0.5), 0, 40)
local vgap = tonumber(db.partyVerticalGap) or PARTY_VERTICAL_GAP
vgap = Clamp(math.floor(vgap + 0.5), 0, 80)
local nameFont = tonumber(db.partyNameFontSize) or 10
nameFont = Clamp(math.floor(nameFont + 0.5), 8, 18)
local valueFont = tonumber(db.partyValueFontSize) or 10
valueFont = Clamp(math.floor(valueFont + 0.5), 8, 18)
local healthFont = tonumber(db.partyHealthFontSize) or valueFont
healthFont = Clamp(math.floor(healthFont + 0.5), 8, 18)
local powerFont = tonumber(db.partyPowerFontSize) or valueFont
powerFont = Clamp(math.floor(powerFont + 0.5), 8, 18)
return {
width = width,
height = height,
portraitWidth = portraitWidth,
healthHeight = healthHeight,
powerHeight = powerHeight,
powerWidth = powerWidth,
powerOffsetX = powerOffsetX,
powerOffsetY = powerOffsetY,
powerOnTop = db.partyPowerOnTop == true,
horizontalGap = hgap,
verticalGap = vgap,
nameFont = nameFont,
valueFont = valueFont,
healthFont = healthFont,
powerFont = powerFont,
healthTexture = SFrames:ResolveBarTexture("partyHealthTexture", "barTexture"),
powerTexture = SFrames:ResolveBarTexture("partyPowerTexture", "barTexture"),
}
end
function SFrames.Party:ApplyFrameStyle(frame, metrics)
if not frame then return end
frame:SetWidth(metrics.width)
frame:SetHeight(metrics.height)
if frame.pbg then
frame.pbg:SetWidth(metrics.portraitWidth + 2)
frame.pbg:SetHeight(metrics.height)
frame.pbg:ClearAllPoints()
frame.pbg:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0)
end
if frame.portrait then
frame.portrait:SetWidth(metrics.portraitWidth)
frame.portrait:SetHeight(metrics.height - 2)
if frame.pbg then
frame.portrait:ClearAllPoints()
frame.portrait:SetPoint("CENTER", frame.pbg, "CENTER", 0, 0)
end
end
if frame.health then
frame.health:ClearAllPoints()
frame.health:SetPoint("TOPLEFT", frame.pbg, "TOPRIGHT", 2, -1)
frame.health:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -1, -1)
frame.health:SetHeight(metrics.healthHeight)
end
if frame.healthBGFrame then
frame.healthBGFrame:ClearAllPoints()
frame.healthBGFrame:SetPoint("TOPLEFT", frame.health, "TOPLEFT", -1, 1)
frame.healthBGFrame:SetPoint("BOTTOMRIGHT", frame.health, "BOTTOMRIGHT", 1, -1)
end
local bgA = (SFramesDB and type(SFramesDB.partyBgAlpha) == "number") and SFramesDB.partyBgAlpha or 0.9
local A = SFrames.ActiveTheme
if A and A.panelBg and bgA < 0.89 then
if frame.pbg and frame.pbg.SetBackdropColor then
frame.pbg:SetBackdropColor(A.panelBg[1], A.panelBg[2], A.panelBg[3], bgA)
end
if frame.healthBGFrame and frame.healthBGFrame.SetBackdropColor then
frame.healthBGFrame:SetBackdropColor(A.panelBg[1], A.panelBg[2], A.panelBg[3], bgA)
end
end
if frame.power then
frame.power:ClearAllPoints()
frame.power:SetPoint("TOPLEFT", frame.health, "BOTTOMLEFT", metrics.powerOffsetX, -1 + metrics.powerOffsetY)
frame.power:SetWidth(metrics.powerWidth)
frame.power:SetHeight(metrics.powerHeight)
end
if frame.powerBGFrame then
frame.powerBGFrame:ClearAllPoints()
frame.powerBGFrame:SetPoint("TOPLEFT", frame.power, "TOPLEFT", -1, 1)
frame.powerBGFrame:SetPoint("BOTTOMRIGHT", frame.power, "BOTTOMRIGHT", 1, -1)
end
SFrames:ApplyStatusBarTexture(frame.health, "partyHealthTexture", "barTexture")
SFrames:ApplyStatusBarTexture(frame.power, "partyPowerTexture", "barTexture")
if frame.health and frame.power then
local healthLevel = frame:GetFrameLevel() + 2
local powerLevel = metrics.powerOnTop and (healthLevel + 1) or (healthLevel - 1)
frame.health:SetFrameLevel(healthLevel)
frame.power:SetFrameLevel(powerLevel)
end
SFrames:ApplyConfiguredUnitBackdrop(frame, "party")
if frame.pbg then SFrames:ApplyConfiguredUnitBackdrop(frame.pbg, "party", true) end
if frame.healthBGFrame then SFrames:ApplyConfiguredUnitBackdrop(frame.healthBGFrame, "party") end
if frame.powerBGFrame then SFrames:ApplyConfiguredUnitBackdrop(frame.powerBGFrame, "party") end
SetTextureIfPresent(frame.health and frame.health.bg, metrics.healthTexture)
SetTextureIfPresent(frame.health and frame.health.healPredMine, metrics.healthTexture)
SetTextureIfPresent(frame.health and frame.health.healPredOther, metrics.healthTexture)
SetTextureIfPresent(frame.health and frame.health.healPredOver, metrics.healthTexture)
SetTextureIfPresent(frame.power and frame.power.bg, metrics.powerTexture)
-- Gradient style preset
if SFrames:IsGradientStyle() then
-- Hide portrait & its backdrop
if frame.portrait then frame.portrait:Hide() end
if frame.pbg then SFrames:ClearBackdrop(frame.pbg); frame.pbg:Hide() end
-- Strip backdrops
SFrames:ClearBackdrop(frame)
SFrames:ClearBackdrop(frame.healthBGFrame)
SFrames:ClearBackdrop(frame.powerBGFrame)
-- Health bar full width
if frame.health then
frame.health:ClearAllPoints()
frame.health:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0)
frame.health:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, 0)
frame.health:SetHeight(metrics.healthHeight)
end
-- Power bar full width
if frame.power then
frame.power:ClearAllPoints()
frame.power:SetPoint("TOPLEFT", frame.health, "BOTTOMLEFT", metrics.powerOffsetX, -2 + metrics.powerOffsetY)
frame.power:SetWidth(metrics.powerWidth)
frame.power:SetHeight(metrics.powerHeight)
end
-- Apply gradient overlays
SFrames:ApplyGradientStyle(frame.health)
SFrames:ApplyGradientStyle(frame.power)
-- Flush BG frames
if frame.healthBGFrame then
frame.healthBGFrame:ClearAllPoints()
frame.healthBGFrame:SetPoint("TOPLEFT", frame.health, "TOPLEFT", 0, 0)
frame.healthBGFrame:SetPoint("BOTTOMRIGHT", frame.health, "BOTTOMRIGHT", 0, 0)
end
if frame.powerBGFrame then
frame.powerBGFrame:ClearAllPoints()
frame.powerBGFrame:SetPoint("TOPLEFT", frame.power, "TOPLEFT", 0, 0)
frame.powerBGFrame:SetPoint("BOTTOMRIGHT", frame.power, "BOTTOMRIGHT", 0, 0)
end
-- Hide bar backgrounds (transparent)
if frame.healthBGFrame then frame.healthBGFrame:Hide() end
if frame.powerBGFrame then frame.powerBGFrame:Hide() end
if frame.health and frame.health.bg then frame.health.bg:Hide() end
if frame.power and frame.power.bg then frame.power.bg:Hide() end
else
SFrames:RemoveGradientStyle(frame.health)
SFrames:RemoveGradientStyle(frame.power)
-- Restore bar backgrounds
if frame.healthBGFrame then frame.healthBGFrame:Show() end
if frame.powerBGFrame then frame.powerBGFrame:Show() end
if frame.health and frame.health.bg then frame.health.bg:Show() end
if frame.power and frame.power.bg then frame.power.bg:Show() end
local use3D = not (SFramesDB and SFramesDB.partyPortrait3D == false)
if use3D then
if frame.portrait then frame.portrait:Show() end
if frame.pbg then frame.pbg:Show() end
else
-- Hide portrait area and extend health/power bars to full width
if frame.portrait then frame.portrait:Hide() end
if frame.pbg then frame.pbg:Hide() end
local fullWidth = metrics.width - 2
if frame.health then
frame.health:ClearAllPoints()
frame.health:SetPoint("TOPLEFT", frame, "TOPLEFT", 1, -1)
frame.health:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -1, -1)
frame.health:SetHeight(metrics.healthHeight)
end
if frame.healthBGFrame then
frame.healthBGFrame:ClearAllPoints()
frame.healthBGFrame:SetPoint("TOPLEFT", frame.health, "TOPLEFT", -1, 1)
frame.healthBGFrame:SetPoint("BOTTOMRIGHT", frame.health, "BOTTOMRIGHT", 1, -1)
end
if frame.power then
frame.power:SetWidth(fullWidth)
end
if frame.powerBGFrame then
frame.powerBGFrame:ClearAllPoints()
frame.powerBGFrame:SetPoint("TOPLEFT", frame.power, "TOPLEFT", -1, 1)
frame.powerBGFrame:SetPoint("BOTTOMRIGHT", frame.power, "BOTTOMRIGHT", 1, -1)
end
end
end
if frame.nameText then
SFrames:ApplyFontString(frame.nameText, metrics.nameFont, "partyNameFontKey", "fontKey")
end
if frame.healthText then
SFrames:ApplyFontString(frame.healthText, metrics.healthFont, "partyHealthFontKey", "fontKey")
end
if frame.powerText then
SFrames:ApplyFontString(frame.powerText, metrics.powerFont, "partyPowerFontKey", "fontKey")
end
end
function SFrames.Party:ApplyConfig()
if not self.parent then return end
local frameScale = tonumber(SFramesDB and SFramesDB.partyFrameScale) or 1
frameScale = Clamp(frameScale, 0.7, 1.8)
self.parent:SetScale(frameScale)
local metrics = self:GetMetrics()
self.metrics = metrics
if self.frames then
for i = 1, 4 do
local item = self.frames[i]
if item and item.frame then
self:ApplyFrameStyle(item.frame, metrics)
end
end
end
self:ApplyLayout()
if self.testing then
for i = 1, 4 do
if self.frames[i] and self.frames[i].frame then
self.frames[i].frame:Show()
end
end
else
self:UpdateAll()
end
end
function SFrames.Party:GetLayoutMode()
if SFramesDB and SFramesDB.partyLayout == "horizontal" then
return "horizontal"
end
return "vertical"
end
function SFrames.Party:SavePosition()
if not (self.parent and SFramesDB) then return end
if not SFramesDB.Positions then SFramesDB.Positions = {} end
local point, _, relativePoint, xOfs, yOfs = self.parent:GetPoint()
if point and relativePoint then
SFramesDB.Positions["PartyFrame"] = {
point = point,
relativePoint = relativePoint,
xOfs = xOfs or 0,
yOfs = yOfs or 0,
}
end
end
function SFrames.Party:ApplyPosition()
if not self.parent then return end
self.parent:ClearAllPoints()
local pos = SFramesDB and SFramesDB.Positions and SFramesDB.Positions["PartyFrame"]
if pos and pos.point and pos.relativePoint and type(pos.xOfs) == "number" and type(pos.yOfs) == "number" then
self.parent:SetPoint(pos.point, UIParent, pos.relativePoint, pos.xOfs, pos.yOfs)
else
self.parent:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 15, -150)
end
end
function SFrames.Party:ApplyLayout()
if not (self.parent and self.frames) then return end
local metrics = self.metrics or self:GetMetrics()
self.metrics = metrics
local mode = self:GetLayoutMode()
for i = 1, 4 do
local f = self.frames[i] and self.frames[i].frame
if f then
f:ClearAllPoints()
if i == 1 then
f:SetPoint("TOPLEFT", self.parent, "TOPLEFT", 0, 0)
else
local prev = self.frames[i - 1].frame
if mode == "horizontal" then
f:SetPoint("TOPLEFT", prev, "TOPRIGHT", metrics.horizontalGap, 0)
else
f:SetPoint("TOPLEFT", prev, "BOTTOMLEFT", 0, -metrics.verticalGap)
end
end
end
end
local auraRowHeight = 24 -- 20px icon + 2px gap above + 2px padding
if mode == "horizontal" then
self.parent:SetWidth((metrics.width * 4) + (metrics.horizontalGap * 3))
self.parent:SetHeight(metrics.height + auraRowHeight)
else
self.parent:SetWidth(metrics.width)
self.parent:SetHeight(metrics.height + ((metrics.height + metrics.verticalGap) * 3) + auraRowHeight)
end
end
function SFrames.Party:SetLayout(mode)
if not SFramesDB then SFramesDB = {} end
if mode ~= "horizontal" then
mode = "vertical"
end
SFramesDB.partyLayout = mode
self:ApplyLayout()
if self.testing then
for i = 1, 4 do
self.frames[i].frame:Show()
end
else
self:UpdateAll()
end
end
function SFrames.Party:Initialize()
self.frames = {}
if not SFramesDB then SFramesDB = {} end
if not SFramesDB.partyLayout then
SFramesDB.partyLayout = "vertical"
end
local parent = CreateFrame("Frame", "SFramesPartyParent", UIParent)
parent:SetWidth(PARTY_FRAME_WIDTH)
parent:SetHeight(PARTY_FRAME_HEIGHT)
local frameScale = (SFramesDB and type(SFramesDB.partyFrameScale) == "number") and SFramesDB.partyFrameScale or 1
parent:SetScale(frameScale)
self.parent = parent
self:ApplyPosition()
parent:SetMovable(true)
for i = 1, 4 do
local unit = "party" .. i
local f = CreateFrame("Button", "SFramesPartyFrame"..i, parent)
f:SetWidth(PARTY_FRAME_WIDTH)
f:SetHeight(PARTY_FRAME_HEIGHT)
f.id = i
f:RegisterForClicks("LeftButtonUp", "RightButtonUp")
f:RegisterForDrag("LeftButton")
f:SetScript("OnDragStart", function()
if IsAltKeyDown() or SFrames.isUnlocked then
SFrames.Party.parent:StartMoving()
end
end)
f:SetScript("OnDragStop", function()
SFrames.Party.parent:StopMovingOrSizing()
SFrames.Party:SavePosition()
end)
f:SetScript("OnClick", function()
if arg1 == "LeftButton" then
if IsShiftKeyDown() and SFrames.Focus and SFrames.Focus.SetFromUnit then
pcall(SFrames.Focus.SetFromUnit, SFrames.Focus, this.unit)
return
end
if TryDropCursorOnUnit(this.unit) then
return
end
if SpellIsTargeting and SpellIsTargeting() then
SpellTargetUnit(this.unit)
return
end
TargetUnit(this.unit)
SFrames.Party:UpdateFrame(this.unit)
elseif arg1 == "RightButton" then
ToggleDropDownMenu(1, nil, getglobal("PartyMemberFrame"..this.id.."DropDown"), "cursor")
end
end)
f:SetScript("OnReceiveDrag", function()
if TryDropCursorOnUnit(this.unit) then
return
end
if SpellIsTargeting and SpellIsTargeting() then
SpellTargetUnit(this.unit)
end
end)
f:SetScript("OnEnter", function()
if SetMouseoverUnit then SetMouseoverUnit(this.unit) end
GameTooltip_SetDefaultAnchor(GameTooltip, this)
GameTooltip:SetUnit(this.unit)
end)
f:SetScript("OnLeave", function()
if SetMouseoverUnit then SetMouseoverUnit() end
GameTooltip:Hide()
end)
f.unit = unit
-- Portrait
local pbg = CreateFrame("Frame", nil, f)
pbg:SetWidth(35)
pbg:SetHeight(35)
pbg:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0)
pbg:SetFrameLevel(f:GetFrameLevel() - 1)
SFrames:CreateUnitBackdrop(pbg)
f.pbg = pbg
f.portrait = CreateFrame("PlayerModel", nil, f)
f.portrait:SetWidth(33)
f.portrait:SetHeight(33)
f.portrait:SetPoint("CENTER", pbg, "CENTER", 0, 0)
local offlineOverlay = CreateFrame("Frame", nil, f)
offlineOverlay:SetFrameLevel((f.portrait:GetFrameLevel() or 0) + 3)
offlineOverlay:SetAllPoints(pbg)
local offlineIcon = SFrames:CreateIcon(offlineOverlay, "offline", 20)
offlineIcon:SetPoint("CENTER", offlineOverlay, "CENTER", 0, 0)
offlineIcon:SetVertexColor(0.7, 0.7, 0.7, 0.9)
offlineIcon:Hide()
f.offlineIcon = offlineIcon
-- Health Bar
f.health = SFrames:CreateStatusBar(f, "SFramesPartyFrame"..i.."Health")
f.health:SetPoint("TOPLEFT", pbg, "TOPRIGHT", 2, -1)
f.health:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -1, 10)
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
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, "OVERLAY")
f.health.healPredMine:SetTexture(SFrames:GetTexture())
f.health.healPredMine:SetVertexColor(0.4, 1.0, 0.55, 0.78)
f.health.healPredMine:SetDrawLayer("OVERLAY", 7)
f.health.healPredMine:Hide()
f.health.healPredOther = f.health:CreateTexture(nil, "OVERLAY")
f.health.healPredOther:SetTexture(SFrames:GetTexture())
f.health.healPredOther:SetVertexColor(0.2, 0.9, 0.35, 0.5)
f.health.healPredOther:SetDrawLayer("OVERLAY", 7)
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, "SFramesPartyFrame"..i.."Power")
f.power:SetPoint("TOPLEFT", f.health, "BOTTOMLEFT", 0, -1)
f.power:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -1, 1)
f.power:SetMinMaxValues(0, 100)
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
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)
-- Texts
f.nameText = SFrames:CreateFontString(f.health, 10, "LEFT")
f.nameText:SetPoint("LEFT", f.health, "LEFT", 4, 0)
f.healthText = SFrames:CreateFontString(f.health, 10, "RIGHT")
f.healthText:SetPoint("RIGHT", f.health, "RIGHT", -4, 0)
f.powerText = SFrames:CreateFontString(f.power, 9, "RIGHT")
f.powerText:SetPoint("RIGHT", f.power, "RIGHT", -4, 0)
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)
-- Leader / Master Looter overlay (high frame level so icons aren't hidden by portrait)
local roleOvr = CreateFrame("Frame", nil, f)
roleOvr:SetFrameLevel((f:GetFrameLevel() or 0) + 4)
roleOvr:SetAllPoints(f)
-- Leader Icon
f.leaderIcon = roleOvr:CreateTexture(nil, "OVERLAY")
f.leaderIcon:SetWidth(14)
f.leaderIcon:SetHeight(14)
f.leaderIcon:SetPoint("TOPLEFT", pbg, "TOPLEFT", -4, 4)
f.leaderIcon:SetTexture("Interface\\GroupFrame\\UI-Group-LeaderIcon")
f.leaderIcon:Hide()
-- Master Looter Icon
f.masterIcon = roleOvr:CreateTexture(nil, "OVERLAY")
f.masterIcon:SetWidth(12)
f.masterIcon:SetHeight(12)
f.masterIcon:SetPoint("TOPRIGHT", pbg, "TOPRIGHT", 4, 4)
f.masterIcon:SetTexture("Interface\\GroupFrame\\UI-Group-MasterLooter")
f.masterIcon:Hide()
-- Raid Target Icon
local raidIconSize = 18
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
-- Pet Frame
local pf = CreateFrame("Button", "SFramesPartyPetFrame"..i, f)
pf:SetHeight(8)
pf:SetPoint("BOTTOMLEFT", f.health, "TOPLEFT", 0, 2)
pf:SetPoint("BOTTOMRIGHT", f.health, "TOPRIGHT", 0, 2)
pf.unit = "partypet"..i
SFrames:CreateUnitBackdrop(pf)
pf.health = SFrames:CreateStatusBar(pf, "SFramesPartyPetHealth"..i)
pf.health:SetAllPoints()
pf.health.bg = pf.health:CreateTexture(nil, "BACKGROUND")
pf.health.bg:SetAllPoints()
pf.health.bg:SetTexture(SFrames:GetTexture())
pf.health.bg:SetVertexColor(_A.slotBg[1], _A.slotBg[2], _A.slotBg[3], _A.slotBg[4] or 1)
pf.nameText = SFrames:CreateFontString(pf.health, 8, "LEFT")
pf.nameText:SetPoint("LEFT", pf.health, "LEFT", 2, 0)
pf.nameText:SetShadowColor(0, 0, 0, 1)
pf.nameText:SetShadowOffset(1, -1)
pf:RegisterForClicks("LeftButtonUp", "RightButtonUp")
pf:SetScript("OnClick", function() TargetUnit(this.unit) end)
pf:SetScript("OnEnter", function()
if SetMouseoverUnit then SetMouseoverUnit(this.unit) end
GameTooltip_SetDefaultAnchor(GameTooltip, this)
GameTooltip:SetUnit(this.unit)
end)
pf:SetScript("OnLeave", function()
if SetMouseoverUnit then SetMouseoverUnit() end
GameTooltip:Hide()
end)
pf:Hide()
f.petFrame = pf
self.frames[i] = { frame = f, unit = unit, index = i }
f.unit = unit
self:CreateAuras(i)
f:SetScript("OnShow", function()
if not SFrames.Party.testing then
SFrames.Party:UpdateFrame(this.unit)
end
end)
f:Hide()
end
if not self._globalUpdateFrame then
self._globalUpdateFrame = CreateFrame("Frame", nil, UIParent)
self._globalUpdateFrame._onUpdateFunc = function()
if SFrames.Party.testing then return end
local dt = arg1
local frames = SFrames.Party.frames
if not frames then return end
for i = 1, 4 do
local entry = frames[i]
if entry then
local f = entry.frame
if f:IsVisible() and f.unit and f.unit ~= "" then
f.rangeTimer = f.rangeTimer + dt
if f.rangeTimer >= 0.5 then
if UnitExists(f.unit) and not CheckInteractDistance(f.unit, 4) then
f:SetAlpha(0.5)
else
f:SetAlpha(1.0)
end
f.rangeTimer = 0
end
f.auraScanTimer = f.auraScanTimer + dt
if f.auraScanTimer >= 0.5 then
SFrames.Party:UpdateAuras(f.unit)
f.auraScanTimer = 0
end
f.healPredTimer = f.healPredTimer + dt
if f.healPredTimer >= 0.2 then
SFrames.Party:UpdateHealPrediction(f.unit)
f.healPredTimer = 0
end
f.tickAuraTimer = f.tickAuraTimer + dt
if f.tickAuraTimer >= 0.5 then
SFrames.Party:TickAuras(f.unit)
f.tickAuraTimer = 0
end
end
end
end
end
self._globalUpdateFrame:Hide()
end
self:ApplyConfig()
SFrames:RegisterEvent("PARTY_MEMBERS_CHANGED", function() self:UpdateAll() end)
SFrames:RegisterEvent("RAID_ROSTER_UPDATE", function() self:UpdateAll() end)
SFrames:RegisterEvent("UNIT_HEALTH", function()
if PARTY_UNIT_LOOKUP[arg1] then self:UpdateHealth(arg1)
elseif PARTYPET_UNIT_LOOKUP[arg1] then self:UpdatePet(arg1) end
end)
SFrames:RegisterEvent("UNIT_MAXHEALTH", function()
if PARTY_UNIT_LOOKUP[arg1] then self:UpdateHealth(arg1)
elseif PARTYPET_UNIT_LOOKUP[arg1] then self:UpdatePet(arg1) end
end)
SFrames:RegisterEvent("UNIT_MANA", function() if PARTY_UNIT_LOOKUP[arg1] then self:UpdatePower(arg1) end end)
SFrames:RegisterEvent("UNIT_MAXMANA", function() if PARTY_UNIT_LOOKUP[arg1] then self:UpdatePower(arg1) end end)
SFrames:RegisterEvent("UNIT_RAGE", function() if PARTY_UNIT_LOOKUP[arg1] then self:UpdatePower(arg1) end end)
SFrames:RegisterEvent("UNIT_MAXRAGE", function() if PARTY_UNIT_LOOKUP[arg1] then self:UpdatePower(arg1) end end)
SFrames:RegisterEvent("UNIT_ENERGY", function() if PARTY_UNIT_LOOKUP[arg1] then self:UpdatePower(arg1) end end)
SFrames:RegisterEvent("UNIT_MAXENERGY", function() if PARTY_UNIT_LOOKUP[arg1] then self:UpdatePower(arg1) end end)
SFrames:RegisterEvent("UNIT_FOCUS", function() if PARTY_UNIT_LOOKUP[arg1] then self:UpdatePower(arg1) end end)
SFrames:RegisterEvent("UNIT_MAXFOCUS", function() if PARTY_UNIT_LOOKUP[arg1] then self:UpdatePower(arg1) end end)
SFrames:RegisterEvent("UNIT_DISPLAYPOWER", function() if PARTY_UNIT_LOOKUP[arg1] then self:UpdatePowerType(arg1) end end)
SFrames:RegisterEvent("UNIT_PORTRAIT_UPDATE", function() if PARTY_UNIT_LOOKUP[arg1] then self:UpdatePortrait(arg1) end end)
SFrames:RegisterEvent("UNIT_AURA", function() if PARTY_UNIT_LOOKUP[arg1] then self:UpdateAuras(arg1) end end)
SFrames:RegisterEvent("UNIT_LEVEL", function() if PARTY_UNIT_LOOKUP[arg1] then self:UpdateFrame(arg1) end end)
SFrames:RegisterEvent("PARTY_LEADER_CHANGED", function() self:UpdateAll() end)
SFrames:RegisterEvent("PARTY_LOOT_METHOD_CHANGED", function() self:UpdateAll() end)
SFrames:RegisterEvent("UNIT_PET", function() if PARTY_UNIT_LOOKUP[arg1] then self:UpdatePet("partypet" .. string.sub(arg1, 6)) end end)
SFrames:RegisterEvent("RAID_TARGET_UPDATE", function() self:UpdateRaidIcons() end)
-- Bulletproof Party load syncer (watches for count changes for 10 seconds after reload)
local syncWatcher = CreateFrame("Frame")
local syncTimer = 0
local lastCount = -1
syncWatcher:SetScript("OnUpdate", function()
syncTimer = syncTimer + arg1
local count = GetNumPartyMembers()
if count ~= lastCount then
lastCount = count
SFrames.Party:UpdateAll()
end
if syncTimer > 10.0 then
this:SetScript("OnUpdate", nil)
end
end)
self:UpdateAll()
if SFrames.Movers and SFrames.Movers.RegisterMover and self.parent then
SFrames.Movers:RegisterMover("PartyFrame", self.parent, "小队",
"TOPLEFT", "UIParent", "TOPLEFT", 15, -150)
end
end
function SFrames.Party:CreateAuras(index)
local f = self.frames[index].frame
-- Use self.parent (plain Frame) as parent for aura buttons so they are
-- never clipped by the party Button frame's boundaries.
local auraParent = self.parent
f.buffs = {}
f.debuffs = {}
local size = 20
local spacing = 2
-- Party Buffs
for i = 1, 4 do
local b = CreateFrame("Button", "SFramesParty"..index.."Buff"..i, auraParent)
b:SetWidth(size)
b:SetHeight(size)
b:SetFrameLevel((f:GetFrameLevel() or 0) + 3)
SFrames:CreateUnitBackdrop(b)
b.icon = b:CreateTexture(nil, "ARTWORK")
b.icon:SetAllPoints()
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)
b:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT")
GameTooltip:SetUnitBuff(f.unit, this:GetID())
end)
b:SetScript("OnLeave", function() GameTooltip:Hide() end)
-- Anchored BELOW the party frame on the left side
if i == 1 then
b:SetPoint("TOPLEFT", f, "BOTTOMLEFT", 0, -2)
else
b:SetPoint("LEFT", f.buffs[i-1], "RIGHT", spacing, 0)
end
b:Hide()
f.buffs[i] = b
end
-- Debuffs (Starting right after Buffs to remain linear)
for i = 1, 4 do
local b = CreateFrame("Button", "SFramesParty"..index.."Debuff"..i, auraParent)
b:SetWidth(size)
b:SetHeight(size)
b:SetFrameLevel((f:GetFrameLevel() or 0) + 3)
SFrames:CreateUnitBackdrop(b)
b.icon = b:CreateTexture(nil, "ARTWORK")
b.icon:SetAllPoints()
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)
b:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT")
GameTooltip:SetUnitDebuff(f.unit, this:GetID())
end)
b:SetScript("OnLeave", function() GameTooltip:Hide() end)
if i == 1 then
b:SetPoint("LEFT", f.buffs[4], "RIGHT", spacing * 4, 0)
else
b:SetPoint("LEFT", f.debuffs[i-1], "RIGHT", spacing, 0)
end
b:Hide()
f.debuffs[i] = b
end
f.auraScanTimer = 0
f.rangeTimer = 0
f.healPredTimer = 0
f.tickAuraTimer = 0
end
function SFrames.Party:UpdatePet(unit)
local _, _, indexStr = string.find(unit, "(%d+)")
local index = tonumber(indexStr)
if not index or not self.frames[index] then return end
local pf = self.frames[index].frame.petFrame
if not pf then return end
if UnitExists(unit) and UnitIsConnected("party"..index) then
pf:Show()
local name = UnitName(unit)
if name == UNKNOWNOBJECT or name == "未知目标" or name == "Unknown" then
name = "宠物"
end
pf.nameText:SetText(name)
local hp = UnitHealth(unit)
local maxHp = UnitHealthMax(unit)
pf.health:SetMinMaxValues(0, maxHp)
pf.health:SetValue(hp)
pf.health:SetStatusBarColor(0.2, 0.8, 0.2)
else
pf:Hide()
end
end
function SFrames.Party:TickAuras(unit)
if self.testing then return end
local data = self:GetFrameByUnit(unit)
if not data then return end
local f = data.frame
local timeNow = GetTime()
for i = 1, 4 do
local b = f.buffs[i]
if b:IsShown() and b.expirationTime then
local timeLeft = b.expirationTime - timeNow
if timeLeft > 0 and timeLeft < 3600 then
b.cdText:SetText(SFrames:FormatTime(timeLeft))
else
b.cdText:SetText("")
end
end
local db = f.debuffs[i]
if db:IsShown() and db.expirationTime then
local timeLeft = db.expirationTime - timeNow
if timeLeft > 0 and timeLeft < 3600 then
db.cdText:SetText(SFrames:FormatTime(timeLeft))
else
db.cdText:SetText("")
end
end
end
end
function SFrames.Party:GetFrameByUnit(unit)
for i = 1, 4 do
if self.frames[i].unit == unit then
return self.frames[i]
end
end
return nil
end
function SFrames.Party:UpdateAll()
if self.testing then return end
local inRaid = GetNumRaidMembers() > 0
local raidFramesEnabled = SFramesDB and SFramesDB.enableRaidFrames ~= false
if inRaid and raidFramesEnabled then
for i = 1, 4 do
if self.frames[i] and self.frames[i].frame then
local f = self.frames[i].frame
f:Hide()
if f.buffs then for j = 1, 4 do f.buffs[j]:Hide() end end
if f.debuffs then for j = 1, 4 do f.debuffs[j]:Hide() end end
end
end
if self._globalUpdateFrame then
self._globalUpdateFrame:SetScript("OnUpdate", nil)
self._globalUpdateFrame:Hide()
end
return
end
if self.testing then return end
local numParty = GetNumPartyMembers()
local hasVisible = false
for i = 1, 4 do
local data = self.frames[i]
local f = data.frame
if i <= numParty then
f:Show()
self:UpdateFrame(data.unit)
hasVisible = true
else
f:Hide()
if f.buffs then for j = 1, 4 do f.buffs[j]:Hide() end end
if f.debuffs then for j = 1, 4 do f.debuffs[j]:Hide() end end
end
end
if self._globalUpdateFrame then
if hasVisible then
self._globalUpdateFrame:SetScript("OnUpdate", self._globalUpdateFrame._onUpdateFunc)
self._globalUpdateFrame:Show()
else
self._globalUpdateFrame:SetScript("OnUpdate", nil)
self._globalUpdateFrame:Hide()
end
end
end
function SFrames.Party:UpdateFrame(unit)
if self.testing then return end
local data = self:GetFrameByUnit(unit)
if not data then return end
local f = data.frame
local use3D = not (SFramesDB and SFramesDB.partyPortrait3D == false)
if use3D then
f.portrait:SetUnit(unit)
f.portrait:SetCamera(0)
f.portrait:Hide()
f.portrait:Show()
else
f.portrait:Hide()
end
local name = UnitName(unit) or ""
local level = UnitLevel(unit)
if level == -1 then level = "??" end
local _, class = UnitClass(unit)
if not UnitIsConnected(unit) then
f.health:SetStatusBarColor(0.5, 0.5, 0.5)
f.nameText:SetText(name)
f.nameText:SetTextColor(0.5, 0.5, 0.5)
if f.offlineIcon then f.offlineIcon:Show() end
else
if f.offlineIcon then f.offlineIcon:Hide() end
if class and SFrames.Config.colors.class[class] then
local color = SFrames.Config.colors.class[class]
f.health:SetStatusBarColor(color.r, color.g, color.b)
f.nameText:SetText(level .. " " .. name)
f.nameText:SetTextColor(color.r, color.g, color.b)
else
f.health:SetStatusBarColor(0, 1, 0)
f.nameText:SetText(level .. " " .. name)
f.nameText:SetTextColor(1, 1, 1)
end
end
-- Re-apply gradient after color change
if SFrames:IsGradientStyle() then
SFrames:ApplyBarGradient(f.health)
end
-- Update Leader/Master Looter
if GetPartyLeaderIndex() == data.index then
f.leaderIcon:Show()
else
f.leaderIcon:Hide()
end
local method, partyIndex, raidIndex = GetLootMethod()
if method == "master" and partyIndex == data.index then
f.masterIcon:Show()
else
f.masterIcon:Hide()
end
self:UpdateHealth(unit)
self:UpdatePowerType(unit)
self:UpdatePower(unit)
self:UpdateAuras(unit)
self:UpdateRaidIcon(unit)
local petUnit = string.gsub(unit, "party", "partypet")
self:UpdatePet(petUnit)
end
function SFrames.Party:UpdatePortrait(unit)
local data = self:GetFrameByUnit(unit)
if not data then return end
local f = data.frame
local use3D = not (SFramesDB and SFramesDB.partyPortrait3D == false)
if not use3D then return end
f.portrait:SetUnit(unit)
f.portrait:SetCamera(0)
f.portrait:Hide()
f.portrait:Show()
end
function SFrames.Party:UpdateHealth(unit)
local data = self:GetFrameByUnit(unit)
if not data then return end
local f = data.frame
if not UnitIsConnected(unit) then
f.health:SetMinMaxValues(0, 100)
f.health:SetValue(0)
f.healthText:SetText("Offline")
if f.health.healPredMine then f.health.healPredMine:Hide() end
if f.health.healPredOther then f.health.healPredOther:Hide() end
if f.offlineIcon then f.offlineIcon:Show() end
return
end
if f.offlineIcon then f.offlineIcon:Hide() end
local hp = UnitHealth(unit)
local maxHp = UnitHealthMax(unit)
f.health:SetMinMaxValues(0, maxHp)
f.health:SetValue(hp)
if maxHp > 0 then
local percent = math.floor((hp / maxHp) * 100)
f.healthText:SetText(percent .. "%")
else
f.healthText:SetText("")
end
self:UpdateHealPrediction(unit)
end
function SFrames.Party:UpdateHealPrediction(unit)
local data = self:GetFrameByUnit(unit)
if not data then return end
local f = data.frame
if not (f.health and f.health.healPredMine and f.health.healPredOther and f.health.healPredOver) then return end
local predMine = f.health.healPredMine
local predOther = f.health.healPredOther
local predOver = f.health.healPredOver
if not UnitExists(unit) or not UnitIsConnected(unit) then
predMine:Hide(); predOther:Hide(); predOver:Hide()
return
end
local hp = UnitHealth(unit) or 0
local maxHp = UnitHealthMax(unit) or 0
if CheckSuperWow then
local ok, hasSW = pcall(CheckSuperWow)
if ok and hasSW then
local ok2, realHp = pcall(UnitHealth, unit)
if ok2 then
hp = realHp or hp
end
local ok3, realMaxHp = pcall(UnitHealthMax, unit)
if ok3 then
maxHp = realMaxHp or maxHp
end
end
end
if maxHp <= 0 then
predMine:Hide(); predOther:Hide(); predOver:Hide()
return
end
local _, mineIncoming, othersIncoming = GetIncomingHeals(unit)
if CheckSuperWow then
local ok, hasSW = pcall(CheckSuperWow)
if ok and hasSW then
local ok2, _, realMine, realOther = pcall(GetIncomingHeals, unit)
if ok2 then
mineIncoming = realMine or mineIncoming
othersIncoming = realOther or othersIncoming
end
end
end
local missing = maxHp - hp
if missing <= 0 and (mineIncoming <= 0 and othersIncoming <= 0) then
predMine:Hide(); predOther:Hide(); predOver:Hide()
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 mineIncoming <= 0 and othersIncoming <= 0 then
predMine:Hide(); predOther:Hide(); predOver:Hide()
return
end
local use3DForPred = not (SFramesDB and SFramesDB.partyPortrait3D == false)
local barWidth
if use3DForPred then
barWidth = f:GetWidth() - (f.portrait:GetWidth() + 4)
else
barWidth = f:GetWidth() - 2
end
if barWidth <= 0 then
predMine:Hide(); predOther:Hide(); predOver:Hide()
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 then availableWidth = 0 end
local mineWidth = 0
local otherWidth = 0
if missing > 0 then
mineWidth = (mineShown / missing) * availableWidth
otherWidth = (otherShown / missing) * availableWidth
end
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
if mineWidth > 0 then
predMine:ClearAllPoints()
predMine:SetPoint("TOPLEFT", f.health, "TOPLEFT", currentWidth, 0)
predMine:SetPoint("BOTTOMLEFT", f.health, "BOTTOMLEFT", currentWidth, 0)
predMine:SetWidth(mineWidth)
predMine:SetHeight(f.health:GetHeight())
predMine:Show()
else
predMine:Hide()
end
if otherWidth > 0 then
predOther:ClearAllPoints()
predOther:SetPoint("TOPLEFT", f.health, "TOPLEFT", currentWidth + mineWidth, 0)
predOther:SetPoint("BOTTOMLEFT", f.health, "BOTTOMLEFT", currentWidth + mineWidth, 0)
predOther:SetWidth(otherWidth)
predOther:SetHeight(f.health:GetHeight())
predOther:Show()
else
predOther:Hide()
end
local totalIncoming = mineIncoming + othersIncoming
local overHeal = totalIncoming - missing
if overHeal > 0 then
local overWidth = (overHeal / maxHp) * barWidth
if overWidth > 0 then
predOver:ClearAllPoints()
predOver:SetPoint("TOPLEFT", f.health, "TOPLEFT", currentWidth + mineWidth + otherWidth, 0)
predOver:SetPoint("BOTTOMLEFT", f.health, "BOTTOMLEFT", currentWidth + mineWidth + otherWidth, 0)
predOver:SetWidth(overWidth)
predOver:SetHeight(f.health:GetHeight())
predOver:Show()
else
predOver:Hide()
end
else
predOver:Hide()
end
end
function SFrames.Party:UpdatePowerType(unit)
local data = self:GetFrameByUnit(unit)
if not data then return end
local f = data.frame
local powerType = UnitPowerType(unit)
local color = SFrames.Config.colors.power[powerType]
if color then
f.power:SetStatusBarColor(color.r, color.g, color.b)
else
f.power:SetStatusBarColor(0, 0, 1)
end
if SFrames:IsGradientStyle() then
SFrames:ApplyBarGradient(f.power)
end
end
function SFrames.Party:UpdatePower(unit)
local data = self:GetFrameByUnit(unit)
if not data then return end
local f = data.frame
if not UnitIsConnected(unit) then
f.power:SetMinMaxValues(0, 100)
f.power:SetValue(0)
if f.powerText then f.powerText:SetText("") end
return
end
local power = UnitMana(unit)
local maxPower = UnitManaMax(unit)
f.power:SetMinMaxValues(0, maxPower)
f.power:SetValue(power)
if f.powerText then
if maxPower and maxPower > 0 then
f.powerText:SetText(SFrames:FormatCompactPair(power, maxPower))
else
f.powerText:SetText("")
end
end
SFrames:UpdateRainbowBar(f.power, power, maxPower, unit)
end
function SFrames.Party:UpdateRaidIcons()
for i = 1, 4 do
if self.frames[i] and self.frames[i].frame:IsShown() then
self:UpdateRaidIcon(self.frames[i].unit)
end
end
end
function SFrames.Party:UpdateRaidIcon(unit)
local data = self:GetFrameByUnit(unit)
if not data then return end
local f = data.frame
if not f.raidIcon then return end
if not GetRaidTargetIndex then
f.raidIcon:Hide()
return
end
if not UnitExists(unit) then
f.raidIcon:Hide()
return
end
local index = GetRaidTargetIndex(unit)
if index and index > 0 and index <= 8 then
local col = math.mod(index - 1, 4)
local row = math.floor((index - 1) / 4)
f.raidIcon:SetTexCoord(col * 0.25, (col + 1) * 0.25, row * 0.25, (row + 1) * 0.25)
f.raidIcon:Show()
else
f.raidIcon:Hide()
end
end
function SFrames.Party:UpdateAuras(unit)
if self.testing then return end
local data = self:GetFrameByUnit(unit)
if not data then return end
local f = data.frame
local showDebuffs = not (SFramesDB and SFramesDB.partyShowDebuffs == false)
local showBuffs = not (SFramesDB and SFramesDB.partyShowBuffs == false)
local hasDebuff = false
_partyDebuffColor.r = _A.slotBg[1]
_partyDebuffColor.g = _A.slotBg[2]
_partyDebuffColor.b = _A.slotBg[3]
SFrames.Tooltip:SetOwner(UIParent, "ANCHOR_NONE")
-- Debuffs
if showDebuffs then
for i = 1, 4 do
local texture, applications, debuffType = UnitDebuff(unit, i)
local b = f.debuffs[i]
b:SetID(i)
if texture then
if debuffType then
hasDebuff = true
if debuffType == "Magic" then _partyDebuffColor.r = 0.2; _partyDebuffColor.g = 0.6; _partyDebuffColor.b = 1
elseif debuffType == "Curse" then _partyDebuffColor.r = 0.6; _partyDebuffColor.g = 0; _partyDebuffColor.b = 1
elseif debuffType == "Disease" then _partyDebuffColor.r = 0.6; _partyDebuffColor.g = 0.4; _partyDebuffColor.b = 0
elseif debuffType == "Poison" then _partyDebuffColor.r = 0; _partyDebuffColor.g = 0.6; _partyDebuffColor.b = 0
end
end
b.icon:SetTexture(texture)
SFrames.Tooltip:ClearLines()
SFrames.Tooltip:SetUnitDebuff(unit, i)
local timeLeft = SFrames:GetAuraTimeLeft(unit, i, false)
SFrames.Tooltip:Hide()
if timeLeft and timeLeft > 0 then
local newExp = GetTime() + timeLeft
if not b.expirationTime or math.abs(b.expirationTime - newExp) > 2 then
b.expirationTime = newExp
end
local currentLeft = b.expirationTime - GetTime()
if currentLeft > 0 and currentLeft < 3600 then
b.cdText:SetText(SFrames:FormatTime(currentLeft))
else
b.cdText:SetText("")
end
else
b.expirationTime = nil
b.cdText:SetText("")
end
b:Show()
else
b.expirationTime = nil
b.cdText:SetText("")
b:Hide()
end
end
else
for i = 1, 4 do
local b = f.debuffs[i]
b.expirationTime = nil
b.cdText:SetText("")
b:Hide()
end
end
if hasDebuff then
f.health.bg:SetVertexColor(_partyDebuffColor.r, _partyDebuffColor.g, _partyDebuffColor.b, 1)
else
f.health.bg:SetVertexColor(_A.slotBg[1], _A.slotBg[2], _A.slotBg[3], _A.slotBg[4] or 1)
end
-- Buffs
if showBuffs then
for i = 1, 4 do
local texture = UnitBuff(unit, i)
local b = f.buffs[i]
b:SetID(i)
if texture then
b.icon:SetTexture(texture)
SFrames.Tooltip:ClearLines()
SFrames.Tooltip:SetUnitBuff(unit, i)
local timeLeft = SFrames:GetAuraTimeLeft(unit, i, true)
SFrames.Tooltip:Hide()
if timeLeft and timeLeft > 0 then
local newExp = GetTime() + timeLeft
if not b.expirationTime or math.abs(b.expirationTime - newExp) > 2 then
b.expirationTime = newExp
end
local currentLeft = b.expirationTime - GetTime()
if currentLeft > 0 and currentLeft < 3600 then
b.cdText:SetText(SFrames:FormatTime(currentLeft))
else
b.cdText:SetText("")
end
else
b.expirationTime = nil
b.cdText:SetText("")
end
b:Show()
else
b.expirationTime = nil
b.cdText:SetText("")
b:Hide()
end
end
else
for i = 1, 4 do
local b = f.buffs[i]
b.expirationTime = nil
b.cdText:SetText("")
b:Hide()
end
end
end
function SFrames.Party:TestMode()
self.testing = not self.testing
if self.testing then
for i = 1, 4 do
local data = self.frames[i]
local f = data.frame
f:Show()
f.health:SetMinMaxValues(0, 100)
f.health:SetValue(math.random(30, 90))
f.health:SetStatusBarColor(SFrames.Config.colors.class["DRUID"].r, SFrames.Config.colors.class["DRUID"].g, SFrames.Config.colors.class["DRUID"].b)
f.nameText:SetText("60 队友" .. i)
f.nameText:SetTextColor(SFrames.Config.colors.class["DRUID"].r, SFrames.Config.colors.class["DRUID"].g, SFrames.Config.colors.class["DRUID"].b)
f.healthText:SetText(math.floor(f.health:GetValue()) .. "%")
if f.health.healPredMine then f.health.healPredMine:Hide() end
if f.health.healPredOther then f.health.healPredOther:Hide() end
f.power:SetMinMaxValues(0, 100)
f.power:SetValue(math.random(20, 100))
f.power:SetStatusBarColor(SFrames.Config.colors.power[0].r, SFrames.Config.colors.power[0].g, SFrames.Config.colors.power[0].b)
f.leaderIcon:Hide()
f.masterIcon:Hide()
if i == 1 then
f.leaderIcon:Show()
f.masterIcon:Show()
end
-- Show test buffs (all 4)
local testBuffIcons = {
"Interface\\Icons\\Spell_Holy_PowerWordFortitude",
"Interface\\Icons\\Spell_Holy_Renew",
"Interface\\Icons\\Spell_Holy_GreaterHeal",
"Interface\\Icons\\Spell_Nature_Abolishmagic",
}
for j = 1, 4 do
local fakeTime = math.random(30, 300)
f.buffs[j].icon:SetTexture(testBuffIcons[j])
f.buffs[j].expirationTime = GetTime() + fakeTime
f.buffs[j].cdText:SetText(SFrames:FormatTime(fakeTime))
f.buffs[j]:Show()
end
-- Show test debuffs (all 4, different types for color test)
local testDebuffIcons = {
"Interface\\Icons\\Spell_Shadow_ShadowWordPain", -- Magic (blue)
"Interface\\Icons\\Spell_Shadow_Curse", -- Curse (purple)
"Interface\\Icons\\Ability_Rogue_FeignDeath", -- Disease (brown)
"Interface\\Icons\\Ability_Poisoning", -- Poison (green)
}
for j = 1, 4 do
local debuffTime = math.random(5, 25)
f.debuffs[j].icon:SetTexture(testDebuffIcons[j])
f.debuffs[j].expirationTime = GetTime() + debuffTime
f.debuffs[j].cdText:SetText(SFrames:FormatTime(debuffTime))
f.debuffs[j]:Show()
end
-- Magic debuff background color
f.health.bg:SetVertexColor(0.2, 0.6, 1, 1)
-- Test pet
if f.petFrame then
f.petFrame:Show()
f.petFrame.nameText:SetText("测试宠物")
f.petFrame.health:SetMinMaxValues(0, 100)
f.petFrame.health:SetValue(100)
f.petFrame.health:SetStatusBarColor(0.2, 0.8, 0.2)
end
end
else
for i = 1, 4 do
local f = self.frames[i].frame
f.health.bg:SetVertexColor(_A.slotBg[1], _A.slotBg[2], _A.slotBg[3], _A.slotBg[4] or 1)
for j = 1, 4 do
f.buffs[j].expirationTime = nil
f.buffs[j].cdText:SetText("")
f.buffs[j]:Hide()
f.debuffs[j].expirationTime = nil
f.debuffs[j].cdText:SetText("")
f.debuffs[j]:Hide()
end
end
self:UpdateAll()
end
end