2d815b1132
Points de combo : ComboFrame, le seul affichage de 3.3.5, était supprimé sans remplaçant ; voleurs et druides félins n'en voyaient aucun. Il est gardé et posé sur notre portrait, au décalage de ComboFrame.xml. Joueur en véhicule : les barres montrent "vehicle", mais seuls les événements de "player" étaient lus ; la vie et l'énergie du véhicule se figeaient (Oculus, Ulduar, Joug-d'hiver). Les textes suivent aussi. Groupe : un membre sorti de son véhicule gardait le jeton partypetN, et son cadre ne se mettait plus jamais à jour ; le test porte sur partyN, et le nom est celui du membre, comme le client. Barre d'incantation : STOP, FAILED et INTERRUPTED ne visent plus que l'incantation affichée (son castID), comme CastingBarFrame : une touche pressée de nouveau pendant un sort affichait « Échec » et effaçait la vraie barre ; un sort instantané refusé montrait une barre rouge. Cible de la cible : "targettarget" n'a aucun événement en 3.3.5 ; elle est interrogée cinq fois par seconde tant qu'elle est montrée (le client le fait à chaque image). Après un rechargement en combat, les cadres joueur et cible du client sont masqués à la sortie du combat. Raid : chaque événement d'unité reconstruisait tout le bouton (barres, rôle, auras, onze icônes réancrées, une table allouée) ; santé, ressource, auras et menace ne refont plus que leur partie, et les grilles d'auras ne bougent que si le décalage des icônes de dissipation change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KxRZNHkq453dyUMcWsyUf1
668 lines
23 KiB
Lua
668 lines
23 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",
|
|
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)
|
|
m.leader: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)
|
|
local function updateLeader(m)
|
|
if GetPartyLeaderIndex() == m:GetID() then
|
|
ForeverUI.SetAtlas(m.leader, HasLFGRestrictions() and P.guide or P.leader)
|
|
m.leader:Show()
|
|
else
|
|
m.leader: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" }) 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()
|
|
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)
|