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

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

2187 lines
75 KiB
Lua

-- ForeverUI : le journal de quetes, volet LISTE de la carte du monde (etape 2
-- du chantier docs/CARTE_ET_JOURNAL.md).
--
-- CHEZ CAMELOT, LE JOURNAL N'EST PAS UNE FENETRE. blizzard_uipanels_game.toc ne
-- charge pas QuestLogFrame pour camelot : c'est mainline/QuestMapFrame, un
-- volet de 330 accroche a droite de la carte (blizzard_worldmap.lua,
-- AttachQuestLog : TOPRIGHT (-3, -25), BOTTOMRIGHT (-3, 3), strate HIGH), et L
-- ouvre la carte avec lui (questlogownermixin.lua).
--
-- RELEVE -- mainline/questmapframe.xml / .lua, camelot/questmapframeoverrides.lua,
-- camelot/questmapframeutils.lua, sharedxml/listtemplates.xml / .lua :
-- QuestsFrame du coin haut-gauche du volet a (-22, +9) du bas-droit
-- QuestScrollFrame TOPLEFT (0, -29), BOTTOMRIGHT ; barre MinimalScrollBar
-- a (8, 2) / (8, -4) de son bord droit
-- Background QuestLog-main-background, taille de l'atlas, rognee a
-- la hauteur du cadre (ResizeBackground) ; liste vide :
-- QuestLog-empty-quest-background + EmptyText Game16Font
-- SearchBox 200 x 20 (camelot), BOTTOMLEFT sur son TOPLEFT (6, 7)
-- QuestLogCount 100 x 20 a droite de la recherche (3, 0), texte
-- GameFontNormalSmall a (-5, -5) du bord droit
-- SettingsDropdown 15 x 16, TOPRIGHT (19, 25) ; une case : objectifs
-- BorderFrame questlog-frame de (-3, 7) a (3, -6), filigrane en haut,
-- ombre questlog-frame-gradient-bottom en bas
-- Lignes, reconstruites a chaque mise a jour, empilees de haut en bas
-- (VerticalLayoutFrame) :
-- en-tete 289 x 22, a x = 9 ; ListHeaderVisualTemplate : fond
-- common-button-list-collapseExpand, texte Game15Font_Shadow
-- a (8, 0), bouton +/- 20 x 20 a RIGHT (-6) ; titre gris
-- (DISABLED_FONT_COLOR), blanc au survol
-- quete 290 de large, a x = 0 ; texte GameFontNormalLeft a
-- (31, -8) ; case de suivi 14 x 14 TOPRIGHT (0, -8) ;
-- hauteur = 8 + texte + objectifs + 6
-- objectif ObjectiveFont, tiret QUEST_DASH puis 205 de texte
-- Ecarts entre lignes (QuestLogQuests_UpdateButtonSpacing) : premier en-tete
-- 8, en-tete apres quete 4, en-tete apres en-tete 6, quete apres en-tete 2,
-- quete apres quete -3.
-- Ce que camelot change : prefixe [niveau] ou [niveau+] toujours ; "(Elite)"
-- a droite ; compteur montre ; pas d'icone de type de quete.
--
-- CE QUE 3.3.5 DONNE : GetQuestLogTitle(i) -> title, level, questTag,
-- suggestedGroup, isHeader, isCollapsed, isComplete (>0 terminee, <0 echouee),
-- isDaily. Un en-tete REPLIE retire ses quetes de la liste : pour chercher, on
-- deplie tout, puis on rend a chaque en-tete son etat -- retenu par son NOM.
--
-- LES REPERES ET LA CARTE (demande du 2026-09-25). camelot pose devant chaque
-- quete son repere (POIButton) et, au clic, montre la quete sur la carte
-- (QuestMapFrame_ShowQuestDetails : la carte passe sur celle de la quete).
-- WotLK a tout ce qu'il faut : apres WorldMapFrame_UpdateQuests, chaque quete
-- qui a un repere sur la carte affichee a son cadre WorldMapQuestFrame<n>,
-- numerote comme sur la carte ; QuestPOI_DisplayButton pose le meme repere
-- dans n'importe quel parent -- WotLK le fait pour sa propre liste ;
-- WorldMap_OpenToQuest met la carte sur la zone de la quete et la
-- selectionne, ce qui dessine sa zone d'objectif ; le survol d'une ligne
-- allume cette zone (DrawQuestBlob), comme la liste de WotLK.
--
-- LA PAGE D'UNE QUETE (etape 3, demande du 2026-09-25) : le clic sur une
-- quete remplace la liste par sa page -- texte, objectifs, recompenses -- et
-- un bouton Retour y ramene. Releve et ecarts : en tete de la section "la
-- page d'une quete", plus bas.
ForeverUI = ForeverUI or {}
local J = {}
ForeverUI.QuestLog = J
local L = ForeverUI.L
local V = {
largeur = 330, x = -3, haut = -25, bas = 3,
listeDroite = -22, listeBas = 9, listeHaut = -29,
enteteL = 289, enteteH = 22, enteteX = 9,
queteL = 290, queteTexteX = 31, queteTexteY = -8,
caseCote = 14, caseY = -8,
objectifL = 220, objectifTexteL = 205,
rechercheL = 200, rechercheH = 20, rechercheX = 6, rechercheY = 7,
compteurL = 100, compteurH = 20, compteurX = 3,
reglageL = 15, reglageH = 16, reglageX = 19, reglageY = 25,
barreX = 8, barreHaut = 2, barreBas = -4,
pas = 16,
}
-- Les ecarts de QuestLogQuests_UpdateButtonSpacing, pour les types que 3.3.5
-- connait.
local ECART = {
enteteApresRien = 8, enteteApresEntete = 6, enteteApresQuete = 4,
queteApresEntete = 2, queteApresQuete = -3,
}
-- Les couleurs de camelot : GlobalColor.db2 du client camelot, et
-- blizzard_framexmlbase/constants.lua pour la difficulte.
local COULEUR = {
objectif = { 0.8, 0.8, 0.8 }, -- QUEST_OBJECTIVE_FONT_COLOR
objectifSurvol = { 1, 1, 1 }, -- QUEST_OBJECTIVE_HIGHLIGHT_FONT_COLOR
enteteRepos = { 0.502, 0.502, 0.502 }, -- DISABLED_FONT_COLOR
enteteSurvol = { 1, 1, 1 }, -- HIGHLIGHT_FONT_COLOR
difficulte = {
trivial = { 0.50, 0.50, 0.50 }, standard = { 0.25, 0.75, 0.25 },
difficult = { 1.00, 0.82, 0.00 }, verydifficult = { 1.00, 0.50, 0.25 },
impossible = { 1.00, 0.10, 0.10 },
},
difficulteSurvol = {
trivial = { 0.70, 0.70, 0.70 }, standard = { 0.43, 0.93, 0.43 },
difficult = { 1.00, 1.00, 0.10 }, verydifficult = { 1.00, 0.75, 0.44 },
impossible = { 1.00, 0.40, 0.40 },
},
-- la page d'une quete : la matiere "Default" de GetMaterialTextColors
texte = { 0.18, 0.122, 0.059 }, -- DEFAULT_MATERIAL_TEXT_COLOR
titre = { 0, 0, 0 }, -- DEFAULT_MATERIAL_TITLETEXT_COLOR
ombreTitre = { 0.49, 0.35, 0.05 }, -- QuestFont_Shadow_Huge
objectifFait = { 0.2, 0.2, 0.2 }, -- QUEST_OBJECTIVE_COMPLETED_FONT_COLOR
recompense = { 0.902, 0.788, 0.671 }, -- QuestMapRewardsFont
communGris = { 0.659, 0.659, 0.659 }, -- COMMON_GRAY_COLOR
}
-- Les textes : ceux de 3.3.5 quand il les a, ceux de camelot sinon
-- (GlobalStrings.db2 du client camelot), dans Textes_<langue>.lua.
local TEXTE = {
recherche = L.QUESTLOG_SEARCH, -- SEARCH_QUEST_LOG
aucunResultat = L.QUESTLOG_NO_RESULTS, -- QUEST_LOG_NO_RESULTS
vide = L.QUESTLOG_EMPTY, -- QUEST_LOG_NO_QUESTS
compteur = L.QUESTLOG_COUNT, -- QUEST_LOG_COUNT_TEMPLATE
montrerObjectifs = L.QUESTLOG_SHOW_OBJECTIVES, -- QUEST_LOG_SHOW_OBJECTIVES
pret = L.QUESTLOG_READY, -- QUEST_WATCH_QUEST_READY
details = L.QUESTLOG_CLICK_DETAILS, -- CLICK_QUEST_DETAILS
nePlusSuivre = L.QUESTLOG_UNTRACK_QUEST, -- UNTRACK_QUEST
partagerChat = L.QUESTLOG_SHARE_IN_CHAT, -- SHARE_IN_CHAT
toutSuivre = L.QUESTLOG_TRACK_ALL, -- QUEST_LOG_TRACK_ALL
neRienSuivre = L.QUESTLOG_UNTRACK_ALL, -- QUEST_LOG_UNTRACK_ALL
-- la page d'une quete
nePlusSuivreCourt = L.QUESTLOG_UNTRACK, -- UNTRACK_QUEST_ABBREV
recompenses = QUEST_REWARDS, -- REWARDS (3.3.5 : QUEST_REWARDS)
titreEchec = L.QUESTLOG_TITLE_FAILED, -- QUEST_TITLE_FORMAT_FAILED
}
local POLICE = "Fonts\\FRIZQT__.TTF"
local function couleur(t, c)
t:SetTextColor(c[1], c[2], c[3])
end
-- ---------------------------------------------------------------- l'etat
-- ForeverUIDB n'existe qu'apres le chargement de nos variables.
local function reglages()
ForeverUIDB = ForeverUIDB or {}
ForeverUIDB.journal = ForeverUIDB.journal or {}
local r = ForeverUIDB.journal
if r.volet == nil then r.volet = true end -- questLogOpen
if r.objectifs == nil then r.objectifs = true end -- decision 4
return r
end
J.reglages = reglages
-- -------------------------------------------------------------- la difficulte
-- Le classement de WotLK, les couleurs de camelot.
local function cleDifficulte(niveau)
local c = GetQuestDifficultyColor and GetQuestDifficultyColor(niveau)
if c and QuestDifficultyColors then
for cle, valeur in pairs(QuestDifficultyColors) do
if valeur == c and COULEUR.difficulte[cle] then
return cle
end
end
end
return "difficult"
end
-- ------------------------------------------------------------- les donnees
-- Une entree du journal, avec ce que la ligne en affiche.
local function entree(index)
local titre, niveau, tag, groupe, estEntete, replie, termine, quotidienne, questID = GetQuestLogTitle(index)
if not titre then
return nil
end
return {
index = index, title = titre, level = niveau, questTag = tag,
isHeader = estEntete and true or false, isCollapsed = replie and true or false,
isComplete = termine, isDaily = quotidienne, questID = questID,
}
end
local function estElite(info)
return info.questTag == ELITE
end
-- QuestLogQuests_GetTitle + la surcharge camelot : "[" niveau ("+" si elite) "] ",
-- et devant, "[n] " si des membres du groupe ont la quete.
local function titreQuete(info)
local titre = "[" .. tostring(info.level) .. (estElite(info) and "+" or "") .. "] " .. info.title
local membres = 0
for i = 1, (GetNumPartyMembers and GetNumPartyMembers() or 0) do
if IsUnitOnQuest and IsUnitOnQuest(info.index, "party" .. i) then
membres = membres + 1
end
end
if membres > 0 then
titre = "[" .. membres .. "] " .. titre
end
return titre
end
-- ------------------------------------------------------------- la recherche
-- QuestSearcher : sans motif, sur le titre, le texte d'objectif et les
-- objectifs. Un en-tete replie cache ses quetes en 3.3.5 : on deplie tout le
-- temps de la recherche, et on rend a chaque en-tete son etat par son NOM.
local R = { texte = nil, etats = nil }
local function deplierTout()
R.etats = {}
for i = GetNumQuestLogEntries(), 1, -1 do
local info = entree(i)
if info and info.isHeader then
R.etats[info.title] = info.isCollapsed
if info.isCollapsed then
ExpandQuestHeader(i)
end
end
end
end
local function rendreEtats()
if not R.etats then
return
end
for i = GetNumQuestLogEntries(), 1, -1 do
local info = entree(i)
if info and info.isHeader and R.etats[info.title] then
CollapseQuestHeader(i)
end
end
R.etats = nil
end
local function correspond(info)
if not R.texte then
return true
end
local cherche = R.texte
if string.find(string.lower(info.title), cherche, 1, true) then
return true
end
local selection = GetQuestLogSelection()
SelectQuestLogEntry(info.index)
local _, objectifs = GetQuestLogQuestText()
SelectQuestLogEntry(selection)
if objectifs and string.find(string.lower(objectifs), cherche, 1, true) then
return true
end
for i = 1, GetNumQuestLeaderBoards(info.index) do
local texte = GetQuestLogLeaderBoard(i, info.index)
if texte and string.find(string.lower(texte), cherche, 1, true) then
return true
end
end
return false
end
function J.chercher(texte)
if texte and texte ~= "" then
if not R.texte then
deplierTout()
end
R.texte = string.lower(texte)
else
if R.texte then
rendreEtats()
end
R.texte = nil
end
J.maj()
end
-- ------------------------------------------------------ les reperes, la carte
local PARENT_REPERES = "ForeverUIQuestScrollContents"
-- La numerotation de WotLK pour la carte affichee : WorldMapFrame_GetQuestFrame
-- compte les quetes terminees a part (QUEST_POI_COMPLETE_IN) et numerote les
-- autres (QUEST_POI_NUMERIC, index - terminees).
local function reperesDeLaCarte()
local reperes = {}
local terminees = 0
for i = 1, (WorldMapFrame and WorldMapFrame.numQuests or 0) do
local f = _G["WorldMapQuestFrame" .. i]
if f and f.questId and f.questId ~= 0 then
if f.completed then
terminees = terminees + 1
reperes[f.questId] = { QUEST_POI_COMPLETE_IN, terminees }
else
reperes[f.questId] = { QUEST_POI_NUMERIC, i - terminees }
end
end
end
return reperes
end
local function queteSelectionnee()
return WORLDMAP_SETTINGS and WORLDMAP_SETTINGS.selectedQuestId
end
-- Allumer ou eteindre la zone d'une quete sur la carte, comme la liste de
-- WotLK au survol (WorldMapQuestFrame_OnEnter / _OnLeave). La quete choisie
-- garde la sienne.
local function zone(info, allumer)
if not (WorldMapBlobFrame and WorldMapBlobFrame.DrawQuestBlob and info.questID) then
return
end
if info.isComplete and info.isComplete > 0 then
return
end
if not allumer and queteSelectionnee() == info.questID then
return
end
WorldMapBlobFrame:DrawQuestBlob(info.questID, allumer and true or false)
end
-- QuestMapFrame_ShowQuestDetails, pour sa partie carte : WorldMap_OpenToQuest
-- met la carte sur la zone de la quete et la selectionne.
local function montrerSurCarte(info)
SelectQuestLogEntry(info.index)
if info.questID and WorldMap_OpenToQuest then
WorldMap_OpenToQuest(info.questID)
end
if J.surSelection then J.surSelection(info.index) end
end
J.montrerSurCarte = montrerSurCarte
-- ------------------------------------------------------------- les actions
-- _QuestLog_ToggleQuestWatch de QuestLogFrame.lua (locale au client : recopiee)
local function basculerSuivi(index)
if IsQuestWatched(index) then
RemoveQuestWatch(index)
WatchFrame_Update()
else
if GetNumQuestWatches() >= MAX_WATCHABLE_QUESTS then
UIErrorsFrame:AddMessage(format(QUEST_WATCH_TOO_MANY, MAX_WATCHABLE_QUESTS), 1.0, 0.1, 0.1, 1.0)
return
end
AddQuestWatch(index)
WatchFrame_Update()
end
end
J.basculerSuivi = basculerSuivi
-- le bouton Abandonner de QuestLogFrame.xml
local function abandonner(index)
SelectQuestLogEntry(index)
SetAbandonQuest()
local objets = GetAbandonQuestItems()
if objets then
StaticPopup_Hide("ABANDON_QUEST")
StaticPopup_Show("ABANDON_QUEST_WITH_ITEMS", GetAbandonQuestName(), objets)
else
StaticPopup_Hide("ABANDON_QUEST_WITH_ITEMS")
StaticPopup_Show("ABANDON_QUEST", GetAbandonQuestName())
end
end
local function partager(index)
SelectQuestLogEntry(index)
QuestLogPushQuest()
PlaySound("igQuestLogOpen")
end
local function enGroupe()
return (GetNumPartyMembers and GetNumPartyMembers() > 0) or (GetNumRaidMembers and GetNumRaidMembers() > 1)
end
local function menu(ancre, liste)
if ForeverUI.WorldMap and ForeverUI.WorldMap.ouvrirMenu then
ForeverUI.WorldMap.ouvrirMenu(ancre, liste)
end
end
-- QuestMapLogTitleButton_CreateContextMenu, sans le super-suivi (absent de 3.3.5)
local function menuQuete(ligne)
local index = ligne.index
local l = {}
table.insert(l, {
text = IsQuestWatched(index) and TEXTE.nePlusSuivre or TRACK_QUEST,
func = function() basculerSuivi(index) end,
})
local partageable = GetQuestLogPushable and GetQuestLogPushable(index) and enGroupe()
table.insert(l, {
text = SHARE_QUEST,
disabled = not partageable,
func = function() partager(index) end,
})
table.insert(l, {
text = TEXTE.partagerChat,
func = function()
local lien = GetQuestLink(index)
if lien and not (ChatEdit_InsertLink and ChatEdit_InsertLink(lien)) then
ChatFrame_OpenChat(lien)
end
end,
})
table.insert(l, {
text = ABANDON_QUEST,
func = function() abandonner(index) end,
})
menu(ligne, l)
end
-- QuestMapLogHeaderButton_CreateContextMenu : suivre ou ne plus suivre les
-- quetes d'un en-tete (SetHeaderQuestsTracked)
local function suivreEntete(nomEntete, suivre)
local dedans = false
for i = 1, GetNumQuestLogEntries() do
local info = entree(i)
if info and info.isHeader then
dedans = (info.title == nomEntete)
elseif info and dedans then
local suivie = IsQuestWatched(i) and true or false
if suivre and not suivie then
if GetNumQuestWatches() >= MAX_WATCHABLE_QUESTS then
UIErrorsFrame:AddMessage(format(QUEST_WATCH_TOO_MANY, MAX_WATCHABLE_QUESTS), 1.0, 0.1, 0.1, 1.0)
break
end
AddQuestWatch(i)
elseif not suivre and suivie then
RemoveQuestWatch(i)
end
end
end
WatchFrame_Update()
J.maj()
end
-- ------------------------------------------------------------- l'infobulle
-- QuestMapLogTitleButton_OnEnter : titre, echec, texte de rendu ou texte
-- d'objectif puis objectifs (blanc, gris si remplis), argent, le rappel du
-- clic, les membres du groupe.
local function infobulle(ligne)
local info = ligne.info
GameTooltip:ClearAllPoints()
GameTooltip:SetPoint("TOPLEFT", ligne, "TOPRIGHT", 34, 0)
GameTooltip:SetOwner(ligne, "ANCHOR_PRESERVE")
GameTooltip:SetText(info.title)
local largeur = 20 + math.max(231, GameTooltipTextLeft1:GetStringWidth())
-- le bord droit de la carte, en unites de l'interface : WorldMapFrame est
-- a sa propre echelle (WorldMap.lua)
local droite = WorldMapFrame:GetRight() * WorldMapFrame:GetEffectiveScale() / UIParent:GetEffectiveScale()
if largeur > UIParent:GetRight() - droite then
GameTooltip:ClearAllPoints()
GameTooltip:SetPoint("TOPRIGHT", ligne, "TOPLEFT", -5, 0)
GameTooltip:SetOwner(ligne, "ANCHOR_PRESERVE")
GameTooltip:SetText(info.title)
end
if info.isComplete and info.isComplete < 0 then
GameTooltip:AddLine(FAILED, 1, 0.125, 0.125)
end
GameTooltip:AddLine(" ")
if info.isComplete and info.isComplete > 0 then
GameTooltip:AddLine(GetQuestLogCompletionText(info.index) or TEXTE.pret, 1, 1, 1, true)
GameTooltip:AddLine(" ")
else
local selection = GetQuestLogSelection()
SelectQuestLogEntry(info.index)
local _, objectifs = GetQuestLogQuestText()
SelectQuestLogEntry(selection)
GameTooltip:AddLine(objectifs, 1, 1, 1, true)
GameTooltip:AddLine(" ")
local separer = false
for i = 1, GetNumQuestLeaderBoards(info.index) do
local texte, _, fini = GetQuestLogLeaderBoard(i, info.index)
if texte then
local g = fini and 0.502 or 1
GameTooltip:AddLine(QUEST_DASH .. texte, g, g, g, true)
separer = true
end
end
local requis = GetQuestLogRequiredMoney(info.index)
if requis and requis > 0 then
local argent = GetMoney()
local g = 1
if requis <= argent then
argent = requis
g = 0.502
end
GameTooltip:AddLine(QUEST_DASH .. GetMoneyString(argent) .. " / " .. GetMoneyString(requis), g, g, g)
separer = true
end
if separer then
GameTooltip:AddLine(" ")
end
end
GameTooltip:AddLine(TEXTE.details, 0.098, 1, 0.098)
GameTooltip:Show()
end
-- ------------------------------------------------------ la page d'une quete
--
-- RELEVE -- mainline/questmapframe.xml, DetailsFrame (QuestLogQuestDetailsMixin) :
-- DetailsFrame 308 x 502, TOPRIGHT (0, -1) de QuestsFrame -- donc a
-- (-22, -1) du coin haut-droit du volet
-- Bg QuestDetailsBackgrounds, a la largeur du cadre, la
-- hauteur au prorata et plafonnee a 440 (rognee par
-- SetTexCoord) -- AdjustBackgroundTexture
-- BorderFrame QuestLogBorderFrameTemplate de (-3, 4) a (3, 17), niveau 100
-- BackFrame 307 x 52 en haut, questlog-reward-top-frame ; bouton
-- BACK (UIPanelButtonTemplate) 90 x 22 a LEFT (11, 4)
-- ScrollFrame 298 x 430 a (5, -43), barre a (13, 17) / (13, -27)
-- RewardsFrameContainer 307 de large, BOTTOMLEFT (0, 23), niveau 50,
-- clipChildren ; dedans RewardsFrame 307 de large :
-- questlog-reward-header-top en haut, questlog-reward-bottom
-- en bas, questlog-reward-tile-vertical entre les deux,
-- "Rewards" en QuestFont_Huge a TOP (0, -22)
-- Abandon 105 x 22 BOTTOMLEFT (-3, -2), Share 103 x 22 puis Track
-- 105 x 22 a la suite ; Share porte deux separateurs
-- UI-Frame-BtnDivMiddle, (6, 0) et (-6, 0) hors de ses bords
-- mainline/questmapframe.lua : QuestMapFrame_ShowQuestDetails,
-- _CloseQuestDetails, _ReturnFromQuestDetails, _UpdateQuestDetailsButtons,
-- SetRewardsHeight et AdjustRewardsFrameContainer.
-- mainline/questinfo.lua / .xml : QUEST_TEMPLATE_MAP_DETAILS (le texte) et
-- QUEST_TEMPLATE_MAP_REWARDS (les recompenses), MapQuestInfoRewardsFrame.
--
-- LE MOTEUR QUESTINFO N'EST PAS REPRIS : ses cadres (QuestInfoTitleHeader,
-- QuestInfoRewardsFrame...) sont uniques et partages avec la fenetre de
-- quete du PNJ, qui les re-parente a chaque affichage. La page a les siens,
-- empiles comme QuestInfo_Display les empile : chaque element en TOPLEFT
-- sur le BOTTOMLEFT du precedent, aux decalages du modele.
--
-- CE QUI DIFFERE, ET POURQUOI.
-- * clipChildren n'existe pas en 3.3.5 : le conteneur des recompenses est
-- un ScrollFrame, le seul cadre de ce client qui rogne ce qu'il porte.
-- * le fond des recompenses se repete par tuiles de 83 : SetTexCoord ne
-- repete pas une feuille d'atlas.
-- * 3.3.5 ne compte pas la hauteur d'un contenu defilant : elle est
-- additionnee element par element.
-- * absents de 3.3.5, donc absents d'ici : le portrait du donneur, les
-- boutons de chemin, le sceau de campagne, les objectifs de sort, la
-- reputation des grandes factions, les devises.
-- * 3.3.5 donne des POINTS D'ARENE en recompense, camelot n'en a plus :
-- ils prennent la forme de l'honneur, dans une section a eux.
local D = {
largeur = 308, hauteur = 502, x = -22, y = -1, fondMax = 440,
bordG = -3, bordH = 4, bordD = 3, bordB = 17,
retourL = 307, retourH = 52, boutonRetourL = 90, boutonRetourX = 11, boutonRetourY = 4,
boutonH = 22, abandonL = 105, partageL = 103, suivreL = 105, boutonsX = -3, boutonsY = -2,
separateurX = 6,
texteL = 298, texteH = 430, texteX = 5, texteY = -43,
barreX = 13, barreHaut = 17, barreBas = -27,
contenu = 289, -- contentWidth
recL = 307, recY = 23, recNiveau = 50, recMin = 124,
recHautTuile = 56, tuile = 83, libelleY = -22,
-- QUEST_TEMPLATE_MAP_REWARDS et questinfo.lua
recX = 12, recY2 = -52, recSeparation = 8, recSection = 5, recRangee = 2,
recEnPlus = 62, recSans = 59, choixL = 265,
objetL = 134, objetH = 30, icone = 30, nomL = 92, nomH = 36,
pas = 16,
}
-- Les modeles de texte de camelot, polices comprises (fontstyles.xml,
-- fonts.xml du client camelot) : QuestTitleFont = MORPHEUS 18 ombre
-- (1, -1) ; QuestFont = FRIZQT 13 ; QuestFontNormalSmall = FRIZQT 12 ;
-- QuestMapRewardsFont = FRIZQT 10 ; QuestFont_Huge = MORPHEUS 18.
local MORPHEUS = "Fonts\\MORPHEUS.ttf"
local POLICES = {
titre = { MORPHEUS, 18 }, texte = { POLICE, 13 }, petit = { POLICE, 12 },
recompense = { POLICE, 10 }, libelle = { MORPHEUS, 18 },
}
local PANNEAU = "Interface\\Buttons\\UI-Panel-Button-"
local ICONES = {
argent = "Interface\\Icons\\INV_Misc_Coin_01",
xp = "interface\\ForeverUI\\icons\\xp_icon",
titre = "Interface\\Icons\\INV_Misc_Note_02",
honneur = "interface\\ForeverUI\\icons\\pvpcurrency-honor-",
arene = "Interface\\PVPFrame\\PVP-ArenaPoints-Icon",
contour = "Interface\\ForeverUI\\common\\whiteiconframe",
}
local function police(fs, modele, c)
local p = POLICES[modele]
fs:SetFont(p[1], p[2])
if c then couleur(fs, c) end
end
-- UIPanelButtonTemplate de camelot : trois morceaux de UI-Panel-Button-*,
-- que le bouton de 3.3.5 etire d'une seule piece.
local function boutonPanneau(parent, nom, texte, largeur)
local b = CreateFrame("Button", nom, parent)
b:SetWidth(largeur)
b:SetHeight(D.boutonH)
local function morceau(u1, u2)
local t = b:CreateTexture(nil, "BACKGROUND")
t:SetTexCoord(u1, u2, 0, 0.6875)
return t
end
local g = morceau(0, 0.09375)
g:SetWidth(12)
g:SetPoint("TOPLEFT", b, "TOPLEFT", 0, 0)
g:SetPoint("BOTTOMLEFT", b, "BOTTOMLEFT", 0, 0)
local d = morceau(0.53125, 0.625)
d:SetWidth(12)
d:SetPoint("TOPRIGHT", b, "TOPRIGHT", 0, 0)
d:SetPoint("BOTTOMRIGHT", b, "BOTTOMRIGHT", 0, 0)
local m = morceau(0.09375, 0.53125)
m:SetPoint("TOPLEFT", g, "TOPRIGHT", 0, 0)
m:SetPoint("BOTTOMRIGHT", d, "BOTTOMLEFT", 0, 0)
b.morceaux = { g, m, d }
local function etat(suffixe)
for _, t in ipairs(b.morceaux) do
t:SetTexture(PANNEAU .. suffixe)
end
end
etat("Up")
local fs = b:CreateFontString(nil, "ARTWORK")
fs:SetFontObject(GameFontNormal)
fs:SetPoint("CENTER", b, "CENTER", 0, 0)
b:SetFontString(fs)
b:SetNormalFontObject(GameFontNormal)
b:SetHighlightFontObject(GameFontHighlight)
b:SetDisabledFontObject(GameFontDisable)
b:SetText(texte)
b:SetHighlightTexture(PANNEAU .. "Highlight")
local s = b:GetHighlightTexture()
if s then
s:SetTexCoord(0, 0.625, 0, 0.6875)
s:SetBlendMode("ADD")
end
b.actif = true
b:SetScript("OnMouseDown", function(self)
if self.actif then etat("Down") end
end)
b:SetScript("OnMouseUp", function(self)
if self.actif then etat("Up") end
end)
-- UIPanelButton_OnEnable / _OnDisable
function b:Activer(oui)
self.actif = oui and true or false
if oui then
self:Enable()
etat("Up")
else
self:Disable()
etat("Disabled")
end
end
return b
end
-- SmallItemButtonTemplate (+ SmallQuestRewardItemButtonTemplate) : icone
-- 30, cadre du nom QuestItemBorder a sa taille, nom 92 x 36, nombre en bas a
-- droite de l'icone, contour de qualite sur l'icone.
local function creerObjet(parent, nom)
local b = CreateFrame("Button", nom, parent)
b:SetWidth(D.objetL)
b:SetHeight(D.objetH)
b:RegisterForClicks("LeftButtonUp", "RightButtonUp")
local icone = b:CreateTexture(nil, "BACKGROUND")
icone:SetWidth(D.icone)
icone:SetHeight(D.icone)
icone:SetPoint("TOPLEFT", b, "TOPLEFT", 0, 0)
local cadre = b:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(cadre, "questitemborder")
cadre:SetPoint("LEFT", icone, "RIGHT", 2, 0)
local texte = b:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
texte:SetWidth(D.nomL)
texte:SetHeight(D.nomH)
texte:SetJustifyH("LEFT")
texte:SetPoint("LEFT", cadre, "LEFT", 4, 0)
local nombre = b:CreateFontString(nil, "ARTWORK", "NumberFontNormalSmall")
nombre:SetJustifyH("RIGHT")
nombre:SetPoint("BOTTOMRIGHT", icone, "BOTTOMRIGHT", 0, 1)
local contour = b:CreateTexture(nil, "OVERLAY")
contour:SetTexture(ICONES.contour)
contour:SetAllPoints(icone)
contour:Hide()
b.Icon, b.NameFrame, b.Name, b.Count, b.IconBorder = icone, cadre, texte, nombre, contour
return b
end
-- SetItemButtonCount : le nombre ne se montre qu'au-dessus de 1
local function nombreObjet(b, n)
if n and n > 1 then
b.Count:SetText(n)
b.Count:Show()
else
b.Count:Hide()
end
end
-- L'index d'une quete dans le journal de 3.3.5, qui n'a que des index.
local function indexDeQuete(questID)
if not questID then return nil end
for i = 1, GetNumQuestLogEntries() do
local info = entree(i)
if info and not info.isHeader and info.questID == questID then
return i
end
end
return nil
end
-- Les boutons d'une recompense : infobulle et clic de QuestInfoRewardItemMixin
-- (objet) et de QuestInfoRewardSpellCodeMixin (sort).
local function survolRecompense(self)
local d = J.details
if d and d.index then SelectQuestLogEntry(d.index) end
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
if self.objectType == "item" then
GameTooltip:SetQuestLogItem(self.type, self:GetID())
if GameTooltip_ShowCompareItem then GameTooltip_ShowCompareItem(GameTooltip) end
elseif self.objectType == "spell" then
GameTooltip:SetQuestLogRewardSpell()
elseif self.infobulle then
GameTooltip:SetText(self.infobulle, 1, 1, 1)
else
GameTooltip:Hide()
return
end
GameTooltip:Show()
end
local function clicRecompense(self)
if not IsModifiedClick() then return end
local d = J.details
if d and d.index then SelectQuestLogEntry(d.index) end
if self.objectType == "item" then
HandleModifiedItemClick(GetQuestLogItemLink(self.type, self:GetID()))
elseif self.objectType == "spell" and IsModifiedClick("CHATLINK") then
ChatEdit_InsertLink(GetQuestLogSpellLink())
end
end
local function brancherRecompense(b)
b:SetScript("OnEnter", survolRecompense)
b:SetScript("OnLeave", function() GameTooltip:Hide() end)
b:SetScript("OnClick", clicRecompense)
return b
end
-- Le fond des recompenses : l'en-tete, le bas, et entre les deux le motif
-- vertical, repete par tuiles de 83.
local function poserFondRecompenses(r, hauteur)
local e = ForeverUI.AtlasEntry("questlog-reward-tile-vertical")
local reste = hauteur - D.recHautTuile
local n = 0
while e and reste > 0 do
n = n + 1
local t = r.tuiles[n]
if not t then
t = r:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(t, "questlog-reward-tile-vertical")
r.tuiles[n] = t
end
local h = math.min(D.tuile, reste)
t:SetHeight(h)
t:SetTexCoord(e[2], e[3], e[4], e[4] + (e[5] - e[4]) * h / D.tuile)
t:ClearAllPoints()
t:SetPoint("TOPLEFT", r.haut, "BOTTOMLEFT", 0, -(n - 1) * D.tuile)
t:Show()
reste = reste - h
end
for i = n + 1, #r.tuiles do
r.tuiles[i]:Hide()
end
end
local function creerDetails(v)
local d = CreateFrame("Frame", "ForeverUIQuestDetailsFrame", v)
d:SetWidth(D.largeur)
d:SetHeight(D.hauteur)
d:SetPoint("TOPRIGHT", v, "TOPRIGHT", D.x, D.y)
d:EnableMouse(true)
-- le fond, a la largeur du cadre, plafonne a 440 (AdjustBackgroundTexture)
local fond = d:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(fond, "questdetailsbackgrounds")
local e = ForeverUI.AtlasEntry("questdetailsbackgrounds")
fond:SetPoint("TOPLEFT", d, "TOPLEFT", 0, 0)
fond:SetPoint("TOPRIGHT", d, "TOPRIGHT", 0, 0)
if e then
local voulue = e[7] * D.largeur / e[6]
if voulue > D.fondMax then
fond:SetTexCoord(e[2], e[3], e[4], e[4] + (e[5] - e[4]) * D.fondMax / voulue)
fond:SetHeight(D.fondMax)
else
fond:SetHeight(voulue)
end
end
d.fond = fond
-- le cadre (QuestLogBorderFrameTemplate), niveau 100
local bord = CreateFrame("Frame", nil, d)
bord:SetPoint("TOPLEFT", d, "TOPLEFT", D.bordG, D.bordH)
bord:SetPoint("BOTTOMRIGHT", d, "BOTTOMRIGHT", D.bordD, D.bordB)
bord:SetFrameLevel(d:GetFrameLevel() + 100)
if ForeverUI.CreateNineSlice then
ForeverUI.CreateNineSlice(bord, "questlog-frame", 53, { 0, 0, 0, 0 }, "BORDER")
end
local filigrane = bord:CreateTexture(nil, "ARTWORK")
ForeverUI.SetAtlas(filigrane, "questlog-frame-filigree")
filigrane:SetPoint("TOP", bord, "TOP", 0, 1)
d.bord = bord
-- le bandeau du haut et le bouton Retour
local bandeau = CreateFrame("Frame", nil, d)
bandeau:SetWidth(D.retourL)
bandeau:SetHeight(D.retourH)
bandeau:SetPoint("TOPLEFT", d, "TOPLEFT", 0, 0)
local image = bandeau:CreateTexture(nil, "BORDER")
ForeverUI.SetAtlas(image, "questlog-reward-top-frame")
image:SetPoint("TOPLEFT", bandeau, "TOPLEFT", 0, 0)
local retour = boutonPanneau(bandeau, "ForeverUIQuestDetailsBackButton", BACK, D.boutonRetourL)
retour:SetPoint("LEFT", bandeau, "LEFT", D.boutonRetourX, D.boutonRetourY)
retour:SetScript("OnClick", function()
PlaySound("igMainMenuOptionCheckBoxOn")
J.revenirDeDetails()
end)
d.retour = retour
-- le texte, qui defile
local defile = CreateFrame("ScrollFrame", "ForeverUIQuestDetailsScrollFrame", d)
defile:SetWidth(D.texteL)
defile:SetHeight(D.texteH)
defile:SetPoint("TOPLEFT", d, "TOPLEFT", D.texteX, D.texteY)
local contenu = CreateFrame("Frame", "ForeverUIQuestDetailsContents", defile)
contenu:SetWidth(D.texteL)
contenu:SetHeight(1)
defile:SetScrollChild(contenu)
d.defile, d.contenu = defile, contenu
local function texte(modele, c)
local fs = contenu:CreateFontString(nil, "BACKGROUND")
police(fs, modele, c)
fs:SetJustifyH("LEFT")
fs:SetWidth(D.contenu)
return fs
end
d.titre = texte("titre", COULEUR.titre)
d.titre:SetShadowOffset(1, -1)
d.titre:SetShadowColor(COULEUR.ombreTitre[1], COULEUR.ombreTitre[2], COULEUR.ombreTitre[3], 1)
d.texteObjectifs = texte("texte", COULEUR.texte)
d.minuteur = texte("petit", COULEUR.texte)
d.groupe = texte("texte", COULEUR.texte)
d.enteteDescription = texte("titre", COULEUR.titre)
d.enteteDescription:SetShadowOffset(1, -1)
d.enteteDescription:SetShadowColor(COULEUR.ombreTitre[1], COULEUR.ombreTitre[2], COULEUR.ombreTitre[3], 1)
d.enteteDescription:SetText(QUEST_DESCRIPTION)
d.description = texte("texte", COULEUR.texte)
d.objectifs = {}
d.nouvelObjectif = function() return texte("petit", COULEUR.texte) end
-- l'argent demande : le libelle et un petit porte-monnaie fige
local argent = CreateFrame("Frame", nil, contenu)
argent:SetWidth(285)
argent:SetHeight(28)
local argentTexte = argent:CreateFontString(nil, "BACKGROUND")
police(argentTexte, "petit")
argentTexte:SetPoint("LEFT", argent, "LEFT", 0, 0)
argentTexte:SetText(REQUIRED_MONEY)
local bourse = CreateFrame("Frame", "ForeverUIQuestDetailsRequiredMoney", argent, "SmallMoneyFrameTemplate")
bourse:SetPoint("LEFT", argentTexte, "RIGHT", 10, 0)
if MoneyFrame_SetType then MoneyFrame_SetType(bourse, "STATIC") end
argent.texte, argent.bourse = argentTexte, bourse
d.argent = argent
-- le compte a rebours (QuestInfoTimerFrame_OnUpdate)
local horloge = CreateFrame("Frame", nil, contenu)
horloge:SetScript("OnUpdate", function(self, ecoule)
if self.reste then
self.reste = math.max(self.reste - ecoule, 0)
d.minuteur:SetText(TIME_REMAINING .. " " .. SecondsToTime(self.reste))
end
end)
d.horloge = horloge
-- l'espace laisse aux recompenses, en bas du texte
local espace = CreateFrame("Frame", nil, contenu)
espace:SetWidth(5)
espace:SetHeight(5)
d.espace = espace
local barre = ForeverUI.CreateScrollBar and ForeverUI.CreateScrollBar("ForeverUIQuestDetailsScrollBar", d, defile)
if barre then
barre:ClearAllPoints()
barre:SetPoint("TOPLEFT", defile, "TOPRIGHT", D.barreX, D.barreHaut)
barre:SetPoint("BOTTOMLEFT", defile, "BOTTOMRIGHT", D.barreX, D.barreBas)
barre:SetFrameLevel(bord:GetFrameLevel() + 1)
barre.surDefilement = function(pas)
J.defilerDetails(pas * D.pas)
end
d.barre = barre
end
defile:EnableMouseWheel(true)
defile:SetScript("OnMouseWheel", function(_, sens)
if d.barre and d.barre:IsShown() then
d.barre:Deplacer(d.barre.decalage - sens * 3)
end
end)
-- les recompenses : un ScrollFrame immobile, qui rogne ce qui depasse
local conteneur = CreateFrame("ScrollFrame", "ForeverUIQuestDetailsRewardsContainer", d)
conteneur:SetWidth(D.recL)
conteneur:SetHeight(100)
conteneur:SetPoint("BOTTOMLEFT", d, "BOTTOMLEFT", 0, D.recY)
conteneur:SetFrameLevel(d:GetFrameLevel() + D.recNiveau)
local r = CreateFrame("Frame", "ForeverUIQuestDetailsRewardsFrame", conteneur)
r:SetWidth(D.recL)
r:SetHeight(275)
conteneur:SetScrollChild(r)
local bas = r:CreateTexture(nil, "BORDER")
ForeverUI.SetAtlas(bas, "questlog-reward-bottom")
bas:SetPoint("BOTTOMLEFT", r, "BOTTOMLEFT", 0, 0)
local haut = r:CreateTexture(nil, "BORDER")
ForeverUI.SetAtlas(haut, "questlog-reward-header-top")
haut:SetPoint("TOPLEFT", r, "TOPLEFT", 0, 0)
local libelle = r:CreateFontString(nil, "ARTWORK")
police(libelle, "libelle", COULEUR.recompense)
libelle:SetPoint("TOP", r, "TOP", 0, D.libelleY)
libelle:SetText(TEXTE.recompenses)
r.haut, r.bas, r.libelle, r.tuiles = haut, bas, libelle, {}
d.conteneur, d.recompenses = conteneur, r
-- MapQuestInfoRewardsFrame, a (12, -52)
local liste = CreateFrame("Frame", nil, r)
liste:SetWidth(285)
liste:SetHeight(10)
liste:SetPoint("TOPLEFT", r, "TOPLEFT", D.recX, D.recY2)
local entete = CreateFrame("Frame", nil, liste)
entete:SetWidth(1)
entete:SetHeight(1)
entete:SetPoint("TOPLEFT", liste, "TOPLEFT", 0, 0)
local function libelleRecompense(largeur)
local fs = liste:CreateFontString(nil, "BACKGROUND")
police(fs, "recompense", COULEUR.recompense)
fs:SetJustifyH("LEFT")
if largeur then fs:SetWidth(largeur) end
return fs
end
liste.entete = entete
liste.choisir = libelleRecompense(D.choixL)
liste.recevoir = libelleRecompense()
liste.titreJoueur = libelleRecompense()
liste.titreJoueur:SetText(REWARD_TITLE)
liste.sort = libelleRecompense()
liste.objets = {}
liste.xp = brancherRecompense(creerObjet(liste))
liste.xp.Icon:SetTexture(ICONES.xp)
liste.xp.Name:SetFontObject(NumberFontNormal)
liste.argent = brancherRecompense(creerObjet(liste))
liste.argent.Icon:SetTexture(ICONES.argent)
liste.argent.Name:SetFontObject(GameFontHighlight)
liste.titre = brancherRecompense(creerObjet(liste))
liste.titre.Icon:SetTexture(ICONES.titre)
liste.honneur = brancherRecompense(creerObjet(liste))
liste.arene = brancherRecompense(creerObjet(liste))
liste.arene.Icon:SetTexture(ICONES.arene)
liste.sortBouton = brancherRecompense(creerObjet(liste))
liste.sortBouton.objectType = "spell"
d.liste = liste
-- Abandon, Share, Track
local abandon = boutonPanneau(d, "ForeverUIQuestDetailsAbandonButton", ABANDON_QUEST_ABBREV, D.abandonL)
abandon:SetPoint("BOTTOMLEFT", d, "BOTTOMLEFT", D.boutonsX, D.boutonsY)
abandon:SetScript("OnClick", function()
if d.index then abandonner(d.index) end
end)
local partage = boutonPanneau(d, "ForeverUIQuestDetailsShareButton", SHARE_QUEST_ABBREV, D.partageL)
partage:SetPoint("LEFT", abandon, "RIGHT", 0, 0)
for i, cote in ipairs({ { "RIGHT", "LEFT", D.separateurX }, { "LEFT", "RIGHT", -D.separateurX } }) do
local s = partage:CreateTexture(nil, "OVERLAY")
ForeverUI.SetAtlas(s, "ui-frame-btndivmiddle")
s:SetPoint(cote[1], partage, cote[2], cote[3], 0)
partage["separateur" .. i] = s
end
partage:SetScript("OnClick", function()
if d.index then partager(d.index) end
end)
local suivre = boutonPanneau(d, "ForeverUIQuestDetailsTrackButton", TRACK_QUEST_ABBREV, D.suivreL)
suivre:SetPoint("LEFT", partage, "RIGHT", 0, 0)
suivre:SetScript("OnClick", function()
PlaySound("igMainMenuOptionCheckBoxOn")
if d.index then
basculerSuivi(d.index)
J.majBoutonsDetails()
J.maj()
end
end)
d.abandon, d.partage, d.suivre = abandon, partage, suivre
d:Hide()
return d
end
-- QuestMapFrame_UpdateQuestDetailsButtons, avec les regles de 3.3.5
-- (QuestLogControlPanel_UpdateState)
function J.majBoutonsDetails()
local d = J.details
if not d or not d.index then return end
-- QuestLog_SetSelection : la quete choisie est aussi celle qu'on
-- abandonnerait, et GetAbandonQuestName la nomme si c'est permis
SelectQuestLogEntry(d.index)
SetAbandonQuest()
d.abandon:Activer(GetAbandonQuestName() and true or false)
if IsQuestWatched(d.index) then
d.suivre:SetText(TEXTE.nePlusSuivreCourt)
else
d.suivre:SetText(TRACK_QUEST_ABBREV)
end
d.partage:Activer(GetQuestLogPushable() and enGroupe() and true or false)
end
-- QuestInfo_ShowRewards, pour ce que 3.3.5 donne. Rend la hauteur de la
-- liste, ou nil s'il n'y a rien a gagner.
local function afficherRecompenses(d)
local l = d.liste
for _, b in ipairs(l.objets) do b:Hide() end
for _, f in ipairs({ l.choisir, l.recevoir, l.titreJoueur, l.sort, l.xp, l.argent,
l.titre, l.honneur, l.arene, l.sortBouton }) do
f:ClearAllPoints()
f:Hide()
end
l.lignes, l.entetes = 0, 0
local nbObjets = GetNumQuestLogRewards() or 0
local nbChoix = GetNumQuestLogChoices() or 0
local argent = GetQuestLogRewardMoney() or 0
local xp = GetQuestLogRewardXP and GetQuestLogRewardXP() or 0
local honneur = GetQuestLogRewardHonor() or 0
local arene = GetQuestLogRewardArenaPoints and GetQuestLogRewardArenaPoints() or 0
local titreJoueur = GetQuestLogRewardTitle()
local sortIcone, sortNom, sortMetier, sortAppris = GetQuestLogRewardSpell()
if nbObjets + nbChoix == 0 and argent == 0 and xp == 0 and honneur == 0 and arene == 0
and not titreJoueur and not sortIcone then
return nil
end
-- l'agencement de camelot : deux elements par rangee, une section
-- repart a gauche, un en-tete occupe sa rangee a lui seul
local total = l.entete:GetHeight()
local ancre = l.entete
local nouvelleSection, unParRangee, droitePosee = true, false, false
local function section(large)
nouvelleSection = true
unParRangee = large and true or false
end
local function ajouter(f, h)
if not nouvelleSection and not droitePosee and not unParRangee then
f:SetPoint("TOPLEFT", ancre, "TOPRIGHT", D.recSeparation, 0)
droitePosee = true
else
local ecart = nouvelleSection and D.recSection or D.recRangee
f:SetPoint("TOPLEFT", ancre, "BOTTOMLEFT", 0, -ecart)
total = total + (h or f:GetHeight()) + ecart
ancre = f
droitePosee = false
nouvelleSection = false
l.lignes = l.lignes + 1
end
f:Show()
end
local function entete(f)
l.entetes = l.entetes + 1
section(true)
ajouter(f)
end
local n = 0
local function objet(type, i, lire)
n = n + 1
local b = l.objets[n]
if not b then
b = brancherRecompense(creerObjet(l, "ForeverUIQuestDetailsRewardItem" .. n))
l.objets[n] = b
end
local nom, icone, nombre, qualite, utilisable = lire(i)
b.type, b.objectType = type, "item"
b:SetID(i)
b.Name:SetText(nom)
b.Icon:SetTexture(icone)
nombreObjet(b, nombre)
-- BAG_ITEM_QUALITY_COLORS de camelot : commun en COMMON_GRAY_COLOR,
-- au-dessus la couleur de qualite, mediocre sans contour
local c = qualite and qualite >= 2 and ITEM_QUALITY_COLORS and ITEM_QUALITY_COLORS[qualite]
if qualite == 1 then
b.IconBorder:SetVertexColor(COULEUR.communGris[1], COULEUR.communGris[2], COULEUR.communGris[3])
b.IconBorder:Show()
elseif c then
b.IconBorder:SetVertexColor(c.r, c.g, c.b)
b.IconBorder:Show()
else
b.IconBorder:Hide()
end
if utilisable then
b.Icon:SetVertexColor(1, 1, 1)
b.NameFrame:SetVertexColor(1, 1, 1)
else
b.Icon:SetVertexColor(0.9, 0, 0)
b.NameFrame:SetVertexColor(0.9, 0, 0)
end
ajouter(b, D.objetH)
end
-- au choix
if nbChoix > 0 then
l.choisir:SetText(nbChoix == 1 and REWARD_ITEMS_ONLY or REWARD_CHOICES)
entete(l.choisir)
section()
for i = 1, nbChoix do
objet("choice", i, GetQuestLogChoiceInfo)
end
end
-- le sort : 3.3.5 n'en donne qu'un
if sortIcone then
l.sort:SetText((sortMetier and REWARD_TRADESKILL_SPELL) or (not sortAppris and REWARD_AURA) or REWARD_SPELL)
entete(l.sort)
section()
l.sortBouton.Icon:SetTexture(sortIcone)
l.sortBouton.Name:SetText(sortNom)
nombreObjet(l.sortBouton, 0)
ajouter(l.sortBouton, D.objetH)
end
-- le titre
if titreJoueur then
entete(l.titreJoueur)
l.titre.Name:SetText(titreJoueur)
nombreObjet(l.titre, 0)
section()
ajouter(l.titre, D.objetH)
end
-- ce qu'on recoit de toute facon
if nbObjets > 0 or argent > 0 or xp > 0 or honneur > 0 or arene > 0 then
l.recevoir:SetText((nbChoix > 0 or sortIcone or titreJoueur) and REWARD_ITEMS or REWARD_ITEMS_ONLY)
entete(l.recevoir)
section()
if xp > 0 then
l.xp.Name:SetText(xp)
nombreObjet(l.xp, 0)
ajouter(l.xp, D.objetH)
end
if argent > 0 then
l.argent.Name:SetText(GetMoneyString(argent))
nombreObjet(l.argent, 0)
ajouter(l.argent, D.objetH)
end
section()
for i = 1, nbObjets do
objet("reward", i, GetQuestLogRewardInfo)
end
if honneur > 0 then
local faction = UnitFactionGroup("player") == "Horde" and "horde" or "alliance"
l.honneur.Icon:SetTexture(ICONES.honneur .. faction)
l.honneur.Name:SetText(HONOR)
nombreObjet(l.honneur, honneur)
l.honneur.infobulle = HONOR_POINTS
section()
ajouter(l.honneur, D.objetH)
end
if arene > 0 then
l.arene.Name:SetText(ARENA_POINTS)
nombreObjet(l.arene, arene)
l.arene.infobulle = ARENA_POINTS
section()
ajouter(l.arene, D.objetH)
end
end
l:SetHeight(total)
return total
end
-- AdjustRewardsFrameContainer : si tout le texte tient, ou s'il n'y a qu'une
-- rangee, les recompenses se montrent en entier ; sinon elles grandissent a
-- mesure qu'on descend, sans passer sous 124.
function J.ajusterRecompenses()
local d = J.details
if not d then return end
local r = d.recompenses
local hauteur = r:GetHeight()
local plage = d.plage or 0
if plage == 0 or (d.liste.lignes - d.liste.entetes) <= 1 then
d.conteneur:SetHeight(hauteur)
return
end
local h = hauteur - (plage - (d.defile:GetVerticalScroll() or 0))
if h < D.recMin then h = D.recMin end
d.conteneur:SetHeight(h)
end
function J.defilerDetails(y)
local d = J.details
if not d then return end
d.defile:SetVerticalScroll(math.min(y, d.plage or 0))
J.ajusterRecompenses()
end
-- QuestInfo_Display(QUEST_TEMPLATE_MAP_DETAILS) puis (..._MAP_REWARDS) :
-- rend faux si la quete n'est plus au journal.
local function afficherDetails(debut)
local d = J.details
local index = indexDeQuete(d.questID)
if not index then
return false
end
d.index = index
SelectQuestLogEntry(index)
local info = entree(index)
local c = d.contenu
local dernier, total = nil, 0
local function poser(region, dx, dy, h, bas)
region:ClearAllPoints()
if dernier then
region:SetPoint("TOPLEFT", dernier, "BOTTOMLEFT", dx, dy)
else
region:SetPoint("TOPLEFT", c, "TOPLEFT", dx, dy)
end
region:Show()
dernier = bas or region
total = total - dy + (h or region:GetHeight() or 0)
end
-- le titre, l'echec en plus
local titre = info.title
if info.isComplete and info.isComplete < 0 then
titre = format(TEXTE.titreEchec, titre)
end
d.titre:SetText(titre)
poser(d.titre, 5, -10)
local description, objectifs = GetQuestLogQuestText()
d.texteObjectifs:SetText(objectifs)
poser(d.texteObjectifs, 0, -5)
-- le compte a rebours
local reste = GetQuestLogTimeLeft()
d.horloge.reste = reste
if reste then
d.minuteur:SetText(TIME_REMAINING .. " " .. SecondsToTime(reste))
poser(d.minuteur, 0, -10)
else
d.minuteur:Hide()
end
-- les objectifs : remplis, ils passent au gris et disent "(Complete)"
for _, o in ipairs(d.objectifs) do o:Hide() end
local vus = 0
for i = 1, GetNumQuestLeaderBoards() do
local texte, genre, fini = GetQuestLogLeaderBoard(i)
if genre ~= "spell" and genre ~= "log" then
vus = vus + 1
local o = d.objectifs[vus]
if not o then
o = d.nouvelObjectif()
d.objectifs[vus] = o
end
if not texte or texte == "" then texte = genre end
if fini then
couleur(o, COULEUR.objectifFait)
texte = texte .. " (" .. COMPLETE .. ")"
else
couleur(o, COULEUR.texte)
end
o:SetText(texte)
poser(o, 0, vus == 1 and -10 or -2)
end
end
-- l'argent demande : noir s'il en manque, gris sinon
local requis = GetQuestLogRequiredMoney() or 0
if requis > 0 then
MoneyFrame_Update(d.argent.bourse:GetName(), requis)
if requis > GetMoney() then
couleur(d.argent.texte, { 0, 0, 0 })
if SetMoneyFrameColor then SetMoneyFrameColor(d.argent.bourse:GetName(), "red") end
else
couleur(d.argent.texte, { 0.2, 0.2, 0.2 })
if SetMoneyFrameColor then SetMoneyFrameColor(d.argent.bourse:GetName(), "white") end
end
poser(d.argent, 0, 0, 28)
else
d.argent:Hide()
end
-- le groupe conseille
local groupe = GetQuestLogGroupNum() or 0
if groupe > 0 then
d.groupe:SetText(format(QUEST_SUGGESTED_GROUP_NUM, groupe))
poser(d.groupe, 0, -10)
else
d.groupe:Hide()
end
poser(d.enteteDescription, 0, -20)
d.description:SetText(description)
poser(d.description, 0, -5)
-- les recompenses (SetRewardsHeight) : leur hauteur, plus 62, ou 59
-- sans rien ; l'espace en bas du texte leur laisse la meme place
local liste = afficherRecompenses(d)
local hauteur
if liste then
d.liste:Show()
hauteur = liste + D.recEnPlus
else
d.liste:Hide()
hauteur = D.recSans
end
d.recompenses:SetHeight(hauteur)
poserFondRecompenses(d.recompenses, hauteur)
if d.liste.lignes - d.liste.entetes > 0 then
d.recompenses.libelle:Show()
else
d.recompenses.libelle:Hide()
end
d.espace:SetHeight(hauteur)
poser(d.espace, 0, 0, hauteur)
c:SetHeight(math.max(total, 1))
d.plage = math.max(0, total - D.texteH)
if d.barre then
local lignes = math.ceil(total / D.pas)
local visibles = math.floor(D.texteH / D.pas)
local decalage = debut and 0 or math.min(d.barre.decalage or 0, math.max(0, lignes - visibles))
d.barre:Regler(lignes, visibles, decalage)
d.defile:SetVerticalScroll(math.min(decalage * D.pas, d.plage))
else
d.defile:SetVerticalScroll(0)
end
J.ajusterRecompenses()
J.majBoutonsDetails()
return true
end
function J.detailsOuverts()
return J.details and J.details:IsShown() and true or false
end
-- QuestMapFrame_ShowQuestDetails : la carte passe sur la quete (en gardant
-- celle qu'on quitte), la liste s'efface, la page se montre.
function J.ouvrirDetails(info)
local d = J.details
if not d or not info or not info.questID then
montrerSurCarte(info)
return
end
d.retourCarte = GetCurrentMapAreaID and GetCurrentMapAreaID()
d.retourEtage = GetCurrentMapDungeonLevel and GetCurrentMapDungeonLevel()
d.carte = nil
d.questID = info.questID
montrerSurCarte(info)
d.carte = GetCurrentMapAreaID and GetCurrentMapAreaID()
StaticPopup_Hide("ABANDON_QUEST")
StaticPopup_Hide("ABANDON_QUEST_WITH_ITEMS")
-- montree AVANT d'etre remplie : les textes se mesurent visibles
J.volet.liste:Hide()
if J.volet.barre then J.volet.barre:Hide() end
d:Show()
if not afficherDetails(true) then
J.fermerDetails()
end
end
-- QuestMapFrame_CloseQuestDetails
function J.fermerDetails()
local d = J.details
if not d then return end
d:Hide()
d.questID, d.index, d.carte, d.retourCarte, d.retourEtage = nil, nil, nil, nil, nil
d.horloge.reste = nil
J.volet.liste:Show()
StaticPopup_Hide("ABANDON_QUEST")
StaticPopup_Hide("ABANDON_QUEST_WITH_ITEMS")
J.maj()
end
-- QuestMapFrame_ReturnFromQuestDetails : la carte revient ou elle etait
function J.revenirDeDetails()
local d = J.details
if not d then return end
local carte, etage = d.retourCarte, d.retourEtage
if carte and SetMapByID then
SetMapByID(carte)
if etage and etage > 0 and SetDungeonMapLevel then
SetDungeonMapLevel(etage)
end
end
J.fermerDetails()
end
-- QuestLogMixin:Refresh et QuestMapFrame_UpdateAll : la page suit le
-- journal ; une quete qui en sort ramene a la liste ; une carte qui n'est
-- plus celle de la quete referme la page.
function J.majDetails()
local d = J.details
if not J.detailsOuverts() then return end
if d.carte and GetCurrentMapAreaID and GetCurrentMapAreaID() ~= d.carte then
J.fermerDetails()
return
end
if not afficherDetails(false) then
J.revenirDeDetails()
end
end
-- ------------------------------------------------------------- le volet
local function creerVolet(carte)
local v = CreateFrame("Frame", "ForeverUIQuestLogPanel", carte)
v:SetFrameStrata("HIGH")
v:EnableMouse(true)
v:SetWidth(V.largeur)
v:SetPoint("TOPRIGHT", carte, "TOPRIGHT", V.x, V.haut)
v:SetPoint("BOTTOMRIGHT", carte, "BOTTOMRIGHT", V.x, V.bas)
-- QuestsFrame, puis QuestScrollFrame
local liste = CreateFrame("ScrollFrame", "ForeverUIQuestScrollFrame", v)
liste:SetPoint("TOPLEFT", v, "TOPLEFT", 0, V.listeHaut)
liste:SetPoint("BOTTOMRIGHT", v, "BOTTOMRIGHT", V.listeDroite, V.listeBas)
v.liste = liste
local fond = liste:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(fond, "questlog-main-background-2x")
fond:SetPoint("TOPLEFT", liste, "TOPLEFT", 0, 0)
v.fond = fond
local vide = liste:CreateFontString(nil, "ARTWORK")
vide:SetFont(POLICE, 16)
vide:SetWidth(250)
vide:SetPoint("CENTER", liste, "CENTER", 0, 50)
vide:SetText(TEXTE.vide)
vide:Hide()
v.vide = vide
local aucun = liste:CreateFontString(nil, "ARTWORK")
aucun:SetFont(POLICE, 14)
aucun:SetWidth(250)
aucun:SetPoint("TOP", liste, "TOP", 0, -30)
aucun:SetText(TEXTE.aucunResultat)
aucun:Hide()
v.aucun = aucun
local contenu = CreateFrame("Frame", "ForeverUIQuestScrollContents", liste)
contenu:SetWidth(304)
contenu:SetHeight(454)
liste:SetScrollChild(contenu)
v.contenu = contenu
-- le cadre : questlog-frame etire, le filigrane en haut, l'ombre en bas
local bord = CreateFrame("Frame", nil, liste)
bord:SetPoint("TOPLEFT", liste, "TOPLEFT", -3, 7)
bord:SetPoint("BOTTOMRIGHT", liste, "BOTTOMRIGHT", 3, -6)
v.bord = bord
bord:SetFrameLevel(liste:GetFrameLevel() + 20)
-- questlog-frame est DECOUPE EN NEUF par le moteur de camelot : ses marges
-- sont dans UiTextureAtlasElementSliceData.db2 du client camelot --
-- 53 / 53 / 53 / 53 pour un element de 107 x 107 (mode 1). Etire d'une
-- seule piece, il deformait ses moulures (constate le 2026-09-25).
if ForeverUI.CreateNineSlice then
ForeverUI.CreateNineSlice(bord, "questlog-frame", 53, { 0, 0, 0, 0 }, "BORDER")
end
local filigrane = bord:CreateTexture(nil, "ARTWORK")
ForeverUI.SetAtlas(filigrane, "questlog-frame-filigree")
filigrane:SetPoint("TOP", bord, "TOP", 0, 1)
local ombre = bord:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(ombre, "questlog-frame-gradient-bottom")
ombre:SetPoint("BOTTOM", bord, "BOTTOM", 0, 4)
v.ombre = ombre
-- la barre de defilement, par pas de 16
local barre = ForeverUI.CreateScrollBar and ForeverUI.CreateScrollBar("ForeverUIQuestScrollBar", v, liste)
if barre then
barre:ClearAllPoints()
barre:SetPoint("TOPLEFT", liste, "TOPRIGHT", V.barreX, V.barreHaut)
barre:SetPoint("BOTTOMLEFT", liste, "BOTTOMRIGHT", V.barreX, V.barreBas)
barre:SetFrameLevel(bord:GetFrameLevel() + 1)
barre.surDefilement = function(pas)
liste:SetVerticalScroll(pas * V.pas)
J.majOmbre()
end
v.barre = barre
end
liste:EnableMouseWheel(true)
liste:SetScript("OnMouseWheel", function(_, sens)
if v.barre and v.barre:IsShown() then
v.barre:Deplacer(v.barre.decalage - sens * 3)
end
end)
-- la recherche (SearchBoxTemplate)
local r = CreateFrame("EditBox", "ForeverUIQuestSearchBox", liste)
r:SetWidth(V.rechercheL)
r:SetHeight(V.rechercheH)
r:SetPoint("BOTTOMLEFT", liste, "TOPLEFT", V.rechercheX, V.rechercheY)
r:SetAutoFocus(false)
r:SetMaxLetters(60)
r:SetFontObject("GameFontHighlightSmall")
r:SetTextInsets(16, 20, 0, 0)
local function bordure(parent, prefixe)
local g = parent:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(g, prefixe .. "-left", true)
g:SetWidth(8) g:SetHeight(20)
g:SetPoint("LEFT", parent, "LEFT", -5, 0)
local d = parent:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(d, prefixe .. "-right", true)
d:SetWidth(8) d:SetHeight(20)
d:SetPoint("RIGHT", parent, "RIGHT", 0, 0)
local m = parent:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(m, prefixe .. "-middle", true)
m:SetPoint("TOPLEFT", g, "TOPRIGHT")
m:SetPoint("BOTTOMRIGHT", d, "BOTTOMLEFT")
return g, m, d
end
bordure(r, "common-search-border")
local loupe = r:CreateTexture(nil, "OVERLAY")
ForeverUI.SetAtlas(loupe, "common-search-magnifyingglass", true)
loupe:SetWidth(10) loupe:SetHeight(10)
loupe:SetPoint("LEFT", r, "LEFT", 1, -1)
local consigne = r:CreateFontString(nil, "ARTWORK", "GameFontDisableSmall")
consigne:SetPoint("TOPLEFT", r, "TOPLEFT", 16, 0)
consigne:SetPoint("BOTTOMRIGHT", r, "BOTTOMRIGHT", -20, 0)
consigne:SetJustifyH("LEFT")
consigne:SetTextColor(0.35, 0.35, 0.35)
consigne:SetText(TEXTE.recherche)
local effacer = CreateFrame("Button", nil, r)
effacer:SetWidth(17) effacer:SetHeight(17)
effacer:SetPoint("RIGHT", r, "RIGHT", -3, 0)
local croix = effacer:CreateTexture(nil, "ARTWORK")
ForeverUI.SetAtlas(croix, "common-search-clearbutton", true)
croix:SetWidth(10) croix:SetHeight(10)
croix:SetPoint("CENTER", effacer, "CENTER", 0, 0)
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()
r:SetText("")
r:ClearFocus()
end)
effacer:Hide()
r:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
r:SetScript("OnEnterPressed", function(self) self:ClearFocus() end)
r:SetScript("OnEditFocusGained", function() consigne:Hide() end)
r:SetScript("OnEditFocusLost", function(self)
if self:GetText() == "" then consigne:Show() end
end)
r:SetScript("OnTextChanged", function(self)
local t = self:GetText()
if t == "" then effacer:Hide() else effacer:Show() consigne:Hide() end
J.chercher(t)
end)
v.recherche = r
-- le compteur (InputBoxVisualTemplate)
local compteur = CreateFrame("Frame", "ForeverUIQuestLogCount", liste)
compteur:SetWidth(V.compteurL)
compteur:SetHeight(V.compteurH)
compteur:SetPoint("TOPLEFT", r, "TOPRIGHT", V.compteurX, 0)
local _, _, droite = bordure(compteur, "common-search-border")
local texteCompteur = compteur:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
texteCompteur:SetPoint("TOPRIGHT", droite, "TOPRIGHT", -5, -5)
compteur.texte = texteCompteur
-- LE COMPTEUR DE QUOTIDIENNES (WotLK seul, conserve par decision) : camelot
-- n'a pas de place pour lui ; il vit dans l'infobulle du compteur.
compteur:EnableMouse(true)
compteur:SetScript("OnEnter", function(self)
GameTooltip:SetOwner(self, "ANCHOR_BOTTOMRIGHT")
GameTooltip:SetText(format(QUEST_LOG_DAILY_COUNT_TEMPLATE, GetDailyQuestsCompleted(), GetMaxDailyQuests()))
if QUEST_LOG_DAILY_TOOLTIP then
GameTooltip:AddLine(format(QUEST_LOG_DAILY_TOOLTIP, GetMaxDailyQuests(), SecondsToTime(GetQuestResetTime(), nil, 1)), 1, 1, 1, true)
end
GameTooltip:Show()
end)
compteur:SetScript("OnLeave", function() GameTooltip:Hide() end)
v.compteur = compteur
-- les reglages : une case, les objectifs
local reglage = CreateFrame("Button", "ForeverUIQuestSettingsButton", liste)
reglage:SetWidth(V.reglageL)
reglage:SetHeight(V.reglageH)
reglage:SetPoint("TOPRIGHT", liste, "TOPRIGHT", V.reglageX, V.reglageY)
local icone = reglage:CreateTexture(nil, "ARTWORK")
ForeverUI.SetAtlas(icone, "questlog-icon-setting")
icone:SetPoint("CENTER", reglage, "CENTER", 0, 0)
local survol = reglage:CreateTexture(nil, "HIGHLIGHT")
ForeverUI.SetAtlas(survol, "questlog-icon-setting")
survol:SetPoint("CENTER", reglage, "CENTER", 0, 0)
survol:SetBlendMode("ADD")
survol:SetAlpha(0.4)
reglage:SetScript("OnMouseDown", function() icone:SetPoint("CENTER", reglage, "CENTER", 1, -1) end)
reglage:SetScript("OnMouseUp", function() icone:SetPoint("CENTER", reglage, "CENTER", 0, 0) end)
reglage:SetScript("OnClick", function(self)
menu(self, { {
text = TEXTE.montrerObjectifs,
checked = reglages().objectifs,
keepShownOnClick = 1,
func = function()
reglages().objectifs = not reglages().objectifs
J.maj()
end,
} })
end)
v.reglage = reglage
v.entetes, v.quetes, v.objectifs = {}, {}, {}
J.details = creerDetails(v)
v:Hide()
return v
end
-- --------------------------------------------------------------- les lignes
local function creerEntete(v, i)
local b = CreateFrame("Button", "ForeverUIQuestLogHeader" .. i, v.contenu)
b:SetWidth(V.enteteL)
b:SetHeight(V.enteteH)
b:RegisterForClicks("LeftButtonUp", "RightButtonUp")
local fondNom = ForeverUI.AtlasEntry("common-button-list-collapseexpand")
b:SetNormalTexture(fondNom[1])
ForeverUI.SetAtlas(b:GetNormalTexture(), "common-button-list-collapseexpand", true)
b:SetHighlightTexture(fondNom[1])
local s = b:GetHighlightTexture()
ForeverUI.SetAtlas(s, "common-button-list-collapseexpand", true)
s:SetBlendMode("ADD")
s:SetAlpha(0.4)
local plus = CreateFrame("Button", nil, b)
plus:SetWidth(20) plus:SetHeight(20)
plus:SetPoint("RIGHT", b, "RIGHT", -6, 0)
plus:EnableMouse(false)
plus.Icon = plus:CreateTexture(nil, "ARTWORK")
plus.Icon:SetPoint("CENTER", plus, "CENTER", 0, 0)
b.plus = plus
local texte = b:CreateFontString(nil, "OVERLAY")
texte:SetFont(POLICE, 15)
texte:SetShadowOffset(1, -1)
texte:SetShadowColor(0, 0, 0, 1)
texte:SetJustifyH("LEFT")
texte:SetPoint("LEFT", b, "LEFT", 8, 0)
texte:SetPoint("RIGHT", plus, "LEFT", -4, 0)
b.texte = texte
b:SetScript("OnEnter", function(self) couleur(self.texte, COULEUR.enteteSurvol) end)
b:SetScript("OnLeave", function(self) couleur(self.texte, COULEUR.enteteRepos) end)
b:SetScript("OnMouseDown", function(self)
self.texte:SetPoint("LEFT", self, "LEFT", 9, -1)
self.plus.Icon:SetPoint("CENTER", self.plus, "CENTER", 1, -1)
end)
b:SetScript("OnMouseUp", function(self)
self.texte:SetPoint("LEFT", self, "LEFT", 8, 0)
self.plus.Icon:SetPoint("CENTER", self.plus, "CENTER", 0, 0)
end)
b:SetScript("OnClick", function(self, bouton)
PlaySound("igMainMenuOptionCheckBoxOn")
if bouton == "RightButton" then
local nom = self.info.title
menu(self, {
{ text = TEXTE.toutSuivre, func = function() suivreEntete(nom, true) end },
{ text = TEXTE.neRienSuivre, func = function() suivreEntete(nom, false) end },
})
elseif self.info.isCollapsed then
ExpandQuestHeader(self.info.index)
else
CollapseQuestHeader(self.info.index)
end
end)
return b
end
local function creerQuete(v, i)
local b = CreateFrame("Button", "ForeverUIQuestLogTitle" .. i, v.contenu)
b:SetWidth(V.queteL)
b:RegisterForClicks("LeftButtonUp", "RightButtonUp")
-- la case de suivi, 14 x 14, marges de souris -10
local case = CreateFrame("Button", nil, b)
case:SetWidth(V.caseCote) case:SetHeight(V.caseCote)
case:SetPoint("TOPRIGHT", b, "TOPRIGHT", 0, V.caseY)
case:SetHitRectInsets(-10, -10, -10, -10)
local carre = case:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(carre, "questlog-icon-ticksquare")
carre:SetPoint("CENTER", case, "CENTER", 0, 0)
local coche = case:CreateTexture(nil, "ARTWORK")
ForeverUI.SetAtlas(coche, "questlog-icon-checkmark-yellow")
coche:SetPoint("CENTER", case, "CENTER", 1, 1)
local survolCase = case:CreateTexture(nil, "HIGHLIGHT")
ForeverUI.SetAtlas(survolCase, "questlog-icon-ticksquare")
survolCase:SetPoint("CENTER", case, "CENTER", 0, 0)
survolCase:SetBlendMode("ADD")
survolCase:SetAlpha(0.4)
case:SetScript("OnClick", function()
PlaySound(IsQuestWatched(b.index) and "igMainMenuOptionCheckBoxOff" or "igMainMenuOptionCheckBoxOn")
basculerSuivi(b.index)
end)
b.case, b.coche = case, coche
-- la surbrillance, sous le texte (couche BORDER)
local lueur = b:CreateTexture(nil, "BORDER")
ForeverUI.SetAtlas(lueur, "questlog-quest-glow-yellow")
lueur:Hide()
b.lueur = lueur
local texte = b:CreateFontString(nil, "ARTWORK", "GameFontNormalLeft")
texte:SetPoint("TOPLEFT", b, "TOPLEFT", V.queteTexteX, V.queteTexteY)
texte:SetJustifyH("LEFT")
b.texte = texte
local tag = b:CreateFontString(nil, "ARTWORK", "GameFontNormalLeft")
tag:SetPoint("RIGHT", case, "LEFT", -4, 0)
tag:SetPoint("TOP", texte, "TOP", 0, 0)
tag:Hide()
b.tag = tag
lueur:SetPoint("TOP", texte, "TOP", 0, 4)
lueur:SetPoint("BOTTOM", texte, "BOTTOM", 0, -4)
b:SetScript("OnEnter", function(self)
local c = COULEUR.difficulteSurvol[self.difficulte]
couleur(self.texte, c)
couleur(self.tag, c)
for _, o in ipairs(self.lignesObjectif) do
couleur(o.texte, COULEUR.objectifSurvol)
couleur(o.tiret, COULEUR.objectifSurvol)
end
self.lueur:Show()
zone(self.info, true)
infobulle(self)
end)
b:SetScript("OnLeave", function(self)
local c = COULEUR.difficulte[self.difficulte]
couleur(self.texte, c)
couleur(self.tag, c)
for _, o in ipairs(self.lignesObjectif) do
couleur(o.texte, COULEUR.objectif)
couleur(o.tiret, COULEUR.objectif)
end
self.lueur:Hide()
zone(self.info, false)
GameTooltip:Hide()
end)
b:SetScript("OnMouseDown", function(self)
self.texte:SetPoint("TOPLEFT", self, "TOPLEFT", V.queteTexteX + 1, V.queteTexteY - 1)
end)
b:SetScript("OnMouseUp", function(self)
self.texte:SetPoint("TOPLEFT", self, "TOPLEFT", V.queteTexteX, V.queteTexteY)
end)
b:SetScript("OnClick", function(self, bouton)
if IsModifiedClick("CHATLINK") and ChatEdit_InsertLink and ChatEdit_InsertLink(GetQuestLink(self.index)) then
return
end
PlaySound("igMainMenuOptionCheckBoxOn")
if IsShiftKeyDown() then
basculerSuivi(self.index)
elseif bouton == "RightButton" then
menuQuete(self)
else
J.ouvrirDetails(self.info)
end
end)
b.lignesObjectif = {}
return b
end
local function creerObjectif(v, i)
local o = CreateFrame("Frame", nil, v.contenu)
o:SetWidth(V.objectifL)
local tiret = o:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
tiret:SetPoint("TOPLEFT", o, "TOPLEFT", 0, 0)
tiret:SetHeight(16)
tiret:SetText(QUEST_DASH)
local texte = o:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
texte:SetWidth(V.objectifTexteL)
texte:SetJustifyH("LEFT")
texte:SetJustifyV("TOP")
texte:SetPoint("TOPLEFT", tiret, "TOPRIGHT", 0, 0)
o.tiret, o.texte = tiret, texte
return o
end
local function prendre(v, liste, n, fabrique)
local f = liste[n]
if not f then
f = fabrique(v, n)
liste[n] = f
end
f:ClearAllPoints()
f:Show()
return f
end
-- QuestLogQuests_AddQuestButton, pour 3.3.5
local function remplirQuete(v, b, info, compte)
b.info, b.index = info, info.index
b.difficulte = cleDifficulte(info.level)
local c = COULEUR.difficulte[b.difficulte]
b.texte:SetText(titreQuete(info))
couleur(b.texte, c)
couleur(b.tag, c)
if estElite(info) then
b.tag:SetText(format(PARENS_TEMPLATE, ELITE))
b.tag:Show()
b.texte:SetPoint("RIGHT", b.tag, "LEFT", -4, 0)
else
b.tag:Hide()
b.texte:SetPoint("RIGHT", b.case, "LEFT", -4, 0)
end
if IsQuestWatched(info.index) then b.coche:Show() else b.coche:Hide() end
b.lueur:Hide()
local hauteur = 8 + b.texte:GetHeight()
for _, o in ipairs(b.lignesObjectif) do o:Hide() end
b.lignesObjectif = {}
local function objectif(texte, precedent)
compte.o = compte.o + 1
local o = prendre(v, v.objectifs, compte.o, creerObjectif)
o.texte:SetText(texte)
couleur(o.texte, COULEUR.objectif)
couleur(o.tiret, COULEUR.objectif)
local h = o.texte:GetHeight()
o:SetHeight(h)
if precedent then
o:SetPoint("TOPLEFT", precedent, "BOTTOMLEFT", 0, -2)
h = h + 2
else
o:SetPoint("TOPLEFT", b.texte, "BOTTOMLEFT", 0, -3)
h = h + 3
end
table.insert(b.lignesObjectif, o)
hauteur = hauteur + h
return o
end
if not reglages().objectifs then
hauteur = hauteur + 4
elseif info.isComplete and info.isComplete > 0 then
objectif(GetQuestLogCompletionText(info.index) or TEXTE.pret)
else
local precedent
for i = 1, GetNumQuestLeaderBoards(info.index) do
local texte, _, fini = GetQuestLogLeaderBoard(i, info.index)
if texte and not fini then
precedent = objectif(texte, precedent)
end
end
local requis = GetQuestLogRequiredMoney(info.index)
local argent = GetMoney()
if requis and requis > argent then
objectif(GetMoneyString(argent) .. " / " .. GetMoneyString(requis), precedent)
end
end
-- le repere : celui de la carte affichee, s'il y en a un. POIButton de
-- camelot : 20 x 20 a (6, -4) ; celui de WotLK fait 32 x 32 : on le centre
-- au meme endroit.
local r = compte.reperes and info.questID and compte.reperes[info.questID]
if r and QuestPOI_DisplayButton then
local poi = QuestPOI_DisplayButton(PARENT_REPERES, r[1], r[2], info.questID)
poi:ClearAllPoints()
poi:SetPoint("CENTER", b, "TOPLEFT", 6 + 10, -4 - 10)
poi:SetFrameLevel(b:GetFrameLevel() + 2)
poi.info = info
poi:SetScript("OnClick", function(self)
PlaySound("igMainMenuOptionCheckBoxOn")
montrerSurCarte(self.info)
end)
if queteSelectionnee() == info.questID and QuestPOI_SelectButton then
QuestPOI_SelectButton(poi)
end
end
-- la place du repere de quete (+6 dans la source)
hauteur = hauteur + 6
b:SetHeight(hauteur)
end
-- QuestLogQuests_Update : tout est reconstruit a chaque passage.
function J.maj()
local v = J.volet
if not v or not v:IsShown() then
return
end
for _, f in ipairs(v.entetes) do f:Hide() end
for _, f in ipairs(v.quetes) do f:Hide() end
for _, f in ipairs(v.objectifs) do f:Hide() end
local compte = { e = 0, q = 0, o = 0, reperes = reperesDeLaCarte() }
if QuestPOI_HideAllButtons then
QuestPOI_HideAllButtons(PARENT_REPERES)
end
local haut = 0
local precedent = nil -- "entete", "quete" ou nil
local enteteCourant = nil
local enteteAffiche = false
local enAttente = nil -- un en-tete qui attend sa premiere quete
local function poser(f, type, gauche)
local ecart
if type == "entete" then
ecart = (precedent == nil and ECART.enteteApresRien)
or (precedent == "entete" and ECART.enteteApresEntete)
or ECART.enteteApresQuete
else
ecart = (precedent == "entete" and ECART.queteApresEntete) or ECART.queteApresQuete
end
haut = haut + ecart
f:SetPoint("TOPLEFT", v.contenu, "TOPLEFT", gauche, -haut)
haut = haut + f:GetHeight()
precedent = type
end
local function poserEntete(info)
compte.e = compte.e + 1
local e = prendre(v, v.entetes, compte.e, creerEntete)
e.info = info
e.texte:SetText(info.title)
couleur(e.texte, COULEUR.enteteRepos)
ForeverUI.SetAtlas(e.plus.Icon, info.isCollapsed and "common-button-list-plus" or "common-button-list-minus")
poser(e, "entete", V.enteteX)
end
local numQuetes = 0
for i = 1, GetNumQuestLogEntries() do
local info = entree(i)
if info and info.isHeader then
enteteCourant = info
-- un en-tete replie cache ses quetes : on le montre tel quel ;
-- deplie, il attend une quete a montrer (QuestLogQuests_ShouldShowHeaderButton)
if info.isCollapsed and not R.texte then
poserEntete(info)
enAttente = nil
else
enAttente = info
end
elseif info then
numQuetes = numQuetes + 1
if correspond(info) then
if enAttente then
poserEntete(enAttente)
enAttente = nil
end
compte.q = compte.q + 1
local b = prendre(v, v.quetes, compte.q, creerQuete)
remplirQuete(v, b, info, compte)
poser(b, "quete", 0)
end
end
end
v.contenu:SetHeight(math.max(haut, 1))
-- le fond : vide, ou normal ; rogne a la hauteur du cadre
local rien = (compte.q == 0 and compte.e == 0)
-- En double densite : sur l'ecran de l'utilisateur (echelle 0,64 en
-- 3840 x 1600), la variante 1x etait agrandie et se voyait pixelisee.
local atlas = (rien and not R.texte) and "questlog-empty-quest-background-2x" or "questlog-main-background-2x"
ForeverUI.SetAtlas(v.fond, atlas)
local e = ForeverUI.AtlasEntry(atlas)
local hFrame = v.liste:GetHeight() or 0
if e and hFrame > 0 and hFrame < e[7] then
v.fond:SetHeight(hFrame)
v.fond:SetTexCoord(e[2], e[3], e[4], e[4] + (e[5] - e[4]) * hFrame / e[7])
end
if rien and not R.texte then v.vide:Show() else v.vide:Hide() end
if rien and R.texte then v.aucun:Show() else v.aucun:Hide() end
if rien and not R.texte then
v.recherche:EnableMouse(false)
else
v.recherche:EnableMouse(true)
end
-- le compteur de camelot, avec le maximum de WotLK
local _, quetes = GetNumQuestLogEntries()
quetes = quetes or numQuetes
local rouge = quetes > MAX_QUESTLOG_QUESTS
v.compteur.texte:SetText(format(TEXTE.compteur, rouge and RED_FONT_COLOR_CODE or "|cffffffff", quetes, MAX_QUESTLOG_QUESTS))
-- la barre
if v.barre then
local total = math.ceil(haut / V.pas)
local visibles = math.floor(hFrame / V.pas)
local decalage = math.min(v.barre.decalage or 0, math.max(0, total - visibles))
v.barre:Regler(total, visibles, decalage)
v.liste:SetVerticalScroll(decalage * V.pas)
end
-- LE CADRE ET LE FOND SUIVENT LA BARRE (demande du 2026-09-25) : barre
-- masquee, ils s'etendent sur les 22 de sa place, jusqu'au bord du volet
-- -- le bord droit du cadre deborde alors de 3, en miroir de son bord
-- gauche ; barre montree, ils gardent la taille de camelot.
local place = (v.barre and v.barre:IsShown()) and 0 or -V.listeDroite
v.bord:ClearAllPoints()
v.bord:SetPoint("TOPLEFT", v.liste, "TOPLEFT", -3, 7)
v.bord:SetPoint("BOTTOMRIGHT", v.liste, "BOTTOMRIGHT", 3 + place, -6)
if e then
v.fond:SetWidth(e[6] + place)
end
J.majOmbre()
-- la page d'une quete est ouverte : la liste reste cachee, sa barre aussi
if J.detailsOuverts() and v.barre then
v.barre:Hide()
end
end
-- l'ombre du bas s'efface a mesure qu'on approche de la fin
function J.majOmbre()
local v = J.volet
if not v then return end
local plage = math.max(0, v.contenu:GetHeight() - (v.liste:GetHeight() or 0))
local defile = v.liste:GetVerticalScroll() or 0
local h = v.ombre:GetHeight() or 1
local alpha = (plage - defile) / (h > 0 and h or 1)
if alpha < 0 then alpha = 0 elseif alpha > 1 then alpha = 1 end
v.ombre:SetAlpha(alpha)
end
-- ------------------------------------------------------ montrer, cacher
function J.estOuvert()
return J.volet and J.volet:IsShown()
end
-- Appele par WorldMap.lua quand il pose la petite fenetre : le volet suit
-- l'etat retenu (questLogOpen).
function J.poser()
local v = J.volet
if not v then return end
-- fils de WorldMapFrame, qui est a l'echelle de la carte : l'inverse le
-- ramene a l'unite de l'interface
v:SetScale(ForeverUI.WorldMap and ForeverUI.WorldMap.inverse or 1)
if reglages().volet then v:Show() else v:Hide() end
J.maj()
end
function J.cacher()
if J.volet then J.volet:Hide() end
end
-- HandleUserActionToggleSidePanel
function J.basculerVolet()
reglages().volet = not reglages().volet
if ForeverUI.WorldMap and ForeverUI.WorldMap.poserReduit then
ForeverUI.WorldMap.poserReduit()
end
end
-- L : HandleUserActionToggleQuestLog. Carte ouverte avec le volet -> on la
-- ferme ; sinon on l'ouvre en petite fenetre, volet ouvert. WotLK garde son
-- mode de taille d'une ouverture a l'autre ; en plein ecran, on repasse en
-- petite fenetre par sa propre fonction.
function J.basculerJournal()
if WorldMapFrame:IsShown() and J.estOuvert() then
HideUIPanel(WorldMapFrame)
return
end
-- Montrer QuestLogFrame a pu fermer la carte avant nous : en mode
-- deplacable elle est un panneau "center" qui cede sa place
-- (UIParent.lua:1355). Fermee dans cette meme image avec son volet, c'est
-- que L voulait la fermer.
if J.fermeeA and J.fermeeA == GetTime() and J.fermeeAvecVolet then
J.fermeeA = nil
return
end
reglages().volet = true
if WORLDMAP_SETTINGS.size ~= WORLDMAP_WINDOWED_SIZE then
if WorldMapFrame:IsShown() then
WorldMapFrame_ToggleWindowSize()
return
end
WorldMap_ToggleSizeDown()
end
if WorldMapFrame:IsShown() then
if ForeverUI.WorldMap and ForeverUI.WorldMap.poserReduit then
ForeverUI.WorldMap.poserReduit()
end
else
ShowUIPanel(WorldMapFrame)
end
end
-- QuestMapFrame_OpenToQuestDetails, appele par le suivi de quetes : la carte
-- en petite fenetre, le volet ouvert, puis la page de la quete.
function J.ouvrirPage(index)
local info = entree(index)
if not info or info.isHeader then
return
end
reglages().volet = true
if WORLDMAP_SETTINGS.size ~= WORLDMAP_WINDOWED_SIZE then
if WorldMapFrame:IsShown() then
WorldMapFrame_ToggleWindowSize()
else
WorldMap_ToggleSizeDown()
end
end
if WorldMapFrame:IsShown() then
if ForeverUI.WorldMap and ForeverUI.WorldMap.poserReduit then
ForeverUI.WorldMap.poserReduit()
end
else
ShowUIPanel(WorldMapFrame)
end
J.ouvrirDetails(info)
end
-- -------------------------------------------------------------- l'assemblage
local function construire()
if J.volet or not WorldMapFrame then
return J.volet
end
J.volet = creerVolet(WorldMapFrame)
return J.volet
end
J.construire = construire
-- QuestLogFrame reste en place, invisible (decision 3) : ses fonctions et son
-- OnShow marchent pour les addons, mais le montrer ouvre le volet. Le greffon
-- passe APRES ShowUIPanel : on le referme dans la meme image, il n'est jamais
-- dessine.
if hooksecurefunc then
hooksecurefunc("ShowUIPanel", function(cadre)
if cadre and cadre == QuestLogFrame and QuestLogFrame:IsShown() then
HideUIPanel(QuestLogFrame)
J.basculerJournal()
end
end)
end
-- A l'entree en jeu, camelot deplie la zone ou l'on est et replie les autres
-- (QuestMapFrame_ResetFilters). 3.3.5 ne dit pas quel en-tete est "sur la
-- carte" : c'est celui qui porte le nom de la zone.
local function replierSelonZone()
local zone = GetRealZoneText and GetRealZoneText()
if not zone or zone == "" then
return
end
for i = GetNumQuestLogEntries(), 1, -1 do
local info = entree(i)
if info and info.isHeader then
if info.title == zone then
if info.isCollapsed then ExpandQuestHeader(i) end
elseif not info.isCollapsed then
CollapseQuestHeader(i)
end
end
end
end
J.replierSelonZone = replierSelonZone
-- WotLK refait ses reperes a chaque changement de carte ou de quete : la liste
-- reprend leurs numeros.
if hooksecurefunc and WorldMapFrame_UpdateQuests then
hooksecurefunc("WorldMapFrame_UpdateQuests", function()
if J.estOuvert() then
J.maj()
J.majDetails()
end
end)
end
if WorldMapFrame and WorldMapFrame.HookScript then
WorldMapFrame:HookScript("OnHide", function()
J.fermeeA = GetTime()
J.fermeeAvecVolet = J.estOuvert() and true or false
end)
end
local veilleur = CreateFrame("Frame")
veilleur:RegisterEvent("QUEST_LOG_UPDATE")
veilleur:RegisterEvent("UNIT_QUEST_LOG_CHANGED")
veilleur:RegisterEvent("PARTY_MEMBERS_CHANGED")
veilleur:RegisterEvent("QUEST_WATCH_UPDATE")
veilleur:RegisterEvent("PLAYER_ENTERING_WORLD")
veilleur:SetScript("OnEvent", function(_, evenement)
if evenement == "PLAYER_ENTERING_WORLD" then
if not J.dejaReplie then
J.dejaReplie = true
replierSelonZone()
end
end
if J.estOuvert() then
J.maj()
J.majDetails()
end
end)
construire()
ForeverUI.QuestLogDebug = function()
local v = J.volet
local prefixe = "|cff66ccffForeverUI|r "
if not v then
DEFAULT_CHAT_FRAME:AddMessage(prefixe .. L.QUESTLOG_DEBUG_NOT_BUILT)
return
end
local entetes, quetes = 0, 0
for _, f in ipairs(v.entetes) do if f:IsShown() then entetes = entetes + 1 end end
for _, f in ipairs(v.quetes) do if f:IsShown() then quetes = quetes + 1 end end
DEFAULT_CHAT_FRAME:AddMessage(prefixe .. string.format(
L.QUESTLOG_DEBUG_STATE,
v:IsShown() and L.QUESTLOG_DEBUG_OPEN or L.QUESTLOG_DEBUG_CLOSED, v:GetWidth(), v:GetHeight(), entetes, quetes,
tostring(R.texte), tostring(reglages().objectifs)))
end