-- ForeverUI : l'interface des sacs. -- -- RELEVE DES SOURCES -- tout vient du code extrait de camelot, qui pour les -- sacs se sert du fichier commun (camelot/ContainerFrame.lua ne fait qu'une -- chose : recadrer le masque rond du portrait). -- -- mainline/ContainerFrame.lua -- LA GEOMETRIE, chiffre par chiffre -- CONTAINER_WIDTH = 178, ITEM_SPACING_X = ITEM_SPACING_Y = 5, quatre -- colonnes, boutons de 37. -- ContainerFrameMixin:GetFirstButtonOffsetY = 9 -- ContainerFrameMixin:GetPaddingHeight = 9 + 48, et +30 pour le sac a dos -- (la bande du champ de recherche) -- ContainerFrameMixin:CalculateHeight = hauteur des rangees + ce -- remplissage + CalculateExtraHeight, qui vaut la hauteur de la bourse pour -- le sac a dos. -- sac ordinaire : premier bouton BOTTOMRIGHT (-7, 9) du cadre. -- sac a dos : bourse BOTTOMLEFT (8, 8) et BOTTOMRIGHT (-8, 8), haute de -- 13 ; premier bouton BOTTOMRIGHT sur le TOPRIGHT de la -- bourse, decale de (0, 4). -- bourse : ContainerFrameCurrencyBorderTemplate, haut de 17, bouts -- de 8 x 17 (common-coinbox-left et -right) et milieu tendu. -- -- mainline/ContainerFrame.xml -- cadre ContainerFrameTemplate herite de PortraitFrameFlatTemplate : -- fond plat + encadrement de metal en neuf tranches, jeu -- HeldBagLayout. Monte par ForeverUI.SetPanelArt. -- emplacement ContainerFrameItemButtonTemplate, 37 x 37, fond d'un -- emplacement vide = bags-item-slot64. -- -- shared/ItemButtonTemplate.lua : SetItemButtonQuality_Base -- Le contour d'un objet, c'est IconBorder : Interface\Common\WhiteIconFrame -- teinte par la couleur de qualite de l'objet. Une case vide n'en a pas ; -- c'est son fond qui se voit. -- champ BagItemSearchBox, 96 x 18, 15 lettres, TOPLEFT (42, -37) -- du sac principal (ContainerFrameMixin:SetSearchBoxPoint). -- bouton de tri BagItemAutoSortButton, 28 x 26, TOPRIGHT (-9, -34), -- images bags-button-autosort-up et -down, survol -- Interface\Buttons\ButtonHilight-Square en ADD, 24 x 23 -- centre. Au clic : un son, puis C_Container.SortBags(). -- les deux ne se montrent QUE sur le sac principal -- (ContainerFrameMixin:UpdateSearchBox). -- -- mainline/SharedUIPanelTemplates.xml -- titre TitleContainer de TOPLEFT (58, -1) a TOPRIGHT (-24, -1), -- haut de 20 ; le texte centre dedans, 5 px sous son haut. -- portrait 62 x 62, dans l'anneau du coin haut gauche. -- -- shared/UIPanelTemplatesShared.lua -- BagSearch_OnTextChanged pousse le texte dans toutes les barres de -- recherche puis appelle C_Container.SetItemSearch ; BagSearch_OnChar rend -- la main des que les quatre derniers caracteres sont identiques ; -- BagSearch_OnHide efface la recherche quand plus aucune barre n'est -- visible. Ces trois comportements sont repris tels quels. -- -- CE QUE 3.3.5 N'A PAS, ET COMMENT C'EST FAIT ICI. -- C_Container.SetItemSearch n'existe pas : le client moderne marque -- lui-meme chaque objet et le code ne fait que lire `isFiltered`. Ici le -- tri des correspondances est fait en Lua, sur le nom, le type et le -- sous-type de l'objet, et l'objet qui ne correspond pas recoit le meme -- voile que la source (ItemButton : searchOverlay, noir a 80 %). -- C_Container.SortBags n'existe pas non plus, et son ordre est decide dans -- le client : il n'est pas lisible. Le rangement est donc refait ici, en -- deplacant les objets un par un ; l'ordre suit celui que le jeu lui-meme -- emploie pour ses categories (GetAuctionItemClasses), puis la qualite, le -- nom et la taille de la pile. CHOIX ASSUME, faute de source. -- ===================================================================== -- REGLAGES DE L'INTERFACE DES SACS -- -- RELEVE -- blizzard_uipanels_game/mainline/containerframe.lua, ContainerFrameMixin. -- Tout ce bloc est recopie de la source ; rien n'y est mesure ni estime. -- -- CalculateWidth() = CONTAINER_WIDTH, une CONSTANTE (178) -- la -- largeur ne se deduit PAS de la grille -- CalculateHeight() = rangees x 37 + (rangees - 1) x 5 -- + GetPaddingHeight() + CalculateExtraHeight() -- GetPaddingHeight() = GetFirstButtonOffsetY() (9) + 48 -- + 30 sur le sac a dos (ContainerFrameBackpackMixin, -- "Account for the search box in the backpack") -- CalculateExtraHeight() = 0 ; + la hauteur de la bourse sur le sac a dos -- GetInitialItemAnchor() = sac porte : BOTTOMRIGHT du cadre, (-7, 9) -- sac a dos : BOTTOMRIGHT de la BOURSE, TOPRIGHT (0, 4) -- UpdateCurrencyFrames() = bourse BOTTOMLEFT (8, 8) / BOTTOMRIGHT (-8, 8) -- SetSearchBoxPoint() = champ TOPLEFT (42, -37), 96 de large -- UpdateSearchBox() = tri TOPRIGHT (-9, -34) -- ContainerFrameItemButtonTemplate (containerframe.xml, ligne 82) : 37 x 37 -- ITEM_SPACING_X = ITEM_SPACING_Y = 5 ; colonnes = 4 -- -- CE QUI FAIT QUE LA FENETRE SUIT SON CONTENU : la hauteur est un CALCUL a -- partir du nombre de rangees, pas une somme d'ancrages. Le comble (titre, -- champ de recherche) est une hauteur fixe ancree en HAUT, exactement comme -- dans la source -- c'est la formule qui s'adapte, pas les ancrages. -- sac a dos, 16 cases : 163 + (9 + 48 + 30) + 13 = 263 -- sac porte, 16 cases : 163 + (9 + 48) = 220 -- -- En jeu, pour essayer sans rien reinstaller : -- /fui sacs la hauteur calculee, celle du cadre, le detail -- /fui sacs emplacement 44 change une valeur et refait la fenetre -- Quand le reglage convient, il se fige dans ce bloc. local R = { -- LA GRILLE emplacement = 37, -- ContainerFrameItemButtonTemplate ecartCases = 5, -- ITEM_SPACING_X et ITEM_SPACING_Y colonnes = 4, -- GetColumns() largeur = 178, -- CONTAINER_WIDTH -- LE CALCUL DE LA HAUTEUR -- ECART ASSUME. camelot donne 9 a GetFirstButtonOffsetY(), mais avec son -- art de metal pose ici, le bord interieur du bas remonte a 7,5 du bord -- du cadre : il ne restait que 1,5 sous la derniere rangee, contre 7,5 a -- gauche et 6 a droite (mesure en jeu, echelle 4/3). Les 6 de plus -- egalisent l'espace des quatre cotes ; la fenetre grandit d'autant, -- puisque la formule de la hauteur compte ce nombre. premierBoutonY = 15, -- camelot 9 premierBoutonX = -7, -- GetInitialItemAnchor() entete = 48, -- GetPaddingHeight() : "titlebar and attic" bandeRecherche = 30, -- le meme, + 30 sur le sac a dos -- LA BOURSE, et la grille qui s'y accroche sur le sac a dos bourseHauteur = 13, -- UpdateMoneyFrame() bourseCote = 8, -- UpdateCurrencyFrames() bourseBas = 14, -- camelot 8, remonte des memes 6 ecartBourseGrille = 4, -- ContainerFrameBackpackMixin:GetInitialItemAnchor() bourseCadre = 17, -- l'encadre de camelot deborde de la bourse -- LE SEGMENT DES MONNAIES SUIVIES, sous la bourse. -- -- RELEVE -- ContainerFrameTokenWatcherMixin:UpdateCurrencyFrames : le -- segment se pose au BAS de la fenetre, BOTTOMLEFT (8, 8) et -- BOTTOMRIGHT (-8, 8), et c'est LA BOURSE QUI MONTE -- son BOTTOMLEFT -- et son BOTTOMRIGHT sur le TOP du segment, (0, 3). Sans monnaie -- suivie, la bourse reprend le bas, et rien ne change. -- CalculateExtraHeight ajoute alors la hauteur du segment. -- -- BackpackTokenFrameTemplate : 17 de haut, encadre par -- ContainerFrameCurrencyBorderTemplate -- deux bouts de 8 x 17 et un -- milieu tendu. BackpackTokenTemplate : 50 x 12, icone de 12 ancree -- RIGHT (4, 1), compte cale a droite jusqu'au bord gauche de l'icone. -- GetInitialTokenAnchor : RIGHT du segment (-17, -1), et les jetons -- s'enchainent vers la GAUCHE. jetonHauteur = 17, -- BackpackTokenFrameTemplate jetonCote = 8, -- UpdateCurrencyFrames() jetonEcart = 3, -- ce que la bourse laisse au-dessus du segment jetonLargeur = 50, -- BackpackTokenTemplate jetonPiece = 12, -- sa hauteur, et celle de l'icone jetonIconeX = 4, jetonIconeY = 1, jetonDepartX = -17, -- GetInitialTokenAnchor() jetonDepartY = -1, -- LE COMBLE -- Le champ et le tri redescendent des 6 dont la grille est remontee : -- ancres en haut, ils suivaient sinon le bord superieur, qui s'est -- eloigne d'autant. champLargeur = 96, -- SetSearchBoxPoint() champHauteur = 18, champX = 42, champY = -43, -- camelot -37 triLargeur = 28, triHauteur = 26, triX = -9, -- UpdateSearchBox() triY = -40, -- camelot -34 -- LES DECORS DU CADRE titreGauche = 35, -- SetTitleOffsets(35) : le bord gauche du titre titreDroite = -24, -- la valeur par defaut de SetTitleOffsets titreConteneur = -1, -- TitleContainer : TOPLEFT / TOPRIGHT a -1 titreBande = 20, -- sa hauteur titreTexte = -5, -- TitleText : TOP (0, -5) dans le conteneur fermeture = 24, fermetureX = 1, fermetureY = 0, -- LE PORTRAIT, a la maniere du client 3.3.5. Celui-ci ne masque JAMAIS -- une icone : pour ses boutons de minimap il pose un anneau par-dessus -- (MiniMap-TrackingBorder, une couronne opaque a trou transparent), -- dessine l'icone assez petite pour tenir dans le trou, et la ROGNE de -- sa bordure. C'est l'anneau qui fait le rond. -- -- Ici l'anneau est celui de camelot, mesure au pixel a la taille ou il -- est dessine : trou net jusqu'a 10,3 du centre, degrade jusqu'a 14,3, -- metal OPAQUE de 14,3 a 20,4. Pour qu'un carre disparaisse, ses bords -- doivent couvrir le degrade et ses coins tomber dans le metal : -- cote / 2 >= 14,3 -> cote >= 28,6 -- (cote / 2) x racine(2) <= 20,4 -> cote <= 28,8 -- 28 est la seule taille qui tienne, et ses coins finissent SOUS le -- metal -- mieux que les boutons de minimap, dont les coins flottent -- dans le trou. portrait = 28, portraitX = 14, -- le centre de l'anneau, mesure sur son art portraitY = -17, -- et le centre du portrait de la source -- L'EMPILEMENT DES SACS -- UpdateContainerFrameAnchors, lignes 1372-1401 ecartSacs = 8, -- CONTAINER_SPACING ecartColonnes = -11, -- le saut de colonne bordDroit = 10, -- GetInitialContainerFrameOffsetX, hors barres bordBas = 85, -- CONTAINER_OFFSET_Y -- L'ENSEMBLE echelle = 1, -- 1 = taille de camelot ; 1.25 = un quart de plus } ForeverUI = ForeverUI or {} ForeverUI.BagsSettings = R -- ===================================================================== local NB_CADRES = NUM_CONTAINER_FRAMES or 13 local SACS = { 0, 1, 2, 3, 4 } -- sac a dos et les quatre sacs portes -- Lua 5.1 lit les antislashs comme des echappements : on pose le separateur -- en clair. local SEP = string.char(92) local SURVOL_CARRE = "Interface" .. SEP .. "Buttons" .. SEP .. "ButtonHilight-Square" local CADRE_QUALITE = "Interface" .. SEP .. "ForeverUI" .. SEP .. "common" .. SEP .. "whiteiconframe" -- RELEVE -- ContainerFrameMixin:UpdateMiscellaneousFrames : le sac a dos -- porte Inv_misc_bag_08, le trousseau une icone a lui, et un sac porte -- montre l'icone de l'objet qu'il est. -- RELEVE -- le rognage que le client applique a toute icone posee dans un -- anneau : une icone fait 64 px et porte 4 px de bordure sombre. local BORDURE_ICONE = 4 / 64 local PORTRAIT_SAC_A_DOS = "Interface" .. SEP .. "Icons" .. SEP .. "INV_Misc_Bag_08" -- ECART : camelot demande "Interface/Icons/ui-hud-actionbar-keyring", qui -- n'existe pas en 3.3.5 ; le client y range son trousseau ici. local PORTRAIT_TROUSSEAU = "Interface" .. SEP .. "ContainerFrame" .. SEP .. "KeyRing-Bag-Icon" -- MESURE SUR LA CAPTURE. camelot pose un cadre sur CHAQUE case, pleine ou -- vide : gris sombre, bords entre 25 et 49, coins vers 100. L'image porte ces -- memes valeurs a 255 et 140 : la teinte vaut donc 0,39. La couleur de -- qualite ne prend le relais qu'a partir de peu commun. local CADRE_GRIS = 0.39 local QUALITE_TEINTEE = 2 local RANGER = BAG_CLEANUP_BAGS or "Ranger les sacs" local RANGER_AIDE = BAG_CLEANUP_BAGS_DESCRIPTION or "Reunit les piles et remet les objets en ordre." ForeverUI = ForeverUI or {} -- ------------------------------------------------------------ les cadres local cadres = {} local function habillerBouton(bouton) if not bouton or bouton.foreverSkinned then return end local nom = bouton:GetName() -- RELEVE -- SetItemButtonTexture_Base (itembuttontemplate.lua) : quand la -- case est vide, emptyBackgroundAtlas est pose SUR L'ICONE elle-meme, et -- non derriere. Il n'y a jamais deux textures superposees. L'art dore de -- 3.3.5, lui, s'efface : camelot n'a pas de cadre autour d'une case. local normale = bouton:GetNormalTexture() if normale then normale:SetAlpha(0) end local survol = bouton:GetHighlightTexture() if survol then survol:SetTexture(SURVOL_CARRE) survol:SetBlendMode("ADD") survol:ClearAllPoints() survol:SetAllPoints(bouton) end local icone = _G[nom .. "IconTexture"] if icone then icone:ClearAllPoints() icone:SetAllPoints(bouton) icone:SetTexCoord(0, 1, 0, 1) icone:SetDrawLayer("BORDER") end -- Le contour d'un objet : WhiteIconFrame teinte par sa qualite, comme -- SetItemButtonQuality_Base le fait. Une case vide n'en a pas. local contour = bouton:CreateTexture(nil, "OVERLAY") contour:SetTexture(CADRE_QUALITE) contour:SetAllPoints(bouton) contour:SetVertexColor(CADRE_GRIS, CADRE_GRIS, CADRE_GRIS) bouton.foreverContour = contour -- Le voile de recherche : la source le declare sur le bouton d'objet -- lui-meme, noir a 80 %, sur toute la surface. local voile = bouton:CreateTexture(nil, "OVERLAY") voile:SetTexture(0, 0, 0, 0.8) voile:SetAllPoints(bouton) voile:Hide() bouton.foreverVoile = voile bouton.foreverSkinned = true end local function habillerCadre(cadre) if not cadre or cadre.foreverSkinned then return end local nom = cadre:GetName() -- L'habillage d'epoque : quatre morceaux empiles, plus la version a un -- seul emplacement. ContainerFrame_GenerateFrame les reaffiche et leur -- change de texture a chaque ouverture, mais ne touche pas leur alpha. for _, suffixe in ipairs({ "BackgroundTop", "BackgroundMiddle1", "BackgroundMiddle2", "BackgroundBottom", "Background1Slot" }) do local texture = _G[nom .. suffixe] if texture then texture:SetAlpha(0) end end ForeverUI.SetPanelArt(cadre) -- L'ICONE SE GLISSE ENTRE LE FOND ET LE CONTOUR. Elle est donc creee -- APRES l'art du panneau, dans le meme calque que son fond -- deux -- regions d'un meme calque ne sont ordonnees que par leur ordre de -- creation, celle-ci passe donc au-dessus -- et le metal, en OVERLAY, -- reste au-dessus d'elle. -- -- L'ordre compte : sous le fond, qui est OPAQUE, l'icone ne se verrait -- meme pas par le trou de l'anneau. C'est le contour seul qui la -- decoupe, exactement comme le cadre de sac de 3.3.5 decoupe la sienne -- (UI-Bag-4x4.blp porte un trou circulaire de 32 px, rien n'y est -- masque). Taille et place sont celles de la source : -- SetPortraitTextureSizeAndOffset(36, -4, 1). local ancien = _G[nom .. "Portrait"] if ancien then ancien:SetAlpha(0) end -- LE CALQUE DECIDE, PAS L'ORDRE DE CREATION. Le fond du panneau est en -- BACKGROUND et le metal en OVERLAY : entre les deux, BORDER est libre. -- Une texture posee la est TOUJOURS au-dessus du fond et TOUJOURS sous -- le metal, quel que soit l'ordre dans lequel les morceaux ont ete -- crees. C'est ce qui manquait : le fond du panneau (corps, ancre a 20 -- px du haut) coupait l'anneau en deux et passait devant l'icone. local portrait = cadre:CreateTexture(nil, "BORDER") portrait:SetWidth(R.portrait) portrait:SetHeight(R.portrait) portrait:SetPoint("CENTER", cadre, "TOPLEFT", R.portraitX, R.portraitY) cadre.foreverPortrait = portrait -- RELEVE -- TitledPanelMixin:SetTitleOffsets, que ContainerFrame appelle -- avec 35, et le modele TitleContainer : un CADRE de 20 de haut allant de -- 35 a la largeur moins 24, pose a -1, contenant un TitleText ancre TOP -- (0, -5), LEFT et RIGHT. Le titre n'est donc PAS centre sur la fenetre : -- il l'est entre le portrait et le bouton de fermeture, dont la largeur -- est ainsi prise en compte. -- -- C'est un cadre FILS, et il le faut : le metal de HeldBagLayout est en -- OVERLAY et couvrirait une simple region du cadre. Le titre du client, -- qui en est une, s'efface. local ancienTitre = _G[nom .. "Name"] if ancienTitre then ancienTitre:SetAlpha(0) end local bandeTitre = CreateFrame("Frame", nil, cadre) bandeTitre:SetFrameLevel(cadre:GetFrameLevel() + 2) bandeTitre:SetHeight(R.titreBande) bandeTitre:SetPoint("TOPLEFT", cadre, "TOPLEFT", R.titreGauche, R.titreConteneur) bandeTitre:SetPoint("TOPRIGHT", cadre, "TOPRIGHT", R.titreDroite, R.titreConteneur) local titre = bandeTitre:CreateFontString(nil, "OVERLAY", "GameFontNormal") titre:SetPoint("TOP", bandeTitre, "TOP", 0, R.titreTexte) titre:SetPoint("LEFT", bandeTitre, "LEFT") titre:SetPoint("RIGHT", bandeTitre, "RIGHT") titre:SetJustifyH("CENTER") cadre.foreverBandeTitre = bandeTitre cadre.foreverTitre = titre -- Le bouton de fermeture : 24 x 24, TOPRIGHT (1, 0), le X rouge des -- panneaux modernes (UIPanelCloseButtonNoScripts, atlas RedButton-Exit). local fermer = _G[nom .. "CloseButton"] if fermer then fermer:SetWidth(R.fermeture) fermer:SetHeight(R.fermeture) fermer:ClearAllPoints() fermer:SetPoint("TOPRIGHT", cadre, "TOPRIGHT", R.fermetureX, R.fermetureY) for atlas, methode in pairs({ ["redbutton-exit"] = "GetNormalTexture", ["redbutton-exit-pressed"] = "GetPushedTexture", ["redbutton-exit-disabled"] = "GetDisabledTexture", ["redbutton-highlight"] = "GetHighlightTexture" }) do local texture = fermer[methode] and fermer[methode](fermer) if texture then ForeverUI.SetAtlas(texture, atlas, true) texture:ClearAllPoints() texture:SetAllPoints(fermer) if atlas == "redbutton-highlight" then texture:SetBlendMode("ADD") end end end end for index = 1, MAX_CONTAINER_ITEMS do habillerBouton(_G[nom .. "Item" .. index]) end cadre.foreverSkinned = true table.insert(cadres, cadre) end -- ---------------------------------------------------------- la recherche local Recherche = { texte = "" } ForeverUI.BagSearch = Recherche -- Le nom d'un objet, meme quand le client ne l'a pas encore en cache : le -- lien porte toujours le nom entre crochets. local function nomDeLObjet(lien) if not lien then return nil end local nom, _, _, _, _, type_, sousType = GetItemInfo(lien) if nom then return nom, type_, sousType end return string.match(lien, "%[(.+)%]") end function Recherche.Correspond(lien) if Recherche.texte == "" then return true end if not lien then return false end local nom, type_, sousType = nomDeLObjet(lien) for _, champ in ipairs({ nom, type_, sousType }) do if champ and string.find(string.lower(champ), Recherche.texte, 1, true) then return true end end return false end -- Le voile de recherche ET le contour de qualite se decident au meme moment : -- les deux dependent de ce que la case contient. -- RELEVE -- SetItemButtonTexture_Base : une seule texture porte soit l'objet, -- soit le fond de case vide. 3.3.5 masque l'icone d'une case vide ; on la -- remontre avec l'element d'atlas, et on rend ses coordonnees pleines des -- qu'un objet revient. local function poserIcone(bouton, texture) local icone = _G[bouton:GetName() .. "IconTexture"] if not icone then return end if texture then icone:SetTexture(texture) icone:SetTexCoord(0, 1, 0, 1) else ForeverUI.SetAtlas(icone, "bags-item-slot64", true) end icone:Show() end function Recherche.Appliquer(cadre) local nom = cadre:GetName() local sac = cadre:GetID() for index = 1, cadre.size or 0 do local bouton = _G[nom .. "Item" .. index] if bouton and bouton.foreverVoile then local emplacement = bouton:GetID() local lien = GetContainerItemLink(sac, emplacement) poserIcone(bouton, GetContainerItemInfo(sac, emplacement)) if lien and not Recherche.Correspond(lien) then bouton.foreverVoile:Show() else bouton.foreverVoile:Hide() end -- Le cadre est toujours la ; seule sa teinte change, et seulement -- a partir de peu commun. local contour = bouton.foreverContour if contour then local qualite = select(4, GetContainerItemInfo(sac, emplacement)) local couleur = lien and qualite and qualite >= QUALITE_TEINTEE and ITEM_QUALITY_COLORS and ITEM_QUALITY_COLORS[qualite] if couleur then contour:SetVertexColor(couleur.r, couleur.g, couleur.b) else contour:SetVertexColor(CADRE_GRIS, CADRE_GRIS, CADRE_GRIS) end contour:Show() end end end end function Recherche.Tout() for _, cadre in ipairs(cadres) do if cadre:IsShown() then Recherche.Appliquer(cadre) end end end function Recherche.Set(texte) Recherche.texte = string.lower(texte or "") Recherche.Tout() end -- -------------------------------------------------------------- le tri -- -- L'ordre des categories est celui du jeu lui-meme : GetAuctionItemClasses -- rend les classes d'objets dans l'ordre ou le client les presente. local Tri = { file = {}, actif = false } ForeverUI.BagSort = Tri local rangClasse local function construireRangs() if rangClasse then return end rangClasse = {} if GetAuctionItemClasses then local classes = { GetAuctionItemClasses() } for index, nom in ipairs(classes) do rangClasse[nom] = index end end end local function lireCase(sac, emplacement) local lien = GetContainerItemLink(sac, emplacement) if not lien then return nil end local _, nombre, verrouille = GetContainerItemInfo(sac, emplacement) local nom, _, qualite, _, _, type_, sousType, pileMax = GetItemInfo(lien) return { lien = lien, nombre = nombre or 1, verrouille = verrouille, nom = nom or string.match(lien, "%[(.+)%]") or lien, qualite = qualite or 0, classe = rangClasse[type_ or ""] or 99, sousType = sousType or "", pileMax = pileMax or 1, } end local function avant(a, b) if a.classe ~= b.classe then return a.classe < b.classe end if a.sousType ~= b.sousType then return a.sousType < b.sousType end if a.qualite ~= b.qualite then return a.qualite > b.qualite end if a.nom ~= b.nom then return a.nom < b.nom end return a.nombre > b.nombre end -- L'etat courant des sacs : une case par emplacement, dans l'ordre. local function relever() local cases = {} for _, sac in ipairs(SACS) do for emplacement = 1, (GetContainerNumSlots(sac) or 0) do table.insert(cases, { sac = sac, emplacement = emplacement, objet = lireCase(sac, emplacement), }) end end return cases end local function verrouille(case) local _, _, estVerrouille = GetContainerItemInfo(case.sac, case.emplacement) return estVerrouille end -- Une seule action par passage : le serveur doit confirmer chaque -- deplacement avant le suivant, sinon la case est encore verrouillee. local function uneEtape() local cases = relever() -- 1. reunir les piles entamees du meme objet for i = 1, #cases do local a = cases[i].objet if a and a.nombre < a.pileMax then for j = i + 1, #cases do local b = cases[j].objet if b and b.lien == a.lien and b.nombre < b.pileMax then if verrouille(cases[i]) or verrouille(cases[j]) then return true end PickupContainerItem(cases[j].sac, cases[j].emplacement) PickupContainerItem(cases[i].sac, cases[i].emplacement) return true end end end end -- 2. mettre en ordre : la case i doit porter le i-eme objet trie local objets = {} for _, case in ipairs(cases) do if case.objet then table.insert(objets, case.objet) end end table.sort(objets, avant) for i = 1, #cases do local voulu = objets[i] local present = cases[i].objet local memeObjet = (voulu == nil and present == nil) or (voulu and present and voulu.lien == present.lien and voulu.nombre == present.nombre) if not memeObjet then if voulu == nil then return false -- plus rien a placer end -- trouver la case qui porte l'objet voulu for j = i + 1, #cases do local candidat = cases[j].objet if candidat and candidat.lien == voulu.lien and candidat.nombre == voulu.nombre then if verrouille(cases[i]) or verrouille(cases[j]) then return true end PickupContainerItem(cases[j].sac, cases[j].emplacement) PickupContainerItem(cases[i].sac, cases[i].emplacement) return true end end return false -- introuvable : on s'arrete plutot que tourner end end return false -- tout est en place end local MAX_ETAPES = 400 local tempsDepuisEtape = 0 local horloge = CreateFrame("Frame", "ForeverUIBagSortTicker") horloge:Hide() horloge:SetScript("OnUpdate", function(self, elapsed) tempsDepuisEtape = tempsDepuisEtape + elapsed if tempsDepuisEtape < 0.1 then return end tempsDepuisEtape = 0 Tri.etapes = (Tri.etapes or 0) + 1 if Tri.etapes > MAX_ETAPES or not uneEtape() then Tri.actif = false self:Hide() Recherche.Tout() end end) function Tri.Lancer() if Tri.actif then return end if CursorHasItem() then ClearCursor() end construireRangs() Tri.actif = true Tri.etapes = 0 tempsDepuisEtape = 0 horloge:Show() end -- ------------------------------------------- le champ et le bouton de tri local champ = CreateFrame("EditBox", "ForeverUIBagSearchBox", UIParent, "InputBoxTemplate") champ:SetWidth(R.champLargeur) champ:SetHeight(R.champHauteur) champ:SetAutoFocus(false) champ:SetMaxLetters(15) champ:SetTextInsets(16, 20, 0, 0) champ:Hide() local loupe = champ:CreateTexture(nil, "OVERLAY") ForeverUI.SetAtlas(loupe, "common-search-magnifyingglass", true) loupe:SetWidth(10) loupe:SetHeight(10) loupe:SetPoint("LEFT", champ, "LEFT", 1, -1) local invite = champ:CreateFontString(nil, "ARTWORK", "GameFontDisableSmall") invite:SetPoint("LEFT", champ, "LEFT", 16, 0) invite:SetText(SEARCH or "Rechercher") local effacer = CreateFrame("Button", nil, champ) effacer:SetWidth(17) effacer:SetHeight(17) effacer:SetPoint("RIGHT", champ, "RIGHT", -3, 0) effacer:Hide() local croix = effacer:CreateTexture(nil, "ARTWORK") ForeverUI.SetAtlas(croix, "common-search-clearbutton", true) croix:SetWidth(10) croix:SetHeight(10) croix:SetPoint("TOPLEFT", effacer, "TOPLEFT", 3, -3) croix:SetAlpha(0.5) effacer:SetScript("OnEnter", function() croix:SetAlpha(1) end) effacer:SetScript("OnLeave", function() croix:SetAlpha(0.5) end) effacer:SetScript("OnClick", function() champ:SetText("") champ:ClearFocus() end) local function majChamp() local texte = champ:GetText() or "" if texte == "" and not champ:HasFocus() then invite:Show() else invite:Hide() end if texte == "" then effacer:Hide() else effacer:Show() end end champ:SetScript("OnTextChanged", function(self) majChamp() Recherche.Set(self:GetText()) end) champ:SetScript("OnEditFocusGained", majChamp) champ:SetScript("OnEditFocusLost", majChamp) champ:SetScript("OnEscapePressed", function(self) self:ClearFocus() end) champ:SetScript("OnEnterPressed", function(self) self:ClearFocus() end) -- RELEVE -- BagSearch_OnChar : quatre caracteres identiques a la suite et le -- champ rend la main, pour ne pas bloquer un joueur qui martele une touche. champ:SetScript("OnChar", function(self) local texte = self:GetText() or "" if string.len(texte) >= 4 then local repete = true for i = 1, 3 do if string.sub(texte, -i, -i) ~= string.sub(texte, -1 - i, -1 - i) then repete = false break end end if repete then self:ClearFocus() end end end) local boutonTri = CreateFrame("Button", "ForeverUIBagSortButton", UIParent) boutonTri:SetWidth(R.triLargeur) boutonTri:SetHeight(R.triHauteur) boutonTri:Hide() -- PIEGE 3.3.5. SetNormalTexture et ses soeurs ne prennent qu'un CHEMIN de -- fichier ; leur passer un objet texture, comme le fait le client moderne, -- leve une erreur -- et une erreur au premier niveau d'un fichier abandonne -- TOUT ce qui suit. On pose donc le chemin de la feuille, puis on regle -- l'atlas sur la texture que le bouton vient de creer. local function poserEtat(bouton, poser, obtenir, atlas, largeur, hauteur, add) local e = ForeverUI.AtlasEntry(atlas) if not e then return nil end bouton[poser](bouton, e[1]) local texture = bouton[obtenir](bouton) if not texture then return nil end ForeverUI.SetAtlas(texture, atlas, true) texture:ClearAllPoints() if largeur then texture:SetWidth(largeur) texture:SetHeight(hauteur) texture:SetPoint("CENTER") else texture:SetAllPoints(bouton) end if add then texture:SetBlendMode("ADD") end return texture end poserEtat(boutonTri, "SetNormalTexture", "GetNormalTexture", "bags-button-autosort-up") poserEtat(boutonTri, "SetPushedTexture", "GetPushedTexture", "bags-button-autosort-down") -- Le survol est un fichier du client, pas un element d'atlas : 24 x 23 centre. boutonTri:SetHighlightTexture(SURVOL_CARRE) local triSurvol = boutonTri:GetHighlightTexture() if triSurvol then triSurvol:SetBlendMode("ADD") triSurvol:ClearAllPoints() triSurvol:SetWidth(24) triSurvol:SetHeight(23) triSurvol:SetPoint("CENTER") end boutonTri:SetScript("OnClick", function() Tri.Lancer() end) boutonTri:SetScript("OnEnter", function(self) GameTooltip:SetOwner(self, "ANCHOR_LEFT") GameTooltip:SetText(RANGER, 1, 1, 1) GameTooltip:AddLine(RANGER_AIDE, nil, nil, nil, true) GameTooltip:Show() end) boutonTri:SetScript("OnLeave", function() GameTooltip:Hide() end) -- ------------------------------------------------------------ les mesures -- RELEVE -- ContainerFrameMixin:GetRows, CalculateWidth, CalculateHeight, -- GetPaddingHeight, CalculateExtraHeight, et leurs surcharges du sac a dos. -- C'est LE calcul qui fait suivre la fenetre a son contenu : la hauteur se -- deduit du nombre de rangees, jamais d'une somme d'ancrages. local function mesures(cadre) local taille = cadre.size or 0 local m = { sacADos = cadre:GetID() == 0 } m.rangees = math.ceil(taille / R.colonnes) -- GetRows() m.grille = m.rangees * R.emplacement + (m.rangees - 1) * R.ecartCases -- GetPaddingHeight() : le bas du premier bouton, plus le titre et le -- comble ; le sac a dos ajoute la bande du champ de recherche. m.comble = R.premierBoutonY + R.entete if m.sacADos then m.comble = m.comble + R.bandeRecherche end -- CalculateExtraHeight() : la bourse, sur le sac a dos seulement, plus -- le segment des monnaies suivies quand il y en a. m.jetons = m.sacADos and ForeverUI.BagsWatchedCount() or 0 m.extra = m.sacADos and R.bourseHauteur or 0 if m.jetons > 0 then m.extra = m.extra + R.jetonHauteur + R.jetonEcart end m.hauteur = m.grille + m.comble + m.extra -- CalculateHeight() m.largeur = R.largeur -- CalculateWidth() return m end -- RELEVE -- ContainerFrameMixin:UpdateSearchBox : les deux ne se montrent que-- RELEVE -- ContainerFrameMixin:UpdateSearchBox : les deux ne se montrent que -- sur le sac principal, le champ en TOPLEFT (42, -37) et le bouton en -- TOPRIGHT (-9, -34). local function poserOutils() local hote for _, cadre in ipairs(cadres) do if cadre:IsShown() and cadre:GetID() == 0 then hote = cadre break end end if not hote then champ:Hide() boutonTri:Hide() -- BagSearch_OnHide : plus de barre visible, la recherche s'efface. if champ:GetText() ~= "" then champ:SetText("") end return end -- Les deux sont ancres en HAUT, comme dans la source : ils occupent le -- comble, dont la hauteur est fixe. C'est la formule de CalculateHeight -- qui adapte la fenetre, pas ces deux ancrages. champ:SetParent(hote) champ:ClearAllPoints() champ:SetWidth(R.champLargeur) champ:SetHeight(R.champHauteur) champ:SetPoint("TOPLEFT", hote, "TOPLEFT", R.champX, R.champY) champ:Show() boutonTri:SetParent(hote) boutonTri:ClearAllPoints() boutonTri:SetWidth(R.triLargeur) boutonTri:SetHeight(R.triHauteur) boutonTri:SetPoint("TOPRIGHT", hote, "TOPRIGHT", R.triX, R.triY) boutonTri:Show() end -- L'encadre de la bourse : deux bouts et un milieu tendu, 17 de haut. local function habillerBourse(bourse) if not bourse or bourse.foreverBorde then return end bourse:SetHeight(R.bourseHauteur) local gauche = bourse:CreateTexture(nil, "BACKGROUND") if not ForeverUI.SetAtlas(gauche, "common-coinbox-left", true) then gauche:Hide() return end gauche:SetWidth(R.bourseCadre / 2) gauche:SetHeight(R.bourseCadre) gauche:SetPoint("LEFT", bourse, "LEFT", 0, 0) local droite = bourse:CreateTexture(nil, "BACKGROUND") ForeverUI.SetAtlas(droite, "common-coinbox-right", true) droite:SetWidth(R.bourseCadre / 2) droite:SetHeight(R.bourseCadre) droite:SetPoint("RIGHT", bourse, "RIGHT", 0, 0) local milieu = bourse:CreateTexture(nil, "BACKGROUND") ForeverUI.SetAtlas(milieu, "_common-coinbox-center", true) milieu:SetPoint("TOPLEFT", gauche, "TOPRIGHT") milieu:SetPoint("BOTTOMRIGHT", droite, "BOTTOMLEFT") bourse.foreverBorde = true end -- ------------------------------------------- les monnaies suivies, sous la bourse -- -- CE QUE 3.3.5 DONNE. GetBackpackCurrencyInfo(i) rend nom, compte, -- typeSpecial, icone et identifiant d'objet, pour i de 1 a -- MAX_WATCHED_TOKENS -- trois. Le client s'en sert lui-meme dans -- BackpackTokenFrame_Update. -- -- DEUX MONNAIES ONT UNE ICONE A PART, la meme regle que l'onglet des -- monnaies : typeSpecial 1 les points d'arene, 2 les points d'honneur, ceux- -- ci suivant la faction et rognes a 0,03125 .. 0,59375. -- -- LE SEGMENT DU CLIENT SE TAIT. BackpackTokenFrame a son propre art et, pire, -- ManageBackpackTokenFrame REPOSE LA HAUTEUR du sac -- BACKPACK_HEIGHT plus -- 22 -- ce qui defait la notre. On le masque, et on se greffe sur cette -- fonction pour repasser derriere elle. local JETON_ARENE = "Interface\\PVPFrame\\PVP-ArenaPoints-Icon" local JETON_HONNEUR = "Interface\\TargetingFrame\\UI-PVP-%s" local JETON_COIN, JETON_COTE = 0.03125, 0.59375 -- LA TAILLE DES CHIFFRES DU SEGMENT. -- -- A LA DEMANDE, et non d'apres la source : camelot ecrit son compte en -- GameFontHighlightSmall. Ici c'est GameFontHighlight, d'un cran au-dessus. -- Celui de la BOURSE ne bouge pas : il appartient au cadre d'argent du -- client, que nous ne touchons pas. -- -- ET LE COMPTE SE CENTRE. camelot l'ancre par son TOPLEFT, ce qui convient a -- une police plus petite que le jeton ; avec celle-ci, le texte depassait -- vers le bas et ne s'alignait plus sur l'icone. Deux ancres horizontales -- -- LEFT et RIGHT -- le bornent comme avant ET le centrent en hauteur. local JETON_POLICE = "GameFontHighlight" local function lireJetons() local liste = {} local maximum = MAX_WATCHED_TOKENS or 3 for rang = 1, maximum do local nom, compte, special, icone = nil, nil, nil, nil if GetBackpackCurrencyInfo then nom, compte, special, icone = GetBackpackCurrencyInfo(rang) end if nom then liste[#liste + 1] = { nom = nom, compte = compte or 0, special = special, icone = icone, } end end return liste end -- COMBIEN DE MONNAIES SUIVIES. La hauteur de la fenetre en depend : elle se -- calcule AVANT que le segment ne soit pose, donc depuis les donnees et non -- depuis ce qui est a l'ecran. function ForeverUI.BagsWatchedCount() return #lireJetons() end local function poserIconeJeton(texture, donnees) if donnees.special == 1 then texture:SetTexture(JETON_ARENE) texture:SetTexCoord(0, 1, 0, 1) elseif donnees.special == 2 then local faction = UnitFactionGroup and UnitFactionGroup("player") if faction then texture:SetTexture(string.format(JETON_HONNEUR, faction)) texture:SetTexCoord(JETON_COIN, JETON_COTE, JETON_COIN, JETON_COTE) else texture:SetTexture("") texture:SetTexCoord(0, 1, 0, 1) end else texture:SetTexture(donnees.icone or "") texture:SetTexCoord(0, 1, 0, 1) end end local function monterSegment(cadre) if cadre.foreverSegment then return cadre.foreverSegment end local segment = CreateFrame("Frame", "ForeverUIBagTokens", cadre) segment:SetHeight(R.jetonHauteur) -- L'ENCADRE : deux bouts de 8 x 17 et un milieu tendu, le meme -- decoupage que la bourse. local gauche = segment:CreateTexture(nil, "BACKGROUND") ForeverUI.SetAtlas(gauche, "common-currencybox-left", true) gauche:SetWidth(R.jetonCote) gauche:SetHeight(R.jetonHauteur) gauche:SetPoint("LEFT", segment, "LEFT", 0, 0) local droite = segment:CreateTexture(nil, "BACKGROUND") ForeverUI.SetAtlas(droite, "common-currencybox-right", true) droite:SetWidth(R.jetonCote) droite:SetHeight(R.jetonHauteur) droite:SetPoint("RIGHT", segment, "RIGHT", 0, 0) local milieu = segment:CreateTexture(nil, "BACKGROUND") ForeverUI.SetAtlas(milieu, "_common-currencybox-center", true) milieu:SetPoint("TOPLEFT", gauche, "TOPRIGHT") milieu:SetPoint("BOTTOMRIGHT", droite, "BOTTOMLEFT") segment.jetons = {} for rang = 1, (MAX_WATCHED_TOKENS or 3) do local jeton = CreateFrame("Button", "ForeverUIBagToken" .. rang, segment) jeton:SetWidth(R.jetonLargeur) jeton:SetHeight(R.jetonPiece) local icone = jeton:CreateTexture(nil, "ARTWORK") icone:SetWidth(R.jetonPiece) icone:SetHeight(R.jetonPiece) icone:SetPoint("RIGHT", jeton, "RIGHT", R.jetonIconeX, R.jetonIconeY) jeton.icone = icone local compte = jeton:CreateFontString(nil, "ARTWORK", JETON_POLICE) compte:SetJustifyH("RIGHT") compte:SetPoint("LEFT", jeton, "LEFT", 0, 0) compte:SetPoint("RIGHT", icone, "LEFT", 0, 0) jeton.compte = compte -- Les jetons s'enchainent vers la GAUCHE depuis le bord droit. if rang == 1 then jeton:SetPoint("RIGHT", segment, "RIGHT", R.jetonDepartX, R.jetonDepartY) else jeton:SetPoint("RIGHT", segment.jetons[rang - 1], "LEFT", 0, 0) end jeton:Hide() segment.jetons[rang] = jeton end cadre.foreverSegment = segment return segment end -- LE SEGMENT SUIT LES DONNEES : rend le nombre de monnaies posees. local function majSegment(cadre) local segment = monterSegment(cadre) local liste = lireJetons() for rang, jeton in ipairs(segment.jetons) do local donnees = liste[rang] if donnees then poserIconeJeton(jeton.icone, donnees) jeton.compte:SetText(donnees.compte) jeton:Show() else jeton:Hide() end end if #liste > 0 then segment:Show() else segment:Hide() end return #liste end -- RELEVE -- 3.3.5 ContainerFrame_GenerateFrame : le premier bouton porte le -- coin BAS DROIT de la grille, les suivants s'enchainent vers la gauche puis -- vers le haut, avec 4 px entre deux rangees. camelot en met 5 : on repose -- donc aussi le premier bouton de chaque rangee. -- RELEVE -- ContainerFrameMixin:UpdateFrameSize, GetInitialItemAnchor, -- GetAnchorLayout (grille BottomRightToTopLeft) et, pour le sac a dos, -- ContainerFrameBackpackMixin:GetInitialItemAnchor + UpdateCurrencyFrames. -- RELEVE -- ContainerFrameMixin:UpdateName et UpdateMiscellaneousFrames. local function majEntete(cadre) -- UpdateName : le nom vient du SAC, il n'est jamais ecrit ici. Le -- trousseau n'en a pas -- GetBagName(-2) ne rend rien -- et on reprend -- alors celui que le client a pose sur son propre titre. local titre = cadre.foreverTitre if titre then local nomDuSac = GetBagName and GetBagName(cadre:GetID()) if not nomDuSac or nomDuSac == "" then local ancienTitre = _G[cadre:GetName() .. "Name"] nomDuSac = ancienTitre and ancienTitre:GetText() or "" end titre:SetText(nomDuSac) end -- UpdateMiscellaneousFrames : sac a dos, trousseau, ou l'icone de l'objet -- que le sac est. local portrait = cadre.foreverPortrait if portrait then local id = cadre:GetID() local texture if id == 0 then texture = PORTRAIT_SAC_A_DOS elseif id == (KEYRING_CONTAINER or -2) then texture = PORTRAIT_TROUSSEAU elseif ContainerIDToInventoryID then texture = GetInventoryItemTexture("player", ContainerIDToInventoryID(id)) end portrait:SetTexture(texture) -- LE ROGNAGE DU CLIENT. Une icone de WoW est un carre de 64 px -- borde de 4 px sombres ; 4 / 64 = 0,0625. Le client retire cette -- bordure partout ou il pose une icone dans un anneau, ce qui fait -- remplir le disque par le dessin au lieu de son cadre. portrait:SetTexCoord(BORDURE_ICONE, 1 - BORDURE_ICONE, BORDURE_ICONE, 1 - BORDURE_ICONE) end end local function poserGrille(cadre) majEntete(cadre) local taille = cadre.size or 0 if taille <= 1 then return -- le cadeau a un seul emplacement garde sa forme end local nom = cadre:GetName() local m = mesures(cadre) local bourse = _G[nom .. "MoneyFrame"] -- UpdateFrameSize() : la fenetre prend la mesure calculee. cadre:SetScale(R.echelle) cadre:SetWidth(m.largeur) cadre:SetHeight(m.hauteur) -- UpdateFrameSize appelle NineSliceUtil.UpdateCornerCropping juste apres -- SetSize : sur une fenetre courte, les coins se chevauchent sinon. if ForeverUI.UpdatePanelCorners then ForeverUI.UpdatePanelCorners(cadre) end -- TEMOIN. On relit la hauteur DANS LA FOULEE. Deux cas se distinguent -- ainsi, et un seul chiffre les separe : -- la relecture ne rend pas ce qu'on vient de poser -> ce sont les -- ancrages du cadre qui decident de sa hauteur, SetHeight est ignore -- la relecture est bonne mais /fui sacs montre autre chose plus tard -- -> quelqu'un repose la taille apres nous cadre.foreverDemande = m.hauteur cadre.foreverRelue = cadre:GetHeight() cadre.foreverAncrages = cadre:GetNumPoints() -- UpdateCurrencyFrames() : le segment des monnaies prend le bas, la -- bourse monte au-dessus de lui, et la grille s'accroche a la bourse. -- Sans monnaie suivie, la bourse reprend le bas et rien ne change. if m.sacADos and bourse then habillerBourse(bourse) local poses = majSegment(cadre) local segment = cadre.foreverSegment bourse:ClearAllPoints() if poses > 0 and segment then segment:ClearAllPoints() segment:SetPoint("BOTTOMLEFT", cadre, "BOTTOMLEFT", R.jetonCote, R.bourseBas) segment:SetPoint("BOTTOMRIGHT", cadre, "BOTTOMRIGHT", -R.jetonCote, R.bourseBas) bourse:SetPoint("BOTTOMLEFT", segment, "TOPLEFT", 0, R.jetonEcart) bourse:SetPoint("BOTTOMRIGHT", segment, "TOPRIGHT", 0, R.jetonEcart) else bourse:SetPoint("BOTTOMLEFT", cadre, "BOTTOMLEFT", R.bourseCote, R.bourseBas) bourse:SetPoint("BOTTOMRIGHT", cadre, "BOTTOMRIGHT", -R.bourseCote, R.bourseBas) end bourse:Show() end -- GetAnchorLayout() : BottomRightToTopLeft, 4 colonnes, 5 d'ecart. Toute -- la grille est reposee, 3.3.5 employant ses propres ecarts. for index = 1, taille do local bouton = _G[nom .. "Item" .. index] if bouton then bouton:SetWidth(R.emplacement) bouton:SetHeight(R.emplacement) bouton:ClearAllPoints() if index == 1 then if m.sacADos and bourse then -- ContainerFrameBackpackMixin:GetInitialItemAnchor() bouton:SetPoint("BOTTOMRIGHT", bourse, "TOPRIGHT", 0, R.ecartBourseGrille) else -- ContainerFrameMixin:GetInitialItemAnchor() bouton:SetPoint("BOTTOMRIGHT", cadre, "BOTTOMRIGHT", R.premierBoutonX, R.premierBoutonY) end elseif math.fmod(index - 1, R.colonnes) == 0 then bouton:SetPoint("BOTTOMRIGHT", _G[nom .. "Item" .. (index - R.colonnes)], "TOPRIGHT", 0, R.ecartCases) else bouton:SetPoint("BOTTOMRIGHT", _G[nom .. "Item" .. (index - 1)], "BOTTOMLEFT", -R.ecartCases, 0) end end end end -- RELEVE -- UpdateContainerFrameAnchors (containerframe.lua, 1372-1401). -- Les sacs s'empilent du bas vers le haut, CONTAINER_SPACING entre deux, et -- passent en colonne a gauche quand l'ecran est plein. local function largeurBarresDroite() -- EditModeUtil:GetRightActionBarWidth() n'existe pas en 3.3.5 : ses -- barres de droite sont MultiBarRight et MultiBarLeft. local largeur = 0 for _, nomBarre in ipairs({ "MultiBarRight", "MultiBarLeft" }) do local barre = _G[nomBarre] if barre and barre:IsShown() then largeur = largeur + barre:GetWidth() end end return largeur end local function sacsOuverts() -- Le client tient l'ordre d'empilement dans ContainerFrame1.bags, ce que -- la source lit avec GetBagsShown. local liste = {} if ContainerFrame1 and ContainerFrame1.bags then for _, nomCadre in ipairs(ContainerFrame1.bags) do local cadre = _G[nomCadre] if cadre and cadre:IsShown() then table.insert(liste, cadre) end end end if #liste == 0 then for _, cadre in ipairs(cadres) do if cadre:IsShown() and (cadre.size or 0) > 0 then table.insert(liste, cadre) end end end return liste end local function poserSacs() local hauteurEcran = GetScreenHeight() / R.echelle local decalageX = (largeurBarresDroite() + R.bordDroit) / R.echelle local decalageY = R.bordBas / R.echelle local libre = hauteurEcran - decalageY local precedent, premierDeColonne for index, cadre in ipairs(sacsOuverts()) do cadre:SetScale(R.echelle) cadre:ClearAllPoints() if index == 1 then cadre:SetPoint("BOTTOMRIGHT", cadre:GetParent(), "BOTTOMRIGHT", -decalageX, decalageY) premierDeColonne = cadre elseif libre < cadre:GetHeight() then libre = hauteurEcran - decalageY cadre:SetPoint("BOTTOMRIGHT", premierDeColonne, "BOTTOMLEFT", R.ecartColonnes, 0) premierDeColonne = cadre else cadre:SetPoint("BOTTOMRIGHT", precedent, "TOPRIGHT", 0, R.ecartSacs) end precedent = cadre libre = libre - cadre:GetHeight() end end ForeverUI.BagsStack = poserSacs -- LE RATTRAPAGE. 3.3.5 repose la taille de ses cadres de sac a des moments -- que les accroches ne couvrent pas toutes (le client ouvre, ferme et -- reagence ses treize cadres entre eux). Un seul passage a l'image suivante -- relit la mesure et la repose si elle a bouge ; il ne se redemande que -- depuis les accroches, jamais depuis lui-meme, donc il ne tourne pas en -- boucle contre le client. local rattrapage = CreateFrame("Frame", "ForeverUIBagsRecheck") rattrapage:Hide() rattrapage:SetScript("OnUpdate", function(self) self:Hide() local refaire = false for _, cadre in ipairs(cadres) do if cadre:IsShown() and (cadre.size or 0) > 1 then local m = mesures(cadre) if math.abs(cadre:GetHeight() - m.hauteur) > 0.5 or math.abs(cadre:GetWidth() - m.largeur) > 0.5 then cadre.foreverDefaite = (cadre.foreverDefaite or 0) + 1 poserGrille(cadre) refaire = true end end end if refaire then -- Une hauteur a change : l'empilement en depend. poserSacs() end end) local function demanderRattrapage() rattrapage:Show() end ForeverUI.BagsRecheck = demanderRattrapage ForeverUI.BagsLayout = poserOutils -- ------------------------------------------------------------- accroches local function habillerTout() for index = 1, NB_CADRES do habillerCadre(_G["ContainerFrame" .. index]) end end habillerTout() majChamp() if hooksecurefunc then -- Le client repose ses propres morceaux a chaque ouverture de sac. hooksecurefunc("ContainerFrame_GenerateFrame", function(cadre) habillerCadre(cadre) poserGrille(cadre) poserOutils() Recherche.Tout() demanderRattrapage() end) -- ContainerFrame_Update est rappele a chaque mise a jour de sac. Le -- client y repose ses propres morceaux : la grille est reposee ensuite, -- sinon la taille d'origine revient des le premier objet ramasse. hooksecurefunc("ContainerFrame_Update", function(cadre) poserGrille(cadre) Recherche.Appliquer(cadre) demanderRattrapage() end) hooksecurefunc("ContainerFrame_OnHide", function() poserOutils() end) -- Le client remet son echelle et repose les cadres a chaque -- reagencement : on repasse derriere lui, taille comprise. hooksecurefunc("updateContainerFrameAnchors", function() for _, cadre in ipairs(cadres) do if cadre:GetScale() ~= R.echelle then cadre:SetScale(R.echelle) end if cadre:IsShown() then poserGrille(cadre) end end -- Le client vient d'empiler ses sacs avec SES ecarts ; on repose -- ceux de camelot par-dessus. poserSacs() demanderRattrapage() end) end -- LE SEGMENT DU CLIENT SE TAIT, ET SA FONCTION DE TAILLE AUSSI. -- -- ManageBackpackTokenFrame reparente BackpackTokenFrame dans le sac et -- REPOSE LA HAUTEUR de celui-ci -- BACKPACK_HEIGHT plus 22 -- ce qui defait -- la notre. On masque son segment et on repasse derriere elle. local function etoufferSegmentDuClient() local ancien = _G["BackpackTokenFrame"] if ancien then ancien:Hide() if ancien.EnableMouse then ancien:EnableMouse(false) end end end if hooksecurefunc and type(_G["ManageBackpackTokenFrame"]) == "function" then hooksecurefunc("ManageBackpackTokenFrame", function() etoufferSegmentDuClient() for _, cadre in ipairs(cadres) do if cadre:IsShown() and cadre:GetID() == 0 then poserGrille(cadre) end end end) end -- COCHER "Show on Backpack" PASSE PAR SetCurrencyBackpack, et par rien -- d'autre : c'est ce que font les deux cases du volet droit de l'onglet des -- monnaies, et le clic modifie de la liste du client. On s'y greffe pour -- que le sac suive dans la foulee. if hooksecurefunc and type(_G["SetCurrencyBackpack"]) == "function" then hooksecurefunc("SetCurrencyBackpack", function() for _, cadre in ipairs(cadres) do if cadre:IsShown() and cadre:GetID() == 0 then poserGrille(cadre) end end end) end local veilleur = CreateFrame("Frame", "ForeverUIBagsWatcher") veilleur:RegisterEvent("PLAYER_ENTERING_WORLD") veilleur:RegisterEvent("BAG_UPDATE") -- La quantite d'une monnaie suivie change : le segment la montre. veilleur:RegisterEvent("CURRENCY_DISPLAY_UPDATE") veilleur:SetScript("OnEvent", function() habillerTout() etoufferSegmentDuClient() for _, cadre in ipairs(cadres) do if cadre:IsShown() then poserGrille(cadre) end end poserOutils() Recherche.Tout() demanderRattrapage() end) -- Refaire toute la mise en page apres un changement de reglage. function ForeverUI.BagsApply() for _, cadre in ipairs(cadres) do poserGrille(cadre) end poserOutils() Recherche.Tout() end -- Le diagnostic ne dit que ce que le JEU montre : pour CHAQUE sac ouvert, la -- hauteur que la formule donne et celle que le cadre porte vraiment. Si les -- deux different, quelque chose repose la taille apres nous, et la ligne le -- dit au lieu de le laisser deviner. ForeverUI.BagsDebug = function() local ouverts = 0 for _, cadre in ipairs(cadres) do if cadre:IsShown() and (cadre.size or 0) > 0 then ouverts = ouverts + 1 local nom = cadre:GetName() local m = mesures(cadre) local reelleL, reelleH = cadre:GetWidth(), cadre:GetHeight() local accord = (math.abs(reelleH - m.hauteur) < 0.5) and (math.abs(reelleL - m.largeur) < 0.5) DEFAULT_CHAT_FRAME:AddMessage(string.format( "|cff66ccffForeverUI|r %s (sac %d) : %d cases, %d rangees", nom, cadre:GetID(), cadre.size or 0, m.rangees)) DEFAULT_CHAT_FRAME:AddMessage(string.format( " calcule %d x %d = grille %d + comble %d + extra %d | le cadre porte %.0f x %.0f %s", m.largeur, m.hauteur, m.grille, m.comble, m.extra, reelleL, reelleH, accord and "|cff44ff44ACCORD|r" or "|cffff4444DESACCORD|r")) if not accord then -- Le temoin dit LEQUEL des deux cas on tient. DEFAULT_CHAT_FRAME:AddMessage(string.format( " temoin : pose %s, relu aussitot %s, defaite %s fois -> %s", tostring(cadre.foreverDemande), tostring(cadre.foreverRelue), tostring(cadre.foreverDefaite or 0), (cadre.foreverRelue and cadre.foreverDemande and math.abs(cadre.foreverRelue - cadre.foreverDemande) < 0.5) and "|cffff4444quelqu'un repose la taille APRES nous|r" or "|cffff4444les ancrages du cadre imposent sa hauteur|r")) local lignes = string.format(" %d ancrage(s) :", cadre:GetNumPoints()) for index = 1, cadre:GetNumPoints() do local point, cible, pointCible, x, y = cadre:GetPoint(index) lignes = lignes .. string.format(" [%s sur %s de %s, %d, %d]", tostring(point), tostring(pointCible), cible and (cible.GetName and cible:GetName() or "?") or "l'ecran", x or 0, y or 0) end DEFAULT_CHAT_FRAME:AddMessage(lignes) end end end if ouverts == 0 then DEFAULT_CHAT_FRAME:AddMessage( "|cff66ccffForeverUI|r aucun sac ouvert : ouvrez-en un puis refaites /fui sacs") end DEFAULT_CHAT_FRAME:AddMessage(string.format( " cadres habilles %d | case %.0f | champ visible=%s", #cadres, R.emplacement, tostring(champ:IsShown()))) -- LE SEGMENT DES MONNAIES SUIVIES : ce que le client donne, et ce qu'on -- en pose. Le segment du CLIENT doit etre muet. local suivies = lireJetons() local noms = {} for _, jeton in ipairs(suivies) do noms[#noms + 1] = string.format("%s=%s", jeton.nom, tostring(jeton.compte)) end local segment = _G["ForeverUIBagTokens"] local ancien = _G["BackpackTokenFrame"] DEFAULT_CHAT_FRAME:AddMessage(string.format( " monnaies suivies %d/%d : %s", #suivies, MAX_WATCHED_TOKENS or 3, (#noms > 0) and table.concat(noms, ", ") or "aucune")) DEFAULT_CHAT_FRAME:AddMessage(string.format( " segment : le notre visible=%s | celui du client visible=%s", tostring(segment and segment:IsShown()), tostring(ancien and ancien:IsShown()))) -- Tous les reglages, par ordre alphabetique : la liste ne peut pas se -- demoder quand un reglage apparait ou disparait. local cles = {} for cle in pairs(R) do table.insert(cles, cle) end table.sort(cles) local ligne = "" for _, cle in ipairs(cles) do ligne = ligne .. string.format("%s=%s ", cle, tostring(R[cle])) if string.len(ligne) > 80 then DEFAULT_CHAT_FRAME:AddMessage(" " .. ligne) ligne = "" end end if ligne ~= "" then DEFAULT_CHAT_FRAME:AddMessage(" " .. ligne) end end -- Changer un reglage en jeu, pour essayer avant de le figer dans le fichier. function ForeverUI.BagsSet(cle, valeur) if R[cle] == nil then DEFAULT_CHAT_FRAME:AddMessage("|cff66ccffForeverUI|r reglage inconnu : " .. tostring(cle)) return false end local nombre = tonumber(valeur) if not nombre then DEFAULT_CHAT_FRAME:AddMessage("|cff66ccffForeverUI|r valeur attendue : un nombre") return false end R[cle] = nombre ForeverUI.BagsApply() DEFAULT_CHAT_FRAME:AddMessage(string.format( "|cff66ccffForeverUI|r sacs : %s = %s", cle, tostring(nombre))) return true end