Files
moon-well-ui/tools/test_addon.py
T
Adrestie 224fbf05af Feuille de personnage : les statistiques, sur le FrameXML reellement charge
patch-enus-9.mpq porte l'extension .disabled : le client ne l'ouvre pas.
Le bloc des statistiques visait donc des cadres qui n'existent pas
(MostrarStatPaperDoll*DropDown, PStatFrameTemplate), d'ou des selecteurs
invisibles.

La chaine d'archives repond patch-enus-2 pour le .xml et patch-enus-3
pour le .lua : c'est le FrameXML d'origine. Les selecteurs y sont
PlayerStatFrame{Left,Right}DropDown, des UIDropDownMenuTemplate dont
l'art deborde du cadre et ne le suit pas ; il s'efface au profit de
l'en-tete UI-Character-Info-Title, la fleche du client revient contre le
bord droit et toute la barre ouvre le menu.

Le modele de ligne est StatFrameTemplate, 104 x 13 : le pas passe de 16
a 13. Rien ne depend d'une archive ajoutee.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 08:40:08 +02:00

2295 lines
121 KiB
Python

# -*- coding: utf-8 -*-
"""Charge l'addon ForeverUI dans un faux client WoW (lupa) et verifie son comportement.
Ce n'est pas le jeu : c'est un bouchon des seules fonctions que l'addon appelle.
Il attrape ce qu'un client attraperait au chargement -- erreur de syntaxe, nom
de fonction faux, champ nil -- sans avoir a lancer WoW.
"""
import io, os, sys
import lupa
# Le depot est la source : c'est lui qu'on charge, pas la copie du client.
RACINE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
ADDON = os.path.join(RACINE, "data", "addon", "ForeverUI")
MOCK = """
-- ---------------------------------------------------------------- faux client
local recorded = { messages = {}, portraits = 0 }
_G.RECORDED = recorded
-- L'ordre de creation des regions decide de leur ordre de dessin a
-- l'interieur d'un calque : le harnais le garde pour pouvoir le verifier.
REGIONS_CREEES = 0
local function newRegion(kind)
REGIONS_CREEES = REGIONS_CREEES + 1
local r = { kind = kind, shown = true, points = {}, rang = REGIONS_CREEES }
function r:SetTexture(a, b, c, d) self.texture = a; self.color = {a,b,c,d} end
function r:GetTexture() return self.texture end
function r:SetHorizTile(v) self.tile = v end
function r:SetTexCoord(a, b, c, d, e, f, g, h)
if e then
-- la forme a huit arguments : les quatre coins, un par un
self.texcoord8 = {a, b, c, d, e, f, g, h}
else
self.texcoord = {a, b, c, d}
end
end
-- SetRotation existe bien dans ce client : il figure dans la table des
-- methodes de Texture relevee dans Wow.exe.
-- GetObjectType : le vrai client le donne sur toute region, et du code
-- s'en sert pour trier les textures d'un cadre. Sans lui, le faux
-- client laissait passer un balayage qui ne balayait rien.
function r:GetObjectType()
if self.kind == "texture" then return "Texture" end
if self.kind == "fontstring" then return "FontString" end
return "Frame"
end
function r:IsObjectType(t) return self:GetObjectType() == t end
function r:SetRotation(angle) self.rotation = angle end
function r:GetRotation() return self.rotation or 0 end
function r:SetWidth(w) self.width = w end
function r:GetWidth() return self.width or 0 end
function r:GetHeight() return self.height or 0 end
function r:SetHeight(h) self.height = h end
function r:SetPoint(...) table.insert(self.points, {...}) end
function r:SetAllPoints(...) self.allPoints = true end
function r:ClearAllPoints() self.points = {} end
function r:Show() self.shown = true end
function r:Hide() self.shown = false end
function r:IsShown() return self.shown end
function r:SetText(t) self.text = t end
function r:GetText() return self.text end
function r:SetJustifyH(j) self.justify = j end
function r:SetJustifyV(j) self.justifyV = j end
function r:SetTextColor(rr, vv, bb, aa) self.textColor = {rr, vv, bb, aa} end
function r:GetTextColor()
local c = self.textColor or {1, 1, 1, 1}
return c[1], c[2], c[3], c[4]
end
function r:SetFontObject(o) self.font = o end
function r:SetHorizTile(v) self.tile = v end
function r:SetVertexColor(a, b, c) self.vertex = {a, b, c} end
function r:SetAlpha(a) self.alpha = a end
function r:GetAlpha() return self.alpha or 1 end
function r:SetDrawLayer() end
function r:SetBlendMode(m) self.blend = m end
return r
end
local frames = {}
_G.FRAMES = frames
function CreateFrame(kind, name, parent, template)
local f = newRegion("frame")
f.name = name
f.parent = parent
f.template = template
f.regions = {} -- pour GetRegions : les textures du cadre lui-meme
f.scripts = {}
f.events = {}
f.attributes = {}
function f:SetScript(event, fn) self.scripts[event] = fn end
function f:HookScript(event, fn)
self.hooks = self.hooks or {}
self.hooks[event] = fn
end
function f:GetScript(event) return self.scripts[event] end
function f:GetName() return self.name end
function f:RegisterEvent(e) self.events[e] = true end
function f:UnregisterAllEvents() self.events = {} end
function f:RegisterForDrag(...) self.dragButtons = { ... } end
function f:RegisterForClicks() end
function f:EnableMouse(v) self.mouseEnabled = (v ~= false) end
function f:SetMovable(v) self.movable = (v ~= false) end
function f:IsMovable() return self.movable end
function f:SetClampedToScreen() end
function f:GetNormalTexture()
if not self._normal then self._normal = newRegion("texture") end
return self._normal
end
function f:GetPushedTexture()
if not self._pushed then self._pushed = newRegion("texture") end
return self._pushed
end
function f:GetDisabledTexture()
if not self._disabled then self._disabled = newRegion("texture") end
return self._disabled
end
function f:SetButtonState(state) self.buttonState = state end
function f:GetButtonState() return self.buttonState or "NORMAL" end
function f:SetChecked(v) self.checked = v and true or false end
function f:GetChecked() return self.checked end
function f:GetCheckedTexture()
if not self._checked then self._checked = newRegion("texture") end
return self._checked
end
function f:GetHighlightTexture()
if not self._highlight then self._highlight = newRegion("texture") end
return self._highlight
end
function f:SetParent(p) self.parent = p end
function f:GetParent() return self.parent end
function f:GetWidth() return self.width or 0 end
function f:GetHeight() return self.height or 0 end
-- Le faux client ne resout pas les ancres : les bords valent nil, comme
-- pour un cadre que le vrai client n'a pas encore place. Le code doit
-- donc y survivre, et c'est ce que ce bouchon verifie.
function f:GetTop() return self._top end
function f:GetBottom() return self._bottom end
function f:GetLeft() return self._left end
function f:GetRight() return self._right end
function f:IsShown() return self.shown end
function f:SetFrameStrata(s) self.strata = s end
function f:SetFrameLevel(l) self.frameLevel = l end
function f:SetScale(v) self.scale = v end
function f:GetScale() return self.scale or 1 end
function f:GetFrameLevel() return self.frameLevel or 1 end
function f:SetHitRectInsets(...) self.hitRect = {...} end
function f:SetID(id) self.id = id end
function f:GetID() return self.id or 0 end
function f:SetAutoFocus(v) self.autoFocus = v end
function f:SetMaxLetters(v) self.maxLetters = v end
function f:SetTextInsets(a, b, c, d) self.insets = {a, b, c, d} end
function f:SetText(t) self.text = t end
function f:GetText() return self.text or "" end
function f:HasFocus() return self.focused end
function f:SetFocus() self.focused = true end
function f:ClearFocus() self.focused = false end
-- 3.3.5 n'accepte qu'un CHEMIN : un objet texture y leve une erreur, et
-- le faux client doit lever la meme, sinon il laisse passer un fichier
-- qui mourra en jeu.
local function poserTexture(self, cle, valeur)
if type(valeur) == "table" then
error("SetTexture attend un chemin, pas un objet (piege 3.3.5)")
end
if not self[cle] then self[cle] = newRegion("texture") end
self[cle].texture = valeur
return self[cle]
end
function f:SetNormalTexture(v) return poserTexture(self, "_normal", v) end
function f:SetPushedTexture(v) return poserTexture(self, "_pushed", v) end
function f:SetHighlightTexture(v) return poserTexture(self, "_highlight", v) end
function f:SetDisabledTexture(v) return poserTexture(self, "_disabled", v) end
function f:SetJustifyH(j) self.justify = j end
function f:SetAttribute(k, v) self.attributes[k] = v end
function f:GetAttribute(k) return self.attributes[k] end
function f:StartMoving() self.moving = true end
function f:StopMovingOrSizing() self.moving = false end
function f:CreateTexture(n, layer)
local t = newRegion("texture"); t.layer = layer; t.owner = self
table.insert(self.regions, t)
return t
end
-- lupa tourne en Lua 5.5, ou unpack n'est plus global ; le jeu est en
-- 5.1, ou il l'est. Le faux client accepte les deux.
function f:GetRegions() return (table.unpack or unpack)(self.regions) end
function f:GetNumRegions() return #self.regions end
function f:CreateFontString(n, layer, font)
local t = newRegion("fontstring"); t.layer = layer; t.font = font; t.owner = self; return t
end
function f:GetPoint(index)
local p = self.points[index or 1]
if not p then return nil end
return p[1], p[2], p[3], p[4], p[5]
end
function f:GetNumPoints()
local n = 0
for _ in pairs(self.points) do n = n + 1 end
return n
end
function f:GetName() return self.name end
if name then _G[name] = f end
table.insert(frames, f)
return f
end
UIParent = CreateFrame("Frame", "UIParent")
DEFAULT_CHAT_FRAME = { AddMessage = function(self, msg) table.insert(recorded.messages, msg) end }
-- le cadre d'origine que l'addon doit neutraliser
PlayerFrame = CreateFrame("Button", "PlayerFrame", UIParent)
PlayerFrame.events = { PLAYER_ENTERING_WORLD = true, UNIT_HEALTH = true }
PlayerFrameDropDown = CreateFrame("Frame", "PlayerFrameDropDown", UIParent)
-- etat simule du joueur
STATE = { health = 50, healthMax = 100, power = 30, powerMax = 100,
powerType = 1, powerToken = "RAGE", level = 80, name = "Papota",
combat = false, inLockdown = false, resting = false, leader = false,
vehicle = false, time = 0, threat = 0, threatWarning = true, dead = false,
hasTarget = false, targetHostile = false, targetsMe = false,
className = "Chevalier de la mort", classToken = "DEATHKNIGHT",
isPlayer = false, classification = "normal", reaction = 2,
selection = { 1.0, 0.0, 0.0 }, tapped = false, tappedByPlayer = false,
pvp = false, ffa = false, faction = "Alliance",
raidMembers = 0, subgroup = 3,
partialPlayTime = false, noPlayTime = false,
runeStart = 0, runeDuration = 10, runeReady = true, runeType = 1,
casting = false, channeling = false, spellName = "Eclair",
castStart = 0, castEnd = 2, notInterruptible = false,
xp = 500, xpMax = 1000, rested = 200 ,
faction_suivie = "Les Fils de Hodir", faction_attitude = 5,
faction_min = 3000, faction_max = 9000, faction_valeur = 6000,
cvars = { playerStatusText = "0", statusTextPercentage = "0",
playerStatLeftDropdown = "PLAYERSTAT_BASE_STATS",
playerStatRightDropdown = "PLAYERSTAT_MELEE_COMBAT" } }
function UnitHealth(unit) return STATE.health end
function UnitHealthMax(unit) return STATE.healthMax end
function UnitPower(unit, kind) return STATE.power end
function UnitPowerMax(unit, kind) return STATE.powerMax end
function UnitPowerType(unit) return STATE.powerType, STATE.powerToken end
function UnitName(unit) return STATE.name end
function UnitLevel(unit) return STATE.level end
function UnitAffectingCombat(unit) return STATE.combat end
function InCombatLockdown() return STATE.inLockdown end
function SetPortraitTexture(texture, unit)
recorded.portraits = recorded.portraits + 1
texture.portraitOf = unit
-- le vrai client pose bien une texture : le faux aussi, sinon un code
-- qui la relit croit l'avoir ratee
texture.texture = "portrait:" .. tostring(unit)
end
function ToggleDropDownMenu() end
function IsResting() return STATE.resting end
function UnitThreatSituation(unit) return STATE.threat end
function IsThreatWarningEnabled() return STATE.threatWarning end
function UnitIsDeadOrGhost(unit) return STATE.dead end
function UnitExists(unit) return STATE.hasTarget end
function UnitCanAttack(a, b) return STATE.targetHostile end
function UnitIsUnit(a, b)
if a == "targettarget" and b == "player" then return STATE.targetsMe end
return a == b
end
function GetThreatStatusColor(status)
if status == 3 then return 1.0, 0.0, 0.0 end
if status == 2 then return 1.0, 0.6, 0.0 end
if status == 1 then return 1.0, 1.0, 0.47 end
return 0.69, 0.69, 0.69
end
function IsPartyLeader() return STATE.leader end
function UnitHasVehicleUI(unit) return STATE.vehicle end
function GetTime() return STATE.time end
function GetCVar(name) return STATE.cvars[name] end
function GetCVarBool(name) return STATE.cvars[name] == "1" end
-- ------------------------------------------------- bouchons supplementaires
function UnitClass(unit) return STATE.className, STATE.classToken end
function UnitIsPlayer(unit) return STATE.isPlayer end
function UnitSelectionColor(unit)
return STATE.selection[1], STATE.selection[2], STATE.selection[3]
end
function UnitIsTapped(unit) return STATE.tapped end
function UnitIsTappedByPlayer(unit) return STATE.tappedByPlayer end
function UnitPlayerControlled(unit) return STATE.isPlayer end
function UnitClassification(unit) return STATE.classification end
function UnitReaction(a, b) return STATE.reaction end
function UnitIsPVP(unit) return STATE.pvp end
function UnitIsPVPFreeForAll(unit) return STATE.ffa end
function UnitFactionGroup(unit) return STATE.faction end
function GetNumRaidMembers() return STATE.raidMembers end
function GetRaidRosterInfo(i) return STATE.name, 0, STATE.subgroup end
function PartialPlayTime() return STATE.partialPlayTime end
function NoPlayTime() return STATE.noPlayTime end
function GetRuneCooldown(i) return STATE.runeStart, STATE.runeDuration, STATE.runeReady end
function GetRuneType(i) return STATE.runeType end
function CooldownFrame_SetTimer(cd, start, duration, enable) cd.timer = {start, duration, enable} end
-- LES POSTURES. 3.3.5 rend (texture, NOM, active, lancable) la ou le client
-- moderne rend (texture, active, lancable, sort) : le faux client suit la
-- signature de 3.3.5, sinon il laisserait passer l'inversion.
NUM_SHAPESHIFT_SLOTS = 10
FORMES = {
{ texture = "forme_ours", nom = "Forme d'ours", active = false, lancable = true },
{ texture = "forme_felin", nom = "Forme de felin", active = true, lancable = true },
{ texture = "forme_voyage", nom = "Forme de voyage", active = false, lancable = false },
}
function GetNumShapeshiftForms() return #FORMES end
function GetShapeshiftFormInfo(i)
local f = FORMES[i]
if not f then return nil end
return f.texture, f.nom, f.active, f.lancable
end
function GetShapeshiftFormCooldown(i) return 0, 0, 0 end
-- LE FAMILIER. 3.3.5 rend (nom, SOUS-TEXTE, texture, isToken, active,
-- autoPossible, autoActif) : un champ de plus au deuxieme rang que le
-- client moderne. Le faux client suit 3.3.5, sinon il laisserait passer
-- la confusion entre le sous-texte et la texture.
NUM_PET_ACTION_SLOTS = 10
PET_ACTIONS = {
{ nom = "Attaquer", texte = "", texture = "icone_attaque", cle = false,
active = true, autoPossible = false, autoActif = false, utilisable = true },
{ nom = "Morsure", texte = "Rang 5", texture = "icone_morsure", cle = false,
active = false, autoPossible = true, autoActif = true, utilisable = true },
{ nom = "PET_MODE_PASSIVE", texte = "", texture = "PET_TEXTURE_PASSIVE", cle = true,
active = false, autoPossible = false, autoActif = false, utilisable = false },
}
PET_TEXTURE_PASSIVE = "icone_passif_resolue"
PET_MODE_PASSIVE = "Passif"
PET_A_UNE_BARRE = true
PET_VISIBLE = true
function PetHasActionBar() return PET_A_UNE_BARRE end
function UnitIsVisible(unite)
if unite == "pet" then return PET_VISIBLE end
return true
end
function GetPetActionInfo(i)
local a = PET_ACTIONS[i]
if not a then return nil end
return a.nom, a.texte, a.texture, a.cle, a.active, a.autoPossible, a.autoActif
end
function GetPetActionCooldown(i) return 0, 0, 0 end
function GetPetActionSlotUsable(i)
local a = PET_ACTIONS[i]
return a and a.utilisable or false
end
function IsPetAttackAction(i)
local a = PET_ACTIONS[i]
return a and a.nom == "Attaquer" or false
end
function CombatFeedback_Initialize(self, text, height) self.feedbackText = text end
function CombatFeedback_OnCombatEvent(self, event, flags, amount, kind) self.lastHit = amount end
function CombatFeedback_OnUpdate(self, elapsed) end
function RegisterUnitWatch(f) f.unitWatch = true end
function ToggleDropDownMenu() end
GameTooltip = { SetOwner = function() end, SetText = function() end,
AddLine = function() end, Show = function() end, Hide = function() end }
RAID_CLASS_COLORS = { WARRIOR = { r = 0.78, g = 0.61, b = 0.43 },
DEATHKNIGHT = { r = 0.77, g = 0.12, b = 0.23 } }
GROUP = "Groupe"
PLAYTIME_TIRED = "Temps de jeu fatigue"
PLAYTIME_UNHEALTHY = "Temps de jeu malsain"
TargetFrame = CreateFrame("Button", "TargetFrame", UIParent)
TargetFrameDropDown = CreateFrame("Frame", "TargetFrameDropDown", UIParent)
ComboFrame = CreateFrame("Frame", "ComboFrame", UIParent)
function UnitCastingInfo(unit)
if not STATE.casting then return nil end
return STATE.spellName, nil, STATE.spellName, "icone", STATE.castStart * 1000,
STATE.castEnd * 1000, false, 1, STATE.notInterruptible
end
function UnitChannelInfo(unit)
if not STATE.channeling then return nil end
return STATE.spellName, nil, STATE.spellName, "icone", STATE.castStart * 1000,
STATE.castEnd * 1000, false, STATE.notInterruptible
end
function GetWatchedFactionInfo()
if not STATE.faction_suivie then return nil end
return STATE.faction_suivie, STATE.faction_attitude, STATE.faction_min,
STATE.faction_max, STATE.faction_valeur
end
ReputationWatchBar = CreateFrame("Frame", "ReputationWatchBar", UIParent)
function UnitXP(unit) return STATE.xp end
function UnitXPMax(unit) return STATE.xpMax end
function GetXPExhaustion() return STATE.rested end
MAX_PLAYER_LEVEL = 80
FAILED = "Echec"
INTERRUPTED = "Interrompu"
CastingBarFrame = CreateFrame("StatusBar", "CastingBarFrame", UIParent)
MainMenuExpBar = CreateFrame("StatusBar", "MainMenuExpBar", UIParent)
ExhaustionTick = CreateFrame("Frame", "ExhaustionTick", UIParent)
RuneFrame = CreateFrame("Frame", "RuneFrame", UIParent)
-- la barre d'action du client, telle que l'addon la trouve
MainMenuBar = CreateFrame("Frame", "MainMenuBar", UIParent)
MainMenuBarLeftEndCap = UIParent:CreateTexture(nil, "ARTWORK")
MainMenuBarRightEndCap = UIParent:CreateTexture(nil, "ARTWORK")
MainMenuBarPageNumber = UIParent:CreateFontString(nil, "OVERLAY")
for i = 0, 3 do
_G["MainMenuBarTexture" .. i] = UIParent:CreateTexture(nil, "ARTWORK")
_G["MainMenuXPBarTexture" .. i] = UIParent:CreateTexture(nil, "ARTWORK")
_G["MainMenuMaxLevelBar" .. i] = UIParent:CreateTexture(nil, "ARTWORK")
end
MainMenuBarExpText = UIParent:CreateFontString(nil, "OVERLAY")
ActionBarUpButton = CreateFrame("Button", "ActionBarUpButton", UIParent)
ActionBarDownButton = CreateFrame("Button", "ActionBarDownButton", UIParent)
NumberFontNormalSmallGray = "NumberFontNormalSmallGray"
NumberFontNormal = "NumberFontNormal"
GameFontHighlightSmallOutline = "GameFontHighlightSmallOutline"
for i = 1, 12 do
local b = CreateFrame("CheckButton", "ActionButton" .. i, MainMenuBar)
_G["ActionButton" .. i .. "Icon"] = b:CreateTexture(nil, "ARTWORK")
_G["ActionButton" .. i .. "Border"] = b:CreateTexture(nil, "OVERLAY")
_G["ActionButton" .. i .. "Flash"] = b:CreateTexture(nil, "ARTWORK")
_G["ActionButton" .. i .. "FloatingBG"] = b:CreateTexture(nil, "BACKGROUND")
_G["ActionButton" .. i .. "HotKey"] = b:CreateFontString(nil, "OVERLAY")
_G["ActionButton" .. i .. "Count"] = b:CreateFontString(nil, "OVERLAY")
_G["ActionButton" .. i .. "Name"] = b:CreateFontString(nil, "OVERLAY")
_G["ActionButton" .. i .. "Cooldown"] = CreateFrame("Cooldown", "ActionButton" .. i .. "Cooldown", b)
end
-- le bas de l'ecran du client : micro-menu, sacs, trousseau
MainMenuBarArtFrame = CreateFrame("Frame", "MainMenuBarArtFrame", MainMenuBar)
MainMenuBarArtFrame:SetFrameLevel(2)
local MICROS = { "CharacterMicroButton", "SpellbookMicroButton", "TalentMicroButton",
"AchievementMicroButton", "QuestLogMicroButton", "SocialsMicroButton",
"PVPMicroButton", "LFDMicroButton", "MainMenuMicroButton",
"HelpMicroButton" }
for _, nom in ipairs(MICROS) do
local b = CreateFrame("Button", nom, MainMenuBarArtFrame)
b:SetWidth(28)
b:SetHeight(58)
end
MicroButtonPortrait = CharacterMicroButton:CreateTexture("MicroButtonPortrait", "OVERLAY")
PVPMicroButtonTexture = PVPMicroButton:CreateTexture("PVPMicroButtonTexture", "OVERLAY")
MainMenuBarPerformanceBar = MainMenuMicroButton:CreateTexture("MainMenuBarPerformanceBar", "OVERLAY")
function UpdateMicroButtons() end
for _, nom in ipairs({ "MainMenuBarBackpackButton", "CharacterBag0Slot", "CharacterBag1Slot",
"CharacterBag2Slot", "CharacterBag3Slot" }) do
local b = CreateFrame("CheckButton", nom, MainMenuBarArtFrame)
_G[nom .. "IconTexture"] = b:CreateTexture(nom .. "IconTexture", "BORDER")
_G[nom .. "Count"] = b:CreateFontString(nom .. "Count", "OVERLAY")
end
KeyRingButton = CreateFrame("CheckButton", "KeyRingButton", MainMenuBarArtFrame)
KeyRingButton:Hide()
-- les sacs du client
NUM_CONTAINER_FRAMES = 13
MAX_CONTAINER_ITEMS = 36
NUM_BAG_SLOTS = 4
CONTAINER_WIDTH = 192
ITEM_QUALITY_COLORS = {
[0] = { r = 0.62, g = 0.62, b = 0.62 },
[1] = { r = 1.00, g = 1.00, b = 1.00 },
[2] = { r = 0.12, g = 1.00, b = 0.00 },
[3] = { r = 0.00, g = 0.44, b = 0.87 },
[4] = { r = 0.64, g = 0.21, b = 0.93 },
}
NUM_CONTAINER_COLUMNS = 4
SEARCH = "Rechercher"
BAG_CLEANUP_BAGS = nil
for i = 1, NUM_CONTAINER_FRAMES do
local nom = "ContainerFrame" .. i
local c = CreateFrame("Frame", nom, UIParent)
c.size = 16
c:SetID(0)
-- Un cadre de sac est masque tant qu'il n'a pas ete ouvert : le jeu ne
-- les montre pas tous les treize.
c:Hide()
_G[nom .. "Portrait"] = c:CreateTexture(nom .. "Portrait", "BACKGROUND")
_G[nom .. "Name"] = c:CreateFontString(nom .. "Name", "ARTWORK")
CreateFrame("Button", nom .. "CloseButton", c)
local bourse = CreateFrame("Frame", nom .. "MoneyFrame", c)
bourse:SetHeight(24)
for _, suffixe in ipairs({ "BackgroundTop", "BackgroundMiddle1", "BackgroundMiddle2",
"BackgroundBottom", "Background1Slot" }) do
_G[nom .. suffixe] = c:CreateTexture(nom .. suffixe, "ARTWORK")
end
for j = 1, MAX_CONTAINER_ITEMS do
local b = CreateFrame("Button", nom .. "Item" .. j, c)
b:SetID(j)
_G[nom .. "Item" .. j .. "IconTexture"] = b:CreateTexture(nil, "BORDER")
end
end
-- la feuille du personnage du client : le cadre, son modele, ses vingt
-- emplacements d'equipement, et l'art d'epoque en quatre quartiers
CharacterFrame = CreateFrame("Frame", "CharacterFrame", UIParent)
for _, coin in ipairs({ "TopLeft", "TopRight", "BottomLeft", "BottomRight" }) do
_G["CharacterFrame" .. coin] = CharacterFrame:CreateTexture(
"CharacterFrame" .. coin, "ARTWORK")
end
CharacterNameText = CharacterFrame:CreateFontString("CharacterNameText", "ARTWORK")
CharacterFrameCloseButton = CreateFrame("Button", "CharacterFrameCloseButton", CharacterFrame)
HIGHLIGHT_FONT_COLOR = { r = 1, g = 1, b = 1 }
NORMAL_FONT_COLOR = { r = 1, g = 0.82, b = 0 }
REPUTATION, CURRENCY, PVP, SKILLS = "Reputation", "Currency", "PvP", "Skills"
function UnitPVPName(unite) return "Robert Polson" end
CharacterModelFrame = CreateFrame("Frame", "CharacterModelFrame", CharacterFrame)
CharacterLevelText = CharacterFrame:CreateFontString("CharacterLevelText", "ARTWORK")
-- le client compose cette ligne ; le faux client en pose une pour qu'on
-- puisse verifier qu'elle est bien recopiee
CharacterLevelText:SetText("Niveau 3 Elfe de la nuit Druide")
for _, cote in ipairs({ "Left", "Right" }) do
local nom = "CharacterModelFrameRotate" .. cote .. "Button"
local b = CreateFrame("Button", nom, CharacterModelFrame)
b:SetWidth(16); b:SetHeight(16)
end
PaperDollFrame = CreateFrame("Frame", "PaperDollFrame", CharacterFrame)
PaperDollFrameTexture = PaperDollFrame:CreateTexture("PaperDollFrameTexture", "ARTWORK")
EMPLACEMENTS_PERSO = {
"Head", "Neck", "Shoulder", "Back", "Chest", "Shirt", "Tabard", "Wrist",
"Hands", "Waist", "Legs", "Feet", "Finger0", "Finger1", "Trinket0", "Trinket1",
"MainHand", "SecondaryHand", "Ranged", "Ammo",
}
for _, nom in ipairs(EMPLACEMENTS_PERSO) do
local plein = "Character" .. nom .. "Slot"
local b = CreateFrame("Button", plein, CharacterFrame)
_G[plein .. "IconTexture"] = b:CreateTexture(plein .. "IconTexture", "BORDER")
_G[plein .. "NormalTexture"] = b:CreateTexture(plein .. "NormalTexture", "ARTWORK")
end
for i = 1, 5 do
local nom = "CharacterFrameTab" .. i
local t = CreateFrame("Button", nom, CharacterFrame)
_G[nom .. "Text"] = t:CreateFontString(nom .. "Text", "ARTWORK")
t:CreateTexture(nom .. "Fond", "ARTWORK")
end
-- les statistiques telles que le client les CHARGE (patch-enUS-2 et -3, le
-- FrameXML d'origine) : deux groupes de six lignes StatFrameTemplate de
-- 104 x 13, chacun coiffe de son UIDropDownMenuTemplate. La categorie
-- choisie vit dans une CVar qui porte une CLE ; le texte est la globale du
-- meme nom.
PLAYERSTAT_BASE_STATS = "Attributs"
PLAYERSTAT_MELEE_COMBAT = "Corps a corps"
PLAYERSTAT_DEFENSES = "Defenses"
function UpdatePaperdollStats(prefixe, cle) end
CharacterAttributesFrame = CreateFrame("Frame", "CharacterAttributesFrame", CharacterFrame)
for _, cote in ipairs({ "Left", "Right" }) do
-- UIDropDownMenuTemplate : 40 x 32, porte par un art qui le deborde,
-- et un bouton fils qui ouvre le menu.
local nom = "PlayerStatFrame" .. cote .. "DropDown"
local sel = CreateFrame("Frame", nom, CharacterAttributesFrame)
sel:SetWidth(149); sel:SetHeight(32)
for _, piece in ipairs({ "Left", "Middle", "Right" }) do
_G[nom .. piece] = sel:CreateTexture(nom .. piece, "ARTWORK")
end
_G[nom .. "Text"] = sel:CreateFontString(nom .. "Text", "ARTWORK")
local bouton = CreateFrame("Button", nom .. "Button", sel)
bouton:SetWidth(24); bouton:SetHeight(24)
for i = 1, 6 do
local l = CreateFrame("Frame", "PlayerStatFrame" .. cote .. i, CharacterAttributesFrame)
l:SetWidth(104); l:SetHeight(13)
end
end
CharacterResistanceFrame = CreateFrame("Frame", "CharacterResistanceFrame", CharacterFrame)
CharacterResistanceFrame:SetPoint("TOPRIGHT", CharacterFrame, "TOPRIGHT", -60, -80)
ReputationFrame = CreateFrame("Frame", "ReputationFrame", CharacterFrame)
ReputationFrame:Hide()
function CharacterFrame_ShowSubFrame() end
function PaperDollFrame_OnShow() end
-- la barre bonus du client : celle qui remplace la barre de sorts quand le
-- joueur change de posture, avec son art glissant
BonusActionBarFrame = CreateFrame("Frame", "BonusActionBarFrame", UIParent)
BonusActionBarTexture0 = BonusActionBarFrame:CreateTexture("BonusActionBarTexture0", "ARTWORK")
BonusActionBarTexture1 = BonusActionBarFrame:CreateTexture("BonusActionBarTexture1", "ARTWORK")
for i = 1, 12 do
local nom = "BonusActionButton" .. i
local b = CreateFrame("CheckButton", nom, BonusActionBarFrame)
b:SetID(i)
_G[nom .. "Icon"] = b:CreateTexture(nom .. "Icon", "BORDER")
_G[nom .. "IconTexture"] = _G[nom .. "Icon"]
_G[nom .. "Cooldown"] = CreateFrame("Frame", nom .. "Cooldown", b)
_G[nom .. "NormalTexture"] = b:CreateTexture(nom .. "NormalTexture", "ARTWORK")
_G[nom .. "HotKey"] = b:CreateFontString(nom .. "HotKey", "ARTWORK")
_G[nom .. "Count"] = b:CreateFontString(nom .. "Count", "ARTWORK")
_G[nom .. "Name"] = b:CreateFontString(nom .. "Name", "ARTWORK")
_G[nom .. "Border"] = b:CreateTexture(nom .. "Border", "OVERLAY")
_G[nom .. "Flash"] = b:CreateTexture(nom .. "Flash", "ARTWORK")
_G[nom .. "FloatingBG"] = b:CreateTexture(nom .. "FloatingBG", "BACKGROUND")
end
-- la barre des postures du client : un cadre, dix boutons
ShapeshiftBarFrame = CreateFrame("Frame", "ShapeshiftBarFrame", UIParent)
for _, suffixe in ipairs({ "Left", "Middle", "Right" }) do
_G["ShapeshiftBar" .. suffixe] = ShapeshiftBarFrame:CreateTexture(
"ShapeshiftBar" .. suffixe, "ARTWORK")
end
for i = 1, NUM_SHAPESHIFT_SLOTS do
local nom = "ShapeshiftButton" .. i
local b = CreateFrame("CheckButton", nom, ShapeshiftBarFrame)
b:SetID(i)
_G[nom .. "Icon"] = b:CreateTexture(nom .. "Icon", "BORDER")
_G[nom .. "Cooldown"] = CreateFrame("Frame", nom .. "Cooldown", b)
_G[nom .. "FloatingBG"] = b:CreateTexture(nom .. "FloatingBG", "BACKGROUND")
_G[nom .. "HotKey"] = b:CreateFontString(nom .. "HotKey", "ARTWORK")
_G[nom .. "Count"] = b:CreateFontString(nom .. "Count", "ARTWORK")
end
function ShapeshiftBar_Update() end
function ShapeshiftBar_UpdateState() end
-- la barre du familier du client : un cadre, dix boutons
PetActionBarFrame = CreateFrame("Frame", "PetActionBarFrame", UIParent)
-- l'art d'epoque : les deux morceaux glissants qui encadrent la barre
SlidingActionBarTexture0 = PetActionBarFrame:CreateTexture("SlidingActionBarTexture0", "ARTWORK")
SlidingActionBarTexture1 = PetActionBarFrame:CreateTexture("SlidingActionBarTexture1", "ARTWORK")
for i = 1, NUM_PET_ACTION_SLOTS do
local nom = "PetActionButton" .. i
local b = CreateFrame("CheckButton", nom, PetActionBarFrame)
b:SetID(i)
_G[nom .. "Icon"] = b:CreateTexture(nom .. "Icon", "BORDER")
_G[nom .. "Cooldown"] = CreateFrame("Frame", nom .. "Cooldown", b)
_G[nom .. "AutoCastable"] = b:CreateTexture(nom .. "AutoCastable", "OVERLAY")
_G[nom .. "Shine"] = CreateFrame("Frame", nom .. "Shine", b)
_G[nom .. "Flash"] = b:CreateTexture(nom .. "Flash", "ARTWORK")
_G[nom .. "FloatingBG"] = b:CreateTexture(nom .. "FloatingBG", "BACKGROUND")
_G[nom .. "HotKey"] = b:CreateFontString(nom .. "HotKey", "ARTWORK")
_G[nom .. "Count"] = b:CreateFontString(nom .. "Count", "ARTWORK")
end
function PetActionBar_Update() end
function PetActionBar_UpdateCooldowns() end
-- l'inventaire simule : SACS[sac][emplacement] = { lien, nombre }
SACS = { [0] = {}, [1] = {}, [2] = {}, [3] = {}, [4] = {} }
TAILLES = { [0] = 4, [1] = 4, [2] = 0, [3] = 0, [4] = 0 }
OBJETS = {
["|cffffffff|Hitem:1|h[Pain]|h|r"] = { nom = "Pain", qualite = 1, type_ = "Consommable",
sousType = "Nourriture", pileMax = 20 },
["|cff0070dd|Hitem:2|h[Epee]|h|r"] = { nom = "Epee", qualite = 3, type_ = "Arme",
sousType = "Epee a une main", pileMax = 1 },
["|cffffffff|Hitem:3|h[Potion]|h|r"] = { nom = "Potion", qualite = 1, type_ = "Consommable",
sousType = "Potion", pileMax = 20 },
}
PRISES = {}
CURSEUR = nil
function GetContainerNumSlots(sac) return TAILLES[sac] or 0 end
function GetContainerItemLink(sac, emplacement)
local case = SACS[sac] and SACS[sac][emplacement]
return case and case.lien or nil
end
function GetContainerItemInfo(sac, emplacement)
local case = SACS[sac] and SACS[sac][emplacement]
if not case then return nil end
local o = OBJETS[case.lien]
return "icone", case.nombre, false, (o and o.qualite) or 1, false
end
-- Le nom d'un sac vient du client : bag 0 = le mot traduit, les autres
-- portent le nom de l'objet qu'ils sont.
function GetScreenWidth() return 1920 end
function GetScreenHeight() return 1080 end
KEYRING_CONTAINER = -2
NOMS_DE_SACS = { [0] = "Sac a dos", [-2] = "Trousseau de cles",
[1] = "Sac en tisse-givre", [2] = "Sac de mineur" }
function GetBagName(id) return NOMS_DE_SACS[id] end
function ContainerIDToInventoryID(id) return 19 + id end
function GetInventoryItemTexture(unite, emplacement)
if emplacement == 20 then return "icone_du_sac_1" end
return nil
end
function GetItemInfo(lien)
local o = OBJETS[lien]
if not o then return nil end
return o.nom, lien, o.qualite, 1, 1, o.type_, o.sousType, o.pileMax
end
function GetAuctionItemClasses() return "Arme", "Armure", "Consommable", "Divers" end
function CursorHasItem() return CURSEUR ~= nil end
function ClearCursor() CURSEUR = nil end
function PickupContainerItem(sac, emplacement)
table.insert(PRISES, { sac = sac, emplacement = emplacement })
local case = SACS[sac][emplacement]
if CURSEUR == nil then
CURSEUR = { sac = sac, emplacement = emplacement, case = case }
SACS[sac][emplacement] = nil
else
-- Le client reunit deux piles entamees du meme objet ; sinon il echange.
local porte = CURSEUR.case
if case and porte and case.lien == porte.lien then
local max = OBJETS[case.lien].pileMax
if case.nombre + porte.nombre <= max then
SACS[sac][emplacement] = { lien = case.lien, nombre = case.nombre + porte.nombre }
CURSEUR = nil
return
end
end
SACS[CURSEUR.sac][CURSEUR.emplacement] = case
SACS[sac][emplacement] = porte
CURSEUR = nil
end
end
function ContainerFrame_GenerateFrame() end
function ContainerFrame_Update() end
function ContainerFrame_OnHide() end
function updateContainerFrameAnchors() end
function ToggleBag() end
HOOKS = {}
-- Le vrai hooksecurefunc ENVELOPPE la fonction : elle s execute, puis le
-- greffon, et les valeurs rendues sont celles de l originale. Le banc se
-- contentait d enregistrer le greffon, si bien qu appeler la fonction ne
-- declenchait rien.
function hooksecurefunc(nom, fn)
HOOKS[nom] = fn
if type(nom) == "string" and type(_G[nom]) == "function" then
local ancienne = _G[nom]
_G[nom] = function(...)
local rendus = { ancienne(...) }
fn(...)
return (table.unpack or unpack)(rendus)
end
end
end
function ActionButton_Update() end
function ActionButton_ShowGrid() end
function ActionButton_HideGrid() end
SlashCmdList = {}
"""
def main():
lua = lupa.LuaRuntime(unpack_returned_tuples=True)
lua.execute(MOCK)
ordre = ["UIAtlas.lua", "UIAtlas_01_selection_perso.lua", "UIAtlas_02_creation_perso.lua",
"UIAtlas_03_barre_action.lua", "UIAtlas_04_cadres_unite.lua",
"UIAtlas_05_feuille_perso.lua", "UIAtlas_06_complements.lua", "AtlasUtil.lua", "Layout.lua", "PlayerFrame.lua",
"PlayerFrameExtras.lua", "PlayerRunes.lua", "TargetFrame.lua",
"CastBar.lua", "ActionBar.lua", "StanceBar.lua", "PetBar.lua",
"BottomBar.lua", "StatusBars.lua", "Bags.lua",
"CharacterFrame.lua"]
# l'ordre du .toc fait foi : on verifie qu'il correspond
toc = io.open(os.path.join(ADDON, "ForeverUI.toc"), encoding="utf-8").read()
listes = [l.strip() for l in toc.splitlines() if l.strip() and not l.startswith("##")]
assert listes == ordre, "l'ordre du toc a change : %s" % listes
echecs = []
for fn in ordre:
source = io.open(os.path.join(ADDON, fn), encoding="utf-8").read()
try:
lua.execute(source)
except Exception as exc:
echecs.append((fn, str(exc).split("\n")[0]))
if echecs:
for fn, err in echecs:
print(" ECHEC %-34s %s" % (fn, err))
sys.exit("chargement interrompu")
print("chargement : %d fichiers, aucune erreur" % len(ordre))
g = lua.globals()
# 1. tables d'atlas
n = sum(1 for _ in g.UIAtlas.data.items())
print("entrees d'atlas chargees : %d" % n)
# 2. enregistrement de la position
system = g.ForeverUI.Layout.systems["playerframe"]
d = system.defaults
print("systeme enregistre : %s | defaut %s/%s %s,%s" % (system.label, d.point, d.relativePoint, d.x, d.y))
frame = g.ForeverUIPlayerFrame
pt = frame.points[1]
print("ancrage applique : %s -> %s (%s, %s)" % (pt[1], pt[3], pt[4], pt[5]))
# 3. evenement d'entree en jeu
frame.scripts.OnEvent(frame, "PLAYER_ENTERING_WORLD")
print("apres PLAYER_ENTERING_WORLD :")
print(" nom : %s" % frame.nameText.text)
print(" niveau : %s" % frame.levelText.text)
print(" portrait : %d appel(s) a SetPortraitTexture" % g.RECORDED.portraits)
print(" art : %s" % frame.art.texture)
hp = frame.healthFill
print(" vie 50%% : largeur %s, texcoord u %.6f -> %.6f (visible: %s)" % (
hp.width, hp.texcoord[1], hp.texcoord[2], hp.shown))
pw = frame.powerFill
print(" rage 30%% : largeur %s, hauteur %s (visible: %s)" % (pw.width, pw.height, pw.shown))
print(" PlayerFrame d'origine : %d evenement(s), visible=%s" % (
sum(1 for _ in g.PlayerFrame.events.items()), g.PlayerFrame.shown))
# 3b. le niveau doit vivre dans un cadre fils, place au-dessus du cadre
holder = frame.levelText.owner
niveau_cadre = frame.frameLevel or 1
print(" niveau : porte par un cadre fils = %s, son niveau %s contre %s" % (
holder is not frame, holder.frameLevel, niveau_cadre))
assert holder is not frame, "le niveau est reste dans le cadre principal"
assert holder.frameLevel > niveau_cadre, "le cadre du niveau n'est pas au-dessus"
# 4. vie a zero : le client refuse une largeur nulle, la texture doit disparaitre
g.STATE.health = 0
frame.scripts.OnEvent(frame, "UNIT_HEALTH", "player")
print("vie a 0 : texture de vie visible = %s" % frame.healthFill.shown)
# 5. un evenement pour une autre unite ne doit rien changer
g.STATE.health = 100
frame.scripts.OnEvent(frame, "UNIT_HEALTH", "target")
print("UNIT_HEALTH sur 'target' ignore : texture toujours masquee = %s" % (not frame.healthFill.shown))
# 6. l'art du cadre ne change jamais ; la lueur est celle de la menace
avant = [frame.art.texcoord[i] for i in (1, 2, 3, 4)]
g.STATE.combat = True
frame.scripts.OnEvent(frame, "PLAYER_REGEN_DISABLED")
apres = [frame.art.texcoord[i] for i in (1, 2, 3, 4)]
print("art du cadre inchange en combat : %s" % (avant == apres))
assert avant == apres, "l'art du cadre a ete remplace"
couleur = frame.threatGlow.vertex
print("engage sans etre pris pour cible : visible=%s alpha=%.2f couleur=(%.2f, %.2f, %.2f)" % (
frame.threatGlow.shown, frame.threatGlow.alpha, couleur[1], couleur[2], couleur[3]))
assert frame.threatGlow.shown, "pas de lueur alors que le joueur est en combat"
faible = frame.threatGlow.alpha
g.STATE.threat = 3
frame.scripts.OnEvent(frame, "UNIT_THREAT_SITUATION_UPDATE")
couleur = frame.threatGlow.vertex
fort = frame.threatGlow.alpha
print("pris pour cible par un ennemi : visible=%s alpha=%.2f couleur=(%.2f, %.2f, %.2f)" % (
frame.threatGlow.shown, fort, couleur[1], couleur[2], couleur[3]))
assert fort > faible, "la lueur ne se renforce pas quand l'ennemi vous vise"
assert (couleur[1], couleur[2], couleur[3]) == (1.0, 0.0, 0.0), "la lueur n'est pas rouge"
g.STATE.threat = 1
frame.scripts.OnEvent(frame, "UNIT_THREAT_SITUATION_UPDATE")
print("menace haute sans etre la cible : alpha=%.2f (niveau discret attendu)" % frame.threatGlow.alpha)
g.STATE.dead = True
frame.scripts.OnEvent(frame, "UNIT_THREAT_SITUATION_UPDATE")
print("joueur mort : visible=%s" % frame.threatGlow.shown)
g.STATE.dead = False
g.STATE.threat = 0
g.STATE.combat = False
frame.scripts.OnEvent(frame, "PLAYER_REGEN_ENABLED")
print("hors combat, sans menace : visible=%s" % frame.threatGlow.shown)
assert not frame.threatGlow.shown, "la lueur reste allumee hors combat"
# 6b. serveur sans donnees de menace : la lueur forte doit quand meme venir
g.STATE.threat = 0 # le serveur ne dit rien
g.STATE.combat = True
frame.scripts.OnEvent(frame, "PLAYER_REGEN_DISABLED") # le joueur est engage
g.STATE.hasTarget, g.STATE.targetHostile, g.STATE.targetsMe = True, True, False
frame.scripts.OnEvent(frame, "PLAYER_TARGET_CHANGED")
engage = frame.threatGlow.alpha
print("sans menace, cible qui ne vise pas : alpha=%.2f" % engage)
g.STATE.targetsMe = True
frame.scripts.OnEvent(frame, "PLAYER_TARGET_CHANGED")
vise = frame.threatGlow.alpha
print("sans menace, la cible me vise : alpha=%.2f" % vise)
assert vise > engage, "la lueur ne se renforce pas quand la cible vise le joueur"
# la relecture periodique doit suffire, meme sans aucun evenement
g.STATE.targetsMe = False
frame.scripts.OnUpdate(frame, 0.4)
print("apres relecture periodique : alpha=%.2f (retour au discret)" % frame.threatGlow.alpha)
assert frame.threatGlow.alpha == engage, "la relecture periodique ne redescend pas"
g.STATE.hasTarget, g.STATE.targetHostile, g.STATE.targetsMe = False, False, False
g.STATE.combat = False
frame.scripts.OnEvent(frame, "PLAYER_REGEN_ENABLED")
# 7. mode edition et deplacement
g.SlashCmdList["FOREVERUI"]("")
overlay = g.ForeverUI.Layout.systems["playerframe"].overlay
print("mode edition : %s, surface creee = %s" % (g.ForeverUI.Layout.editing, overlay is not None))
overlay.scripts.OnDragStart()
frame.points = lua.eval("{}")
frame.SetPoint(frame, "CENTER", g.UIParent, "CENTER", 120, -40)
overlay.scripts.OnDragStop()
saved = g.ForeverUIDB.positions["playerframe"]
print("position retenue apres deplacement : %s/%s %s,%s" % (saved.point, saved.relativePoint, saved.x, saved.y))
g.SlashCmdList["FOREVERUI"]("")
print("mode edition apres seconde bascule : %s" % g.ForeverUI.Layout.editing)
# 8. remise par defaut
g.SlashCmdList["FOREVERUI"]("reset")
reste = g.ForeverUIDB.positions["playerframe"]
pt = frame.points[len(list(frame.points.items()))]
print("apres reset : position sauvegardee = %s, ancrage = %s (%s, %s)" % (reste, pt[1], pt[4], pt[5]))
# 9. combat : le mode edition doit refuser
g.STATE.inLockdown = True
g.SlashCmdList["FOREVERUI"]("")
print("mode edition refuse en combat : %s" % (not g.ForeverUI.Layout.editing))
g.STATE.inLockdown = False # sinon tout ce qui suit tourne en combat
# 10. etats : repos, combat, vehicule
def etat(resting, combat, vehicle):
g.STATE.resting, g.STATE.combat, g.STATE.vehicle = resting, combat, vehicle
frame.scripts.OnEvent(frame, "PLAYER_ENTER_COMBAT" if combat else "PLAYER_LEAVE_COMBAT")
frame.scripts.OnEvent(frame, "PLAYER_UPDATE_RESTING")
return (frame.statusTexture.shown, frame.statusTexture.vertex, frame.combatIcon.shown,
frame.restTexture.shown)
shown, vertex, icon, rest = etat(True, False, False)
print("repos : voile=%s couleur=(%.2f, %.2f, %.2f) icone combat=%s sommeil=%s" % (
shown, vertex[1], vertex[2], vertex[3], icon, rest))
shown, vertex, icon, rest = etat(False, True, False)
print("combat : voile=%s couleur=(%.2f, %.2f, %.2f) icone=%s sommeil=%s" % (
shown, vertex[1], vertex[2], vertex[3], icon, rest))
shown, vertex, icon, rest = etat(False, False, False)
print("ni repos ni combat : voile=%s icone=%s sommeil=%s" % (shown, icon, rest))
shown, vertex, icon, rest = etat(True, True, True)
print("vehicule : voile=%s icone=%s sommeil=%s (tout doit etre masque)" % (shown, icon, rest))
print("voile : melange = %s (ADD attendu, sinon le rouge delave le cadre)"
% frame.statusTexture.blend)
# 10b. les deux drapeaux du client, distincts
etat(False, False, False)
frame.scripts.OnEvent(frame, "PLAYER_REGEN_ENABLED")
frame.scripts.OnEvent(frame, "PLAYER_ENTER_COMBAT")
print("clic droit sur un ennemi (frappe seule) : voile=%s icone=%s ornement=%s" % (
frame.statusTexture.shown, frame.combatIcon.shown, frame.cornerIcon.shown))
assert frame.statusTexture.shown and frame.combatIcon.shown, "la frappe n'allume rien"
frame.scripts.OnEvent(frame, "PLAYER_REGEN_DISABLED")
frame.scripts.OnEvent(frame, "PLAYER_LEAVE_COMBAT")
print("frappe finie, toujours sur la liste : voile=%s icone=%s" % (
frame.statusTexture.shown, frame.combatIcon.shown))
assert frame.combatIcon.shown, "l'icone disparait alors que le combat dure"
assert frame.statusTexture.shown, "le voile s'eteint avant la fin du combat"
frame.scripts.OnEvent(frame, "PLAYER_REGEN_ENABLED")
print("sorti de combat : voile=%s icone=%s ornement=%s" % (
frame.statusTexture.shown, frame.combatIcon.shown, frame.cornerIcon.shown))
assert not frame.statusTexture.shown and not frame.combatIcon.shown, "le combat ne se termine pas"
assert frame.cornerIcon.shown, "l'ornement de coin ne revient pas"
assert frame.statusTexture.blend == "ADD", "le voile n est pas en mode additif"
assert frame.combatGlow is None, "la lueur de combat traine encore"
# 11. animation du sommeil : la vignette doit rester dans l'element
g.STATE.vehicle = False
etat(True, False, False)
entry = g.UIAtlas.data["ui-hud-unitframe-player-rest-flipbook"]
u1, u2, v1, v2 = entry[2], entry[3], entry[4], entry[5]
vues = set()
for pas in range(60):
g.STATE.time = pas * 0.0357
frame.scripts.OnUpdate(frame, 0.0357)
c = frame.restTexture.texcoord
assert u1 - 1e-9 <= c[1] and c[2] <= u2 + 1e-9, "vignette hors de l'element en largeur"
assert v1 - 1e-9 <= c[3] and c[4] <= v2 + 1e-9, "vignette hors de l'element en hauteur"
vues.add((round(c[1], 6), round(c[3], 6)))
print("sommeil : %d vignettes distinctes, toutes dans l'element (42 au total)" % len(vues))
print("voile : transparence pulsee a %.3f" % frame.statusTexture.alpha)
# 12. texte des barres, suivant le reglage du client
g.STATE.health, g.STATE.healthMax = 50, 100
frame.scripts.OnEvent(frame, "UNIT_HEALTH", "player")
print("texte, reglage a 0 sans survol : visible=%s" % frame.healthText.shown)
frame.scripts.OnEnter(frame)
print("texte au survol : vie=%s | ressource=%s" % (
frame.healthText.text, frame.powerText.text))
frame.scripts.OnLeave(frame)
g.STATE.cvars["playerStatusText"] = "1"
frame.scripts.OnEvent(frame, "CVAR_UPDATE")
print("texte, reglage a 1 : visible=%s | vie=%s" % (
frame.healthText.shown, frame.healthText.text))
g.STATE.cvars["statusTextPercentage"] = "1"
frame.scripts.OnEvent(frame, "CVAR_UPDATE")
print("texte en pourcentage : vie=%s" % frame.healthText.text)
# 13. icone de chef de groupe
g.STATE.leader = True
frame.scripts.OnEvent(frame, "PARTY_LEADER_CHANGED")
avec = frame.leaderIcon.shown
g.STATE.leader = False
frame.scripts.OnEvent(frame, "PARTY_LEADER_CHANGED")
g.SlashCmdList["FOREVERUI"]("debug")
print("icone de chef : visible quand chef=%s | masquee sinon=%s" % (
avec, not frame.leaderIcon.shown))
# ---------------------------------------------------------- accessoires
extras = g.ForeverUI.PlayerFrameExtras
g.STATE.pvp = True
extras.updatePvP()
print("icone PvP (Alliance) : cercle=%s icone=%s" % (
extras.pvpCircle.shown, extras.pvpIcon.shown))
assert extras.pvpCircle.shown and extras.pvpIcon.shown, "l'icone PvP ne s'affiche pas"
g.STATE.pvp = False
extras.updatePvP()
print("PvP desactive : cercle=%s" % extras.pvpCircle.shown)
g.STATE.raidMembers, g.STATE.name = 10, "Papota"
extras.updateGroup()
print("indicateur de groupe : visible=%s texte=%s" % (
extras.groupIndicator.shown, extras.groupText.text))
g.STATE.raidMembers = 0
extras.updateGroup()
print("hors raid : visible=%s" % extras.groupIndicator.shown)
g.STATE.partialPlayTime = True
extras.updatePlayTime()
print("temps de jeu fatigue : visible=%s" % extras.playTime.shown)
g.STATE.partialPlayTime = False
extras.updatePlayTime()
print("temps de jeu normal : visible=%s" % extras.playTime.shown)
# ---------------------------------------------------------------- runes
runes = g.ForeverUI.RuneButtons
nb = sum(1 for _ in runes.values())
ancien = g.RuneFrame
print("ancien cadre de runes : visible=%s | neutralise=%s | OnShow accroche=%s" % (
ancien.shown, ancien.foreverSuppressed, ancien.hooks is not None))
assert not ancien.shown, "l'ancien cadre de runes reste affiche sous le notre"
ancien.Show(ancien) # pas d'appel a deux points en Python
ancien.hooks.OnShow(ancien)
print(" s'il se reaffiche : remasque=%s" % (not ancien.shown))
assert not ancien.shown, "l'ancien cadre revient des qu'il se reaffiche"
print("runes construites : %d" % nb)
assert nb == 6, "il faut six runes"
bouton = runes[1]
def alphas():
return dict(fond_actif=bouton.bgActive.alpha, fond_eteint=bouton.bgInactive.alpha,
crane_actif=bouton.runeActive.alpha, crane_eteint=bouton.runeInactive.alpha,
degrade=bouton.runeGrad.alpha, traits=bouton.runeLines.alpha)
g.STATE.runeReady = True
g.ForeverUI.RuneBar.scripts.OnEvent(g.ForeverUI.RuneBar, "RUNE_POWER_UPDATE", 1)
pret = alphas()
print("rune prete : %s" % pret)
assert pret["fond_actif"] == 1 and pret["crane_actif"] == 1, "la rune prete n'est pas allumee"
assert pret["fond_eteint"] == 0 and pret["crane_eteint"] == 0, "les calques eteints restent visibles"
assert pret["degrade"] == 0 and pret["traits"] == 0, "un calque de recharge reste allume quand la rune est prete"
g.STATE.runeReady = False
g.ForeverUI.RuneBar.scripts.OnEvent(g.ForeverUI.RuneBar, "RUNE_POWER_UPDATE", 1)
recharge = alphas()
print("rune en recharge : %s | minuterie=%s" % (recharge, bouton.cooldown.timer is not None))
assert recharge["fond_actif"] == 0 and recharge["crane_actif"] == 0, "la rune reste allumee en recharge"
assert abs(recharge["crane_eteint"] - 0.4) < 1e-6, "le crane eteint doit etre a 0,4"
assert abs(recharge["degrade"] - 0.3) < 1e-6 and abs(recharge["traits"] - 0.3) < 1e-6, "degrade et traits doivent etre a 0,3 pendant la recharge"
# aucun calque additif : la source les declare tous en BLEND
for nom in ("bgActive", "bgInactive", "runeActive", "runeInactive", "runeGrad", "runeLines", "shadow"):
assert bouton[nom].blend is None, "le calque %s est en melange additif" % nom
print("melanges : aucun calque additif, conforme au XML")
g.STATE.runeReady = True
g.ForeverUI.RuneBar.scripts.OnEvent(g.ForeverUI.RuneBar, "RUNE_POWER_UPDATE", 1)
print("art du cadre joueur : %s" % frame.art.texture)
# ------------------------------------------------------ cadre de cible
cible = g.ForeverUITargetFrame
print("cadre de cible : surveille par le client=%s | systeme enregistre=%s" % (
cible.unitWatch, g.ForeverUI.Layout.systems["targetframe"] is not None))
g.STATE.hasTarget = True
g.STATE.health, g.STATE.healthMax = 80, 100
cible.scripts.OnEvent(cible, "PLAYER_TARGET_CHANGED")
print(" nom=%s niveau=%s vie=%.0f%% (largeur %.0f)" % (
cible.nameText.text, cible.levelText.text,
(cible.healthFill.width or 0) / 126.0 * 100, cible.healthFill.width or 0))
assert cible.healthFill.shown, "la barre de vie de la cible est vide"
couleur = cible.reputation.vertex
print(" bandeau de reputation : (%.2f, %.2f, %.2f) -- c'est lui qui porte la reaction" % (
couleur[1], couleur[2], couleur[3]))
assert (couleur[1], couleur[2], couleur[3]) == (1.0, 0.0, 0.0), "le bandeau ne prend pas la couleur de selection"
g.STATE.tapped, g.STATE.tappedByPlayer = True, False
cible.scripts.OnEvent(cible, "UNIT_FACTION", "target")
gris = cible.reputation.vertex
portrait_gris = cible.portrait.vertex
print(" cible verrouillee : bandeau=(%.2f, %.2f, %.2f) portrait=(%.2f, %.2f, %.2f)" % (
gris[1], gris[2], gris[3], portrait_gris[1], portrait_gris[2], portrait_gris[3]))
assert gris[1] == 0.5 and portrait_gris[1] == 0.5, "rien ne grise quand la cible est verrouillee"
g.STATE.tapped = False
cible.scripts.OnEvent(cible, "UNIT_FACTION", "target")
g.STATE.classification = "minus"
cible.scripts.OnEvent(cible, "UNIT_CLASSIFICATION_CHANGED", "target")
art_minus = tuple(round(cible.art.texcoord[i], 6) for i in (1, 2, 3, 4))
g.STATE.classification = "rare"
cible.scripts.OnEvent(cible, "UNIT_CLASSIFICATION_CHANGED", "target")
art_rare = tuple(round(cible.art.texcoord[i], 6) for i in (1, 2, 3, 4))
g.STATE.classification = "normal"
cible.scripts.OnEvent(cible, "UNIT_CLASSIFICATION_CHANGED", "target")
art_normal = tuple(round(cible.art.texcoord[i], 6) for i in (1, 2, 3, 4))
# l'elite ne change pas le cadre mais l'anneau du portrait
anneaux = {}
for classe in ("normal", "elite", "rareelite", "worldboss", "rare"):
g.STATE.classification = classe
cible.scripts.OnEvent(cible, "UNIT_CLASSIFICATION_CHANGED", "target")
anneaux[classe] = (cible.classRing.shown,
tuple(round(cible.classRing.texcoord[i], 6) for i in (1, 2, 3, 4))
if cible.classRing.texcoord else None)
print(" anneau : ordinaire=%s elite=%s rare elite=%s boss=%s" % (
anneaux["normal"][0], anneaux["elite"][0], anneaux["rareelite"][0], anneaux["worldboss"][0]))
assert not anneaux["normal"][0], "un monstre ordinaire ne doit pas porter d'anneau"
assert anneaux["elite"][0] and anneaux["worldboss"][0], "l'elite et le boss doivent porter un anneau"
assert anneaux["elite"][1] != anneaux["rareelite"][1], "elite et rare elite portent le meme anneau"
assert anneaux["elite"][1] != anneaux["worldboss"][1], "elite et boss portent le meme anneau"
g.STATE.classification = "normal"
cible.scripts.OnEvent(cible, "UNIT_CLASSIFICATION_CHANGED", "target")
print(" art par classification : negligeable v=%.4f | rare v=%.4f | ordinaire v=%.4f" % (
art_minus[2], art_rare[2], art_normal[2]))
assert len({art_minus, art_rare, art_normal}) == 3, \
"les trois classifications doivent donner trois arts differents"
# la geometrie des barres suit la classification, comme CheckClassification
g.STATE.classification = "minus"
cible.scripts.OnEvent(cible, "PLAYER_TARGET_CHANGED")
print(" creature negligeable : vie a 80%% = %.0f px sur 125, ressource visible=%s" % (
cible.healthFill.width or 0, cible.powerFill.shown))
assert not cible.powerFill.shown, "la ressource doit disparaitre sur une creature negligeable"
g.STATE.classification = "normal"
cible.scripts.OnEvent(cible, "PLAYER_TARGET_CHANGED")
print(" cible ordinaire : vie a 80%% = %.0f px sur 126, ressource visible=%s" % (
cible.healthFill.width or 0, cible.powerFill.shown))
assert cible.powerFill.shown, "la ressource doit revenir sur une cible ordinaire"
# ------------------------------------------------ barre d'incantation
barre = g.ForeverUICastBar
g.STATE.time = 100.0
g.STATE.casting = True
g.STATE.castStart, g.STATE.castEnd = 100.0, 102.0
barre.scripts.OnEvent(barre, "UNIT_SPELLCAST_START", "player")
print("incantation lancee : visible=%s sort=%s" % (barre.shown, barre.spellText.text))
assert barre.shown, "la barre d'incantation ne s'affiche pas"
g.STATE.time = 101.0 # moitie du sort
barre.scripts.OnUpdate(barre, 0.1)
moitie = barre.fill.width
print("a mi-parcours : largeur=%.1f sur 209 | reste=%s" % (moitie, barre.timeText.text))
assert 95 < moitie < 115, "le remplissage ne suit pas la progression"
g.STATE.time = 102.5 # depassement : la barre se ferme
barre.scripts.OnUpdate(barre, 0.1)
print("sort termine : visible=%s" % barre.shown)
# canalisation : le remplissage descend
g.STATE.casting, g.STATE.channeling = False, True
g.STATE.castStart, g.STATE.castEnd = 200.0, 204.0
g.STATE.time = 200.0
barre.scripts.OnEvent(barre, "UNIT_SPELLCAST_CHANNEL_START", "player")
g.STATE.time = 201.0
barre.scripts.OnUpdate(barre, 0.1)
debut = barre.fill.width
g.STATE.time = 203.0
barre.scripts.OnUpdate(barre, 0.1)
fin = barre.fill.width
print("canalisation : largeur %.1f puis %.1f (elle doit descendre)" % (debut, fin))
assert fin < debut, "une canalisation doit se vider"
barre.scripts.OnEvent(barre, "UNIT_SPELLCAST_CHANNEL_STOP", "player")
g.STATE.channeling = False
print("canalisation arretee : visible=%s" % barre.shown)
g.STATE.casting = True
g.STATE.castStart, g.STATE.castEnd = 300.0, 302.0
g.STATE.time = 300.0
barre.scripts.OnEvent(barre, "UNIT_SPELLCAST_START", "player")
barre.scripts.OnEvent(barre, "UNIT_SPELLCAST_INTERRUPTED", "player")
print("sort interrompu : visible=%s texte=%s" % (barre.shown, barre.spellText.text))
g.STATE.time = 302.0
barre.scripts.OnUpdate(barre, 0.1)
print("apres la pause : visible=%s" % barre.shown)
g.STATE.casting = False
# --------------------------------- barres d'experience et de reputation
xp = g.ForeverUIExperienceBar
rep = g.ForeverUIReputationBar
g.STATE.level = 40
g.ForeverUI.StatusBarsUpdate()
print("experience a 50%% : visible=%s acquis=%.0f repose=%.0f texte=%s" % (
xp.shown, xp.remplissage.width or 0, xp.repos.width or 0, xp.texte.text))
assert xp.remplissage.shown and xp.repos.shown, "les remplissages d'experience sont absents"
assert xp.repos.width > xp.remplissage.width, "la part reposee doit depasser l'acquis"
print("reputation amicale : visible=%s rempli=%.0f sur %.0f texte=%s" % (
rep.shown, rep.remplissage.width or 0, rep.width, rep.texte.text))
assert rep.shown and rep.remplissage.shown, "la barre de reputation est vide"
assert abs(rep.remplissage.width - rep.width * 0.5) < 0.01, "la reputation n'est pas a moitie"
# les huit teintes vivent sur la MEME feuille : c'est le rectangle lu qui
# change, pas le fichier.
vert = tuple(rep.remplissage.texcoord.values())
g.STATE.faction_attitude = 3
g.ForeverUI.StatusBarsUpdate()
orange = tuple(rep.remplissage.texcoord.values())
print("attitude inamicale : rectangle lu different = %s" % (orange != vert))
assert orange != vert, "la teinte ne suit pas l'attitude"
g.STATE.faction_suivie = None
g.ForeverUI.StatusBarsUpdate()
print("aucune faction suivie : visible=%s (la barre doit disparaitre)" % rep.shown)
assert not rep.shown, "la barre de reputation reste sans faction suivie"
g.STATE.faction_suivie = "Les Fils de Hodir"
g.STATE.faction_attitude = 5
g.ForeverUI.StatusBarsUpdate()
# les deux barres vont d'un embout a l'autre, et se touchent
rangee = g.ForeverUI.BottomRow
largeur = rangee.droite - rangee.gauche
print("rangee : %.1f -> %.1f (%.1f de large), haut %.0f" % (
rangee.gauche, rangee.droite, largeur, rangee.haut))
assert abs(xp.width - largeur) < 0.01 and abs(rep.width - largeur) < 0.01, (
"les barres ne font pas la longueur de la rangee")
# la rangee va du bord gauche de la barre d'action au bord droit des sacs
barre = g.ForeverUI.Layout.systems["actionbar"].defaults
sacsdef = g.ForeverUI.Layout.systems["sacs"].defaults
gauche_barre = barre.x - g.ForeverUIActionBarHolder.width
droite_sacs = sacsdef.x + g.ForeverUIBagsBar.width
print("bloc de gauche a %.1f, bloc de droite a %.1f" % (gauche_barre, droite_sacs))
assert abs(rangee.gauche - gauche_barre) < 0.01, "la rangee ne part pas du bord de la barre"
assert abs(rangee.droite - droite_sacs) < 0.01, "la rangee ne finit pas au bord des sacs"
dxp = g.ForeverUI.Layout.systems["experiencebar"].defaults
drep = g.ForeverUI.Layout.systems["reputationbar"].defaults
print("experience posee a y=%.0f, reputation a y=%.0f (13 d'ecart, elles se touchent)" % (
dxp.y, drep.y))
assert dxp.y == rangee.haut, "l'experience ne pose pas sur la rangee"
assert drep.y - dxp.y == 13, "la reputation n'est pas juste au-dessus de l'experience"
assert abs(dxp.x - (rangee.gauche + rangee.droite) / 2) < 0.01
g.STATE.level = 80
g.ForeverUI.StatusBarsUpdate()
print("niveau maximum : visible=%s (la barre doit disparaitre)" % xp.shown)
assert not xp.shown, "la barre d'experience reste au niveau maximum"
assert rep.shown, "la reputation doit rester quand l'experience disparait"
g.STATE.level = 80
# --------------------------------------------------- barre d'action
bouton = g.ActionButton1
icone = g["ActionButton1Icon"]
print("bouton d'action : %d x %d (45 attendu, 36 sur 3.3.5 d'origine)" % (
bouton.width, bouton.height))
assert bouton.width == 45 and bouton.height == 45, "le bouton ne fait pas la taille de camelot"
assert icone.allPoints, "l'icone ne remplit pas le bouton"
assert icone.texcoord[1] == 0 and icone.texcoord[2] == 1, "l'icone est rognee alors que la source la laisse entiere"
for nom, region, add in (("cadre", bouton.foreverFrame, False), ("enfonce", bouton._pushed, False),
("survol", bouton._highlight, False), ("coche", bouton._checked, True)):
assert region.width == 46 and region.height == 45, "l'etat %s n'est pas en 46 x 45" % nom
attendu = "ADD" if add else "BLEND"
assert region.blend == attendu, "l'etat %s devrait etre en %s" % (nom, attendu)
print("quatre etats : 46 x 45, coche en ADD, le reste en BLEND")
# la texture normale du client doit rester muette : il la reecrit sans cesse
print("texture normale du client : alpha=%.2f (0 attendu)" % bouton._normal.alpha)
assert bouton._normal.alpha == 0, "la texture normale du client n'est pas neutralisee"
g.ActionButton_Update(bouton)
if g.HOOKS["ActionButton_Update"]:
g.HOOKS["ActionButton_Update"](bouton)
print("apres une mise a jour du client : alpha=%.2f" % bouton._normal.alpha)
assert bouton._normal.alpha == 0, "l'habillage ne survit pas a ActionButton_Update"
# L'EMPLACEMENT VIDE RESTE AFFICHE. 3.3.5 ne masque pas le fond mais LE
# BOUTON : ActionButton_HideGrid le cache des que showgrid retombe a
# zero, et ce compteur ne monte que le temps d'un glisser-deposer.
print("fond d'emplacement : %s + %s" % (
bouton.foreverBackground is not None, bouton.foreverSlot is not None))
assert bouton.foreverBackground is not None and bouton.foreverSlot is not None
bouton.Hide(bouton)
bouton.showgrid = 0
g.HOOKS["ActionButton_HideGrid"](bouton)
print("apres ActionButton_HideGrid : visible=%s, showgrid=%s" % (
bouton.shown, bouton.showgrid))
assert bouton.shown, "le bouton vide doit rester affiche"
assert (bouton.showgrid or 0) >= 1, "le compteur doit rester a 1"
# en combat on ne touche pas a un cadre securise
g.STATE.inLockdown = True
bouton.Hide(bouton)
g.HOOKS["ActionButton_HideGrid"](bouton)
print("meme chose en combat : visible=%s (on n'y touche pas)" % bouton.shown)
assert not bouton.shown, "afficher un cadre securise en combat est interdit"
g.STATE.inLockdown = False
g.HOOKS["ActionButton_HideGrid"](bouton)
assert bouton.shown, "il doit revenir a la sortie du combat"
# aucun bord de barre en pavage : cela etalerait la feuille d'atlas entiere
for piece in (g.ForeverUI.ActionBarBorder,):
pass
print("bords de barre : etires, jamais paves")
pas = None
for i in (1, 2):
b = g["ActionButton" + str(i)]
pt = b.points[len(list(b.points.items()))]
if i == 1:
depart = pt[4]
else:
pas = pt[4] - depart
print("pas entre boutons : %s (47 attendu : 45 + 2 de marge)" % pas)
assert pas == 47, "l'espacement ne suit pas minButtonPadding = 2"
hk = g["ActionButton1HotKey"]
ct = g["ActionButton1Count"]
nm = g["ActionButton1Name"]
print("raccourci %sx%s %s | quantite %s | nom %sx%s %s" % (
hk.width, hk.height, hk.font, ct.font, nm.width, nm.height, nm.font))
assert hk.width == 32 and hk.height == 10 and hk.justify == "RIGHT"
assert nm.width == 36 and nm.height == 10
cd = g["ActionButton1Cooldown"]
coins = [cd.points[i] for i in (1, 2)]
print("recharge : %s %s puis %s %s (retrait de 3 px)" % (
coins[0][1], coins[0][4], coins[1][1], coins[1][4]))
assert coins[0][4] == 3 and coins[1][4] == -3, "la recharge n'est pas en retrait de 3 px"
print("embouts : gauche %dx%d, droite %dx%d (154 x 95 attendu)" % (
g.ForeverUI.ActionBarEndCaps.left.width, g.ForeverUI.ActionBarEndCaps.left.height,
g.ForeverUI.ActionBarEndCaps.right.width, g.ForeverUI.ActionBarEndCaps.right.height))
assert g.ForeverUI.ActionBarEndCaps.left.width == 154, "embout gauche a la mauvaise taille"
for nom in ("MainMenuBarTexture0", "MainMenuXPBarTexture0", "MainMenuMaxLevelBar0",
"MainMenuBarLeftEndCap", "MainMenuBarRightEndCap", "MainMenuBarExpText"):
assert g[nom].alpha == 0, "%s reste visible sous la nouvelle barre" % nom
print("anciens morceaux effaces : corps, barre d'xp, version niveau max, embouts, texte")
print("fleches de page habillees : %s" % (g.ActionBarUpButton.foreverSkinned == True))
page = g.ForeverUIActionBarPage
ph = page.points[1]
ph_haut = g.ActionBarUpButton.points[1]
print("bloc de pagination : %d x %d, %s sur %s (%s, %s) | fleche haut centree a y=%s" % (
page.width, page.height, ph[1], ph[3], ph[4], ph[5], ph_haut[5]))
assert page.width == 17 and page.height == 34
assert ph[4] == -4 and ph[5] == 9, "le bloc de pagination n est pas a gauche de la barre"
assert ph_haut[5] == 10 and g.ActionBarDownButton.points[1][5] == -10
# LA BARRE BONUS -- celle qui remplace la barre de sorts quand le joueur
# change de posture -- prend exactement la meme place, donc suit aussi la
# position que le joueur a choisie pour le porteur.
principal = g.ActionButton3.points[len(list(g.ActionButton3.points.values()))]
bonus = g.BonusActionButton3.points[len(list(g.BonusActionButton3.points.values()))]
print("barre bonus : bouton 3 en %s (%s, %s) | barre de sorts en %s (%s, %s)" % (
bonus[1], bonus[4], bonus[5], principal[1], principal[4], principal[5]))
assert (bonus[1], bonus[4], bonus[5]) == (principal[1], principal[4], principal[5]), "la barre bonus doit se poser exactement sur la barre de sorts"
assert principal[2].name == "ForeverUIActionBarHolder", "la barre de sorts s ancre au porteur"
assert bonus[2].name == "ForeverUIBonusSlide", "la barre bonus s ancre a la glissiere, qui est posee sur le porteur"
assert g.BonusActionButton1.width == 45, "ses boutons ont la taille des autres"
# LE GLISSEMENT. La glissiere est un cadre a nous : la deplacer est
# permis en combat, alors que deplacer un bouton securise ne l'est pas.
glissiere = g.ForeverUI.ActionBarSlide
g.BonusActionBarFrame.Hide(g.BonusActionBarFrame)
glissiere.scripts.OnUpdate(glissiere, 0.05)
g.BonusActionBarFrame.Show(g.BonusActionBarFrame)
glissiere.scripts.OnUpdate(glissiere, 0.0)
depart = glissiere.points[1][5]
print(" glissement : depart a %.0f (une hauteur de bouton sous le porteur)" % depart)
assert depart == -45, "la barre doit partir d une hauteur de bouton plus bas"
glissiere.scripts.OnUpdate(glissiere, 0.1)
milieu = glissiere.points[1][5]
glissiere.scripts.OnUpdate(glissiere, 0.2)
fin = glissiere.points[1][5]
print(" a mi-course %.1f, puis %.1f (au repos sur le porteur)" % (milieu, fin))
assert depart < milieu < fin, "elle doit remonter progressivement"
assert fin == 0, "elle doit finir exactement sur le porteur"
# et elle ne bouge plus tant que la barre reste affichee
glissiere.scripts.OnUpdate(glissiere, 0.5)
assert glissiere.points[1][5] == 0, "au repos, la glissiere ne bouge plus"
# LE RETRAIT. Le client pose mode = "hide", garde la barre affichee le
# temps du mouvement, puis la masque : la glissiere redescend.
lua.execute('BonusActionBarFrame.mode = "hide"')
glissiere.scripts.OnUpdate(glissiere, 0.1)
descente = glissiere.points[1][5]
print(" retrait : a mi-course %.1f" % descente)
assert -45 < descente < 0, "elle doit redescendre progressivement"
glissiere.scripts.OnUpdate(glissiere, 0.2)
print(" puis %.0f (sortie)" % glissiere.points[1][5])
assert glissiere.points[1][5] == -45, "elle doit finir une hauteur de bouton plus bas"
# le client la masque enfin : elle est prete a remonter
lua.execute('BonusActionBarFrame.mode = "none"')
g.BonusActionBarFrame.Hide(g.BonusActionBarFrame)
glissiere.scripts.OnUpdate(glissiere, 0.05)
g.BonusActionBarFrame.Show(g.BonusActionBarFrame)
glissiere.scripts.OnUpdate(glissiere, 0.0)
print(" a la posture suivante : depart a %.0f" % glissiere.points[1][5])
assert glissiere.points[1][5] == -45, "le mouvement suivant repart d en bas"
print(" art d'epoque de la barre bonus : %s et %s" % (
g.BonusActionBarTexture0.alpha, g.BonusActionBarTexture1.alpha))
assert g.BonusActionBarTexture0.alpha == 0 and g.BonusActionBarTexture1.alpha == 0, "l art glissant d epoque doit disparaitre"
# le porteur deplace : la barre bonus suit, puisqu'elle y est ancree.
# On rend ensuite EXACTEMENT les valeurs d'avant -- la rangee du bas les
# recalcule au chargement, elles ne valent pas celles de Register.
avant = g.ForeverUI.Layout.systems["actionbar"].defaults
garde = (avant.point, avant.relativePoint, avant.x, avant.y)
g.ForeverUI.Layout.SetDefaults("actionbar", "BOTTOMRIGHT", "BOTTOM", -100, 200)
g.HOOKS["ActionButton_Update"](g.ActionButton1)
apres = g.BonusActionButton3.points[len(list(g.BonusActionButton3.points.values()))]
print(" porteur deplace : la barre bonus reste ancree a lui (%s)" % apres[1])
assert apres[2].name == "ForeverUIBonusSlide", "elle doit rester ancree a la glissiere, pas a l ecran"
g.ForeverUI.Layout.SetDefaults("actionbar", garde[0], garde[1], garde[2], garde[3])
print("ancien fond du client efface : %s | porteur enregistre : %s" % (
g["MainMenuBarTexture0"].alpha == 0,
g.ForeverUI.Layout.systems["actionbar"] is not None))
# ------------------------------------------------ barre des postures
porteur = g.ForeverUIStanceBarHolder
b1 = g.ShapeshiftButton1
print("posture : bouton %d x %d (SmallActionButtonTemplate : 30)" % (b1.width, b1.height))
assert b1.width == 30 and b1.height == 30, "le bouton de posture fait 30"
p2 = g.ShapeshiftButton2.points[len(list(g.ShapeshiftButton2.points.values()))]
print(" pas entre boutons : %d (32 attendu : 30 + minButtonPadding 2)" % p2[4])
assert p2[4] == 32, "le pas n est pas 30 + 2"
print(" porteur : %d x %d pour %d formes, visible=%s" % (
porteur.width, porteur.height, g.GetNumShapeshiftForms(), porteur.shown))
assert porteur.width == 3 * 30 + 2 * 2, "la barre ne fait pas la largeur de ses formes"
assert porteur.shown, "la barre doit se montrer des qu il y a une forme"
cadre = b1.foreverCadre
pc = cadre.points[1]
print(" cadre : %s %.1f x %.1f en %s, ancre %s (centre sur le bouton)" % (
cadre.texture and "pose" or "absent", cadre.width, cadre.height, cadre.layer, pc[1]))
assert cadre.width == 35 and cadre.height == 35, "UpdateButtonArt ramene le petit cadre a 35"
assert cadre.layer == "OVERLAY" and pc[1] == "CENTER", "un cadre de 35 sur un bouton de 30 doit etre centre, pas ancre TOPLEFT"
assert b1._normal.alpha == 0, "la texture normale du client doit rester muette"
survol, coche = b1._highlight, b1._checked
print(" survol %.1f x %.1f en %s | coche %.1f x %.1f en %s" % (
survol.width, survol.height, survol.blend,
coche.width, coche.height, coche.blend))
for region, nom in ((survol, "survol"), (coche, "coche")):
assert abs(region.width - 31.6) < 0.01 and abs(region.height - 30.9) < 0.01, \
"SmallActionButtonMixin pose %s en 31,6 x 30,9" % nom
assert coche.blend == "ADD", "le coche est le survol en melange ADD"
assert survol.blend == "BLEND"
print(" emplacement vide : fond=%s + art=%s" % (
b1.foreverFond is not None, b1.foreverEmplacement is not None))
assert b1.foreverFond is not None and b1.foreverEmplacement is not None
raccourci = g["ShapeshiftButton1HotKey"].points[1]
quantite = g["ShapeshiftButton1Count"].points[1]
print(" raccourci %s (%s, %s) | quantite %s (%s, %s)" % (
raccourci[1], raccourci[4], raccourci[5], quantite[1], quantite[4], quantite[5]))
assert (raccourci[1], raccourci[4], raccourci[5]) == ("TOPRIGHT", -3, -4)
assert (quantite[1], quantite[4], quantite[5]) == ("BOTTOMRIGHT", -3, 1)
recharge = g["ShapeshiftButton1Cooldown"]
r1, r2 = recharge.points[1], recharge.points[2]
print(" recharge : %s (%s, %s) et %s (%s, %s)" % (
r1[1], r1[4], r1[5], r2[1], r2[4], r2[5]))
assert (r1[4], r1[5]) == (1.7, -1.7) and (r2[4], r2[5]) == (-1, 1), \
"la recharge n est pas en retrait de l icone comme dans la source"
# LA SIGNATURE DE 3.3.5. GetShapeshiftFormInfo rend ici
# (texture, NOM, active, lancable) ; lire la source au mot pres
# prendrait le nom pour l etat actif.
i1 = g.ShapeshiftButton1.foreverIcone
i2 = g.ShapeshiftButton2.foreverIcone
i3 = g.ShapeshiftButton3.foreverIcone
print(" formes : 1 cochee=%s | 2 cochee=%s (active) | 3 teinte %s (non lancable)" % (
g.ShapeshiftButton1.checked, g.ShapeshiftButton2.checked,
[round(v, 2) for v in i3.vertex.values()] if i3.vertex else None))
assert i1.texture == "forme_ours", "l icone ne prend pas la texture de la forme"
assert not g.ShapeshiftButton1.checked and g.ShapeshiftButton2.checked, \
"c est la forme ACTIVE qui est cochee"
assert [round(v, 2) for v in i3.vertex.values()] == [0.4, 0.4, 0.4], \
"une forme non lancable est grisee a 0,4"
assert [round(v, 2) for v in i2.vertex.values()] == [1, 1, 1], \
"une forme lancable reste blanche"
# Sans forme, la barre disparait -- StanceBarMixin:ShouldShow.
lua.execute("FORMES_GARDEES = FORMES; FORMES = {}")
g.ForeverUI.StanceBar.Apply()
print(" sans aucune forme : visible=%s (masquee attendue)" % porteur.shown)
assert not porteur.shown, "la barre doit disparaitre quand il n y a aucune forme"
lua.execute("FORMES = FORMES_GARDEES")
g.ForeverUI.StanceBar.Apply()
assert porteur.shown
# En combat on ne touche a rien : cadres securises.
g.STATE.inLockdown = True
largeur = porteur.width
lua.execute("table.insert(FORMES, { texture = 'x', nom = 'x', active = false, lancable = true })")
g.ForeverUI.StanceBar.Apply()
print(" une forme de plus en combat : largeur %d (inchangee)" % porteur.width)
assert porteur.width == largeur, "rien ne doit bouger en combat"
g.STATE.inLockdown = False
g.ForeverUI.StanceBar.Apply()
print(" apres le combat : largeur %d pour 4 formes" % porteur.width)
assert porteur.width == 4 * 30 + 3 * 2, "la barre doit se refaire a la sortie du combat"
lua.execute("table.remove(FORMES)")
g.ForeverUI.StanceBar.Apply()
print(" ancien art efface : %s | porteur enregistre : %s" % (
g.ShapeshiftBarLeft.alpha == 0,
g.ForeverUI.Layout.systems["postures"] is not None))
assert g.ShapeshiftBarLeft.alpha == 0, "l art d epoque de la barre doit s effacer"
# -------------------------------------------- barre du familier
pet = g.ForeverUIPetBarHolder
p1 = g.PetActionButton1
print("familier : bouton %d x %d, porteur %d x %d, visible=%s" % (
p1.width, p1.height, pet.width, pet.height, pet.shown))
assert p1.width == 30 and p1.height == 30, "le bouton du familier fait 30"
assert pet.width == 10 * 30 + 9 * 2, "dix emplacements au pas de 32"
assert pet.shown, "la barre se montre quand le familier a la sienne"
pp = g.PetActionButton2.points[len(list(g.PetActionButton2.points.values()))]
assert pp[4] == 32, "le pas n est pas 30 + 2"
cadre = p1.foreverCadre
print(" cadre %.1f x %.1f en %s ancre %s | survol %.1f x %.1f | coche en %s" % (
cadre.width, cadre.height, cadre.layer, cadre.points[1][1],
p1._highlight.width, p1._highlight.height, p1._checked.blend))
assert cadre.width == 35 and cadre.points[1][1] == "CENTER"
assert abs(p1._highlight.width - 31.6) < 0.01 and p1._checked.blend == "ADD"
# UpdateButtonState : l attaque est active, cochee, et son coche tombe
# a 0,5 -- la source le dit en toutes lettres.
print(" attaque : cochee=%s, alpha du coche %.2f (0,5 attendu)" % (
p1.checked, p1._checked.alpha))
assert p1.checked, "l action active doit etre cochee"
assert abs(p1._checked.alpha - 0.5) < 0.01, "le coche de l attaque est a 0,5"
# LA SIGNATURE DE 3.3.5 : (nom, SOUS-TEXTE, texture, ...). Si le
# sous-texte etait pris pour la texture, l icone du rang 5 porterait
# "Rang 5".
i2 = g.PetActionButton2.foreverIcone
print(" icone 2 : %s (la texture, pas le sous-texte)" % i2.texture)
assert i2.texture == "icone_morsure", "le sous-texte a ete pris pour la texture"
# isToken : le nom et la texture sont des cles de variables globales.
i3 = g.PetActionButton3.foreverIcone
print(" icone 3 : %s (isToken : la cle est resolue)" % i3.texture)
assert i3.texture == "icone_passif_resolue", "une cle de token doit etre resolue"
assert [round(v, 2) for v in i3.vertex.values()] == [0.4, 0.4, 0.4], \
"une action inutilisable est grisee a 0,4"
# L'AUTOLANCEMENT RESTE CELUI DU CLIENT. AutoCastTemplates n'existe que
# dans mainline/, et seuls camelot/ et shared/ font foi : camelot garde
# ici sa bordure scintillante et ses etincelles, qu'il allume lui-meme.
# On ne fait que les mettre a l'echelle du bouton, 30 au lieu de 36.
bordure = g["PetActionButton1AutoCastable"]
etincelles = g["PetActionButton1Shine"]
print(" autolancement du client : bordure %.1f (58 x 30/36) | etincelles a l echelle %.3f" % (
bordure.width, etincelles.GetScale(etincelles)))
assert abs(bordure.width - 58 * 30 / 36.0) < 0.01, "la bordure d origine doit suivre l echelle du bouton"
assert abs(etincelles.GetScale(etincelles) - 30 / 36.0) < 0.001, "les etincelles se mettent a l echelle, elles ne se redimensionnent pas"
assert bordure.alpha != 0, "la bordure du client ne doit plus etre effacee"
assert g.PetActionButton1.foreverAnneau is None, "l anneau maison est retire : camelot garde ici le comportement d origine"
# sans familier, la barre disparait
lua.execute("PET_A_UNE_BARRE = false")
g.ForeverUI.PetBar.Apply()
print(" sans familier : visible=%s (masquee attendue)" % pet.shown)
assert not pet.shown, "la barre doit disparaitre sans familier"
lua.execute("PET_A_UNE_BARRE = true")
g.ForeverUI.PetBar.Apply()
assert pet.shown
assert g.ForeverUI.Layout.systems["familier"] is not None
# LA PLACE : juste au-dessus de la reputation, et a droite des postures
# quand elles sont la, separee d'elles par deux icones.
postures = g.ForeverUIStanceBarHolder
pp = pet.points[len(list(pet.points.values()))]
attendu = -587.5 + postures.width + 2 * 30
print(" place : %s (%.1f, %.1f) | postures larges de %d -> %.1f attendu" % (
pp[1], pp[4], pp[5], postures.width, attendu))
assert pp[5] == 84, "la barre doit etre juste au-dessus de la reputation"
assert abs(pp[4] - attendu) < 0.01, "elle doit se decaler de deux icones apres les postures"
# sans posture, elle revient sur le bord gauche
lua.execute("FORMES_2 = FORMES; FORMES = {}")
g.ForeverUI.StanceBar.Apply()
g.ForeverUI.PetBar.Apply()
pp = pet.points[len(list(pet.points.values()))]
print(" sans posture : x=%.1f (-587,5 attendu)" % pp[4])
assert abs(pp[4] + 587.5) < 0.01, "sans postures elle reprend le bord gauche"
lua.execute("FORMES = FORMES_2")
g.ForeverUI.StanceBar.Apply()
g.ForeverUI.PetBar.Apply()
# L'art d'epoque de la barre s'efface -- on balaie les regions du cadre
# plutot que de se fier aux noms.
print(" art d'epoque : morceau 0 alpha=%s, morceau 1 alpha=%s" % (
g.SlidingActionBarTexture0.alpha, g.SlidingActionBarTexture1.alpha))
assert g.SlidingActionBarTexture0.alpha == 0 and g.SlidingActionBarTexture1.alpha == 0, "l art d epoque de la barre du familier doit disparaitre"
tardive = g.PetActionBarFrame.CreateTexture(g.PetActionBarFrame, None, "ARTWORK")
g.ForeverUI.PetBar.Apply()
print(" une texture ajoutee apres coup : alpha=%s" % tardive.alpha)
assert tardive.alpha == 0, "le balayage doit aussi prendre ce qui arrive ensuite"
# ---------------------------------------- feuille du personnage
perso = g.CharacterFrame
gauche = g.ForeverUICharacterLeftPane
droit = g.ForeverUICharacterRightPane
print("feuille du personnage : %d x %d (631 x 484 attendu)" % (perso.width, perso.height))
assert perso.width == 631 and perso.height == 484, "CHARACTER_FRAME_WIDTH et _HEIGHT"
print(" volets : gauche %d, droit %d (398 + 233 = %d)" % (
gauche.width, droit.width, gauche.width + droit.width))
assert gauche.width == 398 and droit.width == 233
assert gauche.width + droit.width == perso.width, "les deux volets font la fenetre"
pg = gauche.points[1]
assert (pg[1], pg[4], pg[5]) == ("TOPLEFT", 0, -20), "le volet gauche part 20 sous le haut"
pd = droit.points[1]
assert pd[1] == "TOPLEFT" and pd[3] == "TOPRIGHT", \
"le volet droit s accroche a la droite du gauche"
assert g.CharacterFrameTopLeft.alpha == 0, "l art d epoque doit disparaitre"
assert g.PaperDollFrameTexture.alpha == 0, "l art d epoque des sous-cadres aussi : ce sont des cadres fils"
# L'ART DU PANNEAU PASSE AU-DESSUS DES VOLETS. Les volets sont des cadres
# fils : ils recouvrent toute region de leur parent. L'art vit donc dans
# un cadre fils de niveau superieur, comme le NineSlice de la source.
habillage = perso.foreverHabillage
print(" habillage : cadre fils de niveau +%d (volets a +%d)" % (
habillage.GetFrameLevel(habillage) - perso.GetFrameLevel(perso),
gauche.GetFrameLevel(gauche) - perso.GetFrameLevel(perso)))
assert habillage is not None, "l art doit vivre dans son propre cadre fils"
assert habillage.GetFrameLevel(habillage) > gauche.GetFrameLevel(gauche), "l art doit passer au-dessus des volets"
assert perso.foreverPanel.coinHautGaucheAtlas == "ui-frame-portraitmetal-cornertopleft", "PortraitFrameTemplate prend le grand anneau, pas celui des sacs"
# LE FOND RESTE AU DERNIER PLAN. Monte avec le metal dans le cadre fils,
# il recouvrait les volets : une region du cadre passe sous tous ses
# cadres fils, c'est la place qu'il lui faut.
fond = list(perso.foreverPanel.fond.values())[0]
print(" fond du panneau : porte par %s (le cadre, pas l habillage)" % (
fond.owner.name or "?"))
assert fond.owner.name == "CharacterFrame", "le fond doit rester sur le cadre, sinon il recouvre les volets"
assert perso.foreverPanel.coinHautGauche.owner.name != "CharacterFrame", "le metal, lui, vit dans le cadre fils"
# LE PORTRAIT : le balayage efface celui du client, on pose le notre.
portrait = perso.foreverPortrait
pp = portrait.points[1]
print(" portrait : %d x %d, %s sur %s (%s, %s), texture=%s" % (
portrait.width, portrait.height, pp[1], pp[3], pp[4], pp[5],
portrait.portraitOf))
assert portrait.width == 44, "44 : ses coins tombent juste sous le metal de l anneau"
assert pp[4] == 25 and pp[5] == -22.5, "il se centre sur le trou mesure de l anneau"
assert portrait.portraitOf == "player", "l anneau ne doit pas rester vide"
# Le fond du volet droit remplit son volet au lieu de s arreter a 383.
fondDroit = droit.regions[1]
print(" fond du volet droit : couvre tout le volet = %s" % (fondDroit.allPoints and True))
assert fondDroit.allPoints, "le fond du volet droit doit remplir son volet"
# Le separateur est en trois tranches : embouts a leur taille, milieu tire.
sep = droit.separateur
print(" separateur : trois tranches (embouts de %d)" % sep.haut.height)
assert sep.haut is not None and sep.milieu is not None and sep.bas is not None, "le separateur doit etre en trois tranches, ses embouts s etalaient"
assert sep.haut.height == 4 and sep.bas.height == 4
tete = g.CharacterHeadSlot
cou = g.CharacterNeckSlot
print(" emplacement : %d x %d (40) | ecart %d (6)" % (
tete.width, tete.height, -cou.points[1][5]))
assert tete.width == 40 and tete.height == 40, "PaperDollItemSlotButtonTemplate : 40"
pt = tete.points[1]
assert (pt[1], pt[4], pt[5]) == ("TOPLEFT", 24, -60), "la colonne gauche part de (24, -60)"
assert cou.points[1][5] == -6, "les emplacements sont espaces de 6"
mains = g.CharacterHandsSlot.points[1]
print(" colonne droite : %s (%s, %s)" % (mains[1], mains[4], mains[5]))
assert (mains[1], mains[4], mains[5]) == ("TOPRIGHT", -20, -60), \
"la colonne droite part de (-20, -60)"
arme = g.CharacterMainHandSlot.points[1]
distance = g.CharacterRangedSlot
munitions = g.CharacterAmmoSlot
print(" armes : principale %s (%s, %s) | distance %d | munitions a +%s" % (
arme[1], arme[4], arme[5], distance.width, munitions.points[1][4]))
assert (arme[1], arme[4], arme[5]) == ("BOTTOM", -60, 30), \
"l arme principale se pose au bas du volet gauche, a (-60, 30)"
assert distance.width == 27 and munitions.width == 27, "distance et munitions font 27"
assert munitions.points[1][4] == 19, "les munitions sont a 19 de la distance"
# LE TITRE ET LA FERMETURE, dans la barre du haut.
bande = perso.foreverBandeTitre
titre = perso.foreverTitre
b1, b2 = bande.points[1], bande.points[2]
print(" titre : \"%s\", bande de %s a %s, texte a %s" % (
titre.text, b1[4], b2[4], titre.points[1][5]))
assert titre.text == "Robert Polson", "le titre est le nom du joueur et son titre"
# Il ne suit PAS le sous-cadre affiche : characterFrameDisplayInfo y met
# REPUTATION, PVP et les autres ; ici c'est toujours le nom.
g.ReputationFrame.Show(g.ReputationFrame)
g.ForeverUI.CharacterSheet.Apply()
print(" panneau de reputation ouvert : titre toujours \"%s\"" % titre.text)
assert titre.text == "Robert Polson", "la barre du haut garde le nom, quel que soit le panneau"
g.ReputationFrame.Hide(g.ReputationFrame)
assert b1[4] == 58 and b2[4] == -24, "CharacterFrame n appelle pas SetTitleOffsets : les valeurs par defaut"
assert titre.points[1][5] == -5, "TitleText est a TOP (0, -5)"
assert titre.justify == "CENTER", "il se centre dans sa bande"
assert not g.CharacterNameText.shown, "le titre du client s efface"
assert bande.GetFrameLevel(bande) > habillage.GetFrameLevel(habillage), "le titre passe au-dessus du metal, comme TitleContainer a 510"
# LA BARRE DU HAUT DEPLACE LA FENETRE, et la place est retenue : le
# systeme de panneaux du client repose la fenetre a chaque ouverture.
print(" barre du haut : souris=%s, glissable=%s, cadre deplacable=%s" % (
bande.mouseEnabled, bande.scripts.OnDragStart is not None, perso.movable))
assert bande.mouseEnabled, "la barre doit prendre la souris"
assert bande.scripts.OnDragStart is not None, "la barre doit servir de poignee"
assert perso.movable, "le cadre doit etre deplacable"
bande.scripts.OnDragStart(bande)
perso.SetPoint(perso, "CENTER", g.UIParent, "CENTER", 120, -40)
bande.scripts.OnDragStop(bande)
retenue = g.ForeverUIDB.positions["feuille"]
print(" apres deplacement : %s (%s, %s) retenu" % (
retenue.point, retenue.x, retenue.y))
assert retenue.point == "CENTER" and retenue.x == 120 and retenue.y == -40, "la place doit etre retenue"
# le client repose sa fenetre : on doit la remettre
perso.SetPoint(perso, "TOPLEFT", g.UIParent, "TOPLEFT", 0, 0)
g.ForeverUI.CharacterSheet.Apply()
pp2 = perso.points[len(list(perso.points.values()))]
print(" apres un passage du client : %s (%s, %s)" % (pp2[1], pp2[4], pp2[5]))
assert (pp2[1], pp2[4], pp2[5]) == ("CENTER", 120, -40), "la place retenue doit etre reposee apres le client"
lua.execute('ForeverUIDB.positions["feuille"] = nil')
# LES STATISTIQUES, dans le volet droit, sous la bande de pierre.
l1 = g.PlayerStatFrameLeft1
l6 = g.PlayerStatFrameLeft6
r1 = g.PlayerStatFrameRight1
sel = g.PlayerStatFrameLeftDropDown
pl1 = l1.points[len(list(l1.points.values()))]
print(" stats : ligne large de %d, premiere a (%s, %s) du volet droit" % (
l1.width, pl1[4], pl1[5]))
assert pl1[2].name == "ForeverUICharacterRightPane", "elles vont dans le volet droit"
assert l1.width == 233 - 40, "elles sont elargies au volet moins ses marges"
assert l1.height == 13, "StatFrameTemplate fait 104 x 13 : le pas vient de la"
ecart = l1.points[len(list(l1.points.values()))][5] - g.PlayerStatFrameLeft2.points[len(list(g.PlayerStatFrameLeft2.points.values()))][5]
print(" pas entre deux lignes : %d (13 : StatFrameTemplate)" % ecart)
assert ecart == 13, "le pas est celui du modele de ligne"
assert sel.points[len(list(sel.points.values()))][5] > pl1[5], "le selecteur coiffe son groupe"
y6 = l6.points[len(list(l6.points.values()))][5]
yr1 = r1.points[len(list(r1.points.values()))][5]
print(" second groupe %d plus bas que la derniere ligne du premier" % (y6 - yr1))
assert yr1 < y6, "le second groupe vient sous le premier"
# LES SELECTEURS DE CATEGORIE. Leur art d origine deborde du cadre et ne
# le suit pas : il s efface, l en-tete moderne prend sa place.
selD = g.PlayerStatFrameRightDropDown
psel = sel.points[len(list(sel.points.values()))]
print(" selecteur : %d x %d, %s (%s, %s), visible=%s" % (
sel.width, sel.height, psel[1], psel[4], psel[5], sel.shown))
assert sel.width == 203 and sel.height == 40, "197 x 40 chez camelot, elargi au volet : ses lignes font 193 et il deborde de 5"
assert psel[4] == 15, "il deborde de 5 a gauche de ses lignes, posees a 20"
assert sel.shown, "il doit etre visible"
assert sel.foreverFond.texture is not None, "il porte le fond UI-Character-Info-Title"
dore = [g.PlayerStatFrameLeftDropDownLeft, g.PlayerStatFrameLeftDropDownMiddle,
g.PlayerStatFrameLeftDropDownRight, g.PlayerStatFrameLeftDropDownText]
print(" cadre dore efface : %s" % [bool(r.shown) for r in dore])
assert not any(r.shown for r in dore), "l art du menu deroulant deborde : il s efface"
fleche = g.PlayerStatFrameLeftDropDownButton
pfl = fleche.points[len(list(fleche.points.values()))]
print(" fleche du menu : %s (%s, %s)" % (pfl[1], pfl[4], pfl[5]))
assert (pfl[1], pfl[3], pfl[4]) == ("RIGHT", "RIGHT", -6), "elle revient contre le bord droit"
assert sel.mouseEnabled and sel.scripts.OnMouseUp is not None, "toute la barre ouvre le menu, pas seulement la fleche de 24"
print(" intitules : gauche \"%s\" | droite \"%s\"" % (
sel.foreverIntitule.text, selD.foreverIntitule.text))
assert sel.foreverIntitule.text == "Attributs", "l intitule est la categorie de la CVar, pas sa cle"
assert selD.foreverIntitule.text == "Corps a corps"
lua.execute('UpdatePaperdollStats("PlayerStatFrameLeft", "PLAYERSTAT_DEFENSES")')
print(" apres changement de categorie : gauche \"%s\"" % sel.foreverIntitule.text)
assert sel.foreverIntitule.text == "Defenses", "l intitule doit suivre le menu du client"
fermer = g.CharacterFrameCloseButton
pf = fermer.points[1]
print(" fermeture : %d x %d, %s (%s, %s)" % (
fermer.width, fermer.height, pf[1], pf[4], pf[5]))
assert fermer.width == 24 and fermer.height == 24
assert (pf[1], pf[4], pf[5]) == ("TOPRIGHT", 1, 0), "le bouton de fermeture va au coin haut droit"
assert fermer._normal.texture is not None, "il porte le X rouge des panneaux"
# LES ONGLETS LATERAUX : en colonne a droite, DEHORS.
barre = g.ForeverUICharacterModeTabs
o1, o2 = g.CharacterFrameTab1, g.CharacterFrameTab2
pb = barre.points[1]
print(" onglets : barre %d x %d, %s sur %s (%s, %s)" % (
barre.width, barre.height, pb[1], pb[3], pb[4], pb[5]))
assert barre.width == 64 and barre.height == 384, "ModeTabs fait 64 x 384"
assert pb[1] == "TOPLEFT" and pb[3] == "TOPRIGHT" and pb[5] == -30, "la barre se pose a droite du cadre, 30 sous son haut"
print(" un onglet : %d x %d (55 x 55 : 55 x 60 moins 5 de transparent)" % (
o1.width, o1.height))
assert o1.width == 55 and o1.height == 55
p2 = o2.points[1]
assert p2[1] == "TOPLEFT" and p2[3] == "BOTTOMLEFT", "ils s empilent"
print(" onglet du personnage : portrait=%s, texte masque=%s" % (
o1.foreverIcone.portraitOf, not g.CharacterFrameTab1Text.shown))
assert o1.foreverIcone.portraitOf == "player", "l onglet du personnage porte le portrait, comme la source"
assert abs(o1.foreverIcone.texcoord[1] - 0.03125) < 1e-6, "rogne a 0,03125 comme UpdateCharacterModeTabPortrait"
# LE NIVEAU, LA RACE ET LA CLASSE : dans le volet droit, la ou la source
# met PaperDollLevelInfo.
niveau = droit.ligneNiveau
pn = niveau.points[1]
assert not g.CharacterLevelText.shown, "la ligne du client s efface"
assert niveau.text == g.CharacterLevelText.text, "le texte vient du client, nous ne faisons que l afficher"
print(" ligne de niveau : %s sur %s (%s, %s), large de %d, parent %s" % (
pn[1], pn[3], pn[4], pn[5], niveau.width, niveau.parent and niveau.parent.name))
assert pn[1] == "TOP" and pn[3] == "TOP", "elle se pose sous le haut du volet"
assert pn[5] == -54, "PaperDollLevelInfo : -4 des onglets lateraux, -50 dessous"
assert pn[2].name == "ForeverUICharacterRightPane", "dans le volet DROIT"
assert niveau.owner.name == "ForeverUICharacterRightPane", "elle appartient au volet : une region ne se reparente pas en 3.3.5"
assert niveau.width == 220, "PaperDollLevelInfo fait 220 de large"
assert niveau.justify == "CENTER", "centree"
# LES FLECHES DU MODELE : centrees sur le volet, cote a cote.
fg = g.CharacterModelFrameRotateLeftButton.points[1]
fd = g.CharacterModelFrameRotateRightButton.points[1]
print(" fleches : %s (%s) et %s (%s), a %s du haut du volet" % (
fg[1], fg[4], fd[1], fd[4], fg[5]))
assert fg[1] == "TOP" and fd[1] == "TOP", "elles s accrochent au haut du volet"
assert fg[2].name == "ForeverUICharacterLeftPane"
assert abs(fg[4] + fd[4]) < 1e-6, "elles se repartissent de part et d autre du milieu"
assert fg[4] < 0 and fd[4] > 0, "la gauche a gauche, la droite a droite"
modele = g.CharacterModelFrame.points[1]
print(" modele : %s sur %s, remonte de %s dans son volet" % (
modele[1], modele[3], modele[5]))
assert modele[1] == "TOPLEFT" and modele[2].name == "ForeverUICharacterLeftPane", "le modele occupe le volet gauche"
assert modele[5] == 24, "il est remonte dans son volet"
# Le panneau des resistances se decale, et UNE SEULE FOIS : on repart
# toujours de son ancrage d origine, jamais de la position courante.
res = g.CharacterResistanceFrame
x1 = res.points[len(list(res.points.values()))][4]
g.ForeverUI.CharacterSheet.Apply()
g.ForeverUI.CharacterSheet.Apply()
x2 = res.points[len(list(res.points.values()))][4]
print(" resistances : %s, puis %s apres deux passages de plus" % (x1, x2))
assert x1 == -60 + 30, "le decalage est de 30 vers la droite"
assert x1 == x2, "le decalage ne doit pas deriver a chaque passage"
# ------------------------------------------------- bas de l'ecran
micro = g.ForeverUIMicroMenu
print("micro-menu : %d x %d pour %d boutons" % (
micro.width, micro.height, len(list(g.ForeverUI.MicroButtons.values()))))
assert micro.width == 322, "le micro-menu ne fait pas 275 + 47 de rallonge"
assert micro.height == 40
b1, b2 = g.CharacterMicroButton, g.SpellbookMicroButton
print("bouton de micro-menu : %d x %d (32 x 46 : l'ouverture du cadre ; 28 x 58 d'origine)" % (
b1.width, b1.height))
assert b1.width == 32 and b1.height == 46, "le bouton ne remplit pas l'ouverture du cadre"
assert micro.height == 40, "le bandeau ne doit pas changer de hauteur"
entree0 = list(g.ForeverUI.MicroButtons.values())[0]
assert entree0.fond.allPoints, "le fond ne suit pas la taille du bouton"
pp0 = g.MicroButtonPortrait
assert pp0.width == 18 and pp0.height == 26, "le portrait n'a plus sa taille d'origine"
assert b1.hitRect and b1.hitRect[3] == 0, "les 18 px inertes du haut sont restes"
print("premier bouton a %s du bord gauche du bandeau (0 attendu)" % b1.points[1][4])
assert b1.points[1][4] == 0, "les boutons ne sont pas serres a gauche"
pas = b2.points[1][4] - b1.points[1][4]
print("pas du micro-menu : %s (27 attendu : 32 - 5 de chevauchement)" % pas)
assert pas == 27, "childXPadding = -5 n'est pas respecte"
porte = g.MainMenuMicroButton
assert porte.frameLevel > b1.frameLevel, "le bouton de droite ne passe pas devant"
# le fond change quand le bouton est enfonce, comme SetPushed le fait
entree = list(g.ForeverUI.MicroButtons.values())[0]
print("fond au repos : up visible=%s, down visible=%s" % (
entree.fond.shown, entree.fondEnfonce.shown))
assert entree.fond.shown and not entree.fondEnfonce.shown
g.CharacterMicroButton.buttonState = "PUSHED"
g.HOOKS["UpdateMicroButtons"]()
print("apres enfoncement : up visible=%s, down visible=%s, ombre enfoncee=%s" % (
entree.fond.shown, entree.fondEnfonce.shown,
entree.ombreEnfoncee and entree.ombreEnfoncee.shown))
assert not entree.fond.shown and entree.fondEnfonce.shown, "le fond enfonce ne prend pas le relais"
g.CharacterMicroButton.buttonState = "NORMAL"
g.HOOKS["UpdateMicroButtons"]()
# le portrait reprend le rognage de camelot
pp = g.MicroButtonPortrait
print("portrait : rogne a (%.4f, %.4f, %.4f, %.4f), %d x %d centre" % (
pp.texcoord[1], pp.texcoord[2], pp.texcoord[3], pp.texcoord[4], pp.width, pp.height))
assert abs(pp.texcoord[1] - 0.2) < 1e-6 and abs(pp.texcoord[4] - 0.9) < 1e-6
assert pp.points[1][1] == "CENTER"
# ----------------------------------------------------- barre des sacs
sacs = g.ForeverUIBagsBar
print("barre des sacs : %d x %d (268 x 45 attendu : 5 x 45 + 33 + 5 x 2)" % (
sacs.width, sacs.height))
assert sacs.width == 268 and sacs.height == 45
dos = g.MainMenuBarBackpackButton
sac0 = g.CharacterBag0Slot
print("sac a dos %d x %d, ancre %s sur %s | sac 1 decale de %s" % (
dos.width, dos.height, dos.points[1][1], dos.points[1][3], sac0.points[1][4]))
assert dos.width == 45 and dos.height == 45
assert sac0.points[1][4] == -2, "bagPadding = 2 n'est pas respecte"
assert dos._normal.width == 46 and dos._normal.height == 46, "le cadre du sac n'est pas en 46 x 46"
assert dos._highlight.blend == "ADD", "le survol du sac n'est pas en ADD"
trousseau = g.KeyRingButton
print("trousseau : %d x %d, visible=%s, cadre %d de large" % (
trousseau.width, trousseau.height, trousseau.shown, trousseau._normal.width))
assert trousseau.width == 33 and trousseau.height == 45
# L'EMPLACEMENT DU TROUSSEAU PORTE SON ICONE. Toujours : en 3.3.5 le
# trousseau est permanent, alors que la condition de CVar de la source
# vient d'un client ou il n'est plus qu'un reste du passe, masque tant
# que le joueur n'a pas ramasse de cle.
icone = trousseau.regions[len(list(trousseau.regions.values()))]
print(" icone du trousseau : %s, %d x %d" % (
icone.texture and icone.texture.split(chr(92))[-1], icone.width, icone.height))
assert icone.texture is not None, "l emplacement du trousseau doit porter une icone"
assert icone.width == 27 and icone.height == 40, "UI-HUD-ActionBar-Keyring-Small fait 27 x 40"
assert trousseau.shown, "camelot garde toujours le trousseau dans la barre"
assert g.ForeverUIBagsFiller is None, "l emplacement decoratif est encore construit"
cellules = list(g.ForeverUI.BagsCells.values())
separateurs = [v for v in g.ForeverUI.BagsDividers.values()]
print("cellules : %d | separateurs : %d (un de moins que de cellules)" % (
len(cellules), len(separateurs)))
assert len(cellules) == 6, "la barre des sacs ne compte pas six cellules"
assert len(separateurs) == 5
# ------------------------------------------------- la rangee complete
for nom, attendu in (("micromenu", (116.5, 6)), ("actionbar", (-49, 2)), ("sacs", (284.5, 2))):
d = g.ForeverUI.Layout.systems[nom].defaults
print("%-10s : %s sur %s (%.1f, %.1f)" % (nom, d.point, d.relativePoint, d.x, d.y))
assert abs(d.x - attendu[0]) < 1e-6 and abs(d.y - attendu[1]) < 1e-6, (
"position par defaut fausse pour %s" % nom)
embout = g.ForeverUI.ActionBarEndCaps.right
pt = embout.points[1]
print("embout droit : %s sur %s (%s, %s)" % (pt[1], pt[3], pt[4], pt[5]))
assert pt[1] == "BOTTOMLEFT" and pt[3] == "BOTTOMRIGHT", "l'embout droit n'est pas cale par le bas"
assert pt[4] == -30 and pt[5] == -2, "l'embout droit ne tient pas au bord des sacs"
gauche = g.ForeverUI.ActionBarEndCaps.left.points[1]
print("embout gauche : %s sur %s (%s, %s)" % (gauche[1], gauche[3], gauche[4], gauche[5]))
assert gauche[1] == "BOTTOMRIGHT" and gauche[3] == "BOTTOMLEFT" and gauche[5] == -2
# ------------------------------------------------------- les sacs
sac = g.ContainerFrame1
print("cadre de sac : habille=%s | ancien fond efface=%s" % (
sac.foreverSkinned == True, g["ContainerFrame1BackgroundTop"].alpha == 0))
assert sac.foreverSkinned, "le cadre de sac n'est pas habille"
assert g["ContainerFrame1BackgroundTop"].alpha == 0, "l'habillage d'epoque est reste"
panneau = sac.foreverPanel
print("panneau : %d morceaux de fond, coins %s" % (
len(list(panneau.fond.values())),
", ".join(sorted(k for k in dict(panneau).keys() if str(k).startswith("coin")))))
assert panneau.coinHautGauche is not None and panneau.bordBas is not None
# HeldBagLayout, au pixel pres : chaque coin porte son decalage, et les
# huit morceaux sont en OVERLAY.
COINS = {"coinHautGauche": (-13, 16), "coinHautDroit": (4, 16),
"coinBasGauche": (-13, -3), "coinBasDroit": (4, -3)}
for cle, (x, y) in sorted(COINS.items()):
t = panneau[cle]
pt = t.points[1]
print(" %-15s %s (%s, %s) en %s" % (cle, pt[1], pt[4], pt[5], t.layer))
assert (pt[4], pt[5]) == (x, y), "%s ne suit pas HeldBagLayout" % cle
assert t.layer == "OVERLAY", "HeldBagLayout declare %s en OVERLAY" % cle
# Le portrait est en BORDER : le fond est en BACKGROUND et le metal en
# OVERLAY, donc il est toujours entre les deux, quel que soit l'ordre de
# creation. Depuis le calque du fond, le corps de celui-ci -- ancre a 20
# px du haut -- passait devant lui et coupait l'anneau.
assert g["ContainerFrame1Portrait"].alpha == 0, "l'ancien portrait doit s'effacer"
# NineSliceUtil.UpdateCornerCropping : sur une fenetre plus courte que
# ses deux coins empiles, le coin du BAS est rogne par le haut.
eHaut = g.ForeverUI.AtlasEntry("ui-frame-portraitmetal-cornertopleftsmall")
eBas = g.ForeverUI.AtlasEntry("ui-frame-metal-cornerbottomleft")
hautCoin, basCoin = eHaut[7], eBas[7]
coinBas = sac.foreverPanel.coinBasGauche
for hauteur, cas in ((269, "sac a dos"), (100, "trousseau, une rangee")):
sac.SetHeight(sac, hauteur)
g.ForeverUI.UpdatePanelCorners(sac)
debord = max(0, min(hautCoin + basCoin - hauteur - 16 - 3, basCoin))
print(" coin du bas, %-22s hauteur %3d -> rogne de %2d, reste %d" % (
cas, hauteur, debord, coinBas.height))
assert abs(coinBas.height - (basCoin - debord)) < 0.01, "le coin du bas n'est pas rogne comme ClipNineSliceBottomCorner"
sac.SetHeight(sac, 269)
g.ForeverUI.UpdatePanelCorners(sac)
portrait = sac.foreverPortrait
pt = portrait.points[1]
fond = list(sac.foreverPanel.fond.values())[0]
print("portrait : %dx%d en %s, %s sur %s (%.0f, %.0f)" % (
portrait.width, portrait.height, portrait.layer, pt[1], pt[3], pt[4], pt[5]))
# La methode du client : l'icone tient dans le trou de l'anneau, et ses
# coins tombent sous le metal opaque (14,3 a 20,4 du centre).
assert portrait.width == 28 and portrait.height == 28, "l'icone doit tenir dans l'anneau : 28"
assert pt[1] == "CENTER" and pt[3] == "TOPLEFT" and pt[4] == 14 and pt[5] == -17, "l'icone doit etre centree sur l'anneau, a (14, -17)"
print(" calques : fond %s, portrait %s, metal %s" % (
fond.layer, portrait.layer, sac.foreverPanel.coinHautGauche.layer))
assert fond.layer == "BACKGROUND" and portrait.layer == "BORDER", "le portrait doit etre dans un calque au-dessus du fond, pas dans le sien"
assert sac.foreverPanel.coinHautGauche.layer == "OVERLAY", "HeldBagLayout declare ses morceaux en OVERLAY"
# UpdateName / UpdateMiscellaneousFrames : le nom et l'icone viennent du
# sac, et se refont a chaque passage du client.
g.HOOKS["ContainerFrame_GenerateFrame"](sac)
assert g["ContainerFrame1Name"].alpha == 0, "l'ancien titre doit s'effacer"
bande = sac.foreverBandeTitre
titre = sac.foreverTitre
b1, b2 = bande.points[1], bande.points[2]
print("titre : \"%s\" (du sac, pas du code) | bande %s (%s) a %s (%s), %d de haut, niveau +%d" % (
titre.text, b1[1], b1[4], b2[1], b2[4], bande.height,
bande.GetFrameLevel(bande) - sac.GetFrameLevel(sac)))
assert titre.text == "Sac a dos", "le nom ne vient pas de GetBagName"
assert b1[4] == 35 and b1[5] == -1, "SetTitleOffsets(35) : TOPLEFT (35, -1)"
assert b2[4] == -24 and b2[5] == -1, "la valeur par defaut a droite est -24"
assert bande.height == 20, "TitleContainer fait 20 de haut"
assert titre.points[1][5] == -5, "TitleText est ancre TOP (0, -5) dans son conteneur"
assert titre.justify == "CENTER", "le titre se centre dans son conteneur"
assert bande.GetFrameLevel(bande) > sac.GetFrameLevel(sac), "le titre doit etre un cadre fils : le metal est en OVERLAY"
print(" portrait du sac a dos : %s, rognage %s (la bordure de 4 px)" % (
portrait.texture, [round(v, 2) for v in portrait.texcoord.values()]))
assert [round(v, 4) for v in portrait.texcoord.values()] == [0.0625, 0.9375, 0.0625, 0.9375], "l'icone doit etre rognee de sa bordure de 4 px, comme le client le fait"
assert portrait.texture and portrait.texture.lower().find("inv_misc_bag_08") >= 0, "le sac a dos porte Inv_misc_bag_08"
sac.id = 1
g.HOOKS["ContainerFrame_GenerateFrame"](sac)
print(" sac porte : titre \"%s\", portrait %s" % (titre.text, portrait.texture))
assert titre.text == "Sac en tisse-givre", "un sac porte prend le nom de l'objet"
assert portrait.texture == "icone_du_sac_1", "un sac porte prend l'icone de l'objet"
sac.id = 0
g.HOOKS["ContainerFrame_GenerateFrame"](sac)
bouton = g["ContainerFrame1Item1"]
print("emplacement : art dore efface (alpha %s) | voile de recherche=%s" % (
bouton._normal.alpha, bouton.foreverVoile is not None))
assert bouton.foreverVoile is not None, "le voile de recherche manque"
assert bouton._normal.alpha == 0, "l'art de 3.3.5 doit s'effacer : pose sur la NormalTexture il couvrait l'icone"
# LA FORMULE DE CAMELOT, recopiee de ContainerFrameMixin :
# hauteur = rangees x 37 + (rangees-1) x 5 + comble + extra
# comble = GetFirstButtonOffsetY() 9 + 48, + 30 sur le sac a dos
# extra = la bourse (13) sur le sac a dos, 0 ailleurs
# largeur = CONTAINER_WIDTH, une constante
# Le client montre le cadre au bout de ContainerFrame_GenerateFrame ;
# le faux client n'appelle que nos accroches, on ouvre donc le sac ici.
g.ContainerFrame1.Show(g.ContainerFrame1) # pas d'appel a deux points en Python
g.ContainerFrame1.size = 16
g.HOOKS["ContainerFrame_GenerateFrame"](g.ContainerFrame1)
g.ForeverUI.BagsLayout()
def ancre(piece):
"""La derniere ancre posee : (point, cible, pointCible, x, y)."""
pt = piece.points[len(list(piece.points.values()))]
return pt[1], pt[2], pt[3], pt[4], pt[5]
premier = g.ContainerFrame1Item1
bourse = g.ContainerFrame1MoneyFrame
champ = g.ForeverUIBagSearchBox
tri = g.ForeverUIBagSortButton
GRILLE = 4 * 37 + 3 * 5 # 163
HAUTEUR = GRILLE + (15 + 48 + 30) + 13 # 269 : 263 de camelot, + 6 remontes
print("fenetre du sac a dos : %d x %d (178 x %d : CalculateHeight, + 6 remontes)" % (
g.ContainerFrame1.width, g.ContainerFrame1.height, HAUTEUR))
assert g.ContainerFrame1.width == 178, "CalculateWidth() vaut CONTAINER_WIDTH"
assert g.ContainerFrame1.height == HAUTEUR, "CalculateHeight() n'est pas respectee"
# ContainerFrameBackpackMixin:GetInitialItemAnchor -- la grille du sac a
# dos s'accroche a la BOURSE, pas au cadre.
point, cible, pointCible, x, y = ancre(premier)
print(" premiere case : %s sur %s de %s (%s, %s)" % (
point, pointCible, cible.name if hasattr(cible, "name") else cible, x, y))
assert point == "BOTTOMRIGHT" and pointCible == "TOPRIGHT", \
"la grille du sac a dos ne s'accroche pas au-dessus de la bourse"
assert x == 0 and y == 4, "GetInitialItemAnchor du sac a dos vaut (0, 4)"
# UpdateCurrencyFrames -- BOTTOMLEFT (8, 8) / BOTTOMRIGHT (-8, 8)
point, _, _, x, y = ancre(bourse)
print(" bourse : %s (%s, %s), %d de haut, encadree=%s" % (
point, x, y, bourse.height, bourse.foreverBorde == True))
assert x == -8 and y == 14, "la bourse doit etre remontee de 6"
assert bourse.height == 13, "UpdateMoneyFrame pose 13"
assert bourse.foreverBorde, "la bourse n'a pas son encadre"
# SetSearchBoxPoint / UpdateSearchBox -- ancres en HAUT, comme la source
point, _, pointCible, x, y = ancre(champ)
print(" champ : %s sur %s (%s, %s), %d de large" % (point, pointCible, x, y, champ.width))
assert (point, x, y) == ("TOPLEFT", 42, -43), "le champ redescend de 6 : TOPLEFT (42, -43)"
assert champ.width == 96, "SetSearchBoxPoint pose 96 de large"
point, _, _, x, y = ancre(tri)
print(" tri : %s (%s, %s)" % (point, x, y))
assert (point, x, y) == ("TOPRIGHT", -9, -40), "le tri redescend de 6 : TOPRIGHT (-9, -40)"
rangee2 = g.ContainerFrame1Item5
print(" ecart entre rangees : %s (ITEM_SPACING_Y = 5)" % ancre(rangee2)[4])
assert ancre(rangee2)[4] == 5, "l'ecart entre rangees n'est pas ITEM_SPACING_Y"
# /fui sacs doit s'executer : c'est la seule facon de relire la formule en
# jeu, et une erreur y passait inapercue jusqu'ici.
g.ForeverUI.BagsDebug()
# LA FENETRE SUIT SON CONTENU. C'est le nombre de rangees qui entre dans
# CalculateHeight : on change la taille du sac et la hauteur doit suivre,
# pour le sac a dos comme pour un sac porte.
print(" le sac a dos suit son contenu :")
for taille, rangees in ((16, 4), (20, 5), (24, 6), (28, 7), (36, 9)):
g.ContainerFrame1.size = taille
g.HOOKS["ContainerFrame_GenerateFrame"](g.ContainerFrame1)
grille = rangees * 37 + (rangees - 1) * 5
attendu = grille + (15 + 48 + 30) + 13
print(" %2d cases (%d rangees) -> %d de haut (%d attendu)" % (
taille, rangees, g.ContainerFrame1.height, attendu))
assert g.ContainerFrame1.height == attendu, "le sac a dos ne suit pas son contenu"
assert g.ContainerFrame1.width == 178, "la largeur est une constante"
g.ContainerFrame1.size = 16
g.HOOKS["ContainerFrame_GenerateFrame"](g.ContainerFrame1)
print(" un sac porte suit son contenu (ni bourse ni recherche) :")
for taille, rangees in ((4, 1), (8, 2), (16, 4), (20, 5), (36, 9)):
g.ContainerFrame2.size = taille
g.ContainerFrame2.id = 1
g.HOOKS["ContainerFrame_GenerateFrame"](g.ContainerFrame2)
grille = rangees * 37 + (rangees - 1) * 5
attendu = grille + 15 + 48
print(" %2d cases (%d rangees) -> %d de haut (%d attendu)" % (
taille, rangees, g.ContainerFrame2.height, attendu))
assert g.ContainerFrame2.height == attendu, "un sac porte ne suit pas son contenu"
assert g.ContainerFrame2.width == 178, "la largeur est une constante"
# ContainerFrameMixin:GetInitialItemAnchor, remonte de 6 : (-7, 15)
point, _, pointCible, x, y = ancre(g["ContainerFrame2Item1"])
assert (point, pointCible, x, y) == ("BOTTOMRIGHT", "BOTTOMRIGHT", -7, 15), \
"un sac porte pose sa grille en (-7, 15) sur le cadre"
g.ContainerFrame2.id = 0
# LE CLIENT NE PEUT PLUS DEFAIRE LA TAILLE. 3.3.5 repose ses morceaux dans
# ContainerFrame_Update et dans updateContainerFrameAnchors ; on verifie
# qu'un passage du client y est bien rattrape.
g.ContainerFrame1.height = 1
g.ContainerFrame1.width = 1
g.HOOKS["ContainerFrame_Update"](g.ContainerFrame1)
print(" apres un passage du client : %d x %d (rattrape)" % (
g.ContainerFrame1.width, g.ContainerFrame1.height))
assert g.ContainerFrame1.height == HAUTEUR, "ContainerFrame_Update ne rattrape pas la taille"
g.ContainerFrame1.height = 1
g.HOOKS["updateContainerFrameAnchors"]()
print(" apres un reagencement du client : %d de haut (rattrape)" % g.ContainerFrame1.height)
assert g.ContainerFrame1.height == HAUTEUR, "le reagencement ne rattrape pas la taille"
# LE RATTRAPAGE A L'IMAGE SUIVANTE. Le jeu a montre 240 la ou la formule
# donne 263 : quelque chose repose la taille apres toutes les accroches.
# On rejoue ce cas -- la taille est defaite APRES notre passage -- et le
# rattrapage doit la remettre au premier OnUpdate.
g.HOOKS["ContainerFrame_GenerateFrame"](g.ContainerFrame1)
g.ContainerFrame1.height = 240 # le client repasse derriere nous
veille = g.ForeverUIBagsRecheck
print(" le client defait la hauteur (240), rattrapage demande=%s" % veille.shown)
assert veille.shown, "le rattrapage n'a pas ete demande"
veille.scripts.OnUpdate(veille, 0.01)
print(" a l'image suivante : %d de haut, defaite %d fois, rattrapage rendormi=%s" % (
g.ContainerFrame1.height, g.ContainerFrame1.foreverDefaite or 0, not veille.shown))
assert g.ContainerFrame1.height == HAUTEUR, "le rattrapage n'a pas remis la mesure"
assert not veille.shown, "le rattrapage doit se rendormir, pas tourner a chaque image"
# Et quand rien n'a bouge, il ne touche a rien.
g.ContainerFrame1.foreverDefaite = 0
veille.Show(veille)
veille.scripts.OnUpdate(veille, 0.01)
print(" rien n'a bouge : defaite %d fois (0 attendu)" % (g.ContainerFrame1.foreverDefaite or 0))
assert (g.ContainerFrame1.foreverDefaite or 0) == 0, "le rattrapage repose une taille deja bonne"
# L'EMPILEMENT -- UpdateContainerFrameAnchors : CONTAINER_SPACING vaut 8
# entre deux sacs, le premier se pose a 10 du bord droit et 85 du bas.
g.ContainerFrame2.id = 1
g.ContainerFrame2.size = 16
g.ContainerFrame2.Show(g.ContainerFrame2)
g.HOOKS["ContainerFrame_GenerateFrame"](g.ContainerFrame2)
lua.execute('ContainerFrame1.bags = { "ContainerFrame1", "ContainerFrame2" }')
g.HOOKS["updateContainerFrameAnchors"]()
p1 = g.ContainerFrame1.points[len(list(g.ContainerFrame1.points.values()))]
p2 = g.ContainerFrame2.points[len(list(g.ContainerFrame2.points.values()))]
print(" empilement : premier %s (%s, %s) | second %s sur %s (%s, %s)" % (
p1[1], p1[4], p1[5], p2[1], p2[3], p2[4], p2[5]))
assert (p1[1], p1[4], p1[5]) == ("BOTTOMRIGHT", -10, 85), "le premier sac n'est pas a 10 du bord droit et 85 du bas"
assert (p2[1], p2[3], p2[4], p2[5]) == ("BOTTOMRIGHT", "TOPRIGHT", 0, 8), "les sacs ne sont pas espaces de CONTAINER_SPACING"
g.ContainerFrame2.Hide(g.ContainerFrame2)
g.ContainerFrame2.id = 0
lua.execute("ContainerFrame1.bags = nil")
# Un reglage change, la fenetre suit.
g.ForeverUI.BagsSet("emplacement", 44)
print(" emplacement 37 -> 44 : fenetre %d x %d, case %d" % (
g.ContainerFrame1.width, g.ContainerFrame1.height, premier.width))
assert g.ContainerFrame1.height == HAUTEUR - GRILLE + 4 * 44 + 3 * 5, \
"la hauteur ne suit pas la taille des cases"
assert premier.width == 44, "la case n'a pas change de taille"
g.ForeverUI.BagsSet("emplacement", 37)
# le contour suit la qualite de l'objet
lua.execute("""
SACS[0][4] = { lien = "|cff0070dd|Hitem:2|h[Epee]|h|r", nombre = 1 }
ContainerFrame1.size = 16
""")
g.ForeverUI.BagSearch.Tout()
fermer = g.ContainerFrame1CloseButton
fpt = fermer.points[len(list(fermer.points.values()))]
print(" fermeture : %d x %d, %s (%s, %s)" % (fermer.width, fermer.height, fpt[1], fpt[4], fpt[5]))
assert fermer.width == 24 and fermer.height == 24, "le bouton de fermeture n'est pas en 24 x 24"
assert fpt[4] == 1 and fpt[5] == 0, "le bouton de fermeture n'est pas au coin"
# SetItemButtonTexture_Base : UNE seule texture. Case pleine = l'icone de
# l'objet, coordonnees pleines ; case vide = l'element d'atlas, sur la
# meme texture. Rien ne se superpose, sinon l'icone passe derriere.
pleine = g.ContainerFrame1Item4IconTexture
vide2 = g.ContainerFrame1Item3IconTexture
print(" icone : case pleine %s %s | case vide %s %s" % (
pleine.texture, [round(v, 2) for v in pleine.texcoord.values()],
vide2.texture and vide2.texture.split(chr(92))[-1],
[round(v, 3) for v in vide2.texcoord.values()]))
assert pleine.texture == "icone" and pleine.shown, "la case pleine montre l'objet"
assert [round(v, 2) for v in pleine.texcoord.values()] == [0, 1, 0, 1], "l'icone d'un objet prend toute la texture"
assert vide2.shown and vide2.texture != "icone", "la case vide montre le fond d'emplacement"
assert "foreverui" in vide2.texture.lower(), "le fond d'une case vide est la feuille d'atlas, pas une texture du client"
contour = g.ContainerFrame1Item4.foreverContour
vide = g.ContainerFrame1Item3.foreverContour
gris = [round(v, 2) for v in vide.vertex.values()] if vide.vertex else None
teinte = [round(v, 2) for v in contour.vertex.values()] if contour.vertex else None
print(" contour : case pleine %s | case vide %s (les deux visibles : %s et %s)" % (
teinte, gris, contour.shown, vide.shown))
assert contour.shown and vide.shown, "camelot pose un cadre sur chaque case"
assert gris and abs(gris[0] - 0.39) < 0.01, "la case vide n'a pas le gris de la capture"
assert teinte and teinte != gris, "la qualite ne teinte pas le cadre"
# le champ et le bouton de tri ne se montrent que sur le sac principal
g.ForeverUI.BagsLayout()
champ = g.ForeverUIBagSearchBox
tri = g.ForeverUIBagSortButton
print("champ %dx%d visible=%s | bouton de tri %dx%d visible=%s" % (
champ.width, champ.height, champ.shown, tri.width, tri.height, tri.shown))
assert champ.width == 96 and champ.height == 18
assert tri.width == 28 and tri.height == 26
pc = champ.points[len(list(champ.points.values()))]
pt2 = tri.points[len(list(tri.points.values()))]
print("champ ancre %s (%s, %s) | tri ancre %s (%s, %s)" % (
pc[1], pc[4], pc[5], pt2[1], pt2[4], pt2[5]))
assert (pc[1], pc[4], pc[5]) == ("TOPLEFT", 42, -43), "le champ n'est pas a sa place"
assert (pt2[1], pt2[4], pt2[5]) == ("TOPRIGHT", -9, -40), "le tri n'est pas a sa place"
# la recherche : le pain reste, l'epee se voile
lua.execute("""
SACS[0][1] = { lien = "|cffffffff|Hitem:1|h[Pain]|h|r", nombre = 5 }
SACS[0][2] = { lien = "|cff0070dd|Hitem:2|h[Epee]|h|r", nombre = 1 }
SACS[0][3] = { lien = "|cffffffff|Hitem:3|h[Potion]|h|r", nombre = 3 }
ContainerFrame1.size = 4
""")
g.ForeverUI.BagSearch.Set("pain")
voiles = [g["ContainerFrame1Item" + str(i)].foreverVoile.shown for i in (1, 2, 3, 4)]
print("recherche \"pain\" : voiles = %s (le premier est le pain)" % voiles)
assert voiles[0] == False and voiles[1] == True, "la recherche ne filtre pas"
g.ForeverUI.BagSearch.Set("consommable")
voiles = [g["ContainerFrame1Item" + str(i)].foreverVoile.shown for i in (1, 2, 3)]
print("recherche par type : voiles = %s" % voiles)
assert voiles[0] == False and voiles[2] == False, "le type ne compte pas dans la recherche"
g.ForeverUI.BagSearch.Set("")
# le tri : deux piles de pain se reunissent, puis l'ordre se fait
lua.execute("""
SACS[0][1] = { lien = "|cffffffff|Hitem:3|h[Potion]|h|r", nombre = 3 }
SACS[0][2] = { lien = "|cffffffff|Hitem:1|h[Pain]|h|r", nombre = 5 }
SACS[0][3] = { lien = "|cff0070dd|Hitem:2|h[Epee]|h|r", nombre = 1 }
SACS[0][4] = { lien = "|cffffffff|Hitem:1|h[Pain]|h|r", nombre = 7 }
PRISES = {}
""")
g.ForeverUI.BagSort.Lancer()
tic = g.ForeverUI.BagSort
horloge = g.ForeverUIBagSortTicker # nomme, plus de "le dernier cree"
for _ in range(60):
if not tic.actif:
break
horloge.scripts.OnUpdate(horloge, 0.2)
contenu = []
for i in range(1, 5):
case = g.SACS[0][i]
contenu.append("%s x%d" % (case.lien.split("[")[1].split("]")[0], case.nombre) if case else "vide")
print("apres le tri : %s" % " | ".join(contenu))
print(" %d prises d'objet, %d etapes" % (len(list(g.PRISES.values())), tic.etapes))
assert not tic.actif, "le tri ne s'est pas arrete"
assert "Pain x12" in " ".join(contenu), "les deux piles de pain n'ont pas ete reunies"
# ------------------------------------------------ cible de la cible
totcadre = g.ForeverUITargetOfTarget
g.STATE.hasTarget = True
totcadre.scripts.OnEvent(totcadre, "PLAYER_TARGET_CHANGED")
print("cible de la cible : nom=%s vie visible=%s surveillee=%s" % (
totcadre.nameText.text, totcadre.healthFill.shown, totcadre.unitWatch))
assert totcadre.healthFill.shown, "la vie de la cible de la cible est vide"
print("\nmessages du chat :")
for msg in g.RECORDED.messages.values():
print(" %s" % msg)
main()