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 -- Pre-allocated tables reused every UpdateAuras call to avoid per-call garbage local _foundIndicators = { [1] = false, [2] = false, [3] = false, [4] = false } local _debuffColor = { r = 0, g = 0, b = 0 } -- Module-level helper: match aura name against a list (no closure allocation) local function MatchesList(auraName, list) for _, name in ipairs(list) do if string.find(auraName, name) then return true end end return false end -- Module-level helper: get buff name via SuperWoW aura ID or tooltip scan local function RaidGetBuffName(unit, index) if SFrames.superwow_active and SpellInfo 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 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 RaidGetDebuffName(unit, index) if SFrames.superwow_active and SpellInfo 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 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 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 local function SetTextureIfPresent(region, texturePath) if region and region.SetTexture and texturePath then region:SetTexture(texturePath) end 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 = tonumber(db.raidPowerHeight) or (height - healthHeight - 3) powerHeight = math.max(0, math.min(height - 3, powerHeight)) if not db.raidShowPower then powerHeight = 0 healthHeight = height - 2 end local gradientStyle = SFrames:IsGradientStyle() local availablePowerWidth = width - 2 if availablePowerWidth < 20 then availablePowerWidth = 20 end local rawPowerWidth = tonumber(db.raidPowerWidth) local legacyFullWidth = tonumber(db.raidFrameWidth) or width 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 - availablePowerWidth) < 0.5 then powerWidth = defaultPowerWidth else powerWidth = rawPowerWidth end powerWidth = Clamp(math.floor(powerWidth + 0.5), 20, maxPowerWidth) local powerOffsetX = Clamp(math.floor((tonumber(db.raidPowerOffsetX) or 0) + 0.5), -120, 120) local powerOffsetY = Clamp(math.floor((tonumber(db.raidPowerOffsetY) or 0) + 0.5), -80, 80) 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 local healthFont = tonumber(db.raidHealthFontSize) or valueFont local powerFont = tonumber(db.raidPowerFontSize) or valueFont return { width = width, height = height, healthHeight = healthHeight, powerHeight = powerHeight, powerWidth = powerWidth, powerOffsetX = powerOffsetX, powerOffsetY = powerOffsetY, powerOnTop = db.raidPowerOnTop == true, horizontalGap = hgap, verticalGap = vgap, groupGap = groupGap, nameFont = nameFont, valueFont = valueFont, healthFont = healthFont, powerFont = powerFont, healthTexture = SFrames:ResolveBarTexture("raidHealthTexture", "barTexture"), powerTexture = SFrames:ResolveBarTexture("raidPowerTexture", "barTexture"), 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", metrics.powerOffsetX, -1 + metrics.powerOffsetY) frame.power:SetWidth(metrics.powerWidth) frame.power:SetHeight(metrics.powerHeight) else frame.power:Hide() if frame.powerBGFrame then frame.powerBGFrame:Hide() end end end SFrames:ApplyStatusBarTexture(frame.health, "raidHealthTexture", "barTexture") SFrames:ApplyStatusBarTexture(frame.power, "raidPowerTexture", "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, "raid") if frame.healthBGFrame then SFrames:ApplyConfiguredUnitBackdrop(frame.healthBGFrame, "raid") end if frame.powerBGFrame then SFrames:ApplyConfiguredUnitBackdrop(frame.powerBGFrame, "raid") 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 -- 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 and metrics.showPower 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 -- 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 end if frame.nameText then SFrames:ApplyFontString(frame.nameText, metrics.nameFont, "raidNameFontKey", "fontKey") end if frame.healthText then SFrames:ApplyFontString(frame.healthText, metrics.healthFont, "raidHealthFontKey", "fontKey") end if frame.powerText then SFrames:ApplyFontString(frame.powerText, metrics.powerFont, "raidPowerFontKey", "fontKey") 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 if SFrames:IsGradientStyle() then SFrames:ApplyBarGradient(f.health) 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 = "-" .. SFrames:FormatCompactNumber(maxHp - hp) end else txt = SFrames:FormatCompactNumber(hp) 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 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 barWidth = f:GetWidth() - 2 if barWidth <= 0 then predMine:Hide(); predOther:Hide(); predOver:Hide() 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 predMine:Hide(); predOther:Hide(); predOver:Hide() 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) if SFrames:IsGradientStyle() then SFrames:ApplyBarGradient(f.power) end SFrames:UpdateRainbowBar(f.power, power, maxPower, unit) 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() -- Reuse pre-allocated table _foundIndicators[1] = false _foundIndicators[2] = false _foundIndicators[3] = false _foundIndicators[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 -- Check Buffs (using module-level helpers, no closures) for i = 1, 32 do local texture, applications = UnitBuff(unit, i) if not texture then break end local buffName = RaidGetBuffName(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 -- Reuse pre-allocated table _debuffColor.r = _A.slotBg[1] _debuffColor.g = _A.slotBg[2] _debuffColor.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; _debuffColor.g = 0.6; _debuffColor.b = 1 elseif dispelType == "Curse" then _debuffColor.r = 0.6; _debuffColor.g = 0; _debuffColor.b = 1 elseif dispelType == "Disease" then _debuffColor.r = 0.6; _debuffColor.g = 0.4; _debuffColor.b = 0 elseif dispelType == "Poison" then _debuffColor.r = 0; _debuffColor.g = 0.6; _debuffColor.b = 0 end end local debuffName = RaidGetDebuffName(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