Files
moon-well-ui/data/addon/ForeverUI/PetTab.lua
T
Adrestie eb681aa9cd Aucun texte en dur : anglais en base, francais, langues extensibles
- socle : Textes.lua (ForeverUI.L) et ForeverUIGlueTextes.lua (G.L) ; les
  textes dans Textes_enUS / Textes_frFR (325 cles en jeu) et
  ForeverUIGlueTextes_enUS / _frFR (41 cles aux ecrans d'accueil) ; une cle
  absente prend l'anglais, une langue sans fichier lit l'anglais ; ajouter
  une langue = un fichier de plus
- les textes que le client 3.3.5 connait se lisent dans ses globales
  (GlobalStrings, GlueStrings en glue) ; les textes de secours sont retires
- tous les fichiers repris : libelles, infobulles, mode edition, messages et
  aide de /fui, erreurs internes ; .toc : Notes + Notes-frFR
- selection des personnages : race et classe traduites ramenees a leur jeton
  par les tables de langue (GetAvailableRaces / GetAvailableClasses font
  planter le client hors de la creation)
- tools/cartes_instances.py : noms des instances dans toutes les langues de
  la LibBabble-Zone de WDM (portails de la carte sur tout client)
- banc : toute cle employee existe en anglais et en francais, memes cles dans
  les deux, bascule frFR / deDE essayee ; chaines 3.3.5 ajoutees au faux client
- README : etat des sections mis a jour, langues, glue, deploiement --glue

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

590 lines
20 KiB
Lua

-- ForeverUI : l'onglet du familier.
--
-- A LA DEMANDE, et non d'apres la source. camelot ne fait pas du familier un
-- onglet de la colonne : son PAPERDOLL_SIDEBARS le met en TROISIEME ONGLET
-- DU VOLET DROIT, a cote des statistiques et du gestionnaire, et son volet
-- gauche garde la silhouette du joueur. 3.3.5, lui, en fait un ecran a part
-- entiere -- PetPaperDollFrame, deuxieme de CHARACTERFRAME_SUBFRAMES -- et
-- c'est celui-la que la colonne ouvre.
--
-- CE QUI EST DEMANDE :
-- volet gauche l'apercu en trois dimensions du familier
-- volet droit la meme interface que les statistiques du personnage --
-- "Niveau X <nom>", puis la categorie "General" avec
-- Sante, Armure, Degats, Puissance d'attaque et Score
-- critique, puis la categorie "Resistances"
--
-- CE QUE 3.3.5 DONNE, ET OU.
--
-- PetModelFrame l'apercu, un PlayerModel que le client cale
-- sur "pet" dans PetPaperDollFrame_Update
-- UnitHealthMax("pet") la sante
-- PaperDollFrame_SetArmor(PetArmorFrame, "Pet")
-- PaperDollFrame_SetDamage(PetDamageFrame, "Pet")
-- PaperDollFrame_SetAttackPower(PetAttackPowerFrame, "Pet")
-- les trois ecrivent dans <cadre>StatText, et
-- c'est de la QU'ON LIT : le calcul et la mise
-- en forme restent au client -- les degats
-- s'ecrivent "45 - 62", teintes s'il y a lieu
-- UnitResistance("pet", ecole) les resistances, l'ecole venant du
-- GetID() de PetMagicResFrame<i>
-- NUM_PET_RESISTANCE_TYPES combien il y en a : cinq
--
-- CE QUI DIFFERE, ET POURQUOI.
--
-- * LE SCORE CRITIQUE EST CELUI DE L'AGILITE, et rien d'autre. 3.3.5
-- n'expose pas le critique d'un familier : son ecran ne le montre pas,
-- et aucune fonction ne le rend. GetCritChanceFromAgility("pet") est ce
-- que le client porte de plus proche -- c'est meme ce dont il se sert
-- pour l'infobulle de l'agilite du familier. La valeur affichee est
-- donc la PART VENANT DE L'AGILITE, pas le total. C'est un manque,
-- signale, pas un choix.
-- * LES CINQ CARACTERISTIQUES ne sont pas montrees : la demande porte sur
-- cinq lignes nommees, et celles-la n'en font pas partie.
local ForeverUI = ForeverUI or {}
_G.ForeverUI = ForeverUI
local L = ForeverUI.L
-- Les memes mesures que les statistiques du personnage, dans
-- CharacterFrame.lua : une page du meme volet doit avoir le meme bord.
local MARGE = 20 -- STAT_MARGE
local PAS = 13 -- STAT_PAS, la hauteur d'une ligne
local ENTETE_H = 34 -- STAT_ENTETE
local ENTETE_DEBORD = 5 -- STAT_ENTETE_DEBORD
local ENTRE_GROUPES = 16 -- STAT_ENTRE_GROUPES
local HAUT = 14 -- sous le haut du volet
local NIVEAU_H = 20
local NIVEAU_ECART = -8
-- LES FLECHES DE ROTATION, A LA MEME PLACE QUE CELLES DU PERSONNAGE.
--
-- A LA DEMANDE. Les memes mesures que poserModele, dans CharacterFrame.lua :
-- centrees sur le HAUT du volet, cote a cote, et un cran au-dessus du
-- modele pour recevoir le clic. Les deux boutons font 35 x 35 dans les deux
-- ecrans -- releve dans les XML du client -- il n'y a donc que la place qui
-- differait.
local ROTATION_Y = -12
local ROTATION_ECART = 4
local ATLAS_ENTETE = "ui-character-info-title"
local ATLAS_NIVEAU = "ui-character-info-itemlevel-bounce"
local ATLAS_FOND_SOMBRE = "ui-character-info-itemlevel-bounce"
local ATLAS_FOND_CLAIR = "ui-character-info-line-bounce"
-- La taille du volet, par construction : un contenu se batit a sa premiere
-- ouverture, qui peut preceder la pose de la fenetre.
local VOLET_L, VOLET_H = 233, 464
local VOLET_GAUCHE_L, VOLET_GAUCHE_H = 398, 464
local panneau, detail
-- ---------------------------------------------------------------- les donnees
local function aUnFamilier()
return (HasPetUI and HasPetUI()) and UnitExists and UnitExists("pet")
end
-- LE TEXTE D'UNE LIGNE. Trois valeurs se lisent dans les cadres du client,
-- apres l'avoir fait calculer : c'est lui qui les met en forme.
local function lireDuClient(nomCadre, poser)
local cadre = _G[nomCadre]
if not cadre then
return nil
end
if poser then
poser(cadre, "Pet")
end
local texte = _G[nomCadre .. "StatText"]
return texte and texte:GetText()
end
local function lireGeneral()
local lignes = {}
lignes[#lignes + 1] = {
nom = HEALTH,
valeur = tostring((UnitHealthMax and UnitHealthMax("pet")) or 0),
}
lignes[#lignes + 1] = {
nom = ARMOR,
valeur = lireDuClient("PetArmorFrame", PaperDollFrame_SetArmor) or "",
}
lignes[#lignes + 1] = {
nom = DAMAGE,
valeur = lireDuClient("PetDamageFrame", PaperDollFrame_SetDamage) or "",
}
-- LE NOM DE LA LIGNE : "Attack Power", et non "Power".
--
-- ATTACK_POWER vaut "Power" dans ce client -- releve dans ses
-- GlobalStrings. La seule chaine qui porte exactement "Attack Power"
-- est ATTACK_POWER_TOOLTIP, celle de l'infobulle ; c'est donc elle
-- qu'on prend, et ATTACK_POWER ne sert plus que de dernier recours.
lignes[#lignes + 1] = {
nom = ATTACK_POWER_TOOLTIP or ATTACK_POWER,
valeur = lireDuClient("PetAttackPowerFrame",
PaperDollFrame_SetAttackPower) or "",
}
-- ECART ASSUME : la part venant de l'agilite, faute de mieux. Voir
-- l'entete.
local critique = (GetCritChanceFromAgility and GetCritChanceFromAgility("pet")) or 0
lignes[#lignes + 1] = {
nom = MELEE_CRIT_CHANCE,
valeur = string.format("%.2f%%", critique),
}
return lignes
end
local function lireResistances()
local lignes = {}
local combien = NUM_PET_RESISTANCE_TYPES or 5
for rang = 1, combien do
local cadre = _G["PetMagicResFrame" .. rang]
local ecole = cadre and cadre.GetID and cadre:GetID()
if ecole then
local _, valeur = UnitResistance("pet", ecole)
lignes[#lignes + 1] = {
nom = _G["RESISTANCE" .. ecole .. "_NAME"] or tostring(ecole),
valeur = tostring(valeur or 0),
}
end
end
return lignes
end
-- --------------------------------------------------------------- le volet droit
local groupes = {}
local rangees = {}
-- UNE LIGNE : le meme gabarit que StatFrameTemplate -- intitule a gauche,
-- valeur calee a droite -- et le meme fond alterne que les statistiques du
-- personnage.
local function creerLigne(rang, largeur)
local ligne = CreateFrame("Frame", "ForeverUIPetStat" .. rang, detail)
ligne:SetWidth(largeur)
ligne:SetHeight(PAS)
local fond = ligne:CreateTexture(nil, "BACKGROUND")
fond:SetPoint("TOPLEFT", ligne, "TOPLEFT", 0, 0)
fond:SetPoint("BOTTOMRIGHT", ligne, "BOTTOMRIGHT", 0, 0)
ligne.fond = fond
local intitule = ligne:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
intitule:SetJustifyH("LEFT")
intitule:SetPoint("LEFT", ligne, "LEFT", 0, 0)
ligne.intitule = intitule
local valeur = ligne:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
valeur:SetJustifyH("RIGHT")
valeur:SetPoint("RIGHT", ligne, "RIGHT", 0, 0)
valeur:SetPoint("LEFT", intitule, "RIGHT", 4, 0)
ligne.valeur = valeur
rangees[rang] = ligne
return ligne
end
local function creerEntete(rang, largeur)
local entete = CreateFrame("Frame", "ForeverUIPetCategory" .. rang, detail)
entete:SetWidth(largeur + 2 * ENTETE_DEBORD)
entete:SetHeight(ENTETE_H)
-- L'encadre de camelot, tendu : le meme que les selecteurs de categorie
-- des statistiques du personnage.
local fond = entete:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(fond, ATLAS_ENTETE, true)
fond:SetAllPoints(entete)
local intitule = entete:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
intitule:SetPoint("CENTER", entete, "CENTER", 0, 1)
entete.intitule = intitule
groupes[rang] = entete
return entete
end
-- LES RESISTANCES SONT DES ICONES, POSEES A L'HORIZONTAL.
--
-- A LA DEMANDE, et non en lignes de texte. Ce sont les CADRES DU CLIENT --
-- PetMagicResFrame1 a 5, MagicResistanceFrameTemplate, 32 x 29 -- qui
-- portent deja l'icone d'ecole, decoupee dans
-- Interface/PaperDollInfoFrame/UI-Character-ResistanceIcons, la valeur au
-- BOTTOM (0, 3) et l'infobulle que PetPaperDollFrame_SetResistances
-- compose. On les reprend donc entiers plutot que de les refaire : le
-- client garde le calcul, la teinte et l'infobulle.
--
-- Ils sont fils de l'ecran du client, que le balayage masque : on les
-- REPARENTE dans le volet droit. Ils n'y perdent rien -- ils ne portent
-- aucun de nos reglages de niveau.
--
-- Rend la hauteur occupee.
local RES_L, RES_H = 32, 29 -- MagicResistanceFrameTemplate
local function poserResistances(largeur, y)
if PetPaperDollFrame_SetResistances then
PetPaperDollFrame_SetResistances()
end
local combien = NUM_PET_RESISTANCE_TYPES or 5
-- Ils s'etalent sur toute la largeur des lignes, a pas egaux.
local ecart = 0
if combien > 1 then
ecart = (largeur - combien * RES_L) / (combien - 1)
if ecart < 0 then
ecart = 0
end
end
for rang = 1, combien do
local cadre = _G["PetMagicResFrame" .. rang]
if cadre then
cadre:SetParent(detail)
cadre:SetWidth(RES_L)
cadre:SetHeight(RES_H)
cadre:ClearAllPoints()
cadre:SetPoint("TOPLEFT", detail, "TOPLEFT",
MARGE + (rang - 1) * (RES_L + ecart), -y)
cadre:Show()
for _, region in ipairs({ cadre:GetRegions() }) do
if region.Show then
region:Show()
end
end
end
end
return RES_H
end
-- UN PASSAGE : empiler l'entete de chaque categorie et ses lignes.
local function disposer()
if not detail then
return
end
local largeur = detail:GetWidth() or 0
if largeur < 50 then
largeur = VOLET_L
end
largeur = largeur - 2 * MARGE
local blocs = {
{ nom = GENERAL, lignes = lireGeneral() },
{ nom = L.PETTAB_RESISTANCES, icones = true },
}
local y = HAUT
-- "Niveau X <nom>", en tete du volet.
local niveau = (UnitLevel and UnitLevel("pet")) or 0
local nom = (UnitName and UnitName("pet")) or ""
detail.niveau:SetWidth(largeur)
detail.niveau:ClearAllPoints()
detail.niveau:SetPoint("TOP", detail, "TOP", 0, -y)
detail.niveau:SetText(string.format("%s %s",
string.format(UNIT_LEVEL_TEMPLATE, niveau), nom))
y = y + NIVEAU_H - NIVEAU_ECART
local rangEntete, rangLigne = 0, 0
for _, bloc in ipairs(blocs) do
rangEntete = rangEntete + 1
local entete = groupes[rangEntete] or creerEntete(rangEntete, largeur)
entete:SetWidth(largeur + 2 * ENTETE_DEBORD)
entete:ClearAllPoints()
entete:SetPoint("TOPLEFT", detail, "TOPLEFT", MARGE - ENTETE_DEBORD, -y)
entete.intitule:SetText(bloc.nom)
entete:Show()
y = y + ENTETE_H
if bloc.icones then
y = y + poserResistances(largeur, y)
else
for numero, donnees in ipairs(bloc.lignes) do
rangLigne = rangLigne + 1
local ligne = rangees[rangLigne] or creerLigne(rangLigne, largeur)
ligne:SetWidth(largeur)
ligne:ClearAllPoints()
ligne:SetPoint("TOPLEFT", detail, "TOPLEFT", MARGE, -y)
ligne.intitule:SetText(string.format(STAT_FORMAT, donnees.nom))
ligne.valeur:SetText(donnees.valeur)
-- La sombre en premier, et le compte repart a chaque
-- categorie : la meme regle que les statistiques du
-- personnage.
ForeverUI.SetAtlas(ligne.fond,
(numero % 2 == 1) and ATLAS_FOND_SOMBRE or ATLAS_FOND_CLAIR,
true)
ligne:Show()
y = y + PAS
end
end
y = y + ENTRE_GROUPES
end
for rang = rangEntete + 1, #groupes do
groupes[rang]:Hide()
end
for rang = rangLigne + 1, #rangees do
rangees[rang]:Hide()
end
end
local function majDetail()
if not detail then
return
end
if not aUnFamilier() then
detail.niveau:SetText("")
for _, entete in ipairs(groupes) do
entete:Hide()
end
for _, ligne in ipairs(rangees) do
ligne:Hide()
end
for rang = 1, (NUM_PET_RESISTANCE_TYPES or 5) do
local cadre = _G["PetMagicResFrame" .. rang]
if cadre then
cadre:Hide()
end
end
return
end
disposer()
end
ForeverUI.PetDetail = majDetail
-- ------------------------------------------------------------- le volet gauche
-- LE BALAYAGE. L'ecran du client s'efface -- son art, ses onglets, sa barre
-- d'experience, ses cinq caracteristiques -- mais PAS l'apercu, ni le cadre
-- qui le porte.
--
-- L'APERCU N'EST PAS FILS DE L'ECRAN, IL EST PETIT-FILS.
--
-- Releve dans le PetPaperDollFrame.xml du client : PetModelFrame, ligne
-- 208, vit dans PetPaperDollFramePetFrame, ligne 128, qui est setAllPoints
-- sur l'ecran. Un balayage qui ne regarde que les fils DIRECTS et n'epargne
-- que le modele masquait donc son PARENT -- et l'apercu avec lui, sans
-- qu'aucune ligne ne l'ait demande. C'est exactement ce qui se voyait :
-- aucun rendu en trois dimensions.
--
-- On balaie donc les deux niveaux, en epargnant a chacun ce qui porte la
-- suite.
local function balayer(cadre, epargnes)
if not cadre then
return
end
if cadre.SetBackdrop then
cadre:SetBackdrop(nil)
end
for _, region in ipairs({ cadre:GetRegions() }) do
local nature = region.GetObjectType and region:GetObjectType()
if nature == "Texture" then
region:SetAlpha(0)
elseif nature == "FontString" and region.Hide then
region:Hide()
end
end
if cadre.GetChildren then
for _, fils in ipairs({ cadre:GetChildren() }) do
if not epargnes[fils] and fils.Hide then
fils:Hide()
end
end
end
end
local function etoufferEcranDuClient()
local ecran = _G["PetPaperDollFrame"]
local porteur = _G["PetPaperDollFramePetFrame"]
local modele = _G["PetModelFrame"]
if not ecran then
return
end
balayer(ecran, { [panneau or false] = true, [porteur or false] = true,
[modele or false] = true })
balayer(porteur, { [modele or false] = true })
-- PetPaperDollFrame_SetTab le masque des qu'un autre onglet du client
-- est choisi : on le remontre, c'est lui qui porte l'apercu.
if porteur then
porteur:Show()
end
end
local function majApercu()
local modele = _G["PetModelFrame"]
if not modele or not panneau then
return
end
etoufferEcranDuClient()
modele:ClearAllPoints()
modele:SetPoint("TOPLEFT", panneau, "TOPLEFT", 0, 0)
modele:SetPoint("BOTTOMRIGHT", panneau, "BOTTOMRIGHT", 0, 0)
-- LES FLECHES sont filles du MODELE -- releve dans le
-- PetPaperDollFrame.xml, ligne 230 -- le balayage ne les atteint donc
-- pas. Il ne reste qu'a les poser la ou sont celles du personnage.
local gauche = _G["PetModelFrameRotateLeftButton"]
local droite = _G["PetModelFrameRotateRightButton"]
if gauche and droite then
local demi = (gauche:GetWidth() or 0) / 2 + ROTATION_ECART / 2
local niveau = (modele:GetFrameLevel() or 0) + 2
gauche:ClearAllPoints()
gauche:SetPoint("TOP", panneau, "TOP", -demi, ROTATION_Y)
gauche:SetFrameLevel(niveau)
droite:ClearAllPoints()
droite:SetPoint("TOP", panneau, "TOP", demi, ROTATION_Y)
droite:SetFrameLevel(niveau)
end
if aUnFamilier() then
if modele.SetUnit then
modele:SetUnit("pet")
end
modele:Show()
if gauche then gauche:Show() end
if droite then droite:Show() end
else
modele:Hide()
if gauche then gauche:Hide() end
if droite then droite:Hide() end
end
end
ForeverUI.PetPreview = majApercu
-- ---------------------------------------------------------- la construction
local function monter(hote)
local cadre = _G["PetPaperDollFrame"]
if not cadre or not hote then
return nil, {}
end
cadre:ClearAllPoints()
cadre:SetPoint("TOPLEFT", hote, "TOPLEFT", 0, 0)
cadre:SetPoint("BOTTOMRIGHT", hote, "BOTTOMRIGHT", 0, 0)
if panneau then
majApercu()
majDetail()
return nil, { cadre }
end
panneau = CreateFrame("Frame", "ForeverUIPetPane", cadre)
panneau:SetPoint("TOPLEFT", hote, "TOPLEFT", 0, 0)
panneau:SetPoint("BOTTOMRIGHT", hote, "BOTTOMRIGHT", 0, 0)
local largeur = hote:GetWidth() or 0
if largeur < 100 then
largeur = VOLET_GAUCHE_L
end
panneau:SetWidth(largeur)
local hauteur = hote:GetHeight() or 0
if hauteur < 100 then
hauteur = VOLET_GAUCHE_H
end
panneau:SetHeight(hauteur)
majApercu()
majDetail()
return nil, { cadre }
end
local function monterDetail(hote)
if detail then
majDetail()
return detail, {}
end
if not hote then
return nil, {}
end
detail = CreateFrame("Frame", "ForeverUIPetStats", hote)
detail:SetPoint("TOPLEFT", hote, "TOPLEFT", 0, 0)
detail:SetPoint("BOTTOMRIGHT", hote, "BOTTOMRIGHT", 0, 0)
local largeur = hote:GetWidth() or 0
if largeur < 50 then
largeur = VOLET_L
end
detail:SetWidth(largeur)
-- "Niveau X <nom>", sur le meme fond que la ligne de niveau du
-- personnage.
local fond = detail:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(fond, ATLAS_NIVEAU, true)
local niveau = detail:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
niveau:SetHeight(NIVEAU_H)
niveau:SetJustifyH("CENTER")
niveau:SetJustifyV("MIDDLE")
detail.niveau = niveau
fond:SetPoint("TOP", niveau, "TOP", 0, 0)
fond:SetPoint("BOTTOM", niveau, "BOTTOM", 0, 0)
fond:SetPoint("LEFT", niveau, "LEFT", 0, 0)
fond:SetPoint("RIGHT", niveau, "RIGHT", 0, 0)
majDetail()
return detail, {}
end
ForeverUI.PetTab = { Build = monter, BuildRight = monterDetail }
-- Le client refait son ecran dans PetPaperDollFrame_Update : on passe apres,
-- et on reprend l'apercu comme le detail.
if hooksecurefunc and type(_G["PetPaperDollFrame_Update"]) == "function" then
hooksecurefunc("PetPaperDollFrame_Update", function()
majApercu()
majDetail()
end)
end
-- CE QUI FAIT BOUGER LE FAMILIER. UNIT_PET dit qu'il change ; les trois
-- autres, que ses chiffres bougent.
local veilleur = CreateFrame("Frame")
veilleur:RegisterEvent("UNIT_PET")
veilleur:RegisterEvent("UNIT_STATS")
veilleur:RegisterEvent("UNIT_ATTACK_POWER")
veilleur:RegisterEvent("UNIT_RESISTANCES")
veilleur:SetScript("OnEvent", function()
majApercu()
majDetail()
end)
-- TEMOIN -- /fui familier.
function ForeverUI.PetDebug()
local dire = function(texte)
DEFAULT_CHAT_FRAME:AddMessage("|cff66ccffForeverUI|r " .. texte)
end
dire(string.format(L.PETTAB_DEBUG_PET,
tostring(HasPetUI and HasPetUI()),
tostring(UnitExists and UnitExists("pet")),
tostring(UnitLevel and UnitLevel("pet")),
tostring(UnitName and UnitName("pet")),
tostring(UnitCreatureFamily and UnitCreatureFamily("pet"))))
for _, donnees in ipairs(lireGeneral()) do
DEFAULT_CHAT_FRAME:AddMessage(string.format(" %-18s %s",
donnees.nom, tostring(donnees.valeur)))
end
for _, donnees in ipairs(lireResistances()) do
DEFAULT_CHAT_FRAME:AddMessage(string.format(" %-18s %s",
donnees.nom, tostring(donnees.valeur)))
end
local modele = _G["PetModelFrame"]
dire(string.format(L.PETTAB_DEBUG_PREVIEW,
tostring(modele and modele:IsShown()),
tostring(detail and detail:IsShown())))
end