Files
moon-well-ui/data/addon/ForeverUI/PartyFrame.lua
T
Adrestie 164c0e22b1 ForeverUI : icônes d'assistant et de maître du butin
Les portraits et les cadres de raid ne montraient que le chef. Camelot n'a
pas ces icônes : celles de 3.3.5 (UI-Group-AssistantIcon,
UI-Group-MasterLooter). Portrait du joueur et du groupe : l'assistant de
raid à la place du chef, le maître du butin placé comme 3.3.5 le fait près
de l'icône de chef (40, 2) et (32, 0). Cadres de raid : rang, rôle, maître
du butin puis le nom, comme l'onglet Raid ; sans eux, rien ne bouge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 03:07:10 +02:00

695 lines
24 KiB
Lua

-- Party frames, reproduced from Camelot's blizzard_unitframe (shared/partyframe.*,
-- mainline/partyframetemplates.xml, partymemberframe.lua). Members and pets are secure
-- buttons shown by RegisterUnitWatch and the container hides in raids through a state driver,
-- so both work in combat. 3.3.5 has no heal prediction, absorbs, phase, summon or away icons.
local ForeverUI = ForeverUI or {}
_G.ForeverUI = ForeverUI
local SEP = string.char(92)
local L = ForeverUI.L
-- Sizes, offsets and atlases from Camelot's code, in one table: Lua 5.1 limits a function
-- to 60 upvalues. x, y: container position (Camelot anchors it under the folded raid manager).
local P = {
memberW = 120, memberH = 53, spacing = 10, petSpacing = 26, down = 2,
x = 22, y = -147,
art = "ui-hud-unitframe-party-portraiton",
vehicleArt = "ui-hud-unitframe-party-portraiton-vehicle",
glow = "ui-hud-unitframe-party-portraiton-incombat",
vehicleGlow = "ui-hud-unitframe-party-portraiton-vehicle-incombat",
status = "ui-hud-unitframe-party-portraiton-status",
vehicleStatus = "ui-hud-unitframe-party-portraiton-vehicle-status",
health = "ui-hud-unitframe-party-portraiton-bar-health",
vehicleHealth = "ui-hud-unitframe-party-portraiton-vehicle-bar-health",
leader = "ui-hud-unitframe-player-group-leadericon",
guide = "ui-hud-unitframe-player-group-guideicon",
-- camelot has no assistant nor master looter icon: the 3.3.5 ones (16 x 16)
assistant = "Interface" .. SEP .. "GroupFrame" .. SEP .. "UI-Group-AssistantIcon",
masterLooter = "Interface" .. SEP .. "GroupFrame" .. SEP .. "UI-Group-MasterLooter",
pvpFfa = "ui-hud-unitframe-player-pvp-ffaicon",
pvpHorde = "ui-hud-unitframe-player-pvp-hordeicon",
pvpAlliance = "ui-hud-unitframe-player-pvp-allianceicon",
pvpScale = 0.6,
disconnected = "Interface" .. SEP .. "CharacterFrame" .. SEP .. "Disconnect-Icon",
auraBorder = "Interface" .. SEP .. "Buttons" .. SEP .. "UI-Debuff-Overlays",
markers = {
ready = "Interface" .. SEP .. "ForeverUI" .. SEP .. "lfgframe" .. SEP .. "ui-lfg-readymark",
notready = "Interface" .. SEP .. "ForeverUI" .. SEP .. "lfgframe" .. SEP .. "ui-lfg-declinemark",
waiting = "Interface" .. SEP .. "ForeverUI" .. SEP .. "lfgframe" .. SEP .. "ui-lfg-pendingmark",
},
readyCheckStay = 10, readyCheckFade = 1.5,
auras = 4, auraSide = 15, auraGap = 2,
pulse = 0.5, pulseMin = 127 / 255,
-- Normal / vehicle: anchors and widths set by ToPlayerArt and ToVehicleArt
-- ({ x, y } or { x, y, width })
player = { art = { 1, -2 }, glow = { 1, -2 }, status = { 1, -2 }, health = { 45, -19, 70 },
resource = { 42, -30, 73 }, name = { 46, -6, 57 } },
vehicle = { art = { 0, 0 }, glow = { -4, 4 }, status = { -3, 3 }, health = { 48, -18, 67 },
resource = { 45, -29, 70 }, name = { 49, -6, 56 } },
}
-- Resource atlas name, built from the power token
local RESOURCE = { MANA = "Mana", RAGE = "Rage", FOCUS = "Focus", ENERGY = "Energy", RUNIC_POWER = "RunicPower" }
local function resourceAtlas(token, vehicle)
local name = RESOURCE[token or ""] or "Mana"
return "ui-hud-unitframe-party-portraiton" .. (vehicle and "-vehicle" or "") .. "-bar-" .. string.lower(name)
end
local G = {} -- shared state
ForeverUI.PartyFrame = G
-- ----------------------------------------------------------------- Container
local container = CreateFrame("Frame", "ForeverUIPartyFrame", UIParent)
container:SetFrameStrata("LOW")
container:SetWidth(P.memberW)
G.container = container
-- Distance between two members: 10 apart, 26 when party pets are shown
local function step()
local pets = GetCVarBool and GetCVarBool("showPartyPets")
return P.memberH + (pets and P.petSpacing or P.spacing)
end
-- ------------------------------------------------------------------ Parts
local function field(parent, layer, font)
local fs = parent:CreateFontString(nil, layer or "OVERLAY", font)
return fs
end
local function createAuras(parent, name, x, y, unit)
local auras = {}
for k = 1, P.auras do
local b = CreateFrame("Button", name .. "Debuff" .. k, parent)
b:SetWidth(P.auraSide)
b:SetHeight(P.auraSide)
b:SetPoint("TOPLEFT", parent, "TOPLEFT", x + (k - 1) * (P.auraSide + P.auraGap), y)
b.icon = b:CreateTexture(nil, "ARTWORK")
b.icon:SetAllPoints(b)
b.border = b:CreateTexture(nil, "OVERLAY")
b.border:SetTexture(P.auraBorder)
b.border:SetTexCoord(0.296875, 0.5703125, 0, 0.515625)
b.border:SetPoint("TOPLEFT", b, "TOPLEFT", -1, 1)
b.border:SetPoint("BOTTOMRIGHT", b, "BOTTOMRIGHT", 1, -1)
b.stack = b:CreateFontString(nil, "OVERLAY", "NumberFontNormalSmall")
b.stack:SetPoint("BOTTOMRIGHT", b, "BOTTOMRIGHT", 5, 0)
b.cooldown = CreateFrame("Cooldown", nil, b)
b.cooldown:SetReverse(true)
b.cooldown:SetPoint("CENTER", b, "CENTER", 0, -1)
b.cooldown:SetWidth(P.auraSide)
b.cooldown:SetHeight(P.auraSide)
b:SetScript("OnEnter", function(self)
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetUnitDebuff(self.unit, self.index, self.filter)
end)
b:SetScript("OnLeave", function() GameTooltip:Hide() end)
b:Hide()
auras[k] = b
end
return auras
end
-- Debuffs follow RefreshDebuffs: filter "RAID" when the dispellable-debuffs option is on
-- and the unit can be assisted
local function updateAuras(auras, unit)
local filter
if GetCVarBool("showDispelDebuffs") and UnitCanAssist("player", unit) then
filter = "RAID"
end
local color
for k, b in ipairs(auras) do
local name, _, icon, stack, type, duration, finish = UnitDebuff(unit, k, filter)
if icon then
b.unit, b.index, b.filter = unit, k, filter
b.icon:SetTexture(icon)
local c = DebuffTypeColor[type or "none"] or DebuffTypeColor["none"]
b.border:SetVertexColor(c.r, c.g, c.b)
if stack and stack > 1 then
b.stack:SetText(stack > 99 and "*" or stack)
else
b.stack:SetText("")
end
if duration and duration > 0 and finish then
CooldownFrame_SetTimer(b.cooldown, finish - duration, duration, 1)
else
b.cooldown:Hide()
end
b:Show()
if type and not color then color = c end
else
b:Hide()
end
end
return color
end
-- --------------------------------------------------------------- Pet
local function createPet(member, i)
local f = CreateFrame("Button", "ForeverUIPartyMemberFrame" .. i .. "PetFrame", member, "SecureUnitButtonTemplate")
f:SetWidth(64)
f:SetHeight(23)
f:SetFrameStrata("LOW")
f:SetPoint("TOPLEFT", member, "TOPLEFT", 23, -43)
f.unit = "partypet" .. i
f:SetAttribute("unit", f.unit)
f:SetAttribute("*type1", "target")
f:SetAttribute("toggleForVehicle", true)
f:RegisterForClicks("AnyUp")
f.portrait = f:CreateTexture(nil, "BACKGROUND")
f.portrait:SetWidth(18)
f.portrait:SetHeight(18)
f.portrait:SetPoint("TOPLEFT", f, "TOPLEFT", 3, -3)
-- 3.3.5 cannot scale a texture: art and glow at scale 0.5 are sized by hand,
-- 120 x 49 -> 60 x 24.5; 114 x 47 -> 57 x 23.5
f.art = f:CreateTexture(nil, "ARTWORK")
ForeverUI.SetAtlas(f.art, P.art, true)
f.art:SetWidth(60)
f.art:SetHeight(24.5)
f.art:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -0.5)
local hovered = CreateFrame("Frame", nil, f)
hovered:SetAllPoints(f)
hovered:SetFrameLevel(f:GetFrameLevel() + 1)
f.glow = hovered:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(f.glow, P.glow, true)
f.glow:SetWidth(57)
f.glow:SetHeight(23.5)
f.glow:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -0.5)
f.glow:Hide()
-- Health: 71 x 10 at (43, -18), at scale 0.5
f.health = hovered:CreateTexture(nil, "BORDER")
f.health:SetPoint("TOPLEFT", f, "TOPLEFT", 21.5, -9)
f.auras = createAuras(f, f:GetName(), 24, -16)
f:SetScript("OnEnter", function(self)
GameTooltip_SetDefaultAnchor(GameTooltip, self)
GameTooltip:SetUnit(self.display or self.unit)
GameTooltip:Show()
end)
f:SetScript("OnLeave", function() GameTooltip:FadeOut() end)
return f
end
-- ----------------------------------------------------------------- Member
local function createMember(i)
local m = CreateFrame("Button", "ForeverUIPartyMemberFrame" .. i, container, "SecureUnitButtonTemplate")
m:SetID(i)
m:SetWidth(P.memberW)
m:SetHeight(P.memberH)
m:SetFrameStrata("LOW")
m.unit = "party" .. i
m:SetAttribute("unit", m.unit)
m:SetAttribute("*type1", "target")
m:SetAttribute("*type2", "menu")
m:SetAttribute("toggleForVehicle", true)
m:RegisterForClicks("AnyUp")
-- The client's menu: that of its own party frame, UnitPopup "PARTY"
m.menu = function(self)
ForeverUI.UnitMenu.open(ToggleDropDownMenu, 1, nil, _G["PartyMemberFrame" .. i .. "DropDown"], self:GetName(), 47, 15)
end
m.portrait = m:CreateTexture(nil, "BACKGROUND")
m.portrait:SetWidth(37)
m.portrait:SetHeight(37)
m.portrait:SetPoint("TOPLEFT", m, "TOPLEFT", 7, -6)
m.art = m:CreateTexture(nil, "ARTWORK")
-- Bars above the art (Camelot puts them in a child frame)
local bars = CreateFrame("Frame", nil, m)
bars:SetAllPoints(m)
bars:SetFrameLevel(m:GetFrameLevel() + 1)
m.health = bars:CreateTexture(nil, "ARTWORK")
m.resource = bars:CreateTexture(nil, "ARTWORK")
m.healthText = field(bars, "OVERLAY", "TextStatusBarText")
m.resourceText = field(bars, "OVERLAY", "TextStatusBarText")
-- What goes above everything
local hovered = CreateFrame("Frame", nil, m)
hovered:SetAllPoints(m)
hovered:SetFrameLevel(m:GetFrameLevel() + 2)
m.hovered = hovered
m.glow = hovered:CreateTexture(nil, "BACKGROUND")
m.glow:Hide()
m.status = hovered:CreateTexture(nil, "BACKGROUND")
m.status:Hide()
m.name = field(hovered, "OVERLAY", "GameFontNormalSmall")
m.name:SetJustifyH("LEFT")
m.name:SetHeight(12)
m.leader = hovered:CreateTexture(nil, "OVERLAY")
m.leader:SetPoint("BOTTOM", m, "TOP", -10, -6)
ForeverUI.SetAtlas(m.leader, P.leader)
m.leader:Hide()
-- 32 right of the leader icon, as PartyMemberFrame places its MasterIcon
m.master = hovered:CreateTexture(nil, "OVERLAY")
m.master:SetTexture(P.masterLooter)
m.master:SetWidth(16)
m.master:SetHeight(16)
m.master:SetPoint("TOPLEFT", m.leader, "TOPLEFT", 32, 0)
m.master:Hide()
m.pvp = hovered:CreateTexture(nil, "OVERLAY")
m.pvp:Hide()
m.disconnected = hovered:CreateTexture(nil, "OVERLAY")
m.disconnected:SetTexture(P.disconnected)
m.disconnected:SetWidth(64)
m.disconnected:SetHeight(64)
m.disconnected:SetPoint("LEFT", m, "LEFT", -7, -1)
m.disconnected:Hide()
m.role = hovered:CreateTexture(nil, "OVERLAY")
m.role:SetWidth(12)
m.role:SetHeight(12)
m.role:SetPoint("TOPRIGHT", m, "TOPRIGHT", -5, -5)
m.role:Hide()
-- Ready check icon: 36 x 36, two levels above (RaiseFrameLevelByTwo). The marks are
-- Camelot's UI-LFG-*Mark, cut from their 2048 sheet (tools/cut_marks.py).
local call = CreateFrame("Frame", nil, m)
call:SetWidth(36)
call:SetHeight(36)
call:SetPoint("CENTER", m.portrait, "CENTER", 0, -2)
call:SetFrameLevel(m:GetFrameLevel() + 4)
call.icon = call:CreateTexture(nil, "ARTWORK")
call.icon:SetAllPoints(call)
call:Hide()
m.call = call
m.auras = createAuras(hovered, m:GetName(), 48, -43)
m.pet = createPet(m, i)
m:SetScript("OnEnter", function(self)
-- Hover shows the bar texts (when CVar partyStatusText is 0). The tooltip has no
-- UNIT_POPUP_RIGHT_CLICK line: that string does not exist in the 3.3.5 client.
self.hover = true
G.updateMember(self)
GameTooltip_SetDefaultAnchor(GameTooltip, self)
GameTooltip:SetUnit(self.unit)
GameTooltip:Show()
-- The client's buff tooltip (PartyMemberBuffTooltip), where Camelot puts its own
if PartyMemberBuffTooltip and PartyMemberBuffTooltip_Update then
PartyMemberBuffTooltip:ClearAllPoints()
PartyMemberBuffTooltip:SetPoint("TOPLEFT", self, "TOPLEFT", 47, -25)
PartyMemberBuffTooltip_Update(self)
end
end)
m:SetScript("OnLeave", function(self)
self.hover = nil
G.updateMember(self)
GameTooltip:FadeOut()
if PartyMemberBuffTooltip then PartyMemberBuffTooltip:Hide() end
end)
return m
end
G.members = {}
for i = 1, 4 do
G.members[i] = createMember(i)
end
-- ------------------------------------------------------------ Updates
-- Displayed unit: the vehicle if any (the frame follows partypetN)
local function units(m)
local i = m:GetID()
if UnitHasVehicleUI("party" .. i) then
return "partypet" .. i, "party" .. i, true
end
return "party" .. i, "partypet" .. i, false
end
local function place(tex, atlas, x, y, rel)
ForeverUI.SetAtlas(tex, atlas)
tex:ClearAllPoints()
tex:SetPoint("TOPLEFT", rel, "TOPLEFT", x, y)
end
-- ToPlayerArt / ToVehicleArt
local function updateArt(m)
local geo = m.vehicle and P.vehicle or P.player
place(m.art, m.vehicle and P.vehicleArt or P.art, geo.art[1], geo.art[2], m)
place(m.glow, m.vehicle and P.vehicleGlow or P.glow, geo.glow[1], geo.glow[2], m)
place(m.status, m.vehicle and P.vehicleStatus or P.status, geo.status[1], geo.status[2], m)
m.health:ClearAllPoints()
m.health:SetPoint("TOPLEFT", m, "TOPLEFT", geo.health[1], geo.health[2])
m.resource:ClearAllPoints()
m.resource:SetPoint("TOPLEFT", m, "TOPLEFT", geo.resource[1], geo.resource[2])
m.name:ClearAllPoints()
m.name:SetPoint("TOPLEFT", m, "TOPLEFT", geo.name[1], geo.name[2])
m.name:SetWidth(geo.name[3])
end
local function fraction(value, maximum)
if not maximum or maximum <= 0 then return 0 end
return value / maximum
end
local function updateBarText(fs, value, maximum, hover)
local always = GetCVar("partyStatusText") == "1"
if not (always or hover) or not maximum or maximum <= 0 then
fs:Hide()
return
end
if GetCVarBool("statusTextPercentage") then
fs:SetText(math.ceil(value / maximum * 100) .. "%")
else
fs:SetText(value .. " / " .. maximum)
end
fs:Show()
end
local function updateHealth(m)
local u = m.unit
local geo = m.vehicle and P.vehicle or P.player
local atlas = m.vehicle and P.vehicleHealth or P.health
local connected = UnitIsConnected(m.unit)
local health, max = UnitHealth(u), UnitHealthMax(u)
-- Disconnected: full bar, desaturated
ForeverUI.SetAtlasFill(m.health, atlas, connected and fraction(health, max) or 1, geo.health[3])
m.health:SetDesaturated(not connected)
m.healthText:ClearAllPoints()
m.healthText:SetPoint("CENTER", m, "TOPLEFT", geo.health[1] + geo.health[3] / 2, geo.health[2] - 5)
if UnitIsDeadOrGhost(u) then
m.healthText:SetText(DEAD)
m.healthText:Show()
else
updateBarText(m.healthText, health, max, m.hover)
end
-- PartyMemberHealthCheck: portrait tint
m.percent = fraction(health, max)
if UnitIsDead(u) then
m.portrait:SetVertexColor(0.35, 0.35, 0.35, 1)
elseif UnitIsGhost(u) then
m.portrait:SetVertexColor(0.2, 0.2, 0.75, 1)
elseif m.percent > 0 and m.percent <= 0.2 then
m.portrait:SetVertexColor(1, 0, 0)
else
m.portrait:SetVertexColor(1, 1, 1, 1)
end
G.updatePulse()
end
local function updateResource(m)
local u = m.unit
local geo = m.vehicle and P.vehicle or P.player
local type, token = UnitPowerType(u)
local value, max = UnitPower(u, type), UnitPowerMax(u, type)
ForeverUI.SetAtlasFill(m.resource, resourceAtlas(token, m.vehicle), fraction(value, max), geo.resource[3])
-- 3.3.5 has no masks. Camelot's mask hides the bar's first pixel, so the bar starts one pixel
-- further, 73 wide (P.player.resource); its 1-3 px top-left bevel is not reproduced.
m.resourceText:ClearAllPoints()
m.resourceText:SetPoint("CENTER", m, "TOPLEFT", geo.resource[1] + geo.resource[3] / 2 + 2, geo.resource[2] - 3.5)
updateBarText(m.resourceText, value, max, m.hover)
end
local function updatePortrait(m)
SetPortraitTexture(m.portrait, m.unit)
m.portrait:SetDesaturated(not UnitIsConnected(m.unit))
end
-- Leader or guide (HasLFGRestrictions: a Dungeon Finder group), or raid assistant in its place
-- (the raid list's rank icon); master looter as PartyMemberFrame_UpdateLeader shows it
local function updateLeader(m)
local raidIndex = UnitInRaid("party" .. m:GetID())
local rank = raidIndex and select(2, GetRaidRosterInfo(raidIndex + 1))
if GetPartyLeaderIndex() == m:GetID() then
ForeverUI.SetAtlas(m.leader, HasLFGRestrictions() and P.guide or P.leader)
m.leader:Show()
elseif rank == 1 then
m.leader:SetTexture(P.assistant)
m.leader:SetTexCoord(0, 1, 0, 1)
m.leader:SetWidth(16)
m.leader:SetHeight(16)
m.leader:Show()
else
m.leader:Hide()
end
local _, master = GetLootMethod()
if master == m:GetID() then m.master:Show() else m.master:Hide() end
end
local function updatePvP(m)
local u = m.unit
local atlas
if UnitIsPVPFreeForAll(u) then
atlas = P.pvpFfa
elseif UnitIsPVP(u) then
local faction = UnitFactionGroup(u)
if faction == "Horde" then atlas = P.pvpHorde elseif faction == "Alliance" then atlas = P.pvpAlliance end
end
if atlas and ForeverUI.SetAtlas(m.pvp, atlas) then
-- At scale 0.6: atlas size and offset (24, -68)
m.pvp:SetWidth(m.pvp:GetWidth() * P.pvpScale)
m.pvp:SetHeight(m.pvp:GetHeight() * P.pvpScale)
m.pvp:ClearAllPoints()
m.pvp:SetPoint("CENTER", m, "TOPLEFT", 24 * P.pvpScale, -68 * P.pvpScale)
m.pvp:Show()
else
m.pvp:Hide()
end
end
-- UnitGroupRolesAssigned returns three booleans in 3.3.5
local function updateRole(m)
local tank, healer, damage = UnitGroupRolesAssigned(m.unit)
local atlas = (tank and "roleicon-tiny-tank") or (healer and "roleicon-tiny-healer") or (damage and "roleicon-tiny-dps")
if atlas and ForeverUI.SetAtlas(m.role, atlas, true) then
m.role:Show()
else
m.role:Hide()
end
end
local function updateThreat(m)
local status = UnitThreatSituation(m.unit)
if IsThreatWarningEnabled() and status and status > 0 and not UnitIsDeadOrGhost(m.unit) then
m.glow:SetVertexColor(GetThreatStatusColor(status))
m.glow:Show()
else
m.glow:Hide()
end
end
local function updateMemberAuras(m)
local color = updateAuras(m.auras, m.unit)
if color then
m.status:SetVertexColor(color.r, color.g, color.b)
m.status:Show()
else
m.status:Hide()
end
end
local function updatePet(m)
local f = m.pet
f.display = m.petUnit
local u = f.display
if not UnitExists(u) then return end
SetPortraitTexture(f.portrait, u)
local connected = UnitIsConnected(m.unit)
-- 71 x 10 at scale 0.5: 35.5 long, green tint (no lockColor)
ForeverUI.SetAtlasFill(f.health, P.health, connected and fraction(UnitHealth(u), UnitHealthMax(u)) or 1, 35.5)
f.health:SetHeight(5)
if connected then f.health:SetVertexColor(0, 1, 0) else f.health:SetVertexColor(0.5, 0.5, 0.5) end
local status = UnitThreatSituation(u)
if IsThreatWarningEnabled() and status and status > 0 then
f.glow:SetVertexColor(GetThreatStatusColor(status))
f.glow:Show()
else
f.glow:Hide()
end
updateAuras(f.auras, u)
end
local function updateDisconnected(m)
if UnitIsConnected(m.unit) then m.disconnected:Hide() else m.disconnected:Show() end
end
function G.updateMember(m)
-- m.unit may still name the vehicle the member has left (partypetN, gone with it): test
-- the member's own token, or the frame never updates again
local member = "party" .. m:GetID()
if not UnitExists(member) then return end
local display, pet, vehicle = units(m)
m.unit, m.petUnit, m.vehicle = display, pet, vehicle
updateArt(m)
-- the member's name, not the vehicle's (PartyMemberFrame_ToVehicleArt: overrideName)
m.name:SetText(GetUnitName(member, true))
updatePortrait(m)
updateHealth(m)
updateResource(m)
updateLeader(m)
updatePvP(m)
updateRole(m)
updateThreat(m)
updateMemberAuras(m)
updateDisconnected(m)
updatePet(m)
end
-- Portrait pulse below 20% health: alpha goes from 127/255 to 1 and back, in 0.5 s
-- half-periods, while any member is in that state
local pulse = CreateFrame("Frame")
pulse.clock = 0
pulse:Hide()
pulse:SetScript("OnUpdate", function(self, elapsed)
self.clock = self.clock + elapsed
local t = (self.clock % (2 * P.pulse)) / P.pulse
local a = t <= 1 and (1 - t * (1 - P.pulseMin)) or (P.pulseMin + (t - 1) * (1 - P.pulseMin))
for _, m in ipairs(G.members) do
if m.pulsing then m.portrait:SetAlpha(a) end
end
end)
function G.updatePulse()
local one = false
for _, m in ipairs(G.members) do
local pulsing = m:IsShown() and not UnitIsDeadOrGhost(m.unit or m.unit) and m.percent
and m.percent > 0 and m.percent <= 0.2
m.pulsing = pulsing
if not pulsing then m.portrait:SetAlpha(1) end
one = one or pulsing
end
if one then pulse:Show() else pulse:Hide() end
end
-- ---------------------------------------------------------------------- Ready check
local function updateReadyCheck(m, finish)
local state = GetReadyCheckStatus(m.unit)
local a = m.call
if not state then
if not finish then a:Hide() end
return
end
if finish and state == "waiting" then state = "notready" end
a.icon:SetTexture(P.markers[state] or P.markers.waiting)
a:SetAlpha(1)
a.rest = nil
if finish then a.rest = P.readyCheckStay + P.readyCheckFade end
a:Show()
end
local timer = CreateFrame("Frame")
timer:SetScript("OnUpdate", function(self, elapsed)
local active = false
for _, m in ipairs(G.members) do
local a = m.call
if a.rest then
a.rest = a.rest - elapsed
if a.rest <= 0 then
a.rest = nil
a:Hide()
else
if a.rest < P.readyCheckFade then a:SetAlpha(a.rest / P.readyCheckFade) end
active = true
end
end
end
if not active then self:Hide() end
end)
timer:Hide()
-- ------------------------------------------------------------ Layout
-- The step depends on the party pets option; out of combat only
function G.layout()
if InCombatLockdown() then
G.needsLayout = true
return
end
G.needsLayout = nil
local p = step()
for i, m in ipairs(G.members) do
m:ClearAllPoints()
m:SetPoint("TOPLEFT", container, "TOPLEFT", 0, -(i - 1) * p)
local f = m.pet
UnregisterUnitWatch(f)
if GetCVarBool("showPartyPets") then
RegisterUnitWatch(f)
else
f:Hide()
end
end
container:SetHeight(4 * P.memberH + 3 * (p - P.memberH) + P.down)
end
-- Client party frames: no events, and kept hidden by a state driver, so a Show from
-- elsewhere (the client, another addon), even in combat, is undone
local function hideClient()
if InCombatLockdown() or G.clientHidden then return end
for i = 1, 4 do
local f = _G["PartyMemberFrame" .. i]
if f then
f:UnregisterAllEvents()
f:Hide()
RegisterStateDriver(f, "visibility", "hide")
end
end
if PartyMemberBackground then ForeverUI.Suppress(PartyMemberBackground) end
G.clientHidden = true
end
-- ---------------------------------------------------------------- Events
local function memberOf(unit)
if not unit then return nil end
local i = unit:match("^party(%d)$") or unit:match("^partypet(%d)$")
return i and G.members[tonumber(i)]
end
local listener = CreateFrame("Frame")
G.listener = listener
for _, ev in ipairs({ "PLAYER_ENTERING_WORLD", "PARTY_MEMBERS_CHANGED", "PARTY_LEADER_CHANGED",
"PARTY_MEMBER_ENABLE", "PARTY_MEMBER_DISABLE", "UNIT_HEALTH", "UNIT_MAXHEALTH", "UNIT_DISPLAYPOWER",
"UNIT_MANA", "UNIT_RAGE", "UNIT_FOCUS", "UNIT_ENERGY", "UNIT_RUNIC_POWER", "UNIT_MAXMANA",
"UNIT_MAXRAGE", "UNIT_MAXFOCUS", "UNIT_MAXENERGY", "UNIT_MAXRUNIC_POWER", "UNIT_NAME_UPDATE",
"UNIT_PORTRAIT_UPDATE", "UNIT_AURA", "UNIT_PET", "UNIT_FACTION", "UNIT_THREAT_SITUATION_UPDATE",
"UNIT_ENTERED_VEHICLE", "UNIT_EXITED_VEHICLE", "READY_CHECK", "READY_CHECK_CONFIRM",
"READY_CHECK_FINISHED", "PLAYER_ROLES_ASSIGNED", "CVAR_UPDATE", "PLAYER_REGEN_ENABLED",
"PARTY_LOOT_METHOD_CHANGED", "RAID_ROSTER_UPDATE" }) do
listener:RegisterEvent(ev)
end
local function all()
for _, m in ipairs(G.members) do G.updateMember(m) end
end
listener:SetScript("OnEvent", function(self, ev, unit)
if ev == "PLAYER_ENTERING_WORLD" then
hideClient()
G.layout()
all()
elseif ev == "PLAYER_REGEN_ENABLED" then
hideClient()
if G.needsLayout then G.layout() end
elseif ev == "PARTY_MEMBERS_CHANGED" or ev == "PARTY_LEADER_CHANGED" or ev == "PLAYER_ROLES_ASSIGNED" then
all()
elseif ev == "CVAR_UPDATE" then
G.layout()
all()
elseif ev == "READY_CHECK" or ev == "READY_CHECK_CONFIRM" then
for _, m in ipairs(G.members) do updateReadyCheck(m) end
elseif ev == "READY_CHECK_FINISHED" then
for _, m in ipairs(G.members) do updateReadyCheck(m, true) end
timer:Show()
elseif ev == "PARTY_LOOT_METHOD_CHANGED" or ev == "RAID_ROSTER_UPDATE" then
for _, m in ipairs(G.members) do
if UnitExists("party" .. m:GetID()) then updateLeader(m) end
end
else
local m = memberOf(unit)
if m then G.updateMember(m) end
end
end)
for _, m in ipairs(G.members) do
RegisterUnitWatch(m)
end
-- In a party, not in a raid (ShouldShowPartyFrames)
RegisterStateDriver(container, "visibility", "[group:raid] hide; [group] show; hide")
G.layout()
ForeverUI.Layout.Register(container, "partyframe", L.PARTYFRAME_EDIT_LABEL, "TOPLEFT", "TOPLEFT", P.x, P.y)