From 065c0f2645dd7f8215179350bd800d2b76ed1ef9 Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Sun, 22 Mar 2026 17:29:15 +0100 Subject: [PATCH 1/6] Quest functions Stage 1 Move code to own frames --- Core/MultiBotInit.lua | 1264 +---------------- MultiBot.toc | 9 + UI/MultiBotGameObjectCopyFrame.lua | 78 + UI/MultiBotGameObjectResultsFrame.lua | 109 ++ UI/MultiBotPromptDialog.lua | 80 ++ UI/MultiBotQuestAllFrame.lua | 158 +++ UI/MultiBotQuestCompletedFrame.lua | 98 ++ UI/MultiBotQuestIncompleteFrame.lua | 98 ++ UI/MultiBotQuestLogFrame.lua | 173 +++ UI/MultiBotQuestUIShared.lua | 317 +++++ UI/MultiBotQuestsMenu.lua | 241 ++++ docs/ace3-expansion-checklist.md | 3 + .../ace3-quests-gobjects-migration-tracker.md | 123 ++ docs/ace3-ui-frame-inventory.md | 34 +- 14 files changed, 1515 insertions(+), 1270 deletions(-) create mode 100644 UI/MultiBotGameObjectCopyFrame.lua create mode 100644 UI/MultiBotGameObjectResultsFrame.lua create mode 100644 UI/MultiBotPromptDialog.lua create mode 100644 UI/MultiBotQuestAllFrame.lua create mode 100644 UI/MultiBotQuestCompletedFrame.lua create mode 100644 UI/MultiBotQuestIncompleteFrame.lua create mode 100644 UI/MultiBotQuestLogFrame.lua create mode 100644 UI/MultiBotQuestUIShared.lua create mode 100644 UI/MultiBotQuestsMenu.lua create mode 100644 docs/ace3-quests-gobjects-migration-tracker.md diff --git a/Core/MultiBotInit.lua b/Core/MultiBotInit.lua index a8d8aa3..65b3188 100644 --- a/Core/MultiBotInit.lua +++ b/Core/MultiBotInit.lua @@ -1771,6 +1771,8 @@ local function GetLocalizedQuestName(questID) local textObj = _G["MB_LocalizeQuestTooltipTextLeft1"] return (textObj and textObj:GetText()) or tostring(questID) end + +MultiBot.GetLocalizedQuestName = MultiBot.GetLocalizedQuestName or GetLocalizedQuestName -- END HIDDEN TOOLTIP -- local function getUniversalPromptAceGUI() @@ -1914,1262 +1916,16 @@ local function createAceQuestPopupHost(title, width, height, missingDepMessage, end MultiBot.CreateAceQuestPopupHost = MultiBot.CreateAceQuestPopupHost or createAceQuestPopupHost +MultiBot.ResolveAceGUI = MultiBot.ResolveAceGUI or resolveAceGUI +MultiBot.SetAceWindowCloseToHide = MultiBot.SetAceWindowCloseToHide or setAceWindowCloseToHide +MultiBot.RegisterAceWindowEscapeClose = MultiBot.RegisterAceWindowEscapeClose or registerAceWindowEscapeClose +MultiBot.BindAceWindowPosition = MultiBot.BindAceWindowPosition or bindAceWindowPosition --- MAIN BUTTON -- -local tButton = tRight.addButton("Quests Menu", 0, 0, - "achievement_quests_completed_06", - MultiBot.L("tips.quests.main")) -local tQuestMenu = tRight.addFrame("QuestMenu", -2, 64) -tQuestMenu:Hide() -tButton.doLeft = function(p) MultiBot.ShowHideSwitch(p.parent.frames["QuestMenu"]) end -tButton.doRight = tButton.doLeft --- END MAIN BUTTON -- - --- BUTTON Accept * -- -tQuestMenu.addButton("AcceptAll", 0, 30, - "inv_misc_note_02", MultiBot.L("tips.quests.accept")) -.doLeft = function() MultiBot.ActionToGroup("accept *") end --- END BUTTON Accept * -- - --- POP-UP Frame for Quests -- -local tQuests = createAceQuestPopupHost(QUEST_LOG, 390, 470, "AceGUI-3.0 is required for MB_QuestPopup", "quest_popup") -assert(tQuests, "AceGUI-3.0 is required for MB_QuestPopup") - --- ScrollFrame + ScrollBar -local scrollFrame = CreateFrame("ScrollFrame", "MB_QuestScroll", tQuests, "UIPanelScrollFrameTemplate") -scrollFrame:SetPoint("TOPLEFT", 10, -8) -scrollFrame:SetPoint("BOTTOMRIGHT", -26, 8) - -local content = CreateFrame("Frame", nil, scrollFrame) -content:SetWidth(1) -- largeur auto -scrollFrame:SetScrollChild(content) --- END POP-UP Frame for “Quests” -- - --- BUTTON Quests -- -local tListBtn = tQuestMenu.addButton("Quests", 0, -30, - "inv_misc_book_07", MultiBot.L("tips.quests.master")) --- requis par MultiBotHandler -tRight.buttons["Quests"] = tListBtn - --- helpers -local function ClearContent() - for _, child in ipairs({content:GetChildren()}) do - child:Hide() - child:SetParent(nil) - end -end - -local function MemberNamesOnQuest(questIndex) - local names = {} - if GetNumRaidMembers() > 0 then - for n = 1, 40 do - local unit = "raid"..n - if UnitExists(unit) and IsUnitOnQuest(questIndex, unit) then - local name = UnitName(unit) -- ← récupère juste le nom - if name then table.insert(names, name) end - end - end - elseif GetNumPartyMembers() > 0 then - for n = 1, 4 do - local unit = "party"..n - if UnitExists(unit) and IsUnitOnQuest(questIndex, unit) then - local name = UnitName(unit) - if name then table.insert(names, name) end - end - end - end - return names -end - --- CLIC DROIT : génère et rafraichit la liste -tListBtn.doRight = function() - ClearContent() - - local entries = GetNumQuestLogEntries() - local lineHeight, y = 24, -4 - - for i = 1, entries do - local link = GetQuestLink(i) - local questID = tonumber(link and link:match("|Hquest:(%d+):")) - local title, level, _, header, collapsed = GetQuestLogTitle(i) - - if collapsed == nil then -- entrée réelle - local line = CreateFrame("Frame", nil, content) - line:SetSize(300, lineHeight) - line:SetPoint("TOPLEFT", 0, y) - - -- icône - local icon = line:CreateTexture(nil, "ARTWORK") - icon:SetTexture("Interface\\Icons\\inv_misc_note_01") - icon:SetSize(20, 20) - icon:SetPoint("LEFT") - - -- lien de quête en SimpleHTML - local html = CreateFrame("SimpleHTML", nil, line) - html:SetSize(260, 20) - html:SetPoint("LEFT", 24, 0) - html:SetFontObject("GameFontNormal") - html:SetText(link:gsub("%[", "|cff00ff00["):gsub("%]", "]|r")) - html:SetHyperlinksEnabled(true) - - -- Tooltip - html:SetScript("OnHyperlinkEnter", function(self, linkData, fullLink) - GameTooltip:SetOwner(self, "ANCHOR_CURSOR") - GameTooltip:SetHyperlink(fullLink) - - -- Ajoute les objectifs de la quête - local numObj = GetNumQuestLeaderBoards(i) - if numObj and numObj > 0 then - for k = 1, numObj do - local txtObj, objType, finished = GetQuestLogLeaderBoard(k, i) - if txtObj then - local r, g, b = finished and 0.5 or 1, finished and 0.5 or 1, finished and 0.5 or 1 - GameTooltip:AddLine("• "..txtObj, r, g, b) - end - end - end - - -- Liste des membres/bots sur la quête - local members = MemberNamesOnQuest(i) - if #members > 0 then - GameTooltip:AddLine(" ", 1, 1, 1) - GameTooltip:AddLine("Groupe :", 0.8, 0.8, 0.8) - for _, n in ipairs(members) do GameTooltip:AddLine("- "..n) end - end - - GameTooltip:Show() - end) - html:SetScript("OnHyperlinkLeave", function() GameTooltip:Hide() end) - - -- CLIC SUR LE LIEN DE LA QUETE - html:SetScript("OnHyperlinkClick", function(self, linkData, link, button) - if link:match("|Hquest:") then - local questIDClicked = tonumber(link:match("|Hquest:(%d+):")) - -- Retrouver l'index de la quête dans le journal - for idx = 1, GetNumQuestLogEntries() do - local qLink = GetQuestLink(idx) - if qLink and tonumber(qLink:match("|Hquest:(%d+):")) == questIDClicked then - SelectQuestLogEntry(idx) - if button == "RightButton" then - if GetNumRaidMembers() > 0 then - SendChatMessage("drop "..qLink, "RAID") - elseif GetNumPartyMembers() > 0 then - SendChatMessage("drop "..qLink, "PARTY") - end - SetAbandonQuest() - AbandonQuest() - else - QuestLogPushQuest() - end - break - end - end - end - end) - - y = y - lineHeight - end - end - content:SetHeight(-y + 4) -- hauteur totale des lignes - scrollFrame:SetVerticalScroll(0) -- remonter en haut -end - --- CLIC GAUCHE : show/hide la fenêtre -tListBtn.doLeft = function() - if tQuests:IsShown() then - tQuests:Hide() - else - tQuests:Show() - tListBtn.doRight() - end -end - --- END BUTTON QUESTS -- - --- BUTTON QUESTS INCOMPLETED with sub buttons -- --- Table de stockage -MultiBot.BotQuestsIncompleted = {} -- [botName] = { [questID]=questName, ... } - --- Popup Liste des quêtes du bot -local tBotPopup = createAceQuestPopupHost(MultiBot.L("tips.quests.incomplist"), 380, 420, "AceGUI-3.0 is required for MB_BotQuestPopup", "bot_quest_popup") -assert(tBotPopup, "AceGUI-3.0 is required for MB_BotQuestPopup") - -local scroll = CreateFrame("ScrollFrame", "MB_BotQuestScroll", tBotPopup, "UIPanelScrollFrameTemplate") -scroll:SetPoint("TOPLEFT", 10, -8) -scroll:SetPoint("BOTTOMRIGHT", -26, 8) - -local contentBot = CreateFrame("Frame", nil, scroll) -contentBot:SetWidth(1) -scroll:SetScrollChild(contentBot) - -MultiBot.tBotPopup = tBotPopup - -local function ClearBotContent() - for _, child in ipairs({ contentBot:GetChildren() }) do - child:Hide() - child:SetParent(nil) - end -end - --- AJOUT ON VIDE TOUT -tBotPopup:SetScript("OnHide", function() - MultiBot.BotQuestsIncompleted = {} - ClearBotContent() -end) --- Fin de l’ajout - -local function BuildBotQuestList(botName) - ClearBotContent() - local quests = MultiBot.BotQuestsIncompleted[botName] or {} - local y = -4 - for id, name in pairs(quests) do - local line = CreateFrame("Frame", nil, contentBot) - line:SetSize(300, 24) - line:SetPoint("TOPLEFT", 0, y) - - local icon = line:CreateTexture(nil, "ARTWORK") - icon:SetTexture("Interface\\Icons\\inv_misc_note_02") - icon:SetSize(20,20) - icon:SetPoint("LEFT") - - local locName = GetLocalizedQuestName(id) or name - local link = ("|cff00ff00|Hquest:%s:0|h[%s]|h|r"):format(id, locName) - local html = CreateFrame("SimpleHTML", nil, line) - html:SetSize(260, 20) - html:SetPoint("LEFT", 24, 0) - html:SetFontObject("GameFontNormal") - html:SetText(link) - html:SetHyperlinksEnabled(true) - html:SetScript("OnHyperlinkEnter", function(self, linkData, link) - GameTooltip:SetOwner(self, "ANCHOR_CURSOR") - GameTooltip:SetHyperlink(link) - GameTooltip:Show() - end) - html:SetScript("OnHyperlinkLeave", function() GameTooltip:Hide() end) - - y = y - 24 - end - contentBot:SetHeight(-y + 4) - scroll:SetVerticalScroll(0) -end - -MultiBot.BuildBotQuestList = BuildBotQuestList - --- Reconstruit la popup en mode GROUP on agrège toutes les quêtes -local function BuildAggregatedQuestList() - ClearBotContent() - - -- Construit la table id { name = ..., bots = { … } } - local questMap = {} - for botName, quests in pairs(MultiBot.BotQuestsIncompleted) do - for id, name in pairs(quests) do - local locName = GetLocalizedQuestName(id) or name - if not questMap[id] then - questMap[id] = { name = locName, bots = {} } - end - table.insert(questMap[id].bots, botName) - end - end - - -- 2Affiche chaque quête puis la ligne des bots - local y = -4 - for id, data in pairs(questMap) do - -- ligne quête - local lineQ = CreateFrame("Frame", nil, contentBot) - lineQ:SetSize(300, 24) - lineQ:SetPoint("TOPLEFT", 0, y) - - -- icône - local icon = lineQ:CreateTexture(nil, "ARTWORK") - icon:SetTexture("Interface\\Icons\\inv_misc_note_02") - icon:SetSize(20,20) - icon:SetPoint("LEFT") - - -- lien cliquable - local link = ("|cff00ff00|Hquest:%s:0|h[%s]|h|r"):format(id, data.name) - local htmlQ = CreateFrame("SimpleHTML", nil, lineQ) - htmlQ:SetSize(260, 20) - htmlQ:SetPoint("LEFT", 24, 0) - htmlQ:SetFontObject("GameFontNormal") - htmlQ:SetText(link) - htmlQ:SetHyperlinksEnabled(true) - htmlQ:SetScript("OnHyperlinkEnter", function(self, linkData, link) - GameTooltip:SetOwner(self, "ANCHOR_CURSOR") - GameTooltip:SetHyperlink(link) - GameTooltip:Show() - end) - htmlQ:SetScript("OnHyperlinkLeave", function() GameTooltip:Hide() end) - - y = y - 24 - - -- ligne bots - local lineB = CreateFrame("Frame", nil, contentBot) - lineB:SetSize(300, 16) - lineB:SetPoint("TOPLEFT", 0, y) - - local botLine = lineB:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall") - botLine:SetPoint("LEFT", 24, 0) - botLine:SetText(MultiBot.L("tips.quests.botsword") .. table.concat(data.bots, ", ")) - - y = y - 16 - end - - contentBot:SetHeight(-y + 4) - scroll:SetVerticalScroll(0) -end - --- Expose la fonction pour l’appeler depuis le handler -MultiBot.BuildAggregatedQuestList = BuildAggregatedQuestList - --- Bouton principal + deux sous-boutons pour choisir /p ou /w -local btnIncomp = tQuestMenu.addButton("BotQuestsIncomp", 0, 90, - "Interface\\Icons\\INV_Misc_Bag_22", - MultiBot.L("tips.quests.incompleted")) - -local btnGroup = tQuestMenu.addButton("BotQuestsIncompGroup", 31, 90, - "Interface\\Icons\\INV_Crate_08", - MultiBot.L("tips.quests.sendpartyraid")) -btnGroup:doHide() - -local btnWhisper = tQuestMenu.addButton("BotQuestsIncompWhisper", 61, 90, - "Interface\\Icons\\INV_Crate_08", - MultiBot.L("tips.quests.sendwhisp")) -btnWhisper:doHide() - -local function SendIncomp(method) - -MultiBot._awaitingQuestsAll = false - MultiBot._lastIncMode = method - if method == "WHISPER" then - local bot = UnitName("target") - if not bot or not UnitIsPlayer("target") then - UIErrorsFrame:AddMessage(MultiBot.L("tips.quests.questcomperror"), 1, 0.2, 0.2, 1) - return - end - -- reset juste pour ce bot - MultiBot.BotQuestsIncompleted[bot] = {} - -- envoi en whisper ciblé - MultiBot.ActionToTarget("quests incompleted", bot) - -- popup + liste pour ce bot - tBotPopup:Show() - ClearBotContent() - MultiBot.TimerAfter(0.5, function() BuildBotQuestList(bot) end) - else - -- reset global - MultiBot.BotQuestsIncompleted = {} - MultiBot.ActionToGroup("quests incompleted") - -- popup - tBotPopup:Show() - ClearBotContent() - end -end - -btnIncomp.doLeft = function() - if btnGroup:IsShown() then - btnGroup:doHide() - btnWhisper:doHide() - else - btnGroup:doShow() - btnWhisper:doShow() - end -end - -btnGroup.doLeft = function() SendIncomp("GROUP") end -btnWhisper.doLeft = function() SendIncomp("WHISPER") end - --- Expose pour le handler -tRight.buttons["BotQuestsIncomp"] = btnIncomp -tRight.buttons["BotQuestsIncompGroup"] = btnGroup -tRight.buttons["BotQuestsIncompWhisper"] = btnWhisper --- END BUTTON quests incompleted -- - - --- BUTTON COMPLETEDQUESTS -- --- Table de stockage pour les quêtes terminées du bot -MultiBot.BotQuestsCompleted = {} -- [botName] = { [questID]=questName, ... } - --- 2) Pop-up Liste des quêtes terminées du bot -local tBotCompPopup = createAceQuestPopupHost(MultiBot.L("tips.quests.complist"), 380, 420, "AceGUI-3.0 is required for MB_BotQuestCompPopup", "bot_quest_comp_popup") -assert(tBotCompPopup, "AceGUI-3.0 is required for MB_BotQuestCompPopup") - -local scroll2 = CreateFrame("ScrollFrame", "MB_BotQuestCompScroll", tBotCompPopup, "UIPanelScrollFrameTemplate") -scroll2:SetPoint("TOPLEFT", 10, -8) -scroll2:SetPoint("BOTTOMRIGHT", -26, 8) - -local contentComp = CreateFrame("Frame", nil, scroll2) -contentComp:SetWidth(1) -scroll2:SetScrollChild(contentComp) - -MultiBot.tBotCompPopup = tBotCompPopup - -local function ClearCompContent() - for _, child in ipairs({ contentComp:GetChildren() }) do - child:Hide() - child:SetParent(nil) - end -end - --- AJOUT ON VIDE TOUT -tBotCompPopup:SetScript("OnHide", function() - MultiBot.BotQuestsCompleted = {} - ClearCompContent() -end) --- Fin de l’ajout - --- Build pour un seul bot -local function BuildBotCompletedList(botName) - ClearCompContent() - local quests = MultiBot.BotQuestsCompleted[botName] or {} - local y = -4 - for id, name in pairs(quests) do - local line = CreateFrame("Frame", nil, contentComp) - line:SetSize(300, 24) - line:SetPoint("TOPLEFT", 0, y) - - local icon = line:CreateTexture(nil, "ARTWORK") - icon:SetTexture("Interface\\Icons\\inv_misc_note_02") - icon:SetSize(20,20) - icon:SetPoint("LEFT") - - local locName = GetLocalizedQuestName(id) or name - local link = ("|cff00ff00|Hquest:%s:0|h[%s]|h|r"):format(id, locName) - local html = CreateFrame("SimpleHTML", nil, line) - html:SetSize(260, 20) - html:SetPoint("LEFT", 24, 0) - html:SetFontObject("GameFontNormal") - html:SetText(link) - html:SetHyperlinksEnabled(true) - html:SetScript("OnHyperlinkEnter", function(self, linkData, link) - GameTooltip:SetOwner(self, "ANCHOR_CURSOR") - GameTooltip:SetHyperlink(link) - GameTooltip:Show() - end) - html:SetScript("OnHyperlinkLeave", function() GameTooltip:Hide() end) - - y = y - 24 - end - contentComp:SetHeight(-y + 4) - scroll2:SetVerticalScroll(0) -end -MultiBot.BuildBotCompletedList = BuildBotCompletedList - --- Build agrégé pour le groupe -local function BuildAggregatedCompletedList() - ClearCompContent() - - -- On agrège les quêtes terminées de tous les bots - local questMap = {} - for botName, quests in pairs(MultiBot.BotQuestsCompleted) do - for id, name in pairs(quests) do - local locName = GetLocalizedQuestName(id) or name - if not questMap[id] then - questMap[id] = { name = locName, bots = {} } - end - table.insert(questMap[id].bots, botName) - end - end - - -- On affiche - local y = -4 - for id, data in pairs(questMap) do - -- ligne quête - local lineQ = CreateFrame("Frame", nil, contentComp) - lineQ:SetSize(300, 24) - lineQ:SetPoint("TOPLEFT", 0, y) - - local icon = lineQ:CreateTexture(nil, "ARTWORK") - icon:SetTexture("Interface\\Icons\\inv_misc_note_02") - icon:SetSize(20, 20) - icon:SetPoint("LEFT") - - local link = ("|cff00ff00|Hquest:%s:0|h[%s]|h|r"):format(id, data.name) - local htmlQ = CreateFrame("SimpleHTML", nil, lineQ) - htmlQ:SetSize(260, 20) - htmlQ:SetPoint("LEFT", 24, 0) - htmlQ:SetFontObject("GameFontNormal") - htmlQ:SetText(link) - htmlQ:SetHyperlinksEnabled(true) - htmlQ:SetScript("OnHyperlinkEnter", function(self, linkData, link) - GameTooltip:SetOwner(self, "ANCHOR_CURSOR") - GameTooltip:SetHyperlink(link) - GameTooltip:Show() - end) - htmlQ:SetScript("OnHyperlinkLeave", function() GameTooltip:Hide() end) - - y = y - 24 - - -- ligne bots - local lineB = CreateFrame("Frame", nil, contentComp) - lineB:SetSize(300, 16) - lineB:SetPoint("TOPLEFT", 0, y) - - local botLine = lineB:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall") - botLine:SetPoint("LEFT", 24, 0) - botLine:SetText(MultiBot.L("tips.quests.botsword") .. table.concat(data.bots, ", ")) - - y = y - 16 - end - - contentComp:SetHeight(-y + 4) - scroll2:SetVerticalScroll(0) +-- QUESTS / GAMEOBJECTS UI -- +if MultiBot.InitializeQuestsMenu then + MultiBot.InitializeQuestsMenu(tRight) end - --- expose la fonction pour le handler -MultiBot.BuildAggregatedCompletedList = BuildAggregatedCompletedList - --- Les boutons -local btnComp = tQuestMenu.addButton("BotQuestsComp", 0, 60, - "Interface\\Icons\\INV_Misc_Bag_20", - MultiBot.L("tips.quests.completed")) - -local btnCompGroup = tQuestMenu.addButton("BotQuestsCompGroup", 31, 60, - "Interface\\Icons\\INV_Crate_09", - MultiBot.L("tips.quests.sendpartyraid")) -btnCompGroup:doHide() - -local btnCompWhisper = tQuestMenu.addButton("BotQuestsCompWhisper", 61, 60, - "Interface\\Icons\\INV_Crate_09", - MultiBot.L("tips.quests.sendwhisp")) -btnCompWhisper:doHide() - -local function SendComp(method) -MultiBot._awaitingQuestsAll = false - MultiBot._lastCompMode = method - if method == "WHISPER" then - local bot = UnitName("target") - if not bot or not UnitIsPlayer("target") then - UIErrorsFrame:AddMessage(MultiBot.L("tips.quests.questcomperror"), 1, 0.2, 0.2, 1) - return - end - MultiBot.BotQuestsCompleted[bot] = {} - MultiBot.ActionToTarget("quests completed", bot) - tBotCompPopup:Show() - ClearCompContent() - MultiBot.TimerAfter(0.5, function() - MultiBot.BuildBotCompletedList(bot) - end) - else - -- GROUP - MultiBot.BotQuestsCompleted = {} - MultiBot.ActionToGroup("quests completed") - tBotCompPopup:Show() - ClearCompContent() - end -end - -btnComp.doLeft = function() - if btnCompGroup:IsShown() then - btnCompGroup:doHide() - btnCompWhisper:doHide() - else - btnCompGroup:doShow() - btnCompWhisper:doShow() - end -end -btnCompGroup.doLeft = function() SendComp("GROUP") end -btnCompWhisper.doLeft = function() SendComp("WHISPER") end - --- Expose pour le handler -tRight.buttons["BotQuestsComp"] = btnComp -tRight.buttons["BotQuestsCompGroup"] = btnCompGroup -tRight.buttons["BotQuestsCompWhisper"] = btnCompWhisper --- END BUTTON COMPLETED QUESTS -- - --- BUTTON TALK -- -local btnTalk = tQuestMenu.addButton("BotQuestsTalk", 0, 0, - "Interface\\Icons\\ability_hunter_pet_devilsaur", - MultiBot.L("tips.quests.talk")) - -btnTalk.doLeft = function() - if not UnitExists("target") or UnitIsPlayer("target") then -- On vérifie qu'on cible bien un PNJ - UIErrorsFrame:AddMessage(MultiBot.L("tips.quests.talkerror"), 1, 0.2, 0.2, 1) - return - end - MultiBot.ActionToGroup("talk") -- Envoie "talk" à tout le groupe ou raid -end - -tRight.buttons["BotQuestsTalk"] = btnTalk --- END BUTTON TALK -- - --- BUTTON QUESTS ALL -- - --- POPUP Quests All -local tBotAllPopup = createAceQuestPopupHost(MultiBot.L("tips.quests.alllist"), 420, 460, "AceGUI-3.0 is required for MB_BotQuestAllPopup", "bot_quest_all_popup") -assert(tBotAllPopup, "AceGUI-3.0 is required for MB_BotQuestAllPopup") - --- On expose immédiatement pour qu'il existe dans SendAll -MultiBot.tBotAllPopup = tBotAllPopup - --- ScrollFrame -local scrollAll = CreateFrame("ScrollFrame", "MB_BotQuestAllScroll", tBotAllPopup, "UIPanelScrollFrameTemplate") -scrollAll:SetPoint("TOPLEFT", 10, -8) -scrollAll:SetPoint("BOTTOMRIGHT", -26, 8) - -local contentAll = CreateFrame("Frame", nil, scrollAll) -contentAll:SetWidth(1) -scrollAll:SetScrollChild(contentAll) -tBotAllPopup.content = contentAll - -function MultiBot.ClearAllContent() - -- 1) Frames (boutons, lignes, etc.) - for _, child in ipairs({ contentAll:GetChildren() }) do - child:Hide() - child:SetParent(nil) - end - - -- 2) Regions (FontStrings, Textures) – les headers sont ici - for _, region in ipairs({ contentAll:GetRegions() }) do - region:Hide() -- on la masque - if region:GetObjectType() == "FontString" then - region:SetText("") -- on vide le texte pour éviter les résidus - elseif region:GetObjectType() == "Texture" then - region:SetTexture(nil) -- on efface la texture éventuelle - end - end - - if contentAll.text then - contentAll.text:SetText("") - end -end - --- AJOUT ON VIDE TOUT -tBotAllPopup:SetScript("OnHide", function() - MultiBot.BotQuestsAll = {} - MultiBot.BotQuestsCompleted = {} - MultiBot.BotQuestsIncompleted = {} - MultiBot.ClearAllContent() -end) --- Fin de l’ajout - --- Build pour un seul bot -function MultiBot.BuildBotAllList(botName) - MultiBot.ClearAllContent() - local contentAll = MultiBot.tBotAllPopup.content - local quests = MultiBot.BotQuestsAll[botName] or {} - local y = -4 - for _, link in ipairs(quests) do - local questID = tonumber(link:match("|Hquest:(%d+):")) - local locName = questID and GetLocalizedQuestName(questID) or link - local displayLink = link:gsub("%[[^%]]+%]", "|cff00ff00["..locName.."]|r") - - local line = CreateFrame("Frame", nil, contentAll) - line:SetSize(360, 20) - line:SetPoint("TOPLEFT", 0, y) - - local icon = line:CreateTexture(nil, "ARTWORK") - icon:SetTexture("Interface\\Icons\\inv_misc_note_02") - icon:SetSize(20,20) - icon:SetPoint("LEFT", 0, 0) - - local html = CreateFrame("SimpleHTML", nil, line) - html:SetSize(320, 20); html:SetPoint("LEFT", 24, 0) - html:SetFontObject("GameFontNormal"); html:SetText(displayLink) - html:SetHyperlinksEnabled(true) - html:SetScript("OnHyperlinkEnter", function(self, _, link) - GameTooltip:SetOwner(self, "ANCHOR_CURSOR") - GameTooltip:SetHyperlink(link) - GameTooltip:Show() - end) - html:SetScript("OnHyperlinkLeave", function() GameTooltip:Hide() end) - - y = y - 22 - end - contentAll:SetHeight(-y + 4) - scrollAll:SetVerticalScroll(0) -end - --- version agrégée pour le groupe -local function BuildAggregatedAllList() - MultiBot.ClearAllContent() - local contentAll = MultiBot.tBotAllPopup.content - local y = -4 - - -- Regroupement comme avant... - local complete = {} - for bot, quests in pairs(MultiBot.BotQuestsCompleted or {}) do - for id, name in pairs(quests or {}) do - id = tonumber(id) - if not complete[id] then complete[id] = { name = name, bots = {} } end - table.insert(complete[id].bots, bot) - end - end - - local incomplete = {} - for bot, quests in pairs(MultiBot.BotQuestsIncompleted or {}) do - for id, name in pairs(quests or {}) do - id = tonumber(id) - if not incomplete[id] then incomplete[id] = { name = name, bots = {} } end - table.insert(incomplete[id].bots, bot) - end - end - - -- === Header Quêtes complètes === - local header = contentAll:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge") - header:SetPoint("TOPLEFT", 0, y) - header:SetText(MultiBot.L("tips.quests.compheader")) - y = y - 28 - - -- Affiche toutes les quêtes complètes - for id, data in pairs(complete) do - local line = CreateFrame("Frame", nil, contentAll) - line:SetSize(360, 20) - line:SetPoint("TOPLEFT", 0, y) - local icon = line:CreateTexture(nil, "ARTWORK") - icon:SetTexture("Interface\\Icons\\inv_misc_note_02") - icon:SetSize(20,20); icon:SetPoint("LEFT") - -- local link = ("|cff00ff00|Hquest:%s:0|h[%s]|h|r"):format(id, data.name) - local locName = GetLocalizedQuestName(id) - local link = ("|cff00ff00|Hquest:%s:0|h[%s]|h|r"):format(id, locName) - local html = CreateFrame("SimpleHTML", nil, line) - html:SetSize(320, 20); html:SetPoint("LEFT", 24, 0) - html:SetFontObject("GameFontNormal"); html:SetText(link) - html:SetHyperlinksEnabled(true) - html:SetScript("OnHyperlinkEnter", function(self, _, l) - GameTooltip:SetOwner(self, "ANCHOR_CURSOR") - GameTooltip:SetHyperlink(l); GameTooltip:Show() - end) - html:SetScript("OnHyperlinkLeave", GameTooltip_Hide) - y = y - 20 - - local botsLine = contentAll:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall") - botsLine:SetPoint("TOPLEFT", 24, y) - botsLine:SetText(MultiBot.L("tips.quests.botsword") .. table.concat(data.bots, ", ")) - y = y - 16 - end - - y = y - 10 - - -- === Header Quêtes incomplètes === - local header2 = contentAll:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge") - header2:SetPoint("TOPLEFT", 0, y) - header2:SetText(MultiBot.L("tips.quests.incompheader")) - y = y - 28 - - -- Affiche toutes les quêtes incomplètes - for id, data in pairs(incomplete) do - local line = CreateFrame("Frame", nil, contentAll) - line:SetSize(360, 20) - line:SetPoint("TOPLEFT", 0, y) - local icon = line:CreateTexture(nil, "ARTWORK") - icon:SetTexture("Interface\\Icons\\inv_misc_note_02") - icon:SetSize(20,20); icon:SetPoint("LEFT") - local locName = GetLocalizedQuestName(id) - local link = ("|cff00ff00|Hquest:%s:0|h[%s]|h|r"):format(id, locName) - local html = CreateFrame("SimpleHTML", nil, line) - html:SetSize(320, 20); html:SetPoint("LEFT", 24, 0) - html:SetFontObject("GameFontNormal"); html:SetText(link) - html:SetHyperlinksEnabled(true) - html:SetScript("OnHyperlinkEnter", function(self, _, l) - GameTooltip:SetOwner(self, "ANCHOR_CURSOR") - GameTooltip:SetHyperlink(l); GameTooltip:Show() - end) - html:SetScript("OnHyperlinkLeave", GameTooltip_Hide) - y = y - 20 - - local botsLine = contentAll:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall") - botsLine:SetPoint("TOPLEFT", 24, y) - botsLine:SetText(MultiBot.L("tips.quests.botsword") .. table.concat(data.bots, ", ")) - y = y - 16 - end - - contentAll:SetHeight(-y + 4) - scrollAll:SetVerticalScroll(0) -end - - -MultiBot.BuildAggregatedAllList = BuildAggregatedAllList - - --- BOUTONS All -local btnAll = tQuestMenu.addButton("BotQuestsAll", 0, 120, - "Interface\\Icons\\INV_Misc_Book_09", - MultiBot.L("tips.quests.allcompleted")) - -local btnAllGroup = tQuestMenu.addButton("BotQuestsAllGroup", 31, 120, - "Interface\\Icons\\INV_Misc_Book_09", - MultiBot.L("tips.quests.sendpartyraid")) -btnAllGroup:doHide() - -local btnAllWhisper = tQuestMenu.addButton("BotQuestsAllWhisper", 61, 120, - "Interface\\Icons\\INV_Misc_Book_09", - MultiBot.L("tips.quests.sendwhisp")) -btnAllWhisper:doHide() - -function SendAll(method) - MultiBot._lastAllMode = method - MultiBot._awaitingQuestsAll = true - MultiBot._blockOtherQuests = true - MultiBot.BotQuestsAll = {} - MultiBot._awaitingQuestsAllBots = {} - - if method == "GROUP" then - for i = 1, GetNumPartyMembers() do - local name = UnitName("party"..i) - if name then MultiBot._awaitingQuestsAllBots[name] = false end - end - MultiBot.ActionToGroup("quests all") - elseif method == "WHISPER" then - local bot = UnitName("target") - if not bot or not UnitIsPlayer("target") then - UIErrorsFrame:AddMessage(MultiBot.L("tips.quests.questcomperror"), 1, 0.2, 0.2, 1) - MultiBot._awaitingQuestsAll = false - MultiBot._blockOtherQuests = false - return - end - MultiBot._awaitingQuestsAllBots[bot] = false - MultiBot.ActionToTarget("quests all", bot) - end - - MultiBot.tBotAllPopup:Show() - MultiBot.ClearAllContent() - local f = MultiBot.tBotAllPopup.content - f.text = f.text or f:CreateFontString(nil, "ARTWORK", "GameFontNormal") - f.text:SetPoint("TOPLEFT", 8, -8) - f.text:SetText(LOADING) -end - -btnAll.doLeft = function() - if btnAllGroup:IsShown() then - btnAllGroup:doHide() - btnAllWhisper:doHide() - else - btnAllGroup:doShow() - btnAllWhisper:doShow() - end -end - -btnAllGroup.doLeft = function() SendAll("GROUP") end -btnAllWhisper.doLeft = function() SendAll("WHISPER") end - -tRight.buttons["BotQuestsAll"] = btnAll -tRight.buttons["BotQuestsAllGroup"] = btnAllGroup -tRight.buttons["BotQuestsAllWhisper"] = btnAllWhisper --- END BUTTON QUESTS ALL -- - --- BUTTONS USE GOB AND LOS -- --- GAME OBJECT POPUP/COPY HELPERS -- -local function getGameObjectEntries(bot) - local entries = MultiBot.LastGameObjectSearch and MultiBot.LastGameObjectSearch[bot] - if type(entries) ~= "table" then - return nil - end - - return entries -end - -local function collectSortedGameObjectBots() - local bots = {} - for bot in pairs(MultiBot.LastGameObjectSearch or {}) do - local entries = getGameObjectEntries(bot) - if entries and #entries > 0 then - table.insert(bots, bot) - end - end - table.sort(bots) - return bots -end - -local function isDashedSectionHeader(text) - return type(text) == "string" and text:find("^%s*%-+%s*.-%s*%-+%s*$") ~= nil -end - -local function clearFrameChildren(frame) - if not frame or not frame.GetNumChildren or not frame.GetChildren then - return - end - - local childCount = frame:GetNumChildren() or 0 - for i = childCount, 1, -1 do - local child = select(i, frame:GetChildren()) - if child then - child:Hide() - child:SetParent(nil) - end - end -end - -local function buildGameObjectCopyText(bots) - local lines = {} - - for _, bot in ipairs(bots) do - local entries = getGameObjectEntries(bot) or {} - table.insert(lines, ("Bot: %s"):format(bot)) - - for _, entry in ipairs(entries) do - table.insert(lines, entry) - end - - table.insert(lines, "") - end - - if #lines == 0 then - return MultiBot.L("tips.quests.gobnosearchdata") - end - - return table.concat(lines, "\n") -end - -local function applyDialogBackdrop(frame) - frame:SetBackdrop({ - bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background", - edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", - tile = true, tileSize = 32, edgeSize = 16, - insets = { left = 4, right = 4, top = 4, bottom = 4 } - }) - frame:SetFrameStrata("DIALOG") -end - -local function createPopupCloseButton(parent) - local close = CreateFrame("Button", nil, parent, "UIPanelCloseButton") - close:SetPoint("TOPRIGHT", -2, -2) - return close -end - -local getUniversalPromptAceGUI - -local function ensureGameObjectPopupFrame() - if MultiBot.GameObjPopup then - return MultiBot.GameObjPopup - end - - local aceGUI = resolveAceGUI("AceGUI-3.0 is required for MB_GameObjPopup") - if not aceGUI then - return nil - end - - local window = aceGUI:Create("Window") - if not window then - return nil - end - - window:SetTitle(MultiBot.L("tips.quests.gobsfound")) - window:SetWidth(420) - window:SetHeight(380) - window:EnableResize(false) - window:SetLayout("Flow") - window.frame:SetFrameStrata("DIALOG") - setAceWindowCloseToHide(window) - registerAceWindowEscapeClose(window, "GameObjPopup") - bindAceWindowPosition(window, "gameobject_popup") - - local scroll = aceGUI:Create("ScrollFrame") - scroll:SetFullWidth(true) - scroll:SetHeight(300) - scroll:SetLayout("List") - window:AddChild(scroll) - - local copyBtn = aceGUI:Create("Button") - copyBtn:SetText(MultiBot.L("tips.quests.gobselectall")) - copyBtn:SetWidth(150) - copyBtn:SetCallback("OnClick", function() - MultiBot.ShowGameObjectCopyBox() - end) - window:AddChild(copyBtn) - - MultiBot.GameObjPopup = { - window = window, - scroll = scroll, - copyBtn = copyBtn, - } - - return MultiBot.GameObjPopup -end - -local function ensureGameObjectCopyBoxFrame() - if MultiBot.GameObjCopyBox then - return MultiBot.GameObjCopyBox - end - - local aceGUI = resolveAceGUI("AceGUI-3.0 is required for MB_GameObjCopyBox") - if not aceGUI then - return nil - end - - local window = aceGUI:Create("Window") - if not window then - return nil - end - - window:SetTitle(MultiBot.L("tips.quests.gobctrlctocopy")) - window:SetWidth(420) - window:SetHeight(300) - window:EnableResize(false) - window:SetLayout("Fill") - window.frame:SetFrameStrata("DIALOG") - setAceWindowCloseToHide(window) - registerAceWindowEscapeClose(window, "GameObjCopy") - bindAceWindowPosition(window, "gameobject_copy") - - local editor = aceGUI:Create("MultiLineEditBox") - editor:SetLabel("") - editor:SetNumLines(14) - editor:DisableButton(true) - window:AddChild(editor) - - MultiBot.GameObjCopyBox = { - window = window, - editor = editor, - } - - return MultiBot.GameObjCopyBox -end - -function MultiBot.ShowGameObjectPopup() - - local popup = ensureGameObjectPopupFrame() - if not popup then - return - end - - if popup.window:IsShown() then - popup.window:Hide() - end - - -- Clear previous popup lines. - popup.scroll:ReleaseChildren() - - -- Render captured lines grouped by bot - local aceGUI = resolveAceGUI("AceGUI-3.0 is required for MB_GameObjPopup") - if not aceGUI then - return - end - - local bots = collectSortedGameObjectBots() - - for _, bot in ipairs(bots) do - local lines = getGameObjectEntries(bot) or {} - local botLine = aceGUI:Create("Label") - botLine:SetFullWidth(true) - botLine:SetText("Bot: |cff80ff80" .. bot .. "|r") - popup.scroll:AddChild(botLine) - - for _, txt in ipairs(lines) do - local line = aceGUI:Create("Label") - line:SetFullWidth(true) - local isSectionHeader = isDashedSectionHeader(txt) - if isSectionHeader then - line:SetText("|cffffff66" .. txt .. "|r") - else - line:SetText(" " .. txt) - end - popup.scroll:AddChild(line) - end - - local spacer = aceGUI:Create("Label") - spacer:SetFullWidth(true) - spacer:SetText(" ") - popup.scroll:AddChild(spacer) - end - - if #bots == 0 then - local noData = aceGUI:Create("Label") - noData:SetFullWidth(true) - noData:SetText(MultiBot.L("tips.quests.gobnosearchdata")) - popup.scroll:AddChild(noData) - end - - popup.window:Show() -end - -function MultiBot.ShowGameObjectCopyBox() - -- Close main popup if already visible - if MultiBot.GameObjPopup and MultiBot.GameObjPopup.window and MultiBot.GameObjPopup.window:IsShown() then - MultiBot.GameObjPopup.window:Hide() - end - - local box = ensureGameObjectCopyBoxFrame() - if not box then - return - end - - -- Build copy text from sorted game-object entries. - local bots = collectSortedGameObjectBots() - local text = buildGameObjectCopyText(bots) - - box.editor:SetText(text) - box.window:Show() - - local editBox = box.editor and box.editor.editBox - if editBox and editBox.SetFocus then - editBox:SetFocus() - end - if editBox and editBox.HighlightText then - editBox:HighlightText() - end -end - -local PROMPT -local PROMPT_WINDOW_WIDTH = 280 -local PROMPT_WINDOW_HEIGHT = 108 -local PROMPT_OK_BUTTON_WIDTH = 100 - -local function stylePromptEditBox(widget) - if not widget or not widget.frame or not widget.editbox then - return - end - - local frame = widget.frame - local editbox = widget.editbox - - if frame.SetBackdrop then - frame:SetBackdrop({ - bgFile = "Interface\\Buttons\\WHITE8x8", - edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", - tile = true, - tileSize = 16, - edgeSize = 14, - insets = { left = 3, right = 3, top = 3, bottom = 3 }, - }) - if frame.SetBackdropColor then - frame:SetBackdropColor(0.06, 0.06, 0.08, 0.92) - end - if frame.SetBackdropBorderColor then - frame:SetBackdropBorderColor(0.35, 0.35, 0.35, 0.95) - end - end - - if editbox.GetRegions then - local regions = { editbox:GetRegions() } - for _, region in ipairs(regions) do - if region and region.GetObjectType and region:GetObjectType() == "Texture" and region.SetAlpha then - region:SetAlpha(0) - end - end - end - - editbox:ClearAllPoints() - editbox:SetPoint("TOPLEFT", frame, "TOPLEFT", 8, -4) - editbox:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -8, 4) - editbox:SetFontObject(ChatFontNormal) - editbox:SetTextInsets(4, 4, 3, 3) - - widget:SetHeight(32) - if frame.SetHeight then - frame:SetHeight(32) - end -end - -function ShowPrompt(title, onOk, defaultText) - local aceGUI = resolveAceGUI("AceGUI-3.0 is required for MBUniversalPrompt") - if not aceGUI then - return - end - - if not PROMPT then - local window = aceGUI:Create("Window") - if not window then - return - end - - window:SetTitle(title or "Enter Value") - window:SetWidth(PROMPT_WINDOW_WIDTH) - window:SetHeight(PROMPT_WINDOW_HEIGHT) - window:EnableResize(false) - window:SetLayout("Flow") - window.frame:SetFrameStrata("DIALOG") - setAceWindowCloseToHide(window) - registerAceWindowEscapeClose(window, "UniversalPrompt") - bindAceWindowPosition(window, "universal_prompt") - - local edit = aceGUI:Create("EditBox") - edit:SetLabel("") - edit:SetFullWidth(true) - edit:DisableButton(true) - stylePromptEditBox(edit) - window:AddChild(edit) - - local okButton = aceGUI:Create("Button") - okButton:SetText(OKAY) - okButton:SetWidth(PROMPT_OK_BUTTON_WIDTH) - window:AddChild(okButton) - - PROMPT = { - window = window, - edit = edit, - okButton = okButton, - } - end - - PROMPT.window:SetTitle(title or "Enter Value") - PROMPT.window:Show() - PROMPT.edit:SetText(defaultText or "") - local promptEditBox = PROMPT.edit and PROMPT.edit.editbox - if promptEditBox and promptEditBox.SetFocus then - promptEditBox:SetFocus() - end - - PROMPT.okButton:SetCallback("OnClick", function() - local text = PROMPT.edit:GetText() - if text and text ~= "" then - onOk(text) - else - UIErrorsFrame:AddMessage(MultiBot.L("tips.quests.gobsnameerror"), 1, 0.2, 0.2, 1) - return - end - - PROMPT.window:Hide() - end) - PROMPT.edit:SetCallback("OnEnterPressed", function() - local button = PROMPT.okButton and PROMPT.okButton.button - if button and button.Click then - button:Click() - end - end) -end - --- BOUTON PRINCIPAL "Use Game Object" --- Boutons "Use Game Object" -local btnGob = tQuestMenu.addButton("BotUseGOB", 0, 150, - "Interface\\Icons\\inv_misc_spyglass_01", MultiBot.L("tips.quests.gobsmaster")) - -local btnGobName = tQuestMenu.addButton("BotUseGOBName", 31, 150, - "Interface\\Icons\\inv_misc_note_05", MultiBot.L("tips.quests.gobenter")) -btnGobName:doHide() - -local btnGobSearch = tQuestMenu.addButton("BotUseGOBSearch", 61, 150, - "Interface\\Icons\\inv_misc_spyglass_02", MultiBot.L("tips.quests.gobsearch")) -btnGobSearch:doHide() - -btnGob.doLeft = function() - if btnGobName:IsShown() then - btnGobName:doHide() - btnGobSearch:doHide() - else - btnGobName:doShow() - btnGobSearch:doShow() - end -end - --- Sous-bouton : prompt pour le nom du GOB -btnGobName.doLeft = function() - ShowPrompt( - MultiBot.L("tips.quests.gobpromptname"), - function(gobName) - gobName = gobName:gsub("^%s+", ""):gsub("%s+$", "") - if gobName == "" then - UIErrorsFrame:AddMessage(MultiBot.L("tips.quests.goberrorname") , 1, 0.2, 0.2, 1) - return - end - local bot = UnitName("target") - if not bot or not UnitIsPlayer("target") then - UIErrorsFrame:AddMessage(MultiBot.L("tips.quests.gobselectboterror"), 1, 0.2, 0.2, 1) - return - end - SendChatMessage("u " .. gobName, "WHISPER", nil, bot) - end - ) -end - --- Sous-bouton envoi la commande "los" à tout le groupe -btnGobSearch.doLeft = function() - MultiBot.ActionToGroup("los") -end - --- Register dans le handler MultiBot si besoin -tRight.buttons["BotUseGOB"] = btnGob -tRight.buttons["BotUseGOBName"] = btnGobName -tRight.buttons["BotUseGOBSearch"]= btnGobSearch --- END NEW QUESTS -- +-- END QUESTS / GAMEOBJECTS UI -- -- GROUP ACTIONS -- -- Main button that opens a submenu for group commands. diff --git a/MultiBot.toc b/MultiBot.toc index b3c80c2..78d0e87 100644 --- a/MultiBot.toc +++ b/MultiBot.toc @@ -70,6 +70,15 @@ UI\MultiBotItem.lua UI\MultiBotTalentFrame.lua UI\MultiBotHunterQuickFrame.lua UI\MultiBotShamanQuickFrame.lua +UI\MultiBotQuestUIShared.lua +UI\MultiBotPromptDialog.lua +UI\MultiBotQuestLogFrame.lua +UI\MultiBotQuestIncompleteFrame.lua +UI\MultiBotQuestCompletedFrame.lua +UI\MultiBotQuestAllFrame.lua +UI\MultiBotGameObjectCopyFrame.lua +UI\MultiBotGameObjectResultsFrame.lua +UI\MultiBotQuestsMenu.lua Core\MultiBotInit.lua UI\MultiBotSpecUI.lua Data\MultiBotIconos.lua diff --git a/UI/MultiBotGameObjectCopyFrame.lua b/UI/MultiBotGameObjectCopyFrame.lua new file mode 100644 index 0000000..3409723 --- /dev/null +++ b/UI/MultiBotGameObjectCopyFrame.lua @@ -0,0 +1,78 @@ +if not MultiBot then return end + +local Shared = MultiBot.QuestUIShared or {} +local CopyFrame = MultiBot.GameObjectCopyFrame or {} +MultiBot.GameObjectCopyFrame = CopyFrame + +function MultiBot.ShowGameObjectCopyBox() + local frame = MultiBot.InitializeGameObjectCopyFrame() + if not frame then + return + end + + if MultiBot.GameObjPopup and MultiBot.GameObjPopup.window and MultiBot.GameObjPopup.window:IsShown() then + MultiBot.GameObjPopup.window:Hide() + end + + local bots = Shared.CollectSortedGameObjectBots and Shared.CollectSortedGameObjectBots() or {} + local text = Shared.BuildGameObjectCopyText and Shared.BuildGameObjectCopyText(bots) or "" + frame.editor:SetText(text) + frame.window:Show() + + local editBox = frame.editor and frame.editor.editBox + if editBox and editBox.SetFocus then + editBox:SetFocus() + end + if editBox and editBox.HighlightText then + editBox:HighlightText() + end +end + +function MultiBot.InitializeGameObjectCopyFrame() + if CopyFrame.window then + return CopyFrame + end + + local aceGUI = Shared.ResolveAceGUI and Shared.ResolveAceGUI("AceGUI-3.0 is required for MB_GameObjCopyBox") or nil + if not aceGUI then + return nil + end + + local window = aceGUI:Create("Window") + if not window then + return nil + end + + window:SetTitle(MultiBot.L("tips.quests.gobctrlctocopy")) + window:SetWidth(420) + window:SetHeight(300) + window:EnableResize(false) + window:SetLayout("Fill") + window.frame:SetFrameStrata("DIALOG") + if MultiBot.SetAceWindowCloseToHide then MultiBot.SetAceWindowCloseToHide(window) end + if MultiBot.RegisterAceWindowEscapeClose then MultiBot.RegisterAceWindowEscapeClose(window, "GameObjCopy") end + if MultiBot.BindAceWindowPosition then MultiBot.BindAceWindowPosition(window, "gameobject_copy") end + + local editor = aceGUI:Create("MultiLineEditBox") + editor:SetLabel("") + editor:SetNumLines(14) + editor:DisableButton(true) + window:AddChild(editor) + + if editor.editBox and editor.editBox.SetFontObject then + editor.editBox:SetFontObject(ChatFontNormal) + end + if editor.editBox and editor.editBox.SetTextInsets then + editor.editBox:SetTextInsets(6, 6, 6, 6) + end + if editor.scrollBG and Shared.ApplyPanelStyle then + Shared.ApplyPanelStyle(editor.scrollBG, 0.92) + elseif editor.frame and Shared.ApplyPanelStyle then + Shared.ApplyPanelStyle(editor.frame, 0.92) + end + + CopyFrame.window = window + CopyFrame.editor = editor + MultiBot.GameObjCopyBox = CopyFrame + return CopyFrame +end \ No newline at end of file diff --git a/UI/MultiBotGameObjectResultsFrame.lua b/UI/MultiBotGameObjectResultsFrame.lua new file mode 100644 index 0000000..7f2df64 --- /dev/null +++ b/UI/MultiBotGameObjectResultsFrame.lua @@ -0,0 +1,109 @@ +if not MultiBot then return end + +local Shared = MultiBot.QuestUIShared or {} +local ResultsFrame = MultiBot.GameObjectResultsFrame or {} +MultiBot.GameObjectResultsFrame = ResultsFrame + +function MultiBot.ShowGameObjectPopup() + local frame = MultiBot.InitializeGameObjectResultsFrame() + if not frame then + return + end + + if frame.window:IsShown() then + frame.window:Hide() + end + + frame.scroll:ReleaseChildren() + + local aceGUI = Shared.ResolveAceGUI and Shared.ResolveAceGUI("AceGUI-3.0 is required for MB_GameObjPopup") or nil + if not aceGUI then + return + end + + local bots = Shared.CollectSortedGameObjectBots and Shared.CollectSortedGameObjectBots() or {} + for _, bot in ipairs(bots) do + local botLabel = aceGUI:Create("Label") + botLabel:SetFullWidth(true) + botLabel:SetText("Bot: |cff80ff80" .. bot .. "|r") + frame.scroll:AddChild(botLabel) + + for _, textLine in ipairs(Shared.GetGameObjectEntries(bot) or {}) do + local line = aceGUI:Create("Label") + line:SetFullWidth(true) + if Shared.IsDashedSectionHeader(textLine) then + line:SetText("|cffffff66" .. textLine .. "|r") + else + line:SetText(" " .. textLine) + end + frame.scroll:AddChild(line) + end + + local spacer = aceGUI:Create("Label") + spacer:SetFullWidth(true) + spacer:SetText(" ") + frame.scroll:AddChild(spacer) + end + + if #bots == 0 then + local noData = aceGUI:Create("Label") + noData:SetFullWidth(true) + noData:SetText(MultiBot.L("tips.quests.gobnosearchdata")) + frame.scroll:AddChild(noData) + end + + frame.window:Show() +end + +function MultiBot.InitializeGameObjectResultsFrame() + if ResultsFrame.window then + return ResultsFrame + end + + local aceGUI = Shared.ResolveAceGUI and Shared.ResolveAceGUI("AceGUI-3.0 is required for MB_GameObjPopup") or nil + if not aceGUI then + return nil + end + + local window = aceGUI:Create("Window") + if not window then + return nil + end + + window:SetTitle(MultiBot.L("tips.quests.gobsfound")) + window:SetWidth(420) + window:SetHeight(380) + window:EnableResize(false) + window:SetLayout("Flow") + window.frame:SetFrameStrata("DIALOG") + if MultiBot.SetAceWindowCloseToHide then MultiBot.SetAceWindowCloseToHide(window) end + if MultiBot.RegisterAceWindowEscapeClose then MultiBot.RegisterAceWindowEscapeClose(window, "GameObjPopup") end + if MultiBot.BindAceWindowPosition then MultiBot.BindAceWindowPosition(window, "gameobject_popup") end + + local description = aceGUI:Create("Label") + description:SetFullWidth(true) + description:SetText(MultiBot.L("tips.quests.gobsmaster") or "") + window:AddChild(description) + + local scroll = aceGUI:Create("ScrollFrame") + scroll:SetFullWidth(true) + scroll:SetHeight(260) + scroll:SetLayout("List") + window:AddChild(scroll) + + local copyButton = aceGUI:Create("Button") + copyButton:SetText(MultiBot.L("tips.quests.gobselectall")) + copyButton:SetWidth(170) + copyButton:SetCallback("OnClick", function() + if MultiBot.ShowGameObjectCopyBox then + MultiBot.ShowGameObjectCopyBox() + end + end) + window:AddChild(copyButton) + + ResultsFrame.window = window + ResultsFrame.scroll = scroll + ResultsFrame.copyButton = copyButton + MultiBot.GameObjPopup = ResultsFrame + return ResultsFrame +end \ No newline at end of file diff --git a/UI/MultiBotPromptDialog.lua b/UI/MultiBotPromptDialog.lua new file mode 100644 index 0000000..4447ecd --- /dev/null +++ b/UI/MultiBotPromptDialog.lua @@ -0,0 +1,80 @@ +if not MultiBot then return end + +local Shared = MultiBot.QuestUIShared or {} +local PROMPT +local PROMPT_WINDOW_WIDTH = 280 +local PROMPT_WINDOW_HEIGHT = 108 +local PROMPT_OK_BUTTON_WIDTH = 100 + +function ShowPrompt(title, onOk, defaultText) + local aceGUI = Shared.ResolveAceGUI and Shared.ResolveAceGUI("AceGUI-3.0 is required for MBUniversalPrompt") or nil + if not aceGUI then + return + end + + if not PROMPT then + local window = aceGUI:Create("Window") + if not window then + return + end + + window:SetTitle(title or "Enter Value") + window:SetWidth(PROMPT_WINDOW_WIDTH) + window:SetHeight(PROMPT_WINDOW_HEIGHT) + window:EnableResize(false) + window:SetLayout("Flow") + window.frame:SetFrameStrata("DIALOG") + if MultiBot.SetAceWindowCloseToHide then MultiBot.SetAceWindowCloseToHide(window) end + if MultiBot.RegisterAceWindowEscapeClose then MultiBot.RegisterAceWindowEscapeClose(window, "UniversalPrompt") end + if MultiBot.BindAceWindowPosition then MultiBot.BindAceWindowPosition(window, "universal_prompt") end + + local edit = aceGUI:Create("EditBox") + edit:SetLabel("") + edit:SetFullWidth(true) + edit:DisableButton(true) + if Shared.ApplyEditBoxStyle then + Shared.ApplyEditBoxStyle(edit) + end + window:AddChild(edit) + + local okButton = aceGUI:Create("Button") + okButton:SetText(OKAY) + okButton:SetWidth(PROMPT_OK_BUTTON_WIDTH) + window:AddChild(okButton) + + PROMPT = { + window = window, + edit = edit, + okButton = okButton, + } + end + + PROMPT.window:SetTitle(title or "Enter Value") + PROMPT.window:Show() + PROMPT.edit:SetText(defaultText or "") + + local editBox = PROMPT.edit and PROMPT.edit.editbox + if editBox and editBox.SetFocus then + editBox:SetFocus() + end + + PROMPT.okButton:SetCallback("OnClick", function() + local value = PROMPT.edit:GetText() + if not value or value == "" then + UIErrorsFrame:AddMessage(MultiBot.L("tips.quests.gobsnameerror"), 1, 0.2, 0.2, 1) + return + end + + onOk(value) + PROMPT.window:Hide() + end) + + PROMPT.edit:SetCallback("OnEnterPressed", function() + local button = PROMPT.okButton and PROMPT.okButton.button + if button and button.Click then + button:Click() + end + end) +end + +MultiBot.ShowPrompt = ShowPrompt \ No newline at end of file diff --git a/UI/MultiBotQuestAllFrame.lua b/UI/MultiBotQuestAllFrame.lua new file mode 100644 index 0000000..6db0c68 --- /dev/null +++ b/UI/MultiBotQuestAllFrame.lua @@ -0,0 +1,158 @@ +if not MultiBot then return end + +local Shared = MultiBot.QuestUIShared or {} +local QuestAllFrame = MultiBot.QuestAllFrame or {} +MultiBot.QuestAllFrame = QuestAllFrame + +MultiBot.BotQuestsAll = MultiBot.BotQuestsAll or {} +MultiBot.BotQuestsCompleted = MultiBot.BotQuestsCompleted or {} +MultiBot.BotQuestsIncompleted = MultiBot.BotQuestsIncompleted or {} + +local function clearContent(self) + Shared.ClearFrameChildren(self.content, true) + if self.content and self.content.text then + self.content.text:SetText("") + end +end + +function MultiBot.ClearAllContent() + local frame = MultiBot.InitializeQuestAllFrame() + clearContent(frame) +end + +local function createHeader(parent, text, yOffset) + local header = parent:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge") + header:SetPoint("TOPLEFT", 0, yOffset) + header:SetText(text or "") + return header +end + +local function renderQuestWithBots(parent, yOffset, entry) + local line = CreateFrame("Frame", nil, parent) + line:SetSize(360, 20) + line:SetPoint("TOPLEFT", 0, yOffset) + Shared.ApplyPanelStyle(line, 0.34) + + local icon = line:CreateTexture(nil, "ARTWORK") + icon:SetTexture(Shared.ICON_BOT_QUEST) + icon:SetSize(18, 18) + icon:SetPoint("LEFT", 6, 0) + + local html = Shared.CreateQuestHTML(line, 320, 20, Shared.BuildQuestLink(entry.id, entry.name)) + html:SetPoint("LEFT", 28, 0) + Shared.BindHyperlinkTooltip(html) + + yOffset = yOffset - 24 + + local botsLine = parent:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall") + botsLine:SetPoint("TOPLEFT", 28, yOffset) + botsLine:SetText(Shared.FormatBotsLabel(entry.bots)) + + return yOffset - 18 +end + +function MultiBot.BuildBotAllList(botName) + local frame = MultiBot.InitializeQuestAllFrame() + clearContent(frame) + + local yOffset = -4 + for _, link in ipairs(MultiBot.BotQuestsAll[botName] or {}) do + local questID = tonumber(link:match("|Hquest:(%d+):")) + local localizedName = questID and Shared.GetLocalizedQuestName(questID, link) or link + local displayLink = link:gsub("%[[^%]]+%]", "|cff00ff00[" .. localizedName .. "]|r") + + local line = CreateFrame("Frame", nil, frame.content) + line:SetSize(360, 20) + line:SetPoint("TOPLEFT", 0, yOffset) + Shared.ApplyPanelStyle(line, 0.34) + + local icon = line:CreateTexture(nil, "ARTWORK") + icon:SetTexture(Shared.ICON_BOT_QUEST) + icon:SetSize(18, 18) + icon:SetPoint("LEFT", 6, 0) + + local html = Shared.CreateQuestHTML(line, 320, 20, displayLink) + html:SetPoint("LEFT", 28, 0) + Shared.BindHyperlinkTooltip(html) + + yOffset = yOffset - 24 + end + + if frame.summaryLabel then + frame.summaryLabel:SetText(botName and ((MultiBot.L("tips.quests.alllist") or "All Quests") .. ": |cff80ff80" .. botName .. "|r") or (MultiBot.L("tips.quests.alllist") or "")) + end + + frame.content:SetHeight(math.max(-yOffset + 4, 1)) + frame.scrollFrame:SetVerticalScroll(0) +end + +function MultiBot.BuildAggregatedAllList() + local frame = MultiBot.InitializeQuestAllFrame() + clearContent(frame) + + local yOffset = -4 + local completeEntries = Shared.BuildAggregatedQuestEntries(MultiBot.BotQuestsCompleted) + local incompleteEntries = Shared.BuildAggregatedQuestEntries(MultiBot.BotQuestsIncompleted) + + createHeader(frame.content, MultiBot.L("tips.quests.compheader"), yOffset) + yOffset = yOffset - 30 + for _, entry in ipairs(completeEntries) do + yOffset = renderQuestWithBots(frame.content, yOffset, entry) + end + + yOffset = yOffset - 12 + createHeader(frame.content, MultiBot.L("tips.quests.incompheader"), yOffset) + yOffset = yOffset - 30 + for _, entry in ipairs(incompleteEntries) do + yOffset = renderQuestWithBots(frame.content, yOffset, entry) + end + + if frame.summaryLabel then + frame.summaryLabel:SetText(MultiBot.L("tips.quests.alllist") or "") + end + + frame.content:SetHeight(math.max(-yOffset + 4, 1)) + frame.scrollFrame:SetVerticalScroll(0) +end + +function QuestAllFrame:SetLoading() + clearContent(self) + self.content.text = self.content.text or self.content:CreateFontString(nil, "OVERLAY", "GameFontNormal") + self.content.text:SetPoint("TOPLEFT", 8, -8) + self.content.text:SetText(LOADING) + self.content:SetHeight(40) + self.scrollFrame:SetVerticalScroll(0) +end + +function QuestAllFrame:Show() + self.host:Show() +end + +function MultiBot.InitializeQuestAllFrame() + if QuestAllFrame.host then + return QuestAllFrame + end + + local host = MultiBot.CreateAceQuestPopupHost and MultiBot.CreateAceQuestPopupHost(MultiBot.L("tips.quests.alllist"), 420, 460, "AceGUI-3.0 is required for MB_BotQuestAllPopup", "bot_quest_all_popup") or nil + assert(host, "AceGUI-3.0 is required for MB_BotQuestAllPopup") + + local panel, scrollFrame, content, summaryLabel = Shared.CreateStyledScrollArea(host, "MB_BotQuestAllScroll", { left = 10, right = -28, top = -34, bottom = 10 }) + Shared.CreateSectionTitle(panel, MultiBot.L("tips.quests.alllist")) + + QuestAllFrame.host = host + QuestAllFrame.panel = panel + QuestAllFrame.scrollFrame = scrollFrame + QuestAllFrame.content = content + QuestAllFrame.summaryLabel = summaryLabel + + host.content = content + host:SetScript("OnHide", function() + MultiBot.BotQuestsAll = {} + MultiBot.BotQuestsCompleted = {} + MultiBot.BotQuestsIncompleted = {} + clearContent(QuestAllFrame) + end) + + MultiBot.tBotAllPopup = host + return QuestAllFrame +end \ No newline at end of file diff --git a/UI/MultiBotQuestCompletedFrame.lua b/UI/MultiBotQuestCompletedFrame.lua new file mode 100644 index 0000000..24ffe4c --- /dev/null +++ b/UI/MultiBotQuestCompletedFrame.lua @@ -0,0 +1,98 @@ +if not MultiBot then return end + +local Shared = MultiBot.QuestUIShared or {} +local QuestCompletedFrame = MultiBot.QuestCompletedFrame or {} +MultiBot.QuestCompletedFrame = QuestCompletedFrame + +MultiBot.BotQuestsCompleted = MultiBot.BotQuestsCompleted or {} + +local function clearContent(self) + Shared.ClearFrameChildren(self.content) +end + +local function renderQuestList(self, entries, summaryText) + clearContent(self) + + local yOffset = -4 + for _, entry in ipairs(entries or {}) do + local line = CreateFrame("Frame", nil, self.content) + line:SetSize(320, Shared.ROW_HEIGHT) + line:SetPoint("TOPLEFT", 0, yOffset) + Shared.ApplyPanelStyle(line, 0.34) + + local icon = line:CreateTexture(nil, "ARTWORK") + icon:SetTexture(Shared.ICON_BOT_QUEST) + icon:SetSize(18, 18) + icon:SetPoint("LEFT", 6, 0) + + local html = Shared.CreateQuestHTML(line, 280, 20, Shared.BuildQuestLink(entry.id, entry.name)) + html:SetPoint("LEFT", 28, 0) + Shared.BindHyperlinkTooltip(html) + + yOffset = yOffset - Shared.ROW_HEIGHT - 4 + + if entry.bots and #entry.bots > 0 then + local botRow = CreateFrame("Frame", nil, self.content) + botRow:SetSize(320, Shared.DETAIL_ROW_HEIGHT) + botRow:SetPoint("TOPLEFT", 0, yOffset) + + local botsLine = botRow:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall") + botsLine:SetPoint("LEFT", 28, 0) + botsLine:SetJustifyH("LEFT") + botsLine:SetText(Shared.FormatBotsLabel(entry.bots)) + + yOffset = yOffset - Shared.DETAIL_ROW_HEIGHT - 2 + end + end + + if self.summaryLabel then + self.summaryLabel:SetText(summaryText or MultiBot.L("tips.quests.complist") or "") + end + self.content:SetHeight(math.max(-yOffset + 4, 1)) + self.scrollFrame:SetVerticalScroll(0) +end + +function MultiBot.BuildBotCompletedList(botName) + local frame = MultiBot.InitializeQuestCompletedFrame() + local entries = Shared.SortQuestEntries(MultiBot.BotQuestsCompleted[botName] or {}) + frame:Show() + renderQuestList(frame, entries, botName and ((MultiBot.L("tips.quests.complist") or "Completed Quests") .. ": |cff80ff80" .. botName .. "|r") or nil) +end + +function MultiBot.BuildAggregatedCompletedList() + local frame = MultiBot.InitializeQuestCompletedFrame() + local entries = Shared.BuildAggregatedQuestEntries(MultiBot.BotQuestsCompleted) + + frame:Show() + renderQuestList(frame, entries, MultiBot.L("tips.quests.complist") or "") +end + +function QuestCompletedFrame:Show() + self.host:Show() +end + +function MultiBot.InitializeQuestCompletedFrame() + if QuestCompletedFrame.host then + return QuestCompletedFrame + end + + local host = MultiBot.CreateAceQuestPopupHost and MultiBot.CreateAceQuestPopupHost(MultiBot.L("tips.quests.complist"), 380, 420, "AceGUI-3.0 is required for MB_BotQuestCompPopup", "bot_quest_comp_popup") or nil + assert(host, "AceGUI-3.0 is required for MB_BotQuestCompPopup") + + local panel, scrollFrame, content, summaryLabel = Shared.CreateStyledScrollArea(host, "MB_BotQuestCompScroll", { left = 10, right = -28, top = -34, bottom = 10 }) + Shared.CreateSectionTitle(panel, MultiBot.L("tips.quests.complist")) + + QuestCompletedFrame.host = host + QuestCompletedFrame.panel = panel + QuestCompletedFrame.scrollFrame = scrollFrame + QuestCompletedFrame.content = content + QuestCompletedFrame.summaryLabel = summaryLabel + + host:SetScript("OnHide", function() + MultiBot.BotQuestsCompleted = {} + clearContent(QuestCompletedFrame) + end) + + MultiBot.tBotCompPopup = host + return QuestCompletedFrame +end \ No newline at end of file diff --git a/UI/MultiBotQuestIncompleteFrame.lua b/UI/MultiBotQuestIncompleteFrame.lua new file mode 100644 index 0000000..f1be9bd --- /dev/null +++ b/UI/MultiBotQuestIncompleteFrame.lua @@ -0,0 +1,98 @@ +if not MultiBot then return end + +local Shared = MultiBot.QuestUIShared or {} +local QuestIncompleteFrame = MultiBot.QuestIncompleteFrame or {} +MultiBot.QuestIncompleteFrame = QuestIncompleteFrame + +MultiBot.BotQuestsIncompleted = MultiBot.BotQuestsIncompleted or {} + +local function clearContent(self) + Shared.ClearFrameChildren(self.content) +end + +local function renderQuestList(self, entries, summaryText) + clearContent(self) + + local yOffset = -4 + for _, entry in ipairs(entries or {}) do + local line = CreateFrame("Frame", nil, self.content) + line:SetSize(320, Shared.ROW_HEIGHT) + line:SetPoint("TOPLEFT", 0, yOffset) + Shared.ApplyPanelStyle(line, 0.34) + + local icon = line:CreateTexture(nil, "ARTWORK") + icon:SetTexture(Shared.ICON_BOT_QUEST) + icon:SetSize(18, 18) + icon:SetPoint("LEFT", 6, 0) + + local html = Shared.CreateQuestHTML(line, 280, 20, Shared.BuildQuestLink(entry.id, entry.name)) + html:SetPoint("LEFT", 28, 0) + Shared.BindHyperlinkTooltip(html) + + yOffset = yOffset - Shared.ROW_HEIGHT - 4 + + if entry.bots and #entry.bots > 0 then + local botRow = CreateFrame("Frame", nil, self.content) + botRow:SetSize(320, Shared.DETAIL_ROW_HEIGHT) + botRow:SetPoint("TOPLEFT", 0, yOffset) + + local botsLine = botRow:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall") + botsLine:SetPoint("LEFT", 28, 0) + botsLine:SetJustifyH("LEFT") + botsLine:SetText(Shared.FormatBotsLabel(entry.bots)) + + yOffset = yOffset - Shared.DETAIL_ROW_HEIGHT - 2 + end + end + + if self.summaryLabel then + self.summaryLabel:SetText(summaryText or MultiBot.L("tips.quests.incomplist") or "") + end + self.content:SetHeight(math.max(-yOffset + 4, 1)) + self.scrollFrame:SetVerticalScroll(0) +end + +function MultiBot.BuildBotQuestList(botName) + local frame = MultiBot.InitializeQuestIncompleteFrame() + local entries = Shared.SortQuestEntries(MultiBot.BotQuestsIncompleted[botName] or {}) + frame:Show() + renderQuestList(frame, entries, botName and ((MultiBot.L("tips.quests.incomplist") or "Current Quests") .. ": |cff80ff80" .. botName .. "|r") or nil) +end + +function MultiBot.BuildAggregatedQuestList() + local frame = MultiBot.InitializeQuestIncompleteFrame() + local entries = Shared.BuildAggregatedQuestEntries(MultiBot.BotQuestsIncompleted) + + frame:Show() + renderQuestList(frame, entries, MultiBot.L("tips.quests.incomplist") or "") +end + +function QuestIncompleteFrame:Show() + self.host:Show() +end + +function MultiBot.InitializeQuestIncompleteFrame() + if QuestIncompleteFrame.host then + return QuestIncompleteFrame + end + + local host = MultiBot.CreateAceQuestPopupHost and MultiBot.CreateAceQuestPopupHost(MultiBot.L("tips.quests.incomplist"), 380, 420, "AceGUI-3.0 is required for MB_BotQuestPopup", "bot_quest_popup") or nil + assert(host, "AceGUI-3.0 is required for MB_BotQuestPopup") + + local panel, scrollFrame, content, summaryLabel = Shared.CreateStyledScrollArea(host, "MB_BotQuestScroll", { left = 10, right = -28, top = -34, bottom = 10 }) + Shared.CreateSectionTitle(panel, MultiBot.L("tips.quests.incomplist")) + + QuestIncompleteFrame.host = host + QuestIncompleteFrame.panel = panel + QuestIncompleteFrame.scrollFrame = scrollFrame + QuestIncompleteFrame.content = content + QuestIncompleteFrame.summaryLabel = summaryLabel + + host:SetScript("OnHide", function() + MultiBot.BotQuestsIncompleted = {} + clearContent(QuestIncompleteFrame) + end) + + MultiBot.tBotPopup = host + return QuestIncompleteFrame +end \ No newline at end of file diff --git a/UI/MultiBotQuestLogFrame.lua b/UI/MultiBotQuestLogFrame.lua new file mode 100644 index 0000000..b1ec022 --- /dev/null +++ b/UI/MultiBotQuestLogFrame.lua @@ -0,0 +1,173 @@ +if not MultiBot then return end + +local Shared = MultiBot.QuestUIShared or {} +local QuestLogFrame = MultiBot.QuestLogFrame or {} +MultiBot.QuestLogFrame = QuestLogFrame + +local function clearContent(self) + Shared.ClearFrameChildren(self.content) +end + +local function getMemberNamesOnQuest(questIndex) + local names = {} + + if GetNumRaidMembers() > 0 then + for index = 1, 40 do + local unit = "raid" .. index + if UnitExists(unit) and IsUnitOnQuest(questIndex, unit) then + local name = UnitName(unit) + if name then + table.insert(names, name) + end + end + end + elseif GetNumPartyMembers() > 0 then + for index = 1, 4 do + local unit = "party" .. index + if UnitExists(unit) and IsUnitOnQuest(questIndex, unit) then + local name = UnitName(unit) + if name then + table.insert(names, name) + end + end + end + end + + table.sort(names) + return names +end + +local function attachQuestLogTooltip(html, questIndex) + html:SetScript("OnHyperlinkEnter", function(self, _, fullLink) + GameTooltip:SetOwner(self, "ANCHOR_CURSOR") + GameTooltip:SetHyperlink(fullLink) + + local objectiveCount = GetNumQuestLeaderBoards(questIndex) + if objectiveCount and objectiveCount > 0 then + for objectiveIndex = 1, objectiveCount do + local objectiveText, _, finished = GetQuestLogLeaderBoard(objectiveIndex, questIndex) + if objectiveText then + local tint = finished and 0.5 or 1 + GameTooltip:AddLine("• " .. objectiveText, tint, tint, tint) + end + end + end + + local members = getMemberNamesOnQuest(questIndex) + if #members > 0 then + GameTooltip:AddLine(" ") + GameTooltip:AddLine("Groupe :", 0.8, 0.8, 0.8) + for _, name in ipairs(members) do + GameTooltip:AddLine("- " .. name) + end + end + + GameTooltip:Show() + end) + html:SetScript("OnHyperlinkLeave", GameTooltip_Hide) +end + +local function attachQuestLogClick(html) + html:SetScript("OnHyperlinkClick", function(_, _, link, button) + if type(link) ~= "string" or not link:match("|Hquest:") then + return + end + + local clickedQuestID = tonumber(link:match("|Hquest:(%d+):")) + if not clickedQuestID then + return + end + + for questIndex = 1, GetNumQuestLogEntries() do + local questLink = GetQuestLink(questIndex) + local questID = tonumber(questLink and questLink:match("|Hquest:(%d+):")) + if questID == clickedQuestID then + SelectQuestLogEntry(questIndex) + if button == "RightButton" then + if GetNumRaidMembers() > 0 then + SendChatMessage("drop " .. questLink, "RAID") + elseif GetNumPartyMembers() > 0 then + SendChatMessage("drop " .. questLink, "PARTY") + end + SetAbandonQuest() + AbandonQuest() + else + QuestLogPushQuest() + end + break + end + end + end) +end + +function QuestLogFrame:Refresh() + clearContent(self) + + local entries = GetNumQuestLogEntries() + local rowOffset = -4 + local visibleCount = 0 + + for questIndex = 1, entries do + local questLink = GetQuestLink(questIndex) + local _, _, _, _, isCollapsed = GetQuestLogTitle(questIndex) + + if questLink and isCollapsed == nil then + visibleCount = visibleCount + 1 + local row = CreateFrame("Frame", nil, self.content) + row:SetSize(332, Shared.ROW_HEIGHT) + row:SetPoint("TOPLEFT", 0, rowOffset) + Shared.ApplyPanelStyle(row, 0.38) + + local icon = row:CreateTexture(nil, "ARTWORK") + icon:SetTexture(Shared.ICON_QUEST) + icon:SetSize(18, 18) + icon:SetPoint("LEFT", 6, 0) + + local html = Shared.CreateQuestHTML(row, 290, 20, questLink:gsub("%[", "|cff00ff00["):gsub("%]", "]|r")) + html:SetPoint("LEFT", 28, 0) + attachQuestLogTooltip(html, questIndex) + attachQuestLogClick(html) + + rowOffset = rowOffset - Shared.ROW_HEIGHT - 4 + end + end + + local emptyState = visibleCount == 0 and (MultiBot.L("tips.quests.gobnosearchdata") or NO_QUESTS_LABEL) or (QUESTS_LABEL or QUEST_LOG) + if self.summaryLabel then + self.summaryLabel:SetText(emptyState) + end + + self.content:SetHeight(math.max(-rowOffset + 4, 1)) + self.scrollFrame:SetVerticalScroll(0) +end + +function QuestLogFrame:Toggle() + if self.host:IsShown() then + self.host:Hide() + return + end + + self.host:Show() + self:Refresh() +end + +function MultiBot.InitializeQuestLogFrame() + if QuestLogFrame.host then + return QuestLogFrame + end + + local host = MultiBot.CreateAceQuestPopupHost and MultiBot.CreateAceQuestPopupHost(QUEST_LOG, 390, 470, "AceGUI-3.0 is required for MB_QuestPopup", "quest_popup") or nil + assert(host, "AceGUI-3.0 is required for MB_QuestPopup") + + local panel, scrollFrame, content, summaryLabel = Shared.CreateStyledScrollArea(host, "MB_QuestScroll", { left = 10, right = -28, top = -34, bottom = 10 }) + Shared.CreateSectionTitle(panel, QUEST_LOG) + + QuestLogFrame.host = host + QuestLogFrame.panel = panel + QuestLogFrame.scrollFrame = scrollFrame + QuestLogFrame.content = content + QuestLogFrame.summaryLabel = summaryLabel + QuestLogFrame.summaryLabel:SetText(QUESTS_LABEL or QUEST_LOG) + + return QuestLogFrame +end \ No newline at end of file diff --git a/UI/MultiBotQuestUIShared.lua b/UI/MultiBotQuestUIShared.lua new file mode 100644 index 0000000..76ca55c --- /dev/null +++ b/UI/MultiBotQuestUIShared.lua @@ -0,0 +1,317 @@ +if not MultiBot then return end + +local Shared = MultiBot.QuestUIShared or {} +MultiBot.QuestUIShared = Shared + +Shared.ROW_HEIGHT = 24 +Shared.DETAIL_ROW_HEIGHT = 16 +Shared.PANEL_ALPHA = 0.90 +Shared.SUBPANEL_ALPHA = 0.72 +Shared.ICON_QUEST = "Interface\\Icons\\inv_misc_note_01" +Shared.ICON_BOT_QUEST = "Interface\\Icons\\inv_misc_note_02" + +local function getAceGUI() + if MultiBot.GetAceGUI then + local ace = MultiBot.GetAceGUI() + if type(ace) == "table" and type(ace.Create) == "function" then + return ace + end + end + + if type(LibStub) == "table" then + local ok, aceGUI = pcall(LibStub.GetLibrary, LibStub, "AceGUI-3.0", true) + if ok and type(aceGUI) == "table" and type(aceGUI.Create) == "function" then + return aceGUI + end + end + + return nil +end + +Shared.GetAceGUI = Shared.GetAceGUI or getAceGUI + +function Shared.ApplyPanelStyle(frame, bgAlpha) + if not frame or not frame.SetBackdrop then + return + end + + frame:SetBackdrop({ + bgFile = "Interface\\Buttons\\WHITE8x8", + edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", + tile = true, + tileSize = 16, + edgeSize = 14, + insets = { left = 3, right = 3, top = 3, bottom = 3 }, + }) + + if frame.SetBackdropColor then + frame:SetBackdropColor(0.06, 0.06, 0.08, bgAlpha or Shared.PANEL_ALPHA) + end + if frame.SetBackdropBorderColor then + frame:SetBackdropBorderColor(0.35, 0.35, 0.35, 0.95) + end +end + +function Shared.ApplyEditBoxStyle(widget) + if not widget or not widget.frame or not widget.editbox then + return + end + + Shared.ApplyPanelStyle(widget.frame, 0.92) + + local editBox = widget.editbox + if editBox.GetRegions then + for _, region in ipairs({ editBox:GetRegions() }) do + if region and region.GetObjectType and region:GetObjectType() == "Texture" and region.SetAlpha then + region:SetAlpha(0) + end + end + end + + editBox:ClearAllPoints() + editBox:SetPoint("TOPLEFT", widget.frame, "TOPLEFT", 8, -4) + editBox:SetPoint("BOTTOMRIGHT", widget.frame, "BOTTOMRIGHT", -8, 4) + editBox:SetFontObject(ChatFontNormal) + editBox:SetTextInsets(4, 4, 3, 3) + + widget:SetHeight(32) + if widget.frame.SetHeight then + widget.frame:SetHeight(32) + end +end + +function Shared.ClearFrameChildren(frame, clearRegions) + if not frame then + return + end + + if frame.GetNumChildren and frame.GetChildren then + for index = (frame:GetNumChildren() or 0), 1, -1 do + local child = select(index, frame:GetChildren()) + if child then + child:Hide() + child:SetParent(nil) + end + end + end + + if clearRegions and frame.GetRegions then + for _, region in ipairs({ frame:GetRegions() }) do + if region and region.Hide then + region:Hide() + end + if region and region.GetObjectType then + local regionType = region:GetObjectType() + if regionType == "FontString" and region.SetText then + region:SetText("") + elseif regionType == "Texture" and region.SetTexture then + region:SetTexture(nil) + end + end + end + end +end + +function Shared.CreateSectionTitle(parent, text) + local title = parent:CreateFontString(nil, "OVERLAY", "GameFontHighlight") + title:SetPoint("TOPLEFT", parent, "TOPLEFT", 10, -10) + title:SetJustifyH("LEFT") + title:SetText(text or "") + return title +end + +function Shared.CreateSummaryLabel(parent, anchor, xOffset, yOffset) + local label = parent:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall") + label:SetPoint(anchor or "TOPLEFT", parent, anchor or "TOPLEFT", xOffset or 10, yOffset or -30) + label:SetWidth(math.max((parent.GetWidth and parent:GetWidth() or 360) - 24, 120)) + label:SetJustifyH("LEFT") + label:SetJustifyV("TOP") + label:SetTextColor(0.85, 0.82, 0.72) + return label +end + +function Shared.CreateStyledScrollArea(parent, name, insets) + local padding = insets or { left = 10, right = -28, top = -48, bottom = 10 } + + local panel = CreateFrame("Frame", nil, parent) + panel:SetPoint("TOPLEFT", parent, "TOPLEFT", 8, -8) + panel:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", -8, 8) + Shared.ApplyPanelStyle(panel, Shared.SUBPANEL_ALPHA) + + local scrollFrame = CreateFrame("ScrollFrame", name, panel, "UIPanelScrollFrameTemplate") + scrollFrame:SetPoint("TOPLEFT", panel, "TOPLEFT", padding.left, padding.top) + scrollFrame:SetPoint("BOTTOMRIGHT", panel, "BOTTOMRIGHT", padding.right, padding.bottom) + + local content = CreateFrame("Frame", nil, scrollFrame) + content:SetWidth(1) + content:SetHeight(1) + scrollFrame:SetScrollChild(content) + + local summary = Shared.CreateSummaryLabel(panel, "TOPLEFT", 12, -30) + + return panel, scrollFrame, content, summary +end + +function Shared.CreateQuestHTML(parent, width, height, text) + local html = CreateFrame("SimpleHTML", nil, parent) + html:SetSize(width or 260, height or 20) + html:SetFontObject("GameFontNormal") + html:SetText(text or "") + html:SetHyperlinksEnabled(true) + return html +end + +function Shared.BindHyperlinkTooltip(html) + if not html then + return + end + + html:SetScript("OnHyperlinkEnter", function(self, _, link) + GameTooltip:SetOwner(self, "ANCHOR_CURSOR") + GameTooltip:SetHyperlink(link) + GameTooltip:Show() + end) + html:SetScript("OnHyperlinkLeave", GameTooltip_Hide) +end + +function Shared.GetLocalizedQuestName(questID, fallback) + if MultiBot.GetLocalizedQuestName then + return MultiBot.GetLocalizedQuestName(questID) or fallback or tostring(questID) + end + + return fallback or tostring(questID) +end + +function Shared.BuildQuestLink(questID, questName) + local localizedName = Shared.GetLocalizedQuestName(questID, questName) + return ("|cff00ff00|Hquest:%s:0|h[%s]|h|r"):format(questID, localizedName) +end + +function Shared.SortQuestEntries(questsById) + local entries = {} + for questID, questName in pairs(questsById or {}) do + local numericID = tonumber(questID) + table.insert(entries, { + id = numericID or questID, + sortID = numericID or 0, + name = Shared.GetLocalizedQuestName(numericID, questName), + originalName = questName, + }) + end + + table.sort(entries, function(left, right) + local leftName = string.lower(tostring(left.name or left.originalName or "")) + local rightName = string.lower(tostring(right.name or right.originalName or "")) + if leftName == rightName then + return (left.sortID or 0) < (right.sortID or 0) + end + return leftName < rightName + end) + + return entries +end + +function Shared.AppendBotName(target, botName) + if not target.bots then + target.bots = {} + end + + table.insert(target.bots, botName) + table.sort(target.bots) +end + +function Shared.FormatBotsLabel(bots) + return (MultiBot.L("tips.quests.botsword") or "Bots: ") .. table.concat(bots or {}, ", ") +end + +function Shared.BuildAggregatedQuestEntries(source) + local questMap = {} + + for botName, quests in pairs(source or {}) do + for questID, questName in pairs(quests or {}) do + local numericID = tonumber(questID) + if numericID then + if not questMap[numericID] then + questMap[numericID] = { + id = numericID, + name = Shared.GetLocalizedQuestName(numericID, questName), + bots = {}, + } + end + Shared.AppendBotName(questMap[numericID], botName) + end + end + end + + local entries = {} + for _, entry in pairs(questMap) do + table.insert(entries, entry) + end + + table.sort(entries, function(left, right) + local leftName = string.lower(tostring(left.name or "")) + local rightName = string.lower(tostring(right.name or "")) + if leftName == rightName then + return (left.id or 0) < (right.id or 0) + end + return leftName < rightName + end) + + return entries +end + +function Shared.GetGameObjectEntries(bot) + local entries = MultiBot.LastGameObjectSearch and MultiBot.LastGameObjectSearch[bot] + if type(entries) ~= "table" then + return nil + end + + return entries +end + +function Shared.CollectSortedGameObjectBots() + local bots = {} + for bot in pairs(MultiBot.LastGameObjectSearch or {}) do + local entries = Shared.GetGameObjectEntries(bot) + if entries and #entries > 0 then + table.insert(bots, bot) + end + end + table.sort(bots) + return bots +end + +function Shared.IsDashedSectionHeader(text) + return type(text) == "string" and text:find("^%s*%-+%s*.-%s*%-+%s*$") ~= nil +end + +function Shared.BuildGameObjectCopyText(bots) + local lines = {} + + for _, bot in ipairs(bots or {}) do + local entries = Shared.GetGameObjectEntries(bot) or {} + table.insert(lines, ("Bot: %s"):format(bot)) + for _, entry in ipairs(entries) do + table.insert(lines, entry) + end + table.insert(lines, "") + end + + if #lines == 0 then + return MultiBot.L("tips.quests.gobnosearchdata") + end + + return table.concat(lines, "\n") +end + +function Shared.ResolveAceGUI(message) + if MultiBot.ResolveAceGUI then + return MultiBot.ResolveAceGUI(message) + end + + local aceGUI = Shared.GetAceGUI() + if not aceGUI and message then + UIErrorsFrame:AddMessage(message, 1, 0.2, 0.2, 1) + end + return aceGUI +end \ No newline at end of file diff --git a/UI/MultiBotQuestsMenu.lua b/UI/MultiBotQuestsMenu.lua new file mode 100644 index 0000000..cf22f69 --- /dev/null +++ b/UI/MultiBotQuestsMenu.lua @@ -0,0 +1,241 @@ +if not MultiBot then return end + +local QuestsMenu = MultiBot.QuestsMenu or {} +MultiBot.QuestsMenu = QuestsMenu + +local function toggleButtons(buttonA, buttonB) + if buttonA:IsShown() then + buttonA:doHide() + buttonB:doHide() + return + end + + buttonA:doShow() + buttonB:doShow() +end + +local function sendIncomplete(method) + MultiBot._awaitingQuestsAll = false + MultiBot._lastIncMode = method + + local frame = MultiBot.InitializeQuestIncompleteFrame and MultiBot.InitializeQuestIncompleteFrame() + if not frame then + return + end + + if method == "WHISPER" then + local bot = UnitName("target") + if not bot or not UnitIsPlayer("target") then + UIErrorsFrame:AddMessage(MultiBot.L("tips.quests.questcomperror"), 1, 0.2, 0.2, 1) + return + end + + MultiBot.BotQuestsIncompleted[bot] = {} + MultiBot.ActionToTarget("quests incompleted", bot) + frame:Show() + MultiBot.TimerAfter(0.5, function() + if MultiBot.BuildBotQuestList then + MultiBot.BuildBotQuestList(bot) + end + end) + return + end + + MultiBot.BotQuestsIncompleted = {} + MultiBot.ActionToGroup("quests incompleted") + frame:Show() +end + +local function sendCompleted(method) + MultiBot._awaitingQuestsAll = false + MultiBot._lastCompMode = method + + local frame = MultiBot.InitializeQuestCompletedFrame and MultiBot.InitializeQuestCompletedFrame() + if not frame then + return + end + + if method == "WHISPER" then + local bot = UnitName("target") + if not bot or not UnitIsPlayer("target") then + UIErrorsFrame:AddMessage(MultiBot.L("tips.quests.questcomperror"), 1, 0.2, 0.2, 1) + return + end + + MultiBot.BotQuestsCompleted[bot] = {} + MultiBot.ActionToTarget("quests completed", bot) + frame:Show() + MultiBot.TimerAfter(0.5, function() + if MultiBot.BuildBotCompletedList then + MultiBot.BuildBotCompletedList(bot) + end + end) + return + end + + MultiBot.BotQuestsCompleted = {} + MultiBot.ActionToGroup("quests completed") + frame:Show() +end + +local function sendAll(method) + local frame = MultiBot.InitializeQuestAllFrame and MultiBot.InitializeQuestAllFrame() + if not frame then + return + end + + MultiBot._lastAllMode = method + MultiBot._awaitingQuestsAll = true + MultiBot._blockOtherQuests = true + MultiBot.BotQuestsAll = {} + MultiBot._awaitingQuestsAllBots = {} + + if method == "GROUP" then + for index = 1, GetNumPartyMembers() do + local botName = UnitName("party" .. index) + if botName then + MultiBot._awaitingQuestsAllBots[botName] = false + end + end + MultiBot.ActionToGroup("quests all") + else + local bot = UnitName("target") + if not bot or not UnitIsPlayer("target") then + UIErrorsFrame:AddMessage(MultiBot.L("tips.quests.questcomperror"), 1, 0.2, 0.2, 1) + MultiBot._awaitingQuestsAll = false + MultiBot._blockOtherQuests = false + return + end + MultiBot._awaitingQuestsAllBots[bot] = false + MultiBot.ActionToTarget("quests all", bot) + end + + frame:Show() + frame:SetLoading() +end + +function MultiBot.InitializeQuestsMenu(tRight) + if QuestsMenu.initialized then + return QuestsMenu + end + + if not tRight or not tRight.addButton or not tRight.addFrame then + return nil + end + + local questLogFrame = MultiBot.InitializeQuestLogFrame and MultiBot.InitializeQuestLogFrame() + MultiBot.InitializeQuestIncompleteFrame() + MultiBot.InitializeQuestCompletedFrame() + MultiBot.InitializeQuestAllFrame() + if MultiBot.InitializeGameObjectResultsFrame then MultiBot.InitializeGameObjectResultsFrame() end + if MultiBot.InitializeGameObjectCopyFrame then MultiBot.InitializeGameObjectCopyFrame() end + + local button = tRight.addButton("Quests Menu", 0, 0, "achievement_quests_completed_06", MultiBot.L("tips.quests.main")) + local menu = tRight.addFrame("QuestMenu", -2, 64) + menu:Hide() + + button.doLeft = function(owner) + MultiBot.ShowHideSwitch(owner.parent.frames["QuestMenu"]) + end + button.doRight = button.doLeft + + menu.addButton("AcceptAll", 0, 30, "inv_misc_note_02", MultiBot.L("tips.quests.accept")).doLeft = function() + MultiBot.ActionToGroup("accept *") + end + + local listButton = menu.addButton("Quests", 0, -30, "inv_misc_book_07", MultiBot.L("tips.quests.master")) + listButton.doRight = function() + if questLogFrame then + questLogFrame:Refresh() + end + end + listButton.doLeft = function() + if questLogFrame then + questLogFrame:Toggle() + end + end + tRight.buttons["Quests"] = listButton + + local incompButton = menu.addButton("BotQuestsIncomp", 0, 90, "Interface\\Icons\\INV_Misc_Bag_22", MultiBot.L("tips.quests.incompleted")) + local incompGroup = menu.addButton("BotQuestsIncompGroup", 31, 90, "Interface\\Icons\\INV_Crate_08", MultiBot.L("tips.quests.sendpartyraid")) + local incompWhisper = menu.addButton("BotQuestsIncompWhisper", 61, 90, "Interface\\Icons\\INV_Crate_08", MultiBot.L("tips.quests.sendwhisp")) + incompGroup:doHide() + incompWhisper:doHide() + incompButton.doLeft = function() toggleButtons(incompGroup, incompWhisper) end + incompGroup.doLeft = function() sendIncomplete("GROUP") end + incompWhisper.doLeft = function() sendIncomplete("WHISPER") end + tRight.buttons["BotQuestsIncomp"] = incompButton + tRight.buttons["BotQuestsIncompGroup"] = incompGroup + tRight.buttons["BotQuestsIncompWhisper"] = incompWhisper + + local completedButton = menu.addButton("BotQuestsComp", 0, 60, "Interface\\Icons\\INV_Misc_Bag_20", MultiBot.L("tips.quests.completed")) + local completedGroup = menu.addButton("BotQuestsCompGroup", 31, 60, "Interface\\Icons\\INV_Crate_09", MultiBot.L("tips.quests.sendpartyraid")) + local completedWhisper = menu.addButton("BotQuestsCompWhisper", 61, 60, "Interface\\Icons\\INV_Crate_09", MultiBot.L("tips.quests.sendwhisp")) + completedGroup:doHide() + completedWhisper:doHide() + completedButton.doLeft = function() toggleButtons(completedGroup, completedWhisper) end + completedGroup.doLeft = function() sendCompleted("GROUP") end + completedWhisper.doLeft = function() sendCompleted("WHISPER") end + tRight.buttons["BotQuestsComp"] = completedButton + tRight.buttons["BotQuestsCompGroup"] = completedGroup + tRight.buttons["BotQuestsCompWhisper"] = completedWhisper + + local talkButton = menu.addButton("BotQuestsTalk", 0, 0, "Interface\\Icons\\ability_hunter_pet_devilsaur", MultiBot.L("tips.quests.talk")) + talkButton.doLeft = function() + if not UnitExists("target") or UnitIsPlayer("target") then + UIErrorsFrame:AddMessage(MultiBot.L("tips.quests.talkerror"), 1, 0.2, 0.2, 1) + return + end + MultiBot.ActionToGroup("talk") + end + tRight.buttons["BotQuestsTalk"] = talkButton + + local allButton = menu.addButton("BotQuestsAll", 0, 120, "Interface\\Icons\\INV_Misc_Book_09", MultiBot.L("tips.quests.allcompleted")) + local allGroup = menu.addButton("BotQuestsAllGroup", 31, 120, "Interface\\Icons\\INV_Misc_Book_09", MultiBot.L("tips.quests.sendpartyraid")) + local allWhisper = menu.addButton("BotQuestsAllWhisper", 61, 120, "Interface\\Icons\\INV_Misc_Book_09", MultiBot.L("tips.quests.sendwhisp")) + allGroup:doHide() + allWhisper:doHide() + allButton.doLeft = function() toggleButtons(allGroup, allWhisper) end + allGroup.doLeft = function() sendAll("GROUP") end + allWhisper.doLeft = function() sendAll("WHISPER") end + tRight.buttons["BotQuestsAll"] = allButton + tRight.buttons["BotQuestsAllGroup"] = allGroup + tRight.buttons["BotQuestsAllWhisper"] = allWhisper + + local gobButton = menu.addButton("BotUseGOB", 0, 150, "Interface\\Icons\\inv_misc_spyglass_01", MultiBot.L("tips.quests.gobsmaster")) + local gobNameButton = menu.addButton("BotUseGOBName", 31, 150, "Interface\\Icons\\inv_misc_note_05", MultiBot.L("tips.quests.gobenter")) + local gobSearchButton = menu.addButton("BotUseGOBSearch", 61, 150, "Interface\\Icons\\inv_misc_spyglass_02", MultiBot.L("tips.quests.gobsearch")) + gobNameButton:doHide() + gobSearchButton:doHide() + gobButton.doLeft = function() toggleButtons(gobNameButton, gobSearchButton) end + gobNameButton.doLeft = function() + if not ShowPrompt then + return + end + ShowPrompt(MultiBot.L("tips.quests.gobpromptname"), function(gobName) + local normalized = tostring(gobName or ""):gsub("^%s+", ""):gsub("%s+$", "") + if normalized == "" then + UIErrorsFrame:AddMessage(MultiBot.L("tips.quests.goberrorname"), 1, 0.2, 0.2, 1) + return + end + local bot = UnitName("target") + if not bot or not UnitIsPlayer("target") then + UIErrorsFrame:AddMessage(MultiBot.L("tips.quests.gobselectboterror"), 1, 0.2, 0.2, 1) + return + end + SendChatMessage("u " .. normalized, "WHISPER", nil, bot) + end) + end + gobSearchButton.doLeft = function() + MultiBot.ActionToGroup("los") + end + tRight.buttons["BotUseGOB"] = gobButton + tRight.buttons["BotUseGOBName"] = gobNameButton + tRight.buttons["BotUseGOBSearch"] = gobSearchButton + + QuestsMenu.initialized = true + QuestsMenu.button = button + QuestsMenu.menu = menu + QuestsMenu.questLogFrame = questLogFrame + return QuestsMenu +end \ No newline at end of file diff --git a/docs/ace3-expansion-checklist.md b/docs/ace3-expansion-checklist.md index 9e8e165..02bb3d7 100644 --- a/docs/ace3-expansion-checklist.md +++ b/docs/ace3-expansion-checklist.md @@ -33,6 +33,9 @@ Checklist for the full addon-wide ACE3 expansion after M7 completion. - [x] AceGUI popup close-behavior parity tightened (`Core/MultiBotInit.lua`): migrated popup windows now hide on close (no release), preserving reopen behavior across the same session. - [x] AceGUI resolver deduplication completed (`Core/MultiBotInit.lua`): migrated popup paths now share a single resolver helper for dependency lookup + error reporting. - [x] Escape-close parity added for migrated AceGUI popups (`Core/MultiBotInit.lua`): popup windows are now registered in `UISpecialFrames` for consistent ESC close behavior. +- [x] Quest/GameObject architectural follow-up documented (`docs/ace3-quests-gobjects-migration-tracker.md`): the remaining extraction out of `Core/MultiBotInit.lua` and Itemus-style skin target are now tracked explicitly for the next M8 Quest-frame PRs. +- [x] Quest/GameObject extraction slice completed structurally (`UI/MultiBotQuestUIShared.lua`, `UI/MultiBotPromptDialog.lua`, `UI/MultiBotQuestLogFrame.lua`, `UI/MultiBotQuestIncompleteFrame.lua`, `UI/MultiBotQuestCompletedFrame.lua`, `UI/MultiBotQuestAllFrame.lua`, `UI/MultiBotGameObjectResultsFrame.lua`, `UI/MultiBotGameObjectCopyFrame.lua`, `UI/MultiBotQuestsMenu.lua` + `Core/MultiBotInit.lua` + `MultiBot.toc`): legacy inline frame construction was removed from Core and replaced by dedicated UI modules with shared helpers, deterministic aggregation helpers, and preserved Ace window close/ESC/position behavior. +- [ ] Quest/GameObject slice final in-game parity validation + Itemus-style polish pass. - [x] Preserve slash entry points and open/close behavior (aliases unchanged in migrated slices; popup close/hide + ESC parity preserved). - [x] Keep persisted state routed through existing AceDB helpers (`Core/MultiBotInit.lua`: migrated popups now persist positions in `MultiBot.db.profile.ui.popupPositions`). - [x] Validate visual/interaction parity per migrated screen (close/hide/ESC parity + popup reopen behavior aligned across migrated slices). diff --git a/docs/ace3-quests-gobjects-migration-tracker.md b/docs/ace3-quests-gobjects-migration-tracker.md new file mode 100644 index 0000000..6eb0042 --- /dev/null +++ b/docs/ace3-quests-gobjects-migration-tracker.md @@ -0,0 +1,123 @@ +# Ace3 Quests + GameObject UI Migration Tracker (Milestone 8) + +Document de suivi dédié à la migration complète de la tranche **Quests** + **GameObject search/results** hors de `Core/MultiBotInit.lua` vers des modules `UI/` dédiés. + +> Objectif de la migration : avoir de vraies fenêtres AceGUI avec contenu recodé nativement, sans réembarquer les anciennes frames legacy dans une coquille Ace3. + +--- + +## État actuel + +### Statut global +- **Extraction UI réalisée** : les frames Quests/GameObjects ne sont plus construites inline dans `Core/MultiBotInit.lua`. +- **Découpage par écran réalisé** : chaque écran important dispose maintenant de son propre fichier `UI/`. +- **Socle partagé en place** : le styling, le tri, les helpers de liens de quête, l’agrégation des bots et le prompt GameObject ont maintenant des helpers communs. +- **Reste à faire** : validation fonctionnelle complète en jeu + harmonisation visuelle plus poussée avec la référence `Itemus`. + +### Source of truth actuelle +- `Core/MultiBotInit.lua` +- `Core/MultiBotHandler.lua` +- `UI/MultiBotQuestUIShared.lua` +- `UI/MultiBotPromptDialog.lua` +- `UI/MultiBotQuestLogFrame.lua` +- `UI/MultiBotQuestIncompleteFrame.lua` +- `UI/MultiBotQuestCompletedFrame.lua` +- `UI/MultiBotQuestAllFrame.lua` +- `UI/MultiBotGameObjectResultsFrame.lua` +- `UI/MultiBotGameObjectCopyFrame.lua` +- `UI/MultiBotQuestsMenu.lua` +- `UI/MultiBotItemusFrame.lua` *(référence visuelle)* + +--- + +## Découpage effectivement livré + +### Fichiers UI dédiés +- `UI/MultiBotQuestLogFrame.lua` + - Popup du journal de quêtes joueur. +- `UI/MultiBotQuestIncompleteFrame.lua` + - Quêtes incomplètes des bots. +- `UI/MultiBotQuestCompletedFrame.lua` + - Quêtes terminées des bots. +- `UI/MultiBotQuestAllFrame.lua` + - Vue agrégée “all quests”. +- `UI/MultiBotGameObjectResultsFrame.lua` + - Résultats de recherche GameObjects. +- `UI/MultiBotGameObjectCopyFrame.lua` + - Fenêtre de copie/export. +- `UI/MultiBotPromptDialog.lua` + - Prompt AceGUI réutilisable pour le flux `u `. +- `UI/MultiBotQuestUIShared.lua` + - Helpers communs de style, tri, agrégation et rendu. +- `UI/MultiBotQuestsMenu.lua` + - Wiring du menu Quests et des actions associées. + +### Rôle résiduel de `Core/MultiBotInit.lua` +- Exposer les helpers transverses encore utiles au slice Quests/GameObjects (`GetLocalizedQuestName`, résolution AceGUI, close/ESC/persist positions). +- Initialiser le menu via `MultiBot.InitializeQuestsMenu(tRight)`. +- Ne plus contenir le gros bloc legacy de construction inline des fenêtres de quêtes/GameObjects. + +--- + +## Checklist d’avancement + +### Contraintes de migration +- [x] Ne pas réembarquer les anciennes frames legacy dans une fenêtre AceGUI. +- [x] Sortir la construction des frames Quests/GameObjects de `Core/MultiBotInit.lua`. +- [x] Déplacer la construction UI dans des fichiers `UI/` dédiés. +- [x] Conserver la logique fonctionnelle globale (group/whisper/loading/close/reopen). +- [x] Introduire des helpers partagés pour limiter la duplication. + +### Partage et architecture +- [x] Ajouter un module partagé `UI/MultiBotQuestUIShared.lua`. +- [x] Ajouter un prompt mutualisé `UI/MultiBotPromptDialog.lua`. +- [x] Extraire le menu Quests dans `UI/MultiBotQuestsMenu.lua`. +- [x] Conserver la logique de parsing/agrégation asynchrone côté `Core/MultiBotHandler.lua`. +- [x] Introduire un tri déterministe pour les listes de quêtes agrégées. +- [x] Mutualiser le formatage “Bots: …” et l’agrégation multi-bots. + +### Réécriture par écran +- [x] `MB_QuestPopup` → `UI/MultiBotQuestLogFrame.lua` +- [x] `MB_BotQuestPopup` → `UI/MultiBotQuestIncompleteFrame.lua` +- [x] `MB_BotQuestCompPopup` → `UI/MultiBotQuestCompletedFrame.lua` +- [x] `MB_BotQuestAllPopup` → `UI/MultiBotQuestAllFrame.lua` +- [x] `MB_GameObjPopup` → `UI/MultiBotGameObjectResultsFrame.lua` +- [x] `MB_GameObjCopyBox` → `UI/MultiBotGameObjectCopyFrame.lua` + +### Validation / finitions restantes +- [ ] Vérifier en jeu le clic gauche/droit sur les quêtes du journal. +- [ ] Vérifier la parité exacte des tooltips de quêtes. +- [ ] Vérifier la parité exacte des modes GROUP / WHISPER. +- [ ] Vérifier les états de chargement et d’absence de données. +- [ ] Vérifier visuellement l’alignement final avec le style `Itemus`. +- [ ] Réduire si besoin la surface des wrappers de compatibilité exposés par `Core/MultiBotInit.lua`. + +--- + +## Détails sur l’état fonctionnel à ce stade + +### Ce qui est déjà migré nativement +- Les hôtes de fenêtres sont créés via AceGUI. +- Le contenu de chaque écran est reconstruit dans son module dédié. +- Le prompt GameObject n’utilise plus d’ancienne frame séparée inline. +- Les listes agrégées sont triées de façon déterministe. +- Les fenêtres de quêtes partagent désormais un socle de style/backdrop commun. +- Le comportement close/hide, ESC et persistance de position réutilise les helpers partagés existants. + +### Ce qui reste principalement à confirmer +- Le rendu exact en client WoW avec toutes les localisations/cas limites. +- La cohérence finale du skin par rapport à `Itemus` sur tous les écrans du slice. +- Les éventuels petits écarts de wording/fallback (`LOADING`, labels vides, cas sans données). + +--- + +## Suivi recommandé pour la suite + +### Si la prochaine PR est une PR de finalisation Quests +1. Faire une passe de validation in-game complète sur tous les flux Quests/GameObjects. +2. Corriger les écarts de polish visuel restants vers le style `Itemus`. +3. Réduire les doublons résiduels éventuels entre les écrans incomplete/completed/all. +4. Mettre à jour ce document en basculant les items de validation en `[x]`. + +### Si la prochaine PR change de slice +Le slice Quests/GameObjects peut désormais être considéré comme **migré structurellement**, avec une étape restante de **parity validation / polish**. \ No newline at end of file diff --git a/docs/ace3-ui-frame-inventory.md b/docs/ace3-ui-frame-inventory.md index e960e7d..b9ffdc1 100644 --- a/docs/ace3-ui-frame-inventory.md +++ b/docs/ace3-ui-frame-inventory.md @@ -13,14 +13,16 @@ Inventory of addon UI frame construction points found via `CreateFrame(...)` sca ## 1) Interface Options / Configuration -- [x] **Options panel** (`/mbopt`, Interface Options category) — AceGUI path in place with temporary legacy fallback. - Files: `UI/MultiBotOptions.lua` (panel + sliders/dropdowns/buttons). +- [x] **Options panel** (`/mbopt`, Interface Options category) — AceGUI path in place with temporary legacy fallback. + Files: `UI/MultiBotOptions.lua` (panel + sliders/dropdowns/buttons).. References: `UI/MultiBotOptions.lua:234`, `UI/MultiBotOptions.lua:268`. --- ## 2) Dedicated top-level windows/popups (user-facing) +> Follow-up note: the Quest/GameObject slice already has AceGUI hosts, but its implementation still lives mainly in `Core/MultiBotInit.lua`. The extraction/rewrite plan is tracked in `docs/ace3-quests-gobjects-migration-tracker.md`. + - [x] **PVP window** (`MultiBotPVPFrame`) with tabs and dropdown (AceGUI widgets for tab group + bot dropdown, with legacy fallback). File: `UI/MultiBotPVPUI.lua`. References: lines `11`, `98`, `201`. @@ -33,32 +35,32 @@ Inventory of addon UI frame construction points found via `CreateFrame(...)` sca File: `Features/MultiBotRaidus.lua`. References: lines `119`, `164`, `599`, `1372`. -- [x] **Quest summary popup** (`MB_QuestPopup`) migrated to AceGUI host window path (no legacy frame fallback) while preserving dynamic rows/html content rendering. +- [x] **Quest summary popup** (`MB_QuestPopup`) migrated to an AceGUI host window path, but the screen implementation is still in `Core/MultiBotInit.lua`; dedicated `UI/` extraction + full native rewrite plan is tracked in `docs/ace3-quests-gobjects-migration-tracker.md`. File: `Core/MultiBotInit.lua`. References: lines `1760`, `1787`, `1845`. -- [x] **Bot quest popup** (`MB_BotQuestPopup`) migrated to AceGUI host window path (no legacy frame fallback) while preserving dynamic quest rows/html content rendering. +- [x] **Bot quest popup** (`MB_BotQuestPopup`) migrated to an AceGUI host window path, but the screen implementation is still in `Core/MultiBotInit.lua`; dedicated `UI/` extraction + full native rewrite plan is tracked in `docs/ace3-quests-gobjects-migration-tracker.md`. File: `Core/MultiBotInit.lua`. References: lines `1942`, `1966`, `1995`. -- [x] **Bot quest complete popup** (`MB_BotQuestCompPopup`) migrated to AceGUI host window path (no legacy frame fallback) while preserving dynamic rows/html content rendering. +- [x] **Bot quest complete popup** (`MB_BotQuestCompPopup`) migrated to an AceGUI host window path, but the screen implementation is still in `Core/MultiBotInit.lua`; dedicated `UI/` extraction + full native rewrite plan is tracked in `docs/ace3-quests-gobjects-migration-tracker.md`. File: `Core/MultiBotInit.lua`. References: lines `2161`, `2185`, `2215`. -- [x] **Bot quest all popup** (`MB_BotQuestAllPopup`) migrated to AceGUI host window path (no legacy frame fallback) while preserving dynamic rows/html content rendering. +- [x] **Bot quest all popup** (`MB_BotQuestAllPopup`) migrated to an AceGUI host window path, but the screen implementation is still in `Core/MultiBotInit.lua`; dedicated `UI/` extraction + full native rewrite plan is tracked in `docs/ace3-quests-gobjects-migration-tracker.md`. File: `Core/MultiBotInit.lua`. References: lines `2387`, `2416`, `2467` -- [x] **GameObject popup/copy box** (`MB_GameObjPopup`, `MB_GameObjCopyBox`) migrated to AceGUI windows/widgets path (no legacy frame fallback). - File: `Core/MultiBotInit.lua`. +- [x] **GameObject popup/copy box** (`MB_GameObjPopup`, `MB_GameObjCopyBox`) migrated to AceGUI windows/widgets paths, but the implementation is still coupled to `Core/MultiBotInit.lua`; dedicated `UI/` extraction + Itemus-style harmonization plan is tracked in `docs/ace3-quests-gobjects-migration-tracker.md`. + File: `Core/MultiBotInit.lua`. References: lines `2745`, `2789` -- [x] **Universal prompt dialog** (`MBUniversalPrompt`) migrated to AceGUI window+widgets path (no legacy frame fallback). +- [x] **Universal prompt dialog** (`MBUniversalPrompt`) migrated to AceGUI window+widgets path (no legacy frame fallback). File: `Core/MultiBotInit.lua`. References: line `2893`. -- [x] **Hunter prompt/search/family windows** (`MBHunterPrompt`, `MBHunterPetSearch`, `MBHunterPetFamily`) migrated to AceGUI host/prompt paths (no legacy frame fallback for prompt/search/family hosts; preview model retained). - File: `Core/MultiBotInit.lua`. +- [x] **Hunter prompt/search/family windows** (`MBHunterPrompt`, `MBHunterPetSearch`, `MBHunterPetFamily`) migrated to AceGUI host/prompt paths (no legacy frame fallback for prompt/search/family hosts; preview model retained). + File: `Core/MultiBotInit.lua`. References: lines `5505`, `5551`, `5730`, `5588`. - [x] **SpellBook window** (`MultiBot.spellbook`) migrated to AceGUI host window path with programmatic slot/check generation and stateful chat-collection handling (replacing legacy hardcoded slot blocks and inline footer-only stop logic). @@ -97,11 +99,11 @@ Inventory of addon UI frame construction points found via `CreateFrame(...)` sca File: `Features/MultiBotRaidus.lua`. References: lines `415`, `945`, `989`, `1021`. -- [-] **Quest/localization tooltip frame** (`MB_LocalizeQuestTooltip`) kept as native tooltip; creation is now centralized via hidden-tooltip helper. +- [-] **Quest/localization tooltip frame** (`MB_LocalizeQuestTooltip`) kept as native tooltip; creation is now centralized via hidden-tooltip helper. File: `Core/MultiBotInit.lua`. Reference: line `1730`. -- [-] **Hidden glyph tooltip** (`MBHiddenTip`) kept as native tooltip; now reuses the same hidden-tooltip helper. +- [-] **Hidden glyph tooltip** (`MBHiddenTip`) kept as native tooltip; now reuses the same hidden-tooltip helper. File: `Core/MultiBotInit.lua`. Reference: line `4701`. @@ -109,13 +111,13 @@ Inventory of addon UI frame construction points found via `CreateFrame(...)` sca ## 4) Runtime/utility frames (not direct Milestone 8 AceGUI screens) -- [-] **Minimap button** (`MultiBot_MinimapButton`) keep native frame. +- [-] **Minimap button** (`MultiBot_MinimapButton`) keep native frame. File: `Core/MultiBotInit.lua`. -- [-] **Event/timer/dispatch helper frames** (`CreateFrame("Frame")` without visible UI). +- [-] **Event/timer/dispatch helper frames** (`CreateFrame("Frame")` without visible UI). Files: `Core/MultiBot.lua`, `Core/MultiBotThrottle.lua`, `Core/MultiBotHandler.lua`, `UI/MultiBotSpecUI.lua` (timer frame), `Core/MultiBotInit.lua` (misc helper frame usages). -- [-] **Engine widget factory primitives** in `Core/MultiBotEngine.lua` (button/check/model constructors for core UI system). +- [-] **Engine widget factory primitives** in `Core/MultiBotEngine.lua` (button/check/model constructors for core UI system). These are shared low-level primitives and should be migrated only when the owning screen is migrated. --- From 1ea86203735d7c4d6268ec991136ca23955d4ce4 Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Sun, 22 Mar 2026 17:38:01 +0100 Subject: [PATCH 2/6] Lua lint fixes --- .luacheckrc | 2 +- UI/MultiBotSpell.lua | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.luacheckrc b/.luacheckrc index d42d000..c3ff305 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -36,7 +36,7 @@ globals = { "TooltipBackdropTemplateMixin", "NORMAL_FONT_COLOR", "HIGHLIGHT_FONT_COLOR", "GameTooltip_SetDefaultAnchor", "ChatFrame1", "UISpecialFrames", "GetMouseFocus", "ShowUIPanel", "tremove", "min", "max", "GetMinimapShape", "GetMinimapShape", "PanelTemplates_TabResize", "GetGuildRosterShowOffline", "SetGuildRosterShowOffline", "IsInGuild", "GetGuildInfo", "SetGuildRosterShowOffline", "PLAYER", "INVENTORY_TOOLTIP", "BAGSLOT", "UNKNOWN", "UnitIsDead", "ShowPrompt", "_MB_GetOrCreateShamanPos", "ensureHiddenTooltip", "MB_TAB_TITLE_DEFAULT", "SPELLBOOK", "MB_PAGE_DEFAULT", "SPELLBOOK_END_NON_SPELL_STREAK", "sendInventoryItemCommand", - "RAID_CLASS_COLORS", "INSPECT", "MB_INVENTORY_LABEL", "LOADING", "ITEM", "ITEMS", "SEARCH" + "RAID_CLASS_COLORS", "INSPECT", "MB_INVENTORY_LABEL", "LOADING", "ITEM", "ITEMS", "SEARCH", "NO_QUESTS_LABEL", "QUESTS_LABEL", "QUEST_LOG" } read_globals = { diff --git a/UI/MultiBotSpell.lua b/UI/MultiBotSpell.lua index 11c9830..6f48f46 100644 --- a/UI/MultiBotSpell.lua +++ b/UI/MultiBotSpell.lua @@ -106,7 +106,7 @@ MultiBot.addSpell = function(pInfo, pName) end MultiBot.beginSpellbookCollection = function(pName) - local tOverlay = MultiBot.spellbook.frames["Overlay"] + --local tOverlay = MultiBot.spellbook.frames["Overlay"] local tSpellbook = MultiBot.spellbook local tWindowTitle = MultiBot.doReplace(MultiBot.L("info.spellbook"), "NAME", pName) @@ -144,7 +144,8 @@ MultiBot.isSpellbookHeaderLine = function(pLine) return false end - return MultiBot.isInside(pLine, SPELLBOOK, "Spells", "法术", "Магия") + --return MultiBot.isInside(pLine, SPELLBOOK, "Spells", "法术", "Магия") + return MultiBot.isInside(pLine, unpack(getSpellbookHeaderTokens())) end MultiBot.isSpellbookFooterLine = function(pLine) From 5e9feaa8a5c7be136571c47fb35da92706024b27 Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Sun, 22 Mar 2026 20:06:46 +0100 Subject: [PATCH 3/6] some spellbook debug --- UI/MultiBotSpell.lua | 95 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 79 insertions(+), 16 deletions(-) diff --git a/UI/MultiBotSpell.lua b/UI/MultiBotSpell.lua index 6f48f46..4dadf2c 100644 --- a/UI/MultiBotSpell.lua +++ b/UI/MultiBotSpell.lua @@ -1,4 +1,5 @@ local SPELLBOOK_PAGE_SIZE = 18 +local SPELLBOOK_FOOTER_REQUEST_DELAY = 0.35 local function getSpellBookUI() return MultiBot.SpellBookUISettings or {} @@ -19,7 +20,9 @@ local function ensureSpellbookCollectionState(pButton) if(type(pButton.spellbookCollectionState) ~= "table") then pButton.spellbookCollectionState = { hasCollectedSpell = false, - nonSpellStreak = 0, + --nonSpellStreak = 0, + footerRequestToken = 0, + hasRequestedFooter = false, } end @@ -43,15 +46,55 @@ local function shouldFinishSpellbookCollection(pLine, pCollectionState) return true end - local tNonSpellStreak = tonumber(pCollectionState.nonSpellStreak) or 0 - local tNonSpellThreshold = tonumber(SPELLBOOK_END_NON_SPELL_STREAK) or 4 - if(pCollectionState.hasCollectedSpell and tNonSpellStreak >= tNonSpellThreshold) then - return true + return false +end + + +local function scheduleSpellbookFooterRequest(pButton, pSender) + if(type(pButton) ~= "table" or type(pSender) ~= "string" or pSender == "") then + return end - return false + local tCollectionState = ensureSpellbookCollectionState(pButton) + tCollectionState.footerRequestToken = (tonumber(tCollectionState.footerRequestToken) or 0) + 1 + local tToken = tCollectionState.footerRequestToken + + local function requestFooter() + local tCurrentState = pButton and pButton.spellbookCollectionState + if(tCurrentState ~= tCollectionState) then return end + if((tonumber(tCurrentState.footerRequestToken) or 0) ~= tToken) then return end + if(pButton.waitFor ~= "SPELL") then return end + if(tCurrentState.hasRequestedFooter) then return end + + tCurrentState.hasRequestedFooter = true + SendChatMessage("stats", "WHISPER", nil, pSender) + end + + if(type(MultiBot.TimerAfter) == "function") then + MultiBot.TimerAfter(SPELLBOOK_FOOTER_REQUEST_DELAY, requestFooter) + else + requestFooter() + end end +-- DEBUG -- +local function debugSpellbookCapture(pKind, pSender, pLine, pSpellID) + if(type(MultiBot) ~= "table" or type(MultiBot.dprint) ~= "function") then + return + end + + local tSender = tostring(pSender or "?") + local tLine = tostring(pLine or "") + local tSpellIDText = "" + + if(type(pSpellID) == "number" and pSpellID > 0) then + tSpellIDText = " spellID=" .. pSpellID + end + + MultiBot.dprint("SPELLBOOK", "[" .. tostring(pKind) .. "]", "sender=" .. tSender .. tSpellIDText, "line=" .. tLine) +end + +-- END DEBUG -- MultiBot.getSpellID = function(pInfo) if(type(pInfo) ~= "string" or pInfo == "") then return 0 @@ -80,7 +123,14 @@ end MultiBot.addSpell = function(pInfo, pName) local tID = MultiBot.getSpellID(pInfo) - if(tID == 0) then return false end + -- if(tID == 0) then return false end // Moded to DEBUG + + -- DEBUG -- + if(tID == 0) then + debugSpellbookCapture("IGNORED", pName, pInfo, 0) + return false + end + -- DEBUG END -- local tName, tRank, tIcon = GetSpellInfo(tID) local tLink = GetSpellLink(tID) @@ -102,6 +152,9 @@ MultiBot.addSpell = function(pInfo, pName) MultiBot.setSpell(MultiBot.spellbook.index, tSpell, pName) end +-- DEBUG -- + debugSpellbookCapture("CAPTURED", pName, pInfo, tID) +-- DEBUG END -- return true end @@ -178,28 +231,26 @@ MultiBot.handleSpellbookChatLine = function(pButton, pLine, pSender) end if(pButton.waitFor == "SPELLBOOK" and MultiBot.isSpellbookHeaderLine and MultiBot.isSpellbookHeaderLine(pLine)) then + -- DEBUG -- + debugSpellbookCapture("HEADER", pSender, pLine, 0) + -- DEBUG END -- if(MultiBot.beginSpellbookCollection) then MultiBot.beginSpellbookCollection(pSender) end resetSpellbookCollectionState(pButton) ensureSpellbookCollectionState(pButton) pButton.waitFor = "SPELL" - SendChatMessage("stats", "WHISPER", nil, pSender) + scheduleSpellbookFooterRequest(pButton, pSender) return true end if(pButton.waitFor == "SPELL") then local tCollectionState = ensureSpellbookCollectionState(pButton) - local tAddedSpell = MultiBot.addSpell(pLine, pSender) - if(tAddedSpell) then - tCollectionState.hasCollectedSpell = true - tCollectionState.nonSpellStreak = 0 - return true - end - - tCollectionState.nonSpellStreak = (tonumber(tCollectionState.nonSpellStreak) or 0) + 1 if(shouldFinishSpellbookCollection(pLine, tCollectionState)) then + -- DEBUG -- + debugSpellbookCapture("FOOTER", pSender, pLine, 0) + -- DEBUG END -- if(MultiBot.finishSpellbookCollection) then MultiBot.finishSpellbookCollection() end @@ -209,6 +260,15 @@ MultiBot.handleSpellbookChatLine = function(pButton, pLine, pSender) return true end + local tAddedSpell = MultiBot.addSpell(pLine, pSender) + if(tAddedSpell) then + tCollectionState.hasCollectedSpell = true + --tCollectionState.nonSpellStreak = 0 + --else + --tCollectionState.nonSpellStreak = (tonumber(tCollectionState.nonSpellStreak) or 0) + 1 + end + + scheduleSpellbookFooterRequest(pButton, pSender) return true end @@ -220,12 +280,15 @@ MultiBot.setSpell = function(pIndex, pSpell, pName) local tOverlay = MultiBot.spellbook.frames["Overlay"] if(pSpell ~= nil) then + --local tTitle = MultiBot.IF(string.len(pSpell[2]) > 16, string.sub(pSpell[2], 1, 16) .. "...", pSpell[2]) tOverlay.setButton("S" .. tIndex, pSpell[4], pSpell[5]) + --tOverlay.setText("T" .. tIndex, "|cffffcc00" .. tTitle .. "|r") tOverlay.setText("R" .. tIndex, "|cff" .. (getSpellBookUI().RANK_TEXT_COLOR_HEX or "ffcc00") .. pSpell[3] .. "|r") tOverlay.buttons["S" .. tIndex].spell = pSpell[1] tOverlay.buttons["C" .. tIndex].spell = pSpell[1] tOverlay.buttons["S" .. tIndex].doShow() tOverlay.buttons["C" .. tIndex].doShow() + --tOverlay.texts["T" .. tIndex]:Show() tOverlay.texts["R" .. tIndex]:Show() tOverlay.buttons["C" .. tIndex]:SetChecked(MultiBot.spells[pName][pSpell[1]]) tOverlay.buttons["C" .. tIndex].doClick = function(pButton) From d6d89e8dbb5f441ad2b7a382b951810fb6e43f0e Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Sun, 22 Mar 2026 20:38:51 +0100 Subject: [PATCH 4/6] Some bugs fix --- MultiBot.toc | 2 +- UI/MultiBotSpecUI.lua | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/MultiBot.toc b/MultiBot.toc index 78d0e87..4679e62 100644 --- a/MultiBot.toc +++ b/MultiBot.toc @@ -80,9 +80,9 @@ UI\MultiBotGameObjectCopyFrame.lua UI\MultiBotGameObjectResultsFrame.lua UI\MultiBotQuestsMenu.lua Core\MultiBotInit.lua -UI\MultiBotSpecUI.lua Data\MultiBotIconos.lua Data\MultiBotItemus.lua +UI\MultiBotSpecUI.lua UI\MultiBotTalent.lua UI\MultiBotPVPUI.lua Features\MultiBotReward.lua diff --git a/UI/MultiBotSpecUI.lua b/UI/MultiBotSpecUI.lua index 6602e34..abeab04 100644 --- a/UI/MultiBotSpecUI.lua +++ b/UI/MultiBotSpecUI.lua @@ -697,8 +697,11 @@ local function bindSpecSelection(button, spec, build, tip, bot, className, curre end button:SetScript("OnEnter", function(activeButton) - GameTooltip:SetOwner(activeButton, "ANCHOR_RIGHT") + GameTooltip:SetOwner(activeButton, "ANCHOR_NONE") + GameTooltip:ClearAllPoints() + GameTooltip:SetPoint("LEFT", activeButton, "RIGHT", 30, 0) GameTooltip:SetText(tip, nil, nil, nil, nil, true) + GameTooltip:Show() end) button:SetScript("OnLeave", GameTooltip_Hide) end From 4ecef844efb9d5d69596c815f792ff678c19dc74 Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Sun, 22 Mar 2026 20:41:30 +0100 Subject: [PATCH 5/6] remove blank spaces --- UI/MultiBotSpell.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/UI/MultiBotSpell.lua b/UI/MultiBotSpell.lua index 4dadf2c..a018627 100644 --- a/UI/MultiBotSpell.lua +++ b/UI/MultiBotSpell.lua @@ -124,12 +124,12 @@ end MultiBot.addSpell = function(pInfo, pName) local tID = MultiBot.getSpellID(pInfo) -- if(tID == 0) then return false end // Moded to DEBUG - + -- DEBUG -- if(tID == 0) then debugSpellbookCapture("IGNORED", pName, pInfo, 0) return false - end + end -- DEBUG END -- local tName, tRank, tIcon = GetSpellInfo(tID) @@ -232,7 +232,7 @@ MultiBot.handleSpellbookChatLine = function(pButton, pLine, pSender) if(pButton.waitFor == "SPELLBOOK" and MultiBot.isSpellbookHeaderLine and MultiBot.isSpellbookHeaderLine(pLine)) then -- DEBUG -- - debugSpellbookCapture("HEADER", pSender, pLine, 0) + debugSpellbookCapture("HEADER", pSender, pLine, 0) -- DEBUG END -- if(MultiBot.beginSpellbookCollection) then MultiBot.beginSpellbookCollection(pSender) @@ -249,7 +249,7 @@ MultiBot.handleSpellbookChatLine = function(pButton, pLine, pSender) if(shouldFinishSpellbookCollection(pLine, tCollectionState)) then -- DEBUG -- - debugSpellbookCapture("FOOTER", pSender, pLine, 0) + debugSpellbookCapture("FOOTER", pSender, pLine, 0) -- DEBUG END -- if(MultiBot.finishSpellbookCollection) then MultiBot.finishSpellbookCollection() From 8835f28fcd8b7a352d4d1278d1325c51772304ba Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Sun, 22 Mar 2026 21:24:45 +0100 Subject: [PATCH 6/6] Some frames adjust --- UI/MultiBotGameObjectResultsFrame.lua | 12 ++++++------ UI/MultiBotQuestAllFrame.lua | 14 +++++++------- UI/MultiBotQuestCompletedFrame.lua | 11 ++++++----- UI/MultiBotQuestIncompleteFrame.lua | 12 +++++++----- UI/MultiBotQuestLogFrame.lua | 12 +++++++----- 5 files changed, 33 insertions(+), 28 deletions(-) diff --git a/UI/MultiBotGameObjectResultsFrame.lua b/UI/MultiBotGameObjectResultsFrame.lua index 7f2df64..5ac4f65 100644 --- a/UI/MultiBotGameObjectResultsFrame.lua +++ b/UI/MultiBotGameObjectResultsFrame.lua @@ -80,17 +80,17 @@ function MultiBot.InitializeGameObjectResultsFrame() if MultiBot.RegisterAceWindowEscapeClose then MultiBot.RegisterAceWindowEscapeClose(window, "GameObjPopup") end if MultiBot.BindAceWindowPosition then MultiBot.BindAceWindowPosition(window, "gameobject_popup") end - local description = aceGUI:Create("Label") - description:SetFullWidth(true) - description:SetText(MultiBot.L("tips.quests.gobsmaster") or "") - window:AddChild(description) - local scroll = aceGUI:Create("ScrollFrame") scroll:SetFullWidth(true) - scroll:SetHeight(260) + scroll:SetHeight(280) scroll:SetLayout("List") window:AddChild(scroll) + local buttonSpacer = aceGUI:Create("Label") + buttonSpacer:SetFullWidth(true) + buttonSpacer:SetText(" ") + window:AddChild(buttonSpacer) + local copyButton = aceGUI:Create("Button") copyButton:SetText(MultiBot.L("tips.quests.gobselectall")) copyButton:SetWidth(170) diff --git a/UI/MultiBotQuestAllFrame.lua b/UI/MultiBotQuestAllFrame.lua index 6db0c68..e47804c 100644 --- a/UI/MultiBotQuestAllFrame.lua +++ b/UI/MultiBotQuestAllFrame.lua @@ -35,11 +35,11 @@ local function renderQuestWithBots(parent, yOffset, entry) local icon = line:CreateTexture(nil, "ARTWORK") icon:SetTexture(Shared.ICON_BOT_QUEST) - icon:SetSize(18, 18) + icon:SetSize(12, 12) icon:SetPoint("LEFT", 6, 0) - local html = Shared.CreateQuestHTML(line, 320, 20, Shared.BuildQuestLink(entry.id, entry.name)) - html:SetPoint("LEFT", 28, 0) + local html = Shared.CreateQuestHTML(line, 320, Shared.ROW_HEIGHT, Shared.BuildQuestLink(entry.id, entry.name)) + html:SetPoint("LEFT", icon, "RIGHT", 6, -6) Shared.BindHyperlinkTooltip(html) yOffset = yOffset - 24 @@ -68,11 +68,11 @@ function MultiBot.BuildBotAllList(botName) local icon = line:CreateTexture(nil, "ARTWORK") icon:SetTexture(Shared.ICON_BOT_QUEST) - icon:SetSize(18, 18) + icon:SetSize(12, 12) icon:SetPoint("LEFT", 6, 0) - local html = Shared.CreateQuestHTML(line, 320, 20, displayLink) - html:SetPoint("LEFT", 28, 0) + local html = Shared.CreateQuestHTML(line, 320, Shared.ROW_HEIGHT, displayLink) + html:SetPoint("LEFT", icon, "RIGHT", 6, -6) Shared.BindHyperlinkTooltip(html) yOffset = yOffset - 24 @@ -108,7 +108,7 @@ function MultiBot.BuildAggregatedAllList() end if frame.summaryLabel then - frame.summaryLabel:SetText(MultiBot.L("tips.quests.alllist") or "") + frame.summaryLabel:SetText("") end frame.content:SetHeight(math.max(-yOffset + 4, 1)) diff --git a/UI/MultiBotQuestCompletedFrame.lua b/UI/MultiBotQuestCompletedFrame.lua index 24ffe4c..66cd4d4 100644 --- a/UI/MultiBotQuestCompletedFrame.lua +++ b/UI/MultiBotQuestCompletedFrame.lua @@ -22,11 +22,11 @@ local function renderQuestList(self, entries, summaryText) local icon = line:CreateTexture(nil, "ARTWORK") icon:SetTexture(Shared.ICON_BOT_QUEST) - icon:SetSize(18, 18) + icon:SetSize(14, 14) icon:SetPoint("LEFT", 6, 0) - local html = Shared.CreateQuestHTML(line, 280, 20, Shared.BuildQuestLink(entry.id, entry.name)) - html:SetPoint("LEFT", 28, 0) + local html = Shared.CreateQuestHTML(line, 280, Shared.ROW_HEIGHT, Shared.BuildQuestLink(entry.id, entry.name)) + html:SetPoint("LEFT", icon, "RIGHT", 6, -5) Shared.BindHyperlinkTooltip(html) yOffset = yOffset - Shared.ROW_HEIGHT - 4 @@ -56,7 +56,8 @@ function MultiBot.BuildBotCompletedList(botName) local frame = MultiBot.InitializeQuestCompletedFrame() local entries = Shared.SortQuestEntries(MultiBot.BotQuestsCompleted[botName] or {}) frame:Show() - renderQuestList(frame, entries, botName and ((MultiBot.L("tips.quests.complist") or "Completed Quests") .. ": |cff80ff80" .. botName .. "|r") or nil) + --renderQuestList(frame, entries, botName and ((MultiBot.L("tips.quests.complist") or "Completed Quests") .. ": |cff80ff80" .. botName .. "|r") or nil) + renderQuestList(frame, entries, botName and ("|cff80ff80" .. botName .. "|r") or nil) end function MultiBot.BuildAggregatedCompletedList() @@ -64,7 +65,7 @@ function MultiBot.BuildAggregatedCompletedList() local entries = Shared.BuildAggregatedQuestEntries(MultiBot.BotQuestsCompleted) frame:Show() - renderQuestList(frame, entries, MultiBot.L("tips.quests.complist") or "") + renderQuestList(frame, entries, "") end function QuestCompletedFrame:Show() diff --git a/UI/MultiBotQuestIncompleteFrame.lua b/UI/MultiBotQuestIncompleteFrame.lua index f1be9bd..b86e24e 100644 --- a/UI/MultiBotQuestIncompleteFrame.lua +++ b/UI/MultiBotQuestIncompleteFrame.lua @@ -22,11 +22,11 @@ local function renderQuestList(self, entries, summaryText) local icon = line:CreateTexture(nil, "ARTWORK") icon:SetTexture(Shared.ICON_BOT_QUEST) - icon:SetSize(18, 18) + icon:SetSize(14, 14) icon:SetPoint("LEFT", 6, 0) - local html = Shared.CreateQuestHTML(line, 280, 20, Shared.BuildQuestLink(entry.id, entry.name)) - html:SetPoint("LEFT", 28, 0) + local html = Shared.CreateQuestHTML(line, 280, Shared.ROW_HEIGHT, Shared.BuildQuestLink(entry.id, entry.name)) + html:SetPoint("LEFT", icon, "RIGHT", 6, -5) Shared.BindHyperlinkTooltip(html) yOffset = yOffset - Shared.ROW_HEIGHT - 4 @@ -56,7 +56,8 @@ function MultiBot.BuildBotQuestList(botName) local frame = MultiBot.InitializeQuestIncompleteFrame() local entries = Shared.SortQuestEntries(MultiBot.BotQuestsIncompleted[botName] or {}) frame:Show() - renderQuestList(frame, entries, botName and ((MultiBot.L("tips.quests.incomplist") or "Current Quests") .. ": |cff80ff80" .. botName .. "|r") or nil) + --renderQuestList(frame, entries, botName and ((MultiBot.L("tips.quests.incomplist") or "Current Quests") .. ": |cff80ff80" .. botName .. "|r") or nil) + renderQuestList(frame, entries, botName and ("|cff80ff80" .. botName .. "|r") or nil) end function MultiBot.BuildAggregatedQuestList() @@ -64,7 +65,8 @@ function MultiBot.BuildAggregatedQuestList() local entries = Shared.BuildAggregatedQuestEntries(MultiBot.BotQuestsIncompleted) frame:Show() - renderQuestList(frame, entries, MultiBot.L("tips.quests.incomplist") or "") + --renderQuestList(frame, entries, MultiBot.L("tips.quests.incomplist") or "") + renderQuestList(frame, entries, "") end function QuestIncompleteFrame:Show() diff --git a/UI/MultiBotQuestLogFrame.lua b/UI/MultiBotQuestLogFrame.lua index b1ec022..5a96588 100644 --- a/UI/MultiBotQuestLogFrame.lua +++ b/UI/MultiBotQuestLogFrame.lua @@ -120,11 +120,11 @@ function QuestLogFrame:Refresh() local icon = row:CreateTexture(nil, "ARTWORK") icon:SetTexture(Shared.ICON_QUEST) - icon:SetSize(18, 18) + icon:SetSize(14, 14) icon:SetPoint("LEFT", 6, 0) - local html = Shared.CreateQuestHTML(row, 290, 20, questLink:gsub("%[", "|cff00ff00["):gsub("%]", "]|r")) - html:SetPoint("LEFT", 28, 0) + local html = Shared.CreateQuestHTML(row, 290, Shared.ROW_HEIGHT, questLink:gsub("%[", "|cff00ff00["):gsub("%]", "]|r")) + html:SetPoint("LEFT", icon, "RIGHT", 6, -5) attachQuestLogTooltip(html, questIndex) attachQuestLogClick(html) @@ -132,7 +132,8 @@ function QuestLogFrame:Refresh() end end - local emptyState = visibleCount == 0 and (MultiBot.L("tips.quests.gobnosearchdata") or NO_QUESTS_LABEL) or (QUESTS_LABEL or QUEST_LOG) + --local emptyState = visibleCount == 0 and (MultiBot.L("tips.quests.gobnosearchdata") or NO_QUESTS_LABEL) or (QUESTS_LABEL or QUEST_LOG) + local emptyState = visibleCount == 0 and (MultiBot.L("tips.quests.gobnosearchdata") or NO_QUESTS_LABEL) or "" if self.summaryLabel then self.summaryLabel:SetText(emptyState) end @@ -167,7 +168,8 @@ function MultiBot.InitializeQuestLogFrame() QuestLogFrame.scrollFrame = scrollFrame QuestLogFrame.content = content QuestLogFrame.summaryLabel = summaryLabel - QuestLogFrame.summaryLabel:SetText(QUESTS_LABEL or QUEST_LOG) + --QuestLogFrame.summaryLabel:SetText(QUESTS_LABEL or QUEST_LOG) + QuestLogFrame.summaryLabel:SetText("") return QuestLogFrame end \ No newline at end of file