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