39fbfc8c10
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
322 lines
10 KiB
Lua
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()
|