Files
moon-well-ui/data/addon/ForeverUI/PetFrame.lua
T
Adrestie d110de1cbe Chat et barres d'auras a la DA de camelot ; plus aucune globale du client ecrite par l'addon
- Chat : cadre, barre et bouton de retour, colonne, bouton d'amis de camelot ;
  au repos fond, onglets, colonne et saisie s'effacent, allumage au survol
- Barres d'ameliorations et d'affaiblissements (Buffs.lua) : grille, bordures
  par type, clignotement, repli, regroupement, enchantements d'arme
- Souillure : StaticPopupDialogs n'est plus reaffectee (BindEnchant bloque) ;
  menus deroulants, suivi des objectifs, choix d'icone, ajout de canal et
  carte du monde n'ecrivent plus les globales du client ; menus d'unite :
  lignes protegees servies par des boutons securises hors combat, grisees en
  combat ; seule MAP_QUEST_DIFFICULTY reste, par decision
- Gestionnaire d'ensembles : plus d'appel GetEquipmentSetIconInfo(nil)
- Banc : garde des globales ecrites (bytecode Lua 5.1), faux client durci

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 18:36:19 +02:00

392 lines
13 KiB
Lua

-- ForeverUI : le cadre du familier.
--
-- POURQUOI. Le PetFrame de 3.3.5 est un enfant de PlayerFrame : en etouffant
-- le cadre joueur du client (PlayerFrame.lua), ForeverUI l'avait emporte avec
-- lui. Il est refait ici a la maniere de camelot (demande du 2026-09-25).
--
-- SOURCE. blizzard_unitframe.toc charge [Family]\PetFrame.xml et .lua pour
-- tous les types de jeu : le gabarit de mainline/ est donc celui de camelot.
-- cadre 120 x 49, strate LOW, zone cliquable rognee (7, 66, 6, 7)
-- portrait 37 x 37 ancre TOPLEFT (5, -5), rond
-- art UI-HUD-UnitFrame-TargetofTarget-PortraitOn, centre (le
-- familier reprend VOLONTAIREMENT l'art de la cible de la cible)
-- nom 68 x 10 a droite du portrait (2, 0), GameFontNormalSmall
-- vie 70 x 10, BOTTOMLEFT sur le RIGHT du portrait (2, -3.5),
-- soit TOPLEFT (44, -17)
-- ressource 74 x 7, TOPLEFT sur le BOTTOMLEFT de la vie (-4, -1), soit
-- TOPLEFT (40, -28)
-- menace ...-PortraitOn-InCombat, TOPLEFT (1, 0) : la lueur de
-- menace de UnitFrame_Initialize
-- attaque ...-PortraitOn-Status en ADD, TOPLEFT (0, 1) : pulsation
-- rouge entre PET_ATTACK_START et PET_ATTACK_STOP
-- degats PetHitIndicator, NumberFontNormalHuge, TOPLEFT (5, -5)
-- humeur PetFrameHappinessTemplate 24 x 23, LEFT sur le RIGHT du
-- cadre (0, -4), Interface\PetPaperDollFrame\UI-PetHappiness
-- place PlayerBottomManagedFrameContainer : TOP sur le BOTTOM du
-- cadre joueur (30, 25) ; le familier y est centre avec une
-- marge gauche de 15, soit (30 + 7.5, 25). Sous les runes du
-- chevalier de la mort s'il y en a, a 2 d'ecart (spacing).
-- survol UnitFrame_OnEnter, puis PartyMemberBuffTooltip en (60, -35) ;
-- en 3.3.5, PartyMemberBuffTooltip_Update prend le CADRE (il lit
-- self.unit) et montre ou cache l'infobulle lui-meme
-- vehicule le cadre montre le JOUEUR quand un vehicule prend le cadre
-- joueur (toggleForVehicle, comme SecureButton_GetModifiedUnit)
--
-- CE QUI DIFFERE, ET POURQUOI :
-- * pas de MaskTexture en 3.3.5 : les barres ne sont pas rognees a la forme
-- du cadre, comme pour la cible de la cible ;
-- * les petites icones d'affaiblissement sous le cadre (AuraFrameContainer)
-- ne sont pas reprises ; les ameliorations restent dans l'infobulle.
local player = ForeverUI.PlayerFrame
local FRAME_ART = "ui-hud-unitframe-targetoftarget-portraiton"
local THREAT_ART = "ui-hud-unitframe-targetoftarget-portraiton-incombat"
local ATTACK_ART = "ui-hud-unitframe-targetoftarget-portraiton-status"
local HEALTH_FILL = "ui-hud-unitframe-targetoftarget-portraiton-bar-health"
local POWER_FILL = {
MANA = "ui-hud-unitframe-targetoftarget-portraiton-bar-mana",
RAGE = "ui-hud-unitframe-targetoftarget-portraiton-bar-rage",
FOCUS = "ui-hud-unitframe-targetoftarget-portraiton-bar-focus",
ENERGY = "ui-hud-unitframe-targetoftarget-portraiton-bar-energy",
RUNIC_POWER = "ui-hud-unitframe-targetoftarget-portraiton-bar-runicpower",
}
local HAPPINESS_FILE = "Interface\\PetPaperDollFrame\\UI-PetHappiness"
local HAPPINESS_COORDS = {
[1] = { 0.375, 0.5625, 0, 0.359375 },
[2] = { 0.1875, 0.375, 0, 0.359375 },
[3] = { 0, 0.1875, 0, 0.359375 },
}
local frame = CreateFrame("Button", "ForeverUIPetFrame", player, "SecureUnitButtonTemplate")
frame:SetWidth(120)
frame:SetHeight(49)
frame:SetFrameStrata("LOW")
frame:SetHitRectInsets(7, 66, 6, 7)
-- sous les runes s'il y en a (le conteneur les porte deja a la place de
-- camelot), sinon a la place du conteneur
local runes = _G.ForeverUIClassResourceContainer
if runes then
frame:SetPoint("TOP", runes, "BOTTOM", 7.5, -2)
else
frame:SetPoint("TOP", player, "BOTTOM", 30 + 7.5, 25)
end
frame.unit = "pet"
frame:SetAttribute("unit", "pet")
frame:SetAttribute("toggleForVehicle", true)
frame:SetAttribute("*type1", "target")
frame:SetAttribute("*type2", "menu")
frame:RegisterForClicks("AnyUp")
frame.menu = function(self)
ForeverUI.MenuUnite.ouvrir(ToggleDropDownMenu, 1, nil, PetFrameDropDown, self, 44, 8)
end
local portrait = frame:CreateTexture(nil, "BACKGROUND")
portrait:SetWidth(37)
portrait:SetHeight(37)
portrait:SetPoint("TOPLEFT", 5, -5)
local healthFill = frame:CreateTexture(nil, "BORDER")
healthFill:SetPoint("TOPLEFT", 44, -17)
local powerFill = frame:CreateTexture(nil, "BORDER")
powerFill:SetPoint("TOPLEFT", 40, -28)
local art = frame:CreateTexture(nil, "ARTWORK")
art:SetPoint("CENTER")
ForeverUI.SetAtlas(art, FRAME_ART)
local threat = frame:CreateTexture(nil, "OVERLAY")
threat:SetPoint("TOPLEFT", 1, 0)
ForeverUI.SetAtlas(threat, THREAT_ART)
threat:Hide()
local attack = frame:CreateTexture(nil, "OVERLAY")
attack:SetPoint("TOPLEFT", 0, 1)
ForeverUI.SetAtlas(attack, ATTACK_ART)
attack:SetBlendMode("ADD")
attack:Hide()
local nameText = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
nameText:SetWidth(68)
nameText:SetHeight(10)
nameText:SetJustifyH("LEFT")
nameText:SetJustifyV("BOTTOM")
nameText:SetPoint("TOPLEFT", portrait, "TOPRIGHT", 2, 0)
local healthText = frame:CreateFontString(nil, "OVERLAY", "TextStatusBarText")
healthText:SetPoint("CENTER", frame, "TOPLEFT", 44 + 35, -17 - 5)
healthText:Hide()
local powerText = frame:CreateFontString(nil, "OVERLAY", "TextStatusBarText")
powerText:SetPoint("CENTER", frame, "TOPLEFT", 40 + 37 + 2, -28 - 3.5)
powerText:Hide()
local hitText = frame:CreateFontString(nil, "OVERLAY", "NumberFontNormalHuge")
hitText:SetPoint("TOPLEFT", 5, -5)
hitText:Hide()
if CombatFeedback_Initialize then
CombatFeedback_Initialize(frame, hitText, 30)
end
-- l'humeur du familier de chasseur
local happiness = CreateFrame("Frame", "ForeverUIPetFrameHappiness", frame)
happiness:SetWidth(24)
happiness:SetHeight(23)
happiness:SetPoint("LEFT", frame, "RIGHT", 0, -4)
happiness:EnableMouse(true)
local happinessTexture = happiness:CreateTexture(nil, "BACKGROUND")
happinessTexture:SetTexture(HAPPINESS_FILE)
happinessTexture:SetAllPoints(happiness)
happiness:Hide()
-- ------------------------------------------------------------ mises a jour
local function displayedUnit()
if UnitHasVehicleUI and UnitHasVehicleUI("player") then
return "player"
end
return "pet"
end
local function updateHealth()
local unit = displayedUnit()
local maximum = UnitHealthMax(unit)
local fraction = 0
if maximum and maximum > 0 then
fraction = UnitHealth(unit) / maximum
end
ForeverUI.SetAtlasFill(healthFill, HEALTH_FILL, fraction)
end
local function updatePower()
local unit = displayedUnit()
local powerType, powerToken = UnitPowerType(unit)
local maximum = UnitPowerMax(unit, powerType)
local fraction = 0
if maximum and maximum > 0 then
fraction = UnitPower(unit, powerType) / maximum
end
ForeverUI.SetAtlasFill(powerFill, POWER_FILL[powerToken or ""] or POWER_FILL.MANA, fraction)
end
-- le texte des barres suit petStatusText, et s'affiche au survol
local function formatValue(value, maximum)
if not maximum or maximum <= 0 then
return ""
end
if GetCVarBool and GetCVarBool("statusTextPercentage") then
return tostring(math.ceil(value / maximum * 100)) .. "%"
end
return value .. " / " .. maximum
end
local function updateTexts()
local always = GetCVar and GetCVar("petStatusText") == "1"
if not (always or frame.hovered) then
healthText:Hide()
powerText:Hide()
return
end
local unit = displayedUnit()
local powerType = UnitPowerType(unit)
healthText:SetText(formatValue(UnitHealth(unit), UnitHealthMax(unit)))
healthText:Show()
-- PetFrameMixin:Update : pas de texte de ressource sans ressource
local powerMax = UnitPowerMax(unit, powerType)
if powerMax and powerMax > 0 then
powerText:SetText(formatValue(UnitPower(unit, powerType), powerMax))
powerText:Show()
else
powerText:Hide()
end
end
-- UnitFrame_UpdateThreatIndicator : allumee au-dessus de zero, teinte du
-- niveau de menace
local function updateThreat()
local status = UnitThreatSituation and UnitThreatSituation(displayedUnit())
if status and status > 0 and GetThreatStatusColor then
threat:SetVertexColor(GetThreatStatusColor(status))
threat:Show()
else
threat:Hide()
end
end
local function updateHappiness()
local mood, damage, loyalty = nil, 0, 0
if GetPetHappiness then
mood, damage, loyalty = GetPetHappiness()
end
local _, isHunterPet = HasPetUI()
if not mood or not isHunterPet then
happiness:Hide()
return
end
local c = HAPPINESS_COORDS[mood]
if c then
happinessTexture:SetTexCoord(c[1], c[2], c[3], c[4])
end
happiness.tooltip = _G["PET_HAPPINESS" .. mood] or NONE
happiness.tooltipDamage = PET_DAMAGE_PERCENTAGE and format(PET_DAMAGE_PERCENTAGE, damage or 0)
if (loyalty or 0) < 0 then
happiness.tooltipLoyalty = LOSING_LOYALTY
elseif (loyalty or 0) > 0 then
happiness.tooltipLoyalty = GAINING_LOYALTY
else
happiness.tooltipLoyalty = nil
end
happiness:Show()
end
local function updateAll()
local unit = displayedUnit()
-- l'unite que lisent l'infobulle et PartyMemberBuffTooltip_Update
frame.unit = unit
updateHealth()
updatePower()
nameText:SetText(UnitName(unit))
SetPortraitTexture(portrait, unit)
updateTexts()
updateThreat()
updateHappiness()
end
happiness:SetScript("OnEnter", function(self)
if not self.tooltip then return end
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText(self.tooltip)
if self.tooltipDamage then GameTooltip:AddLine(self.tooltipDamage, "", 1, 1, 1) end
if self.tooltipLoyalty then GameTooltip:AddLine(self.tooltipLoyalty, "", 1, 1, 1) end
if GetPetFoodTypes and PET_DIET_TEMPLATE then
local diet = { GetPetFoodTypes() }
local text = #diet > 0 and table.concat(diet, PET_FOOD_DELIMIT or ", ") or NONE
GameTooltip:AddLine(format(PET_DIET_TEMPLATE, text), "", 1, 1, 1)
end
GameTooltip:Show()
end)
happiness:SetScript("OnLeave", function() GameTooltip:Hide() end)
frame:SetScript("OnEnter", function(self)
self.hovered = true
updateTexts()
if UnitFrame_OnEnter then UnitFrame_OnEnter(self) end
if PartyMemberBuffTooltip and PartyMemberBuffTooltip_Update then
PartyMemberBuffTooltip:SetPoint("TOPLEFT", self, "TOPLEFT", 60, -35)
PartyMemberBuffTooltip_Update(self)
end
end)
frame:SetScript("OnLeave", function(self)
self.hovered = false
updateTexts()
if UnitFrame_OnLeave then UnitFrame_OnLeave(self) end
if PartyMemberBuffTooltip then PartyMemberBuffTooltip:Hide() end
end)
frame:SetScript("OnShow", updateAll)
-- la pulsation d'attaque (PetFrameMixin:OnUpdate) et les degats recus
frame.attackCounter, frame.attackSign = 0, -1
frame:SetScript("OnUpdate", function(self, elapsed)
if attack:IsShown() then
local counter = self.attackCounter + elapsed
if counter > 0.5 then
self.attackSign = -self.attackSign
end
counter = math.fmod(counter, 0.5)
self.attackCounter = counter
local alpha
if self.attackSign == 1 then
alpha = (55 + counter * 400) / 255
else
alpha = (255 - counter * 400) / 255
end
attack:SetVertexColor(1, 0, 0, alpha)
end
if CombatFeedback_OnUpdate then
CombatFeedback_OnUpdate(self, elapsed)
end
end)
frame:SetScript("OnEvent", function(self, event, unit, ...)
if event == "PET_ATTACK_START" then
attack:SetVertexColor(1, 0, 0, 1)
attack:Show()
return
end
if event == "PET_ATTACK_STOP" then
attack:Hide()
return
end
if event == "CVAR_UPDATE" then
updateTexts()
return
end
if event == "PLAYER_ENTERING_WORLD" or event == "PET_UI_UPDATE" then
updateAll()
return
end
if event == "UNIT_PET" or event == "UNIT_ENTERED_VEHICLE" or event == "UNIT_EXITED_VEHICLE" then
if unit == "player" then
attack:Hide()
updateAll()
end
return
end
if unit ~= displayedUnit() then
return
end
if event == "UNIT_COMBAT" then
if CombatFeedback_OnCombatEvent then
CombatFeedback_OnCombatEvent(self, ...)
end
elseif event == "UNIT_HEALTH" or event == "UNIT_MAXHEALTH" then
updateHealth()
updateTexts()
elseif event == "UNIT_NAME_UPDATE" then
nameText:SetText(UnitName(unit))
elseif event == "UNIT_PORTRAIT_UPDATE" or event == "UNIT_MODEL_CHANGED" then
SetPortraitTexture(portrait, unit)
elseif event == "UNIT_THREAT_SITUATION_UPDATE" then
updateThreat()
elseif event == "UNIT_HAPPINESS" then
updateHappiness()
else
updatePower()
updateTexts()
end
end)
for _, event in ipairs({
"PLAYER_ENTERING_WORLD", "UNIT_PET", "PET_UI_UPDATE", "UNIT_ENTERED_VEHICLE", "UNIT_EXITED_VEHICLE",
"PET_ATTACK_START", "PET_ATTACK_STOP", "UNIT_COMBAT", "UNIT_HEALTH", "UNIT_MAXHEALTH",
"UNIT_NAME_UPDATE", "UNIT_PORTRAIT_UPDATE", "UNIT_MODEL_CHANGED", "UNIT_THREAT_SITUATION_UPDATE",
"UNIT_HAPPINESS", "UNIT_DISPLAYPOWER", "UNIT_MANA", "UNIT_RAGE", "UNIT_FOCUS", "UNIT_ENERGY",
"UNIT_RUNIC_POWER", "UNIT_MAXMANA", "UNIT_MAXRAGE", "UNIT_MAXFOCUS", "UNIT_MAXENERGY",
"UNIT_MAXRUNIC_POWER", "CVAR_UPDATE",
}) do
frame:RegisterEvent(event)
end
-- le client montre et cache le cadre selon que le familier existe
if RegisterUnitWatch then
RegisterUnitWatch(frame)
end
frame.portrait = portrait
frame.healthFill = healthFill
frame.powerFill = powerFill
frame.art = art
frame.threat = threat
frame.attack = attack
frame.nameText = nameText
frame.healthText = healthText
frame.powerText = powerText
frame.happiness = happiness
frame.happinessTexture = happinessTexture
ForeverUI.PetFrame = frame
ForeverUI.PetFrameUpdate = updateAll