Files
Claude 39fbfc8c10 ForeverUI : barres, la barre de forme reste à l'écran en combat
Les boutons bonus sont ancrés à la glissière, ce qui la rend protégée :
en combat, ses SetPoint étaient bloqués (action bloquée par image). Après
un retrait hors combat elle reposait 45 px plus bas, et un druide prenant
une forme en combat gardait sa barre à moitié sous l'écran tout le combat.
Elle ne bouge plus en combat, attend au repos, y revient avant le
verrouillage, et suit une barre réaffichée pendant son propre retrait.
Durée : BONUSACTIONBAR_SLIDETIME du client (0,15 s).

Familier et postures : le porteur, protégé par les boutons qu'on y ancre,
était masqué avant le test de combat (familier mort ou expiré en combat :
action bloquée). Le test passe d'abord ; la sortie de combat le replace.

Choix d'icône d'un ensemble : une icône portée deux fois (deux anneaux,
bijoux ou armes identiques) ne compte qu'une fois, comme
RefreshEquipmentSetIconInfo, sinon la grille gagnait des cases vides au
delà de la liste du client.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KxRZNHkq453dyUMcWsyUf1
2026-10-01 23:19:58 +00:00

322 lines
10 KiB
Lua

-- Pet action bar: the client's secure PetActionButtons, reskinned as camelot's
-- SmallActionButtonTemplate (shared/PetActionBar.lua UpdateButtonState); never recreated,
-- never resized or moved in combat. 3.3.5 has no HasPetActionHighlightMark: no highlight mark.
local SIZE = 30 -- SmallActionButtonTemplate
local GAP = 2 -- minButtonPadding
local STEP = SIZE + GAP -- 32
local FRAME_W, FRAME_H = 35, 35 -- NormalTexture and PushedTexture
local STATE_W, STATE_H = 31.6, 30.9 -- highlight, checked, border, flash
local NUM_BUTTONS = 10
local ORIGINAL_SIZE = 36 -- the 3.3.5 button
local AUTOCAST_BORDER = 58 -- its autocast border
local L = ForeverUI.L
local GRAYED = 0.4 -- unusable action
local ATTACK_CHECK_ALPHA = 0.5 -- check mark alpha on the attack action
-- Default place: just above the reputation bar, on the action bar's left edge. When the
-- stance bar, or the totem bar in its default place, is shown, the pet bar goes to its RIGHT,
-- two icons apart.
local LEFT_EDGE = -587.5 -- left edge of the action bar
local ROW_Y = 84 -- above the reputation bar
local BARS_GAP = 2 * SIZE -- two icons between the two bars
local ATLAS = {
normal = "ui-hud-actionbar-iconframe",
pushed = "ui-hud-actionbar-iconframe-down",
highlight = "ui-hud-actionbar-iconframe-mouseover",
flash = "ui-hud-actionbar-iconframe-flash",
slot = "ui-hud-actionbar-iconframe-slot",
background = "ui-hud-actionbar-iconframe-background",
}
local buttonCount = NUM_PET_ACTION_SLOTS or NUM_BUTTONS
local carrier = CreateFrame("Frame", "ForeverUIPetBarHolder", UIParent)
carrier:SetWidth(STEP)
carrier:SetHeight(SIZE)
carrier:Hide()
local function hideNormalTexture(button)
local normalFont = button:GetNormalTexture()
if normalFont then
normalFont:SetAlpha(0)
normalFont:SetVertexColor(1, 1, 1, 0)
end
end
-- The four states are CENTERED: a 35 frame on a 30 button overflows by 5, and its hole
-- would leave the icon (see StanceBar.lua). add: ADD blend mode.
local function applyState(texture, atlas, width, height, add)
if not texture then
return
end
ForeverUI.SetAtlas(texture, atlas, true)
texture:SetWidth(width)
texture:SetHeight(height)
texture:ClearAllPoints()
texture:SetPoint("CENTER", 0, 0)
texture:SetBlendMode(add and "ADD" or "BLEND")
end
local function applySkin(button)
if not button or button.foreverSkinned then
return
end
local name = button:GetName()
button:SetWidth(SIZE)
button:SetHeight(SIZE)
hideNormalTexture(button)
local floatingBg = _G[name .. "FloatingBG"]
if floatingBg then
floatingBg:SetAlpha(0)
end
-- Autocast stays the client's: AutoCastTemplates exists only in mainline/, and only
-- camelot/ and shared/ count, so camelot keeps the original border and four sparkles. They
-- are only scaled to the button, from 36 to 30.
local scale = SIZE / ORIGINAL_SIZE
local autoCastable = _G[name .. "AutoCastable"]
if autoCastable then
autoCastable:SetWidth(AUTOCAST_BORDER * scale)
autoCastable:SetHeight(AUTOCAST_BORDER * scale)
end
-- The sparkles cannot be resized: the client places them at a fixed size and
-- AutoCastShine_OnUpdate moves them without resizing, so the frame is SCALED instead:
-- sizes and orbit follow.
local sparkles = _G[name .. "Shine"]
if sparkles then
sparkles:SetScale(scale)
end
local background = button:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(background, ATLAS.background, true)
background:SetAllPoints(button)
local slot = button:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(slot, ATLAS.slot, true)
slot:SetAllPoints(button)
button.foreverBackground = background
button.foreverSlot = slot
local icon = _G[name .. "Icon"]
if icon then
icon:ClearAllPoints()
icon:SetAllPoints(button)
icon:SetTexCoord(0, 1, 0, 1)
icon:SetDrawLayer("BORDER")
button.foreverIcon = icon
end
local frame = button:CreateTexture(nil, "OVERLAY")
applyState(frame, ATLAS.normal, FRAME_W, FRAME_H)
button.foreverFrame = frame
applyState(button:GetPushedTexture(), ATLAS.pushed, FRAME_W, FRAME_H)
applyState(button:GetHighlightTexture(), ATLAS.highlight, STATE_W, STATE_H)
applyState(button:GetCheckedTexture(), ATLAS.highlight, STATE_W, STATE_H, true)
applyState(_G[name .. "Flash"], ATLAS.flash, STATE_W, STATE_H)
local cooldown = _G[name .. "Cooldown"]
if cooldown and icon then
cooldown:ClearAllPoints()
cooldown:SetPoint("TOPLEFT", icon, "TOPLEFT", 1.7, -1.7)
cooldown:SetPoint("BOTTOMRIGHT", icon, "BOTTOMRIGHT", -1, 1)
button.foreverCooldown = cooldown
end
local hotkey = _G[name .. "HotKey"]
if hotkey then
hotkey:ClearAllPoints()
hotkey:SetPoint("TOPRIGHT", button, "TOPRIGHT", -3, -4)
end
local quantity = _G[name .. "Count"]
if quantity then
quantity:ClearAllPoints()
quantity:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", -3, 1)
end
button.foreverSkinned = true
end
-- Legacy bar art: 3.3.5 frames the pet bar with two sliding pieces (SlidingActionBarTexture0
-- and 1). All the frame's own texture regions are hidden; the buttons are child frames, not
-- regions, so they are untouched.
local function clearLegacyArt()
local bar = PetActionBarFrame
if not bar or not bar.GetNumRegions then
return
end
local regions = { bar:GetRegions() }
for _, region in ipairs(regions) do
if region and region.GetObjectType and region:GetObjectType() == "Texture" then
region:SetAlpha(0)
end
end
end
local function skinAll()
for index = 1, buttonCount do
applySkin(_G["PetActionButton" .. index])
end
clearLegacyArt()
end
-- The bar shows when the pet has an action bar AND is visible.
local function hasPet()
if not PetHasActionBar or not PetHasActionBar() then
return false
end
return UnitIsVisible and UnitIsVisible("pet") and true or false
end
-- The default place is recomputed: it depends on the stance bar, which comes and goes with
-- shapeshift forms, and rises one row when action bar 2 or 3 is shown under it
-- (MultiBars.lua). SetDefaults only moves the frame when the user has not moved it.
local function placeDefault()
local x = LEFT_EDGE
local stances = ForeverUI.StanceBar and ForeverUI.StanceBar.Holder
if stances and stances:IsShown() then
x = x + stances:GetWidth() + BARS_GAP
end
local totems = ForeverUI.TotemBar
if totems and totems.Frame:IsShown() and ForeverUI.Layout.IsDefault("totembar") then
x = LEFT_EDGE + totems.ContentWidth + BARS_GAP
end
local lift = ForeverUI.MultiBars and ForeverUI.MultiBars.Lift() or 0
ForeverUI.Layout.SetDefaults("pet", "BOTTOMLEFT", "BOTTOM", x, ROW_Y + lift)
end
local function place()
-- The pet buttons are anchored to the carrier, which makes it protected: even its Hide is
-- blocked in combat (a pet dying or expiring there). PLAYER_REGEN_ENABLED places it again.
if InCombatLockdown() then
return
end
if not hasPet() then
carrier:Hide()
return
end
carrier:SetWidth(buttonCount * SIZE + (buttonCount - 1) * GAP)
carrier:SetHeight(SIZE)
for index = 1, buttonCount do
local button = _G["PetActionButton" .. index]
if button then
button:SetWidth(SIZE)
button:SetHeight(SIZE)
button:ClearAllPoints()
button:SetPoint("LEFT", carrier, "LEFT", (index - 1) * STEP, 0)
button:Show()
end
end
carrier:Show()
placeDefault()
end
-- PetActionButtonMixin:UpdateButtonState
local function updateState()
for index = 1, buttonCount do
local button = _G["PetActionButton" .. index]
if button and button.foreverSkinned then
-- 3.3.5: (name, SUBTEXT, texture, isToken, active, autoCastAllowed, autoCastEnabled).
-- Camelot has no subtext: copying its code would take the subtext for the texture.
local _, _, texture, isToken, active =
GetPetActionInfo(index)
local icon = button.foreverIcon
if icon then
-- isToken: name and texture are KEYS of global variables, not values.
icon:SetTexture(isToken and _G[texture] or texture)
if texture then
local usable = not GetPetActionSlotUsable
or GetPetActionSlotUsable(index)
if usable then
icon:SetVertexColor(1, 1, 1)
else
icon:SetVertexColor(GRAYED, GRAYED, GRAYED)
end
icon:Show()
else
icon:Hide()
end
end
local checkMark = button:GetCheckedTexture()
if active then
-- The attack action flashes and its check mark is half transparent: at full alpha it would
-- look like one more selected ability.
local isAttack = IsPetAttackAction and IsPetAttackAction(index)
if checkMark then
checkMark:SetAlpha(isAttack and ATTACK_CHECK_ALPHA or 1)
end
button:SetChecked(1)
else
button:SetChecked(nil)
end
local cooldown = button.foreverCooldown
if cooldown and GetPetActionCooldown then
local start, duration, active = GetPetActionCooldown(index)
if CooldownFrame_SetTimer then
CooldownFrame_SetTimer(cooldown, start, duration, active)
elseif CooldownFrame_Set then
CooldownFrame_Set(cooldown, start, duration, active)
end
end
hideNormalTexture(button)
end
end
end
local function all()
skinAll()
place()
updateState()
end
ForeverUI.PetBar = { Apply = all, Holder = carrier, PlaceDefault = placeDefault }
if hooksecurefunc then
for _, funcName in ipairs({ "PetActionBar_Update", "PetActionBar_UpdateCooldowns" }) do
if type(_G[funcName]) == "function" then
hooksecurefunc(funcName, function()
place()
updateState()
end)
end
end
end
local listener = CreateFrame("Frame", "ForeverUIPetBarWatcher")
listener:RegisterEvent("PLAYER_ENTERING_WORLD")
listener:RegisterEvent("PLAYER_REGEN_ENABLED")
listener:RegisterEvent("UNIT_PET")
listener:RegisterEvent("PET_BAR_UPDATE")
listener:RegisterEvent("PET_BAR_UPDATE_COOLDOWN")
listener:RegisterEvent("PET_BAR_UPDATE_USABLE")
listener:RegisterEvent("PET_UI_UPDATE")
listener:RegisterEvent("PLAYER_CONTROL_LOST")
listener:RegisterEvent("PLAYER_CONTROL_GAINED")
-- Forms change the stance bar's width, hence this bar's place. StanceBar.lua loads first:
-- its handler runs first, so the width is already right when read.
listener:RegisterEvent("UPDATE_SHAPESHIFT_FORMS")
listener:SetScript("OnEvent", all)
all()
-- No fixed position here, as for the other bars: everything goes through /fui.
-- placeDefault then moves the default right of the stance bar when it is shown.
ForeverUI.Layout.Register(carrier, "pet", L.PETBAR_EDIT_LABEL,
"BOTTOMLEFT", "BOTTOM", LEFT_EDGE, ROW_Y)
placeDefault()