Files
Nanami-UI/Units/Raid.lua
2026-03-31 18:03:23 +08:00

1199 lines
44 KiB
Lua

SFrames.Raid = {}
local _A = SFrames.ActiveTheme
local RAID_FRAME_WIDTH = 60
local RAID_FRAME_HEIGHT = 40
local GROUP_PADDING = 8
local UNIT_PADDING = 2
local RAID_UNIT_LOOKUP = {}
for i = 1, 40 do RAID_UNIT_LOOKUP["raid" .. i] = true end
local function GetIncomingHeals(unit)
return SFrames:GetIncomingHeals(unit)
end
function SFrames.Raid:GetMetrics()
local db = SFramesDB or {}
local width = tonumber(db.raidFrameWidth) or RAID_FRAME_WIDTH
width = math.max(30, math.min(150, width))
local height = tonumber(db.raidFrameHeight) or RAID_FRAME_HEIGHT
height = math.max(20, math.min(80, height))
local healthHeight = tonumber(db.raidHealthHeight) or math.floor((height - 3) * 0.8)
healthHeight = math.max(10, math.min(height - 6, healthHeight))
local powerHeight = height - healthHeight - 3
if not db.raidShowPower then
powerHeight = 0
healthHeight = height - 2
end
local hgap = tonumber(db.raidHorizontalGap) or UNIT_PADDING
local vgap = tonumber(db.raidVerticalGap) or UNIT_PADDING
local groupGap = tonumber(db.raidGroupGap) or GROUP_PADDING
local nameFont = tonumber(db.raidNameFontSize) or 10
local valueFont = tonumber(db.raidValueFontSize) or 9
return {
width = width,
height = height,
healthHeight = healthHeight,
powerHeight = powerHeight,
horizontalGap = hgap,
verticalGap = vgap,
groupGap = groupGap,
nameFont = nameFont,
valueFont = valueFont,
showPower = db.raidShowPower ~= false,
}
end
function SFrames.Raid:ApplyFrameStyle(frame, metrics)
if not frame then return end
frame:SetWidth(metrics.width)
frame:SetHeight(metrics.height)
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
local bgA = (SFramesDB and type(SFramesDB.raidBgAlpha) == "number") and SFramesDB.raidBgAlpha or 0.9
local A = SFrames.ActiveTheme
if A and A.panelBg and bgA < 0.89 then
if frame.healthBGFrame and frame.healthBGFrame.SetBackdropColor then
frame.healthBGFrame:SetBackdropColor(A.panelBg[1], A.panelBg[2], A.panelBg[3], bgA)
end
if frame.powerBGFrame and frame.powerBGFrame.SetBackdropColor then
frame.powerBGFrame:SetBackdropColor(A.panelBg[1], A.panelBg[2], A.panelBg[3], bgA)
end
end
if frame.power then
if metrics.showPower then
frame.power:Show()
if frame.powerBGFrame then frame.powerBGFrame:Show() end
frame.power:ClearAllPoints()
frame.power:SetPoint("TOPLEFT", frame.health, "BOTTOMLEFT", 0, -1)
frame.power:SetPoint("TOPRIGHT", frame.health, "BOTTOMRIGHT", 0, 0)
frame.power:SetHeight(metrics.powerHeight)
else
frame.power:Hide()
if frame.powerBGFrame then frame.powerBGFrame:Hide() end
end
end
local outline = (SFrames and SFrames.Media and SFrames.Media.fontOutline) or "OUTLINE"
local fontPath = SFrames:GetFont()
if frame.nameText then
frame.nameText:SetFont(fontPath, metrics.nameFont, outline)
end
if frame.healthText then
frame.healthText:SetFont(fontPath, metrics.valueFont, outline)
end
end
function SFrames.Raid:ApplyLayout()
if not (self.parent and self._framesBuilt) then return end
local metrics = self:GetMetrics()
self.metrics = metrics
local mode = (SFramesDB and SFramesDB.raidLayout) or "horizontal"
local showLabel = SFramesDB and SFramesDB.raidShowGroupLabel ~= false
local LABEL_H = 16 -- px above each column (horizontal mode)
local LABEL_W = 16 -- px left of each row (vertical mode)
for g = 1, 8 do
local groupParent = self.groups[g]
groupParent:ClearAllPoints()
if mode == "horizontal" then
local extraTop = showLabel and LABEL_H or 0
if g == 1 then
groupParent:SetPoint("TOPLEFT", self.parent, "TOPLEFT", 0, 0)
else
groupParent:SetPoint("TOPLEFT", self.groups[g-1], "TOPRIGHT", metrics.groupGap, 0)
end
groupParent:SetWidth(metrics.width)
groupParent:SetHeight(extraTop + (metrics.height * 5) + (metrics.verticalGap * 4))
else
local extraLeft = showLabel and LABEL_W or 0
if g == 1 then
groupParent:SetPoint("TOPLEFT", self.parent, "TOPLEFT", 0, 0)
else
groupParent:SetPoint("TOPLEFT", self.groups[g-1], "BOTTOMLEFT", 0, -metrics.groupGap)
end
groupParent:SetWidth(extraLeft + (metrics.width * 5) + (metrics.horizontalGap * 4))
groupParent:SetHeight(metrics.height)
end
-- Position label; visibility is controlled per-group in UpdateAll
local lbl = self.groupLabels and self.groupLabels[g]
if lbl then
lbl:ClearAllPoints()
if showLabel then
if mode == "horizontal" then
-- Place label at the very top of the column, centered horizontally
-- Frames start at -LABEL_H, so the strip [0, -LABEL_H] is exclusively for the label
lbl:SetPoint("TOP", groupParent, "TOP", 0, -2)
else
-- Vertically centered on the left strip, centered horizontally within it
lbl:SetPoint("CENTER", groupParent, "LEFT", LABEL_W / 2, 0)
end
else
lbl:Hide()
end
end
for i = 1, 5 do
local index = (g - 1) * 5 + i
local f = self.frames[index].frame
f:ClearAllPoints()
if mode == "horizontal" then
local extraTop = showLabel and LABEL_H or 0
if i == 1 then
-- Frames start below the label strip
f:SetPoint("TOPLEFT", groupParent, "TOPLEFT", 0, -extraTop)
else
f:SetPoint("TOPLEFT", self.frames[index-1].frame, "BOTTOMLEFT", 0, -metrics.verticalGap)
end
else
local extraLeft = showLabel and LABEL_W or 0
if i == 1 then
f:SetPoint("TOPLEFT", groupParent, "TOPLEFT", extraLeft, 0)
else
f:SetPoint("TOPLEFT", self.frames[index-1].frame, "TOPRIGHT", metrics.horizontalGap, 0)
end
end
end
end
local extraTop = showLabel and LABEL_H or 0
local extraLeft = showLabel and LABEL_W or 0
if mode == "horizontal" then
self.parent:SetWidth((metrics.width * 8) + (metrics.groupGap * 7))
self.parent:SetHeight(extraTop + (metrics.height * 5) + (metrics.verticalGap * 4))
else
self.parent:SetWidth(extraLeft + (metrics.width * 5) + (metrics.horizontalGap * 4))
self.parent:SetHeight((metrics.height * 8) + (metrics.groupGap * 7))
end
end
function SFrames.Raid: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["RaidFrame"] = {
point = point,
relativePoint = relativePoint,
xOfs = xOfs or 0,
yOfs = yOfs or 0,
}
end
end
function SFrames.Raid:ApplyPosition()
if not self.parent then return end
self.parent:ClearAllPoints()
local pos = SFramesDB and SFramesDB.Positions and SFramesDB.Positions["RaidFrame"]
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, -200)
end
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)
return not CursorHasItem()
end
function SFrames.Raid:Initialize()
self.frames = {}
self.groups = {}
self._framesBuilt = false
if not SFramesDB then SFramesDB = {} end
local parent = CreateFrame("Frame", "SFramesRaidParent", UIParent)
local frameScale = (SFramesDB and type(SFramesDB.raidFrameScale) == "number") and SFramesDB.raidFrameScale or 1
parent:SetScale(frameScale)
self.parent = parent
self:ApplyPosition()
parent:SetMovable(true)
self.groupLabels = {}
for g = 1, 8 do
local groupParent = CreateFrame("Frame", "SFramesRaidGroup"..g, parent)
self.groups[g] = groupParent
local lbl = groupParent:CreateFontString(nil, "OVERLAY")
lbl:SetFont(SFrames:GetFont(), 9, "OUTLINE")
lbl:SetTextColor(0.85, 0.78, 0.92)
lbl:SetShadowColor(0, 0, 0, 1)
lbl:SetShadowOffset(1, -1)
lbl:SetText("小队 " .. g)
lbl:Hide()
self.groupLabels[g] = lbl
end
SFrames:RegisterEvent("RAID_ROSTER_UPDATE", function() self:UpdateAll() end)
SFrames:RegisterEvent("PARTY_LEADER_CHANGED", function() self:UpdateAll() end)
SFrames:RegisterEvent("PLAYER_TARGET_CHANGED", function() self:UpdateTargetHighlight() end)
SFrames:RegisterEvent("UNIT_HEALTH", function() if RAID_UNIT_LOOKUP[arg1] then self:UpdateHealth(arg1) end end)
SFrames:RegisterEvent("UNIT_MAXHEALTH", function() if RAID_UNIT_LOOKUP[arg1] then self:UpdateHealth(arg1) end end)
SFrames:RegisterEvent("UNIT_MANA", function() if RAID_UNIT_LOOKUP[arg1] then self:UpdatePower(arg1) end end)
SFrames:RegisterEvent("UNIT_MAXMANA", function() if RAID_UNIT_LOOKUP[arg1] then self:UpdatePower(arg1) end end)
SFrames:RegisterEvent("UNIT_AURA", function() if RAID_UNIT_LOOKUP[arg1] then self:UpdateAuras(arg1) end end)
SFrames:RegisterEvent("RAID_TARGET_UPDATE", function() self:UpdateRaidIcons() end)
self:UpdateAll()
if SFrames.Movers and SFrames.Movers.RegisterMover and self.parent then
SFrames.Movers:RegisterMover("RaidFrame", self.parent, "团队",
"TOPLEFT", "UIParent", "TOPLEFT", 15, -200)
end
end
function SFrames.Raid:EnsureFrames()
if self._framesBuilt then return end
self._framesBuilt = true
for i = 1, 40 do
local unit = "raid" .. i
local groupIndex = math.ceil(i / 5)
local groupParent = self.groups[groupIndex]
local f = CreateFrame("Button", "SFramesRaidFrame"..i, groupParent)
f.id = i
f:RegisterForClicks("LeftButtonUp", "RightButtonUp")
f:RegisterForDrag("LeftButton")
f:SetScript("OnDragStart", function()
if IsAltKeyDown() or SFrames.isUnlocked then
SFrames.Raid.parent:StartMoving()
end
end)
f:SetScript("OnDragStop", function()
SFrames.Raid.parent:StopMovingOrSizing()
SFrames.Raid: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)
elseif arg1 == "RightButton" then
if UnitExists(this.unit) then
GameTooltip:Hide()
if not SFrames.Raid.dropDown then
SFrames.Raid.dropDown = CreateFrame("Frame", "SFramesRaidDropDown", UIParent, "UIDropDownMenuTemplate")
SFrames.Raid.dropDown.displayMode = "MENU"
SFrames.Raid.dropDown.initialize = function()
local dd = SFrames.Raid.dropDown
local unit = dd.unit
local name = dd.name
if not unit or not name then return end
local info = {}
info.text = name
info.isTitle = 1
info.notCheckable = 1
UIDropDownMenu_AddButton(info)
if not UnitIsUnit(unit, "player") then
info = {}
info.text = WHISPER or "密语"
info.notCheckable = 1
info.func = function() ChatFrame_SendTell(name) end
UIDropDownMenu_AddButton(info)
info = {}
info.text = INSPECT or "检查"
info.notCheckable = 1
info.func = function() InspectUnit(unit) end
UIDropDownMenu_AddButton(info)
info = {}
info.text = TRADE or "交易"
info.notCheckable = 1
info.func = function() InitiateTrade(unit) end
UIDropDownMenu_AddButton(info)
info = {}
info.text = FOLLOW or "跟随"
info.notCheckable = 1
info.func = function() FollowUnit(unit) end
UIDropDownMenu_AddButton(info)
end
if (IsRaidLeader and IsRaidLeader()) or (IsRaidOfficer and IsRaidOfficer()) then
if not UnitIsUnit(unit, "player") then
info = {}
info.text = "提升为助手"
info.notCheckable = 1
info.func = function() PromoteToAssistant(name) end
UIDropDownMenu_AddButton(info)
info = {}
info.text = "移出团队"
info.notCheckable = 1
info.func = function() UninviteByName(name) end
UIDropDownMenu_AddButton(info)
end
end
info = {}
info.text = CANCEL or "取消"
info.notCheckable = 1
UIDropDownMenu_AddButton(info)
end
end
SFrames.Raid.dropDown.unit = this.unit
SFrames.Raid.dropDown.name = UnitName(this.unit)
ToggleDropDownMenu(1, nil, SFrames.Raid.dropDown, "cursor")
end
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
-- Health Bar
f.health = SFrames:CreateStatusBar(f, "SFramesRaidFrame"..i.."Health")
local hbg = CreateFrame("Frame", nil, f)
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, "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, "SFramesRaidFrame"..i.."Power")
f.power:SetMinMaxValues(0, 100)
local powerbg = CreateFrame("Frame", nil, f)
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)
-- Target Highlight
f.targetHighlight = f:CreateTexture(nil, "OVERLAY")
f.targetHighlight:SetPoint("TOPLEFT", f, "TOPLEFT", -2, 2)
f.targetHighlight:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 2, -2)
f.targetHighlight:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
f.targetHighlight:SetBlendMode("ADD")
f.targetHighlight:SetVertexColor(1, 1, 1, 0.6)
f.targetHighlight:Hide()
-- Texts
f.nameText = SFrames:CreateFontString(f.health, 10, "CENTER")
f.nameText:SetPoint("TOP", f.health, "TOP", 0, -2)
f.nameText:SetShadowColor(0, 0, 0, 1)
f.nameText:SetShadowOffset(1, -1)
f.healthText = SFrames:CreateFontString(f.health, 9, "CENTER")
f.healthText:SetPoint("BOTTOM", f.health, "BOTTOM", 0, 2)
f.healthText:SetShadowColor(0, 0, 0, 1)
f.healthText:SetShadowOffset(1, -1)
f.healthText:SetTextColor(0.8, 0.8, 0.8)
local offOvr = CreateFrame("Frame", nil, f)
offOvr:SetFrameLevel((f:GetFrameLevel() or 0) + 4)
offOvr:SetAllPoints(f)
local offIco = SFrames:CreateIcon(offOvr, "offline", 16)
offIco:SetPoint("CENTER", offOvr, "CENTER", 0, 0)
offIco:SetVertexColor(0.7, 0.7, 0.7, 0.9)
offIco:Hide()
f.offlineIcon = offIco
local roleOvr = CreateFrame("Frame", nil, f)
roleOvr:SetFrameLevel((f:GetFrameLevel() or 0) + 6)
roleOvr:SetAllPoints(f)
f.leaderIcon = roleOvr:CreateTexture(nil, "OVERLAY")
f.leaderIcon:SetWidth(12)
f.leaderIcon:SetHeight(12)
f.leaderIcon:SetPoint("TOPLEFT", f, "TOPLEFT", -3, 3)
f.leaderIcon:SetTexture("Interface\\GroupFrame\\UI-Group-LeaderIcon")
f.leaderIcon:Hide()
f.assistantIcon = roleOvr:CreateFontString(nil, "OVERLAY")
f.assistantIcon:SetFont(SFrames:GetFont(), 12, "OUTLINE")
f.assistantIcon:SetPoint("TOPLEFT", f, "TOPLEFT", -1, 1)
f.assistantIcon:SetTextColor(0.5, 0.8, 1.0)
f.assistantIcon:SetShadowColor(0, 0, 0, 1)
f.assistantIcon:SetShadowOffset(1, -1)
f.assistantIcon:SetText("+")
f.assistantIcon:Hide()
-- Raid Target Icon
local raidIconSize = 16
local raidIconOvr = CreateFrame("Frame", nil, f)
raidIconOvr:SetFrameLevel((f:GetFrameLevel() or 0) + 7)
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
f.indicators = {}
for pos = 1, 4 do
local ind = CreateFrame("Button", nil, f)
ind:SetWidth(10)
ind:SetHeight(10)
ind:SetFrameLevel(f:GetFrameLevel() + 5)
SFrames:CreateUnitBackdrop(ind)
ind.icon = ind:CreateTexture(nil, "ARTWORK")
ind.icon:SetAllPoints()
ind.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
if pos == 1 then
ind:SetPoint("TOPLEFT", f.health, "TOPLEFT", 1, -1)
elseif pos == 2 then
ind:SetPoint("TOPRIGHT", f.health, "TOPRIGHT", -1, -1)
elseif pos == 3 then
ind:SetPoint("BOTTOMLEFT", f.health, "BOTTOMLEFT", 1, 1)
elseif pos == 4 then
ind:SetPoint("BOTTOMRIGHT", f.health, "BOTTOMRIGHT", -1, 1)
end
ind:SetScript("OnEnter", function()
if this.index then
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT")
if this.isDebuff then
GameTooltip:SetUnitDebuff(f.unit, this.index)
else
GameTooltip:SetUnitBuff(f.unit, this.index)
end
end
end)
ind:SetScript("OnLeave", function() GameTooltip:Hide() end)
ind:Hide()
f.indicators[pos] = ind
end
self.frames[i] = { frame = f, unit = unit, index = i }
self:ApplyFrameStyle(f, self:GetMetrics())
f.rangeTimer = 0
f.auraScanTimer = 0
f.healPredTimer = 0
f:SetScript("OnShow", function()
if not SFrames.Raid.testing then
SFrames.Raid: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.Raid.testing then return end
local dt = arg1
local frames = SFrames.Raid.frames
if not frames then return end
local visibleCount = SFrames.Raid._visibleCount or 0
if visibleCount == 0 then return end
for i = 1, 40 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.6)
else
f:SetAlpha(1.0)
end
f.rangeTimer = 0
end
f.auraScanTimer = f.auraScanTimer + dt
if f.auraScanTimer >= 0.7 then
SFrames.Raid:UpdateAuras(f.unit)
f.auraScanTimer = 0
end
f.healPredTimer = f.healPredTimer + dt
if f.healPredTimer >= 0.2 then
SFrames.Raid:UpdateHealPrediction(f.unit)
f.healPredTimer = 0
end
end
end
end
end
self._globalUpdateFrame:Hide()
end
self:ApplyLayout()
end
function SFrames.Raid:UpdateAll()
if self.testing then return end
if SFramesDB and SFramesDB.enableRaidFrames == false then
self.parent:Hide()
return
else
self.parent:Show()
end
if GetNumRaidMembers() > 0 then
self:EnsureFrames()
for i = 1, 40 do
self.frames[i].frame:Hide()
self.frames[i].frame.unit = ""
self.frames[i].unit = ""
end
local groupSlots = {}
for g = 1, 8 do
groupSlots[g] = 0
end
local visibleCount = 0
for i = 1, 40 do
local name, rank, subgroup = GetRaidRosterInfo(i)
if name and subgroup and subgroup >= 1 and subgroup <= 8 then
groupSlots[subgroup] = groupSlots[subgroup] + 1
local slot = groupSlots[subgroup]
if slot <= 5 then
local frameIndex = (subgroup - 1) * 5 + slot
local f = self.frames[frameIndex].frame
f.unit = "raid" .. i
f.raidRank = rank
self.frames[frameIndex].unit = "raid" .. i
f:Show()
self:UpdateFrame("raid" .. i)
visibleCount = visibleCount + 1
end
end
end
self._visibleCount = visibleCount
if self._globalUpdateFrame then
self._globalUpdateFrame:SetScript("OnUpdate", self._globalUpdateFrame._onUpdateFunc)
self._globalUpdateFrame:Show()
end
-- Show/hide group labels based on whether each group has members
local showLabel = SFramesDB and SFramesDB.raidShowGroupLabel ~= false
for g = 1, 8 do
local lbl = self.groupLabels and self.groupLabels[g]
if lbl then
if showLabel and groupSlots[g] > 0 then
lbl:Show()
else
lbl:Hide()
end
end
end
else
self._visibleCount = 0
if self._globalUpdateFrame then
self._globalUpdateFrame:SetScript("OnUpdate", nil)
self._globalUpdateFrame:Hide()
end
if not self.testing and self._framesBuilt then
for i = 1, 40 do
self.frames[i].frame:Hide()
self.frames[i].frame.unit = ""
self.frames[i].unit = ""
end
end
for g = 1, 8 do
local lbl = self.groupLabels and self.groupLabels[g]
if lbl then lbl:Hide() end
end
end
end
function SFrames.Raid:UpdateFrame(unit)
if self.testing or not self._framesBuilt then return end
for i=1, 40 do
if self.frames[i].unit == unit then
local f = self.frames[i].frame
local name = UnitName(unit) or ""
local _, class = UnitClass(unit)
local display = string.sub(name, 1, 15)
f.nameText:SetText(display)
local rank = f.raidRank or 0
if f.leaderIcon then
if rank == 2 then f.leaderIcon:Show() else f.leaderIcon:Hide() end
end
if f.assistantIcon then
if rank == 1 then f.assistantIcon:Show() else f.assistantIcon:Hide() end
end
if not UnitIsConnected(unit) then
f.health:SetStatusBarColor(0.5, 0.5, 0.5)
f.health.bg:SetVertexColor(_A.slotBg[1], _A.slotBg[2], _A.slotBg[3], _A.slotBg[4] or 1)
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:SetTextColor(1, 1, 1)
else
f.health:SetStatusBarColor(0, 1, 0)
f.nameText:SetTextColor(1, 1, 1)
end
end
self:UpdateHealth(unit)
self:UpdatePower(unit)
self:UpdateAuras(unit)
self:UpdateRaidIcon(unit)
self:UpdateTargetHighlight()
break
end
end
end
function SFrames.Raid:UpdateTargetHighlight()
if self.testing or not self._framesBuilt then return end
for i=1, 40 do
local f = self.frames[i].frame
if UnitExists("target") and UnitIsUnit("target", self.frames[i].unit) then
f.targetHighlight:Show()
else
f.targetHighlight:Hide()
end
end
end
function SFrames.Raid:UpdateHealth(unit)
if not self._framesBuilt then return end
for i=1, 40 do
if self.frames[i].unit == unit then
local f = self.frames[i].frame
if not UnitIsConnected(unit) then
f.health:SetMinMaxValues(0, 100)
f.health:SetValue(0)
f.healthText:SetText("离线")
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)
if UnitIsDead(unit) then
f.health:SetMinMaxValues(0, 100)
f.health:SetValue(0)
f.healthText:SetText("死亡")
f.nameText:SetTextColor(0.5, 0.5, 0.5)
return
end
if UnitIsGhost(unit) then
f.health:SetMinMaxValues(0, 100)
f.health:SetValue(0)
f.healthText:SetText("灵魂")
f.nameText:SetTextColor(0.5, 0.5, 0.5)
return
end
f.health:SetMinMaxValues(0, maxHp)
f.health:SetValue(hp)
local percent = 0
if maxHp > 0 then
percent = math.floor((hp / maxHp) * 100)
end
local db = SFramesDB or {}
local txt = ""
if db.raidHealthFormat == "percent" then
txt = percent .. "%"
elseif db.raidHealthFormat == "deficit" then
if maxHp - hp > 0 then
txt = "-" .. (maxHp - hp)
end
else
txt = (math.floor(hp/100)/10).."k" -- default compact e.g. 4.5k
if hp < 1000 then txt = tostring(hp) end
end
f.healthText:SetText(txt)
self:UpdateHealPrediction(unit)
break
end
end
end
function SFrames.Raid:UpdateHealPrediction(unit)
if not self._framesBuilt then return end
local frameData = nil
for i=1, 40 do
if self.frames[i].unit == unit then
frameData = self.frames[i]
break
end
end
if not frameData then return end
local f = frameData.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
local function HidePredictions()
predMine:Hide()
predOther:Hide()
predOver:Hide()
end
if not UnitExists(unit) or not UnitIsConnected(unit) then
HidePredictions()
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
HidePredictions()
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
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 mineIncoming <= 0 and othersIncoming <= 0 then
HidePredictions()
return
end
local barWidth = f:GetWidth() - 2
if barWidth <= 0 then
HidePredictions()
return
end
local currentPosition = (hp / maxHp) * barWidth
if currentPosition < 0 then currentPosition = 0 end
if currentPosition > barWidth then currentPosition = barWidth end
local availableWidth = barWidth - currentPosition
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", f.health, "TOPLEFT", currentPosition, 0)
predMine:SetPoint("BOTTOMLEFT", f.health, "BOTTOMLEFT", currentPosition, 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", currentPosition + mineWidth, 0)
predOther:SetPoint("BOTTOMLEFT", f.health, "BOTTOMLEFT", currentPosition + 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, "TOPRIGHT", 0, 0)
predOver:SetPoint("BOTTOMLEFT", f.health, "BOTTOMRIGHT", 0, 0)
predOver:SetWidth(overWidth)
predOver:SetHeight(f.health:GetHeight())
predOver:Show()
else
predOver:Hide()
end
else
predOver:Hide()
end
end
function SFrames.Raid:UpdatePower(unit)
if not self._framesBuilt then return end
local db = SFramesDB or {}
if db.raidShowPower == false then return end
for i=1, 40 do
if self.frames[i].unit == unit then
local f = self.frames[i].frame
local power = UnitMana(unit)
local maxPower = UnitManaMax(unit)
local _, class = UnitClass(unit)
f.power:SetMinMaxValues(0, maxPower)
f.power:SetValue(power)
local pType = UnitPowerType(unit)
local color = SFrames.Config.colors.power[pType] or SFrames.Config.colors.power[0]
f.power:SetStatusBarColor(color.r, color.g, color.b)
break
end
end
end
function SFrames.Raid:UpdateRaidIcons()
if not self._framesBuilt then return end
for i = 1, 40 do
local unit = self.frames[i].unit
if unit and unit ~= "" and self.frames[i].frame:IsShown() then
self:UpdateRaidIcon(unit)
end
end
end
function SFrames.Raid:UpdateRaidIcon(unit)
if not self._framesBuilt then return end
for i = 1, 40 do
if self.frames[i].unit == unit then
local f = self.frames[i].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
break
end
end
end
local PlayerClassBuffs = nil
function SFrames.Raid:GetClassBuffs()
if PlayerClassBuffs then return PlayerClassBuffs end
local _, playerClass = UnitClass("player")
PlayerClassBuffs = {}
if playerClass == "PRIEST" then
PlayerClassBuffs = {
[1] = { "真言术:韧", "坚韧祷言" },
[2] = { "神圣之灵", "精神祷言" },
[3] = { "恢复" },
[4] = { "虚弱灵魂", isDebuff = true },
[5] = { "防恐结界" }
}
elseif playerClass == "DRUID" then
PlayerClassBuffs = {
[1] = { "野性印记", "野性赐福" },
[2] = { "荆棘术" },
[3] = { "回春术" },
[4] = { "愈合" },
}
elseif playerClass == "PALADIN" then
PlayerClassBuffs = {
[1] = { "王者祝福", "强效王者祝福" },
[2] = { "拯救祝福", "强效拯救祝福" },
[3] = { "智慧祝福", "强效智慧祝福" },
[4] = { "力量祝福", "强效力量祝福" },
[5] = { "庇护祝福", "强效庇护祝福" },
[6] = { "光明祝福", "强效光明祝福" }
}
elseif playerClass == "MAGE" then
PlayerClassBuffs = {
[1] = { "奥术智慧", "奥术光辉" },
[2] = { "魔法抑制" },
[3] = { "魔法增效" }
}
end
return PlayerClassBuffs
end
function SFrames.Raid:UpdateAuras(unit)
if not self._framesBuilt then return end
local frameData = nil
for i=1, 40 do
if self.frames[i].unit == unit then
frameData = self.frames[i]
break
end
end
if not frameData then return end
local f = frameData.frame
local buffsNeeded = self:GetClassBuffs()
local foundIndicators = { [1] = false, [2] = false, [3] = false, [4] = false }
-- Hide all first
for i = 1, 4 do
f.indicators[i]:Hide()
f.indicators[i].index = nil
end
if not buffsNeeded or not UnitIsConnected(unit) or UnitIsDeadOrGhost(unit) then
f.health.bg:SetVertexColor(_A.slotBg[1], _A.slotBg[2], _A.slotBg[3], _A.slotBg[4] or 1)
return
end
local function MatchesList(auraName, list)
for _, name in ipairs(list) do
if string.find(auraName, name) then
return true
end
end
return false
end
-- Helper: get buff name via SuperWoW aura ID (fast) or tooltip scan (fallback)
local hasSuperWoW = SFrames.superwow_active and SpellInfo
local function GetBuffName(unit, index)
if hasSuperWoW then
local texture, auraID = UnitBuff(unit, index)
if auraID and SpellInfo then
local spellName = SpellInfo(auraID)
if spellName and spellName ~= "" then
return spellName, texture
end
end
end
-- Fallback: tooltip scan
SFrames.Tooltip:SetOwner(UIParent, "ANCHOR_NONE")
SFrames.Tooltip:SetUnitBuff(unit, index)
local buffName = SFramesScanTooltipTextLeft1:GetText()
SFrames.Tooltip:Hide()
return buffName, UnitBuff(unit, index)
end
local function GetDebuffName(unit, index)
if hasSuperWoW then
local texture, count, dtype, auraID = UnitDebuff(unit, index)
if auraID and SpellInfo then
local spellName = SpellInfo(auraID)
if spellName and spellName ~= "" then
return spellName, texture, count, dtype
end
end
end
-- Fallback: tooltip scan
SFrames.Tooltip:SetOwner(UIParent, "ANCHOR_NONE")
SFrames.Tooltip:SetUnitDebuff(unit, index)
local debuffName = SFramesScanTooltipTextLeft1:GetText()
SFrames.Tooltip:Hide()
local texture, count, dtype = UnitDebuff(unit, index)
return debuffName, texture, count, dtype
end
-- Check Buffs
for i = 1, 32 do
local texture, applications = UnitBuff(unit, i)
if not texture then break end
local buffName = GetBuffName(unit, i)
if buffName then
for pos, listData in pairs(buffsNeeded) do
if pos <= 4 and not listData.isDebuff and not foundIndicators[pos] then
if MatchesList(buffName, listData) then
f.indicators[pos].icon:SetTexture(texture)
f.indicators[pos].index = i
f.indicators[pos].isDebuff = false
f.indicators[pos]:Show()
foundIndicators[pos] = true
end
end
end
end
end
local hasDebuff = false
local debuffColor = {r=_A.slotBg[1], g=_A.slotBg[2], b=_A.slotBg[3]}
-- Check Debuffs
for i = 1, 16 do
local texture, applications, dispelType = UnitDebuff(unit, i)
if not texture then break end
if dispelType then
hasDebuff = true
if dispelType == "Magic" then debuffColor = {r=0.2, g=0.6, b=1}
elseif dispelType == "Curse" then debuffColor = {r=0.6, g=0, b=1}
elseif dispelType == "Disease" then debuffColor = {r=0.6, g=0.4, b=0}
elseif dispelType == "Poison" then debuffColor = {r=0, g=0.6, b=0}
end
end
local debuffName = GetDebuffName(unit, i)
if debuffName then
for pos, listData in pairs(buffsNeeded) do
if pos <= 4 and listData.isDebuff and not foundIndicators[pos] then
if MatchesList(debuffName, listData) then
f.indicators[pos].icon:SetTexture(texture)
f.indicators[pos].index = i
f.indicators[pos].isDebuff = true
f.indicators[pos]:Show()
foundIndicators[pos] = true
end
end
end
end
end
if hasDebuff then
f.health.bg:SetVertexColor(debuffColor.r, debuffColor.g, debuffColor.b, 1)
else
f.health.bg:SetVertexColor(_A.slotBg[1], _A.slotBg[2], _A.slotBg[3], _A.slotBg[4] or 1)
end
end