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