diff --git a/APR-Recorder.toc b/APR-Recorder.toc index 8006906..7255053 100644 --- a/APR-Recorder.toc +++ b/APR-Recorder.toc @@ -1,5 +1,5 @@ -## Interface: 100207 -## X-Min-Interface: 100205 +## Interface: 110002 +## X-Min-Interface: 110000 ## Title: Azeroth Pilot Reloaded - [|cffeda55fRoute Recorder|r] ## Notes: A route recorder for Azeroth Pilot Reloaded @@ -16,7 +16,7 @@ ## X-Discord: https://discord.gg/YgcdybKdWX ## X-Curse-Project-ID: 965240 ## X-Wago-ID: rN4VYvKD -## X-WoWI-ID: ------ +## X-WoWI-ID: 26704 libs/embeds.xml locales/locales.xml @@ -29,12 +29,13 @@ helper/RouteManagement.lua Commands.lua Event.lua -frames/autocompleteLocales.lua +frames/autocomplete.lua frames/CommandsBar.lua +frames/CommandsBarSetting.lua frames/exportExtraLineText.lua frames/exportRoute.lua -frames/fillersSelection.lua frames/QuestionPopUp.lua +frames/QuestObjectiveSelector.lua frames/RecorderBar.lua frames/routeSelector.lua frames/selectButton.lua diff --git a/Commands.lua b/Commands.lua index 9e25110..0c3cfcb 100644 --- a/Commands.lua +++ b/Commands.lua @@ -1,5 +1,6 @@ local L = LibStub("AceLocale-3.0"):GetLocale("APR-Recorder") local L_APR = LibStub("AceLocale-3.0"):GetLocale("APR") +local AceGUI = LibStub("AceGUI-3.0") AprRC.command = AprRC:NewModule("Command") @@ -125,72 +126,94 @@ function AprRC.command:SlashCmd(input) print("|cff00bfffNoArrow|r Added") return elseif inputText == "text" or inputText == "txt" then - AprRC.autocomplete:Show() + AprRC.autocomplete:ShowLocaleAutoComplete() return elseif inputText == "button" or inputText == "btn" then AprRC.SelectButton:Show() return elseif inputText == "fillers" or inputText == "filler" then - AprRC.fillers:Show() + AprRC.QuestObjectiveSelector:Show({ + title = "Fillers quest list", + statusText = "Click on an objective to add it as a filler", + questList = AprRC.QuestObjectiveSelector:GetQuestList(), + onClick = function(questID, objectiveID) + local currentStep = AprRC:GetLastStep() + if not currentStep.Fillers then + currentStep.Fillers = {} + end + if not currentStep.Fillers[questID] then + currentStep.Fillers[questID] = {} + end + table.insert(currentStep.Fillers[questID], objectiveID) + print("|cff00bfffFillers - [" .. + C_QuestLog.GetTitleForQuestID(questID) .. "] - " .. objectiveID .. "|r Added") + end + }) return elseif inputText == "spelltrigger" then - AprRC.questionDialog:CreateEditBoxPopupWithCallback("SpellTrigger (Spell ID)", function(text) + AprRC.autocomplete:ShowSpellAutoComplete(questID, objectiveID, function(_, spellID, frame) local currentStep = AprRC:GetLastStep() - currentStep.SpellTrigger = tonumber(text, 10) - print("|cff00bfffSpellTrigger -" .. tonumber(text, 10) .. "|r Added") + + currentStep.SpellTrigger = tonumber(spellID, 10) + print("|cff00bfff SpellTrigger |r Added") + AceGUI:Release(frame) end) + return elseif inputText == "pickupdb" then if AprRC:HasStepOption("PickUp") then - AprRC.questionDialog:CreateEditBoxPopupWithCallback("PickUp DB (QuestID)", function(questId) - local currentStep = AprRC:GetLastStep() - if AprRC:HasStepOption("PickUpDB") then - tinsert(currentStep.PickUpDB, tonumber(questId, 10)) - else - currentStep.PickUpDB = { tonumber(questId, 10) } - for _, qID in pairs(currentStep.PickUp) do - tinsert(currentStep.PickUpDB, qID) + AprRC.questionDialog:CreateEditBoxPopupWithCallback("PickUp DB (QuestID) - Also add PickUp QuestID", + function(questId) + local currentStep = AprRC:GetLastStep() + if AprRC:HasStepOption("PickUpDB") then + tinsert(currentStep.PickUpDB, tonumber(questId, 10)) + else + currentStep.PickUpDB = { tonumber(questId, 10) } + for _, qID in pairs(currentStep.PickUp) do + tinsert(currentStep.PickUpDB, qID) + end end - end - print("|cff00bfffPickUpDB - " .. tonumber(questId, 10) .. "|r Added") - end) + print("|cff00bfffPickUpDB - " .. tonumber(questId, 10) .. "|r Added") + end) else AprRC:Error('Missing PickUp option on current step') end return elseif inputText == "qpartdb" then if AprRC:HasStepOption("Qpart") then - AprRC.questionDialog:CreateEditBoxPopupWithCallback("Qpart DB (QuestID)", function(questId) - local currentStep = AprRC:GetLastStep() - if AprRC:HasStepOption("QpartDB") then - tinsert(currentStep.QpartDB, tonumber(questId, 10)) - else - currentStep.QpartDB = { tonumber(questId, 10) } + AprRC.questionDialog:CreateEditBoxPopupWithCallback("Qpart DB (QuestID) - Also add Qpart QuestID", + function(questId) + local currentStep = AprRC:GetLastStep() + if AprRC:HasStepOption("QpartDB") then + tinsert(currentStep.QpartDB, tonumber(questId, 10)) + else + currentStep.QpartDB = { tonumber(questId, 10) } - for qID, _ in pairs(currentStep.Qpart) do - tinsert(currentStep.QpartDB, qID) + for qID, _ in pairs(currentStep.Qpart) do + tinsert(currentStep.QpartDB, qID) + end end - end - print("|cff00bfffQpartDB - " .. tonumber(questId, 10) .. "|r Added") - end) + print("|cff00bfffQpartDB - " .. tonumber(questId, 10) .. "|r Added") + end) else AprRC:Error('Missing Qpart option on current step') end return elseif inputText == "donedb" then if AprRC:HasStepOption("Done") then - AprRC.questionDialog:CreateEditBoxPopupWithCallback("Done DB (QuestID)", function(questId) - local currentStep = AprRC:GetLastStep() - if AprRC:HasStepOption("DoneDB") then - tinsert(currentStep.DoneDB, tonumber(questId, 10)) - else - currentStep.DoneDB = { tonumber(questId, 10) } - for _, qID in pairs(currentStep.Done) do - tinsert(currentStep.DoneDB, qID) + AprRC.questionDialog:CreateEditBoxPopupWithCallback("Done DB (QuestID) - Also add Done QuestID", + function(questId) + local currentStep = AprRC:GetLastStep() + if AprRC:HasStepOption("DoneDB") then + tinsert(currentStep.DoneDB, tonumber(questId, 10)) + else + currentStep.DoneDB = { tonumber(questId, 10) } + for _, qID in pairs(currentStep.Done) do + tinsert(currentStep.DoneDB, qID) + end end - end - print("|cff00bfffDoneDB - " .. tonumber(questId, 10) .. "|r Added") - end) + print("|cff00bfffDoneDB - " .. tonumber(questId, 10) .. "|r Added") + end) else AprRC:Error('Missing Done option on current step') end @@ -225,17 +248,21 @@ function AprRC.command:SlashCmd(input) print("|cff00bfffClass - " .. select(2, UnitClass("player")) .. "|r Added") return elseif inputText == "achievement" then - AprRC.questionDialog:CreateEditBoxPopupWithCallback("Has Achievement (ID)", function(text) + AprRC.autocomplete:ShowAchievementAutoComplete(function(name, achievementID, frame) local currentStep = AprRC:GetLastStep() - currentStep.HasAchievement = tonumber(text, 10) - print("|cff00bfffHasAchievement - " .. tonumber(text, 10) .. "|r Added") + currentStep.HasAchievement = tonumber(achievementID, 10) + print("|cff00bfffHasAchievement - " .. name .. "|r Added") + + AceGUI:Release(frame) end) return elseif inputText == "noachievement" then - AprRC.questionDialog:CreateEditBoxPopupWithCallback("Dont Have Achievement (ID)", function(text) + AprRC.autocomplete:ShowAchievementAutoComplete(function(name, achievementID, frame) local currentStep = AprRC:GetLastStep() - currentStep.DontHaveAchievement = tonumber(text, 10) - print("|cff00bfffDontHaveAchievement - " .. tonumber(text, 10) .. "|r Added") + currentStep.DontHaveAchievement = tonumber(achievementID, 10) + print("|cff00bfffDontHaveAchievement - " .. name .. "|r Added") + + AceGUI:Release(frame) end) return elseif inputText == "vehicle" then diff --git a/Config.lua b/Config.lua index 15f0d99..c9634d0 100644 --- a/Config.lua +++ b/Config.lua @@ -47,8 +47,8 @@ function AprRC.settings:InitializeSettings() commandsBarFrame = { rotation = "HORIZONTAL", position = {}, - isRecording = false, }, + commandBarSettingFrame = {}, --debug minimap = { minimapPos = 285 }, enableMinimapButton = true, @@ -120,31 +120,20 @@ function AprRC.settings:createBlizzOptions() order = 2, type = "header", width = "full", - name = "Somthing :)", + name = "Settings", }, - somthing = { + icon = { order = 3, type = "group", - name = "What a group", + name = "Minimap", inline = true, args = { - enableAddon = { - order = 3.1, - type = "toggle", - name = L_APR["ENABLE_ADDON"], - width = "full", - get = GetProfileOption, - set = function(info, value) - SetProfileOption(info, value) - self:ToggleAddon() - end, - }, enableMinimapButton = { name = L_APR["ENABLE_MINIMAP_BUTTON"], desc = L_APR["ENABLE_MINIMAP_BUTTON_DESC"], type = "toggle", width = "full", - order = 9.20, + order = 3.1, get = GetProfileOption, set = function(info, value) SetProfileOption(info, value) @@ -155,8 +144,28 @@ function AprRC.settings:createBlizzOptions() end end }, + + } + }, + debug = { + order = 4, + type = "group", + name = "Debug", + inline = true, + args = { + enableAddon = { + order = 4.1, + type = "toggle", + name = L_APR["ENABLE_ADDON"], + width = "full", + get = GetProfileOption, + set = function(info, value) + SetProfileOption(info, value) + self:ToggleAddon() + end, + }, debug = { - order = 3.2, + order = 4.2, type = "toggle", name = L_APR["DEBUG"], width = "full", @@ -179,6 +188,9 @@ function AprRC.settings:createBlizzOptions() -- add profile to bliz option aceConfig:RegisterOptionsTable(AprRC.title .. "/Profile", _G.LibStub("AceDBOptions-3.0"):GetOptionsTable(SettingsDB)) aceDialog:AddToBlizOptions(AprRC.title .. "/Profile", L_APR["PROFILES"], AprRC.title) + + local category, layout = Settings.RegisterCanvasLayoutCategory(AprRC, AprRC.title); + AprRC.settings.category = category end function AprRC.settings:CreateMiniMapButton() @@ -192,7 +204,11 @@ function AprRC.settings:CreateMiniMapButton() self.profile.enableAddon = not self.profile.enableAddon self:ToggleAddon() else - AprRC.settings:OpenSettings(AprRC.title) + if SettingsPanel:IsShown() then + self:CloseSettings() + else + self:OpenSettings(AprRC.title) + end end end, OnTooltipShow = function(tooltip) @@ -219,7 +235,11 @@ end function AprRC.settings:OpenSettings(name) if name == AprRC.title then - InterfaceOptionsFrame_OpenToCategory(AprRC.title) + if InterfaceOptionsFrame_OpenToCategory then + InterfaceOptionsFrame_OpenToCategory(AprRC.title) + else + Settings.OpenToCategory(self.category.ID) + end AprRC.settings:OpenSettings(L_APR["PROFILES"]) end if AprRC.Options then @@ -244,6 +264,20 @@ function AprRC.settings:OpenSettings(name) InterfaceOptionsFrame_OpenToCategory(AprRC.OptionsRoute) end return + else + Settings.OpenToCategory(self.category.ID) + end + end +end + +function AprRC.settings:CloseSettings() + if AprRC.Options then + if SettingsPanel then + local category = SettingsPanel:GetCategoryList():GetCategory(AprRC.Options.name) + if category then + SettingsPanel:Hide() + end + return end end end diff --git a/Core.lua b/Core.lua index a5fea25..6d86657 100644 --- a/Core.lua +++ b/Core.lua @@ -34,9 +34,12 @@ function AprRC:OnInitialize() AprRCData.ExtraLineTexts = AprRCData.ExtraLineTexts or {} AprRCData.QuestLookup = AprRCData.QuestLookup or {} AprRCData.TaxiLookup = AprRCData.TaxiLookup or {} + AprRCData.BeforePortal = AprRCData.BeforePortal or {} + AprRCData.CommandBarCommands = AprRCData.CommandBarCommands or {} -- Init module AprRC.settings:InitializeBlizOptions() + AprRC.CommandBar:OnInit() AprRC.record:OnInit() AprRC.event:MyRegisterEvent() AprRC:saveQuestInfo() diff --git a/Event.lua b/Event.lua index 156913d..2d101af 100644 --- a/Event.lua +++ b/Event.lua @@ -29,7 +29,8 @@ local events = { buy = "MERCHANT_SHOW", qpart = "QUEST_WATCH_UPDATE", loot = "CHAT_MSG_LOOT", - target = "PLAYER_TARGET_CHANGED" + target = "PLAYER_TARGET_CHANGED", + portal = { "PLAYER_LEAVING_WORLD", "PLAYER_ENTERING_WORLD" } -- warMode = "WAR_MODE_STATUS_UPDATE", -- vehicle = { "UNIT_ENTERING_VEHICLE", "UNIT_EXITING_VEHICLE" }, } @@ -532,6 +533,45 @@ function AprRC.event.functions.pet(event, ...) AprRC.record:RefreshFrameAnchor() end +function AprRC.event.functions.portal(event, ...) + if event == "PLAYER_LEAVING_WORLD" then + local step = {} + AprRC:SetStepCoord(step) + AprRCData.BeforePortal.lastStep = AprRC:GetLastStep() + AprRCData.BeforePortal.lastCoord = step + else + local isInitialLogin, isReloadingUi = ... + -- wait 2s si last step = saved last step alors waypoints + text prendre portal -- not skippable waypoints (new option) + -- sinon check IsCurrentStepFarAway si trop loin alors override coord + text prendre portal + -- pour les tp sans portail ni cast => LOSS_OF_CONTROL_ADDED into Zone changed indoors, waypoint update, area pois updated + -- pour les tp avec spell => spell list teleport (sans les spell de class) + unit spellcast-succeeded + + if not isInitialLogin and not isReloadingUi then + C_Timer.After(3, function() + local last = AprRC:GetLastStep() + if AprRC:DeepCompare(AprRCData.BeforePortal.lastStep, last) then + local step = { + Waypoint = AprRC:FindClosestIncompleteQuest(), + ExtraLineText = "USE_PORTAL", + Coord = AprRCData.BeforePortal.lastStep.Coord + } + AprRC:NewStep(step) + print("|cff00bfffWaypoint|r Added") + elseif AprRC:IsCurrentStepFarAway() then + local last = AprRC:GetLastStep() + last.Coord = AprRCData.BeforePortal.lastStep.Coord + last.ExtraLineText = "USE_PORTAL" + AprRCData.BeforePortal = {} + print("|cff00bfffLast Step coord updated|r Added") + end + end) + end + if isInitialLogin then + AprRCData.BeforePortal = {} + end + end +end + --------------------- -- V2 --------------------- diff --git a/assets/icons/Button.blp b/assets/icons/Button.blp new file mode 100644 index 0000000..3a2f09b Binary files /dev/null and b/assets/icons/Button.blp differ diff --git a/assets/icons/Class.blp b/assets/icons/Class.blp new file mode 100644 index 0000000..9549459 Binary files /dev/null and b/assets/icons/Class.blp differ diff --git a/assets/icons/Coord.blp b/assets/icons/Coord.blp new file mode 100644 index 0000000..2e2445c Binary files /dev/null and b/assets/icons/Coord.blp differ diff --git a/assets/icons/DoneDB.blp b/assets/icons/DoneDB.blp new file mode 100644 index 0000000..d673c15 Binary files /dev/null and b/assets/icons/DoneDB.blp differ diff --git a/assets/icons/DontHaveAchievement.blp b/assets/icons/DontHaveAchievement.blp new file mode 100644 index 0000000..f065ed3 Binary files /dev/null and b/assets/icons/DontHaveAchievement.blp differ diff --git a/assets/icons/Dontskipvid.blp b/assets/icons/Dontskipvid.blp new file mode 100644 index 0000000..317c2d1 Binary files /dev/null and b/assets/icons/Dontskipvid.blp differ diff --git a/assets/icons/ETA.blp b/assets/icons/ETA.blp new file mode 100644 index 0000000..9833711 Binary files /dev/null and b/assets/icons/ETA.blp differ diff --git a/assets/icons/ExtraLineText.blp b/assets/icons/ExtraLineText.blp new file mode 100644 index 0000000..220d7e0 Binary files /dev/null and b/assets/icons/ExtraLineText.blp differ diff --git a/assets/icons/Faction.blp b/assets/icons/Faction.blp new file mode 100644 index 0000000..2dce519 Binary files /dev/null and b/assets/icons/Faction.blp differ diff --git a/assets/icons/Fillers.blp b/assets/icons/Fillers.blp new file mode 100644 index 0000000..8176656 Binary files /dev/null and b/assets/icons/Fillers.blp differ diff --git a/assets/icons/Grind.blp b/assets/icons/Grind.blp new file mode 100644 index 0000000..12791f4 Binary files /dev/null and b/assets/icons/Grind.blp differ diff --git a/assets/icons/HasAchievement.blp b/assets/icons/HasAchievement.blp new file mode 100644 index 0000000..2ec87fc Binary files /dev/null and b/assets/icons/HasAchievement.blp differ diff --git a/assets/icons/InstanceQuest.blp b/assets/icons/InstanceQuest.blp new file mode 100644 index 0000000..e33e515 Binary files /dev/null and b/assets/icons/InstanceQuest.blp differ diff --git a/assets/icons/LearnProfession.blp b/assets/icons/LearnProfession.blp new file mode 100644 index 0000000..fa9072b Binary files /dev/null and b/assets/icons/LearnProfession.blp differ diff --git a/assets/icons/NoArrow.blp b/assets/icons/NoArrow.blp new file mode 100644 index 0000000..8110f99 Binary files /dev/null and b/assets/icons/NoArrow.blp differ diff --git a/assets/icons/PickUpDB.blp b/assets/icons/PickUpDB.blp new file mode 100644 index 0000000..972f30d Binary files /dev/null and b/assets/icons/PickUpDB.blp differ diff --git a/assets/icons/QpartDB.blp b/assets/icons/QpartDB.blp new file mode 100644 index 0000000..2d21c57 Binary files /dev/null and b/assets/icons/QpartDB.blp differ diff --git a/assets/icons/QpartPart.blp b/assets/icons/QpartPart.blp new file mode 100644 index 0000000..038b011 Binary files /dev/null and b/assets/icons/QpartPart.blp differ diff --git a/assets/icons/SpellTrigger.blp b/assets/icons/SpellTrigger.blp new file mode 100644 index 0000000..00c7c61 Binary files /dev/null and b/assets/icons/SpellTrigger.blp differ diff --git a/assets/icons/VehicleExit.blp b/assets/icons/VehicleExit.blp new file mode 100644 index 0000000..7026ac9 Binary files /dev/null and b/assets/icons/VehicleExit.blp differ diff --git a/assets/icons/WarMode.blp b/assets/icons/WarMode.blp new file mode 100644 index 0000000..12b11fb Binary files /dev/null and b/assets/icons/WarMode.blp differ diff --git a/assets/icons/Waypoint.blp b/assets/icons/Waypoint.blp new file mode 100644 index 0000000..2ccd01a Binary files /dev/null and b/assets/icons/Waypoint.blp differ diff --git a/assets/icons/ZoneStepTrigger.blp b/assets/icons/ZoneStepTrigger.blp new file mode 100644 index 0000000..3f7ad43 Binary files /dev/null and b/assets/icons/ZoneStepTrigger.blp differ diff --git a/assets/icons/gender.blp b/assets/icons/gender.blp new file mode 100644 index 0000000..f2fb74c Binary files /dev/null and b/assets/icons/gender.blp differ diff --git a/assets/icons/race.blp b/assets/icons/race.blp new file mode 100644 index 0000000..1c2888c Binary files /dev/null and b/assets/icons/race.blp differ diff --git a/assets/icons/range.blp b/assets/icons/range.blp new file mode 100644 index 0000000..a2bfb86 Binary files /dev/null and b/assets/icons/range.blp differ diff --git a/assets/icons/rec.blp b/assets/icons/rec.blp new file mode 100644 index 0000000..f0802fd Binary files /dev/null and b/assets/icons/rec.blp differ diff --git a/assets/icons/rotate.blp b/assets/icons/rotate.blp new file mode 100644 index 0000000..d5da390 Binary files /dev/null and b/assets/icons/rotate.blp differ diff --git a/assets/icons/settings.blp b/assets/icons/settings.blp new file mode 100644 index 0000000..c6d6f76 Binary files /dev/null and b/assets/icons/settings.blp differ diff --git a/assets/icons/stop.blp b/assets/icons/stop.blp new file mode 100644 index 0000000..06b684f Binary files /dev/null and b/assets/icons/stop.blp differ diff --git a/frames/CommandsBar.lua b/frames/CommandsBar.lua index cd7f6ce..0830f8d 100644 --- a/frames/CommandsBar.lua +++ b/frames/CommandsBar.lua @@ -2,127 +2,128 @@ local _G = _G local L = LibStub("AceLocale-3.0"):GetLocale("APR-Recorder") local LibWindow = LibStub("LibWindow-1.1") -AprRC.commandsBar = AprRC:NewModule('CommandsBar') +AprRC.CommandBar = AprRC:NewModule('CommandBar') --- Frame dimensions -local FRAME_WIDTH = 200 +local FRAME_WIDTH = 80 local FRAME_HEIGHT = 35 -local BUTTON_SIZE = 24 -local BUTTON_PADDING = 5 - --- --------------------------------------------------------------------------------------- --- --------------------------------- Command Frames -------------------------------------- --- --------------------------------------------------------------------------------------- - --- -- Create the frame for Command Bar --- local CommandBarFrame = CreateFrame("Frame", "CommandBarFrame", UIParent, "BackdropTemplate") --- CommandBarFrame:SetSize(FRAME_WIDTH, FRAME_HEIGHT) --- CommandBarFrame:SetFrameStrata("MEDIUM") --- CommandBarFrame:SetClampedToScreen(true) --- CommandBarFrame:SetBackdrop(AprRC.Backdrop.defaut) --- CommandBarFrame:SetBackdropColor(unpack(AprRC.Backdrop.defaultBackdrop)) - --- -- Define buttons for each command --- local commands = { --- "achievement", --- "button", --- "class", --- "donedb", --- "eta", --- "faction", --- "fillers", --- "gender", --- "grind", --- "noachievement", --- "noarrow", --- "pickupdb", --- "qpartdb", --- "race", --- "range", --- "spelltrigger", --- "text", --- "waypoint", --- "zonetrigger", --- } - --- local buttons = {} --- for i, cmd in ipairs(commands) do --- local btn = CreateFrame("Button", "APRCommandBarButton" .. i, CommandBarFrame, "UIPanelButtonTemplate") --- btn:SetSize(BUTTON_SIZE, BUTTON_SIZE) --- btn:SetPoint("TOPLEFT", 0, 0) --- btn:SetText(cmd) --- btn:SetScript("OnEnter", function(self) --- GameTooltip:SetOwner(self, "ANCHOR_BOTTOM") --- GameTooltip:AddLine(cmd, unpack(AprRC.Color.darkblue)) --- GameTooltip:Show() --- end) --- btn:SetScript("OnLeave", function(self) GameTooltip:Hide() end) --- btn:SetScript("OnClick", function() --- AprRC.command:SlashCmd(cmd) --- end) --- table.insert(buttons, btn) --- end - --- local rotationBtn = CreateButton(RecordBarFrame, "interface/buttons/ui-rotationleft-button-up", --- AprRC.Color.white, "Rotate") --- rotationBtn:SetScript("OnClick", function() --- AprRC.settings.profile.recordBarFrame.rotation = AprRC.settings.profile.recordBarFrame.rotation == "HORIZONTAL" and --- "VERTICAL" or "HORIZONTAL" --- AprRC.record:AdjustBarRotation(RecordBarFrame) --- end) - - --- --------------------------------------------------------------------------------------- --- ----------------------------- Function Command Frames --------------------------------- --- --------------------------------------------------------------------------------------- --- function AprRC.commandsBar:OnInit() --- LibWindow.RegisterConfig(CommandBarFrame, AprRC.settings.profile.recordBarFrame.position) --- RecordBarFrame.RegisteredForLibWindow = true --- LibWindow.MakeDraggable(RecordBarFrame) --- RecordBarFrame:SetPoint("CENTER", UIParent, "CENTER", 0, 0) - --- self:RefreshFrameAnchor() --- end - --- function AprRC.commandsBar:RefreshFrameAnchor() --- if not AprRC.settings.profile.enableAddon or C_PetBattles.IsInBattle() then --- RecordBarFrame:Hide() --- return --- end --- RecordBarFrame:EnableMouse(true) --- self:AdjustBarRotation(RecordBarFrame) --- UpdateRecordButton(recordBtn) --- LibWindow.RestorePosition(RecordBarFrame) --- RecordBarFrame:Show() --- end - - --- function AprRC.commandsBar:AdjustBarRotation(bar) --- local buttons = { recordBtn, updateBtn, rotationBtn } --- local spacing = 10 --- local offsetX, offsetY = 5, -5 --- local rotation = AprRC.settings.profile.recordBarFrame.rotation --- for i, btn in ipairs(buttons) do --- if rotation == "HORIZONTAL" then --- btn:SetPoint("TOPLEFT", offsetX, offsetY) --- offsetX = offsetX + btn:GetWidth() + spacing --- else -- VERTICAL --- btn:SetPoint("TOPLEFT", offsetX, offsetY) --- offsetY = offsetY - btn:GetHeight() - spacing --- end --- end --- if rotation == "HORIZONTAL" then --- bar:SetHeight(FRAME_HEIGHT) --- bar:SetWidth(FRAME_WIDTH + (#buttons - 1) * spacing) --- else --- bar:SetWidth(FRAME_HEIGHT) --- bar:SetHeight(FRAME_WIDTH + (#buttons - 1) * spacing) --- end --- end - --- -- Function to update or hide buttons based on conditions --- function AprRC.commandBar:UpdateButtons() --- for i, btn in ipairs(buttons) do --- btn:SetShown(AprRC.settings.profile.recordBarFrame.isRecording) --- end --- end + +AprRC.CommandBar.btnList = {} + +--------------------------------------------------------------------------------------- +--------------------------------- CommandBar Frames ----------------------------------- +--------------------------------------------------------------------------------------- + +local CommandBarFrame = CreateFrame("Frame", "CommandBarFrame", UIParent, "BackdropTemplate") +CommandBarFrame:SetSize(FRAME_WIDTH, FRAME_HEIGHT) +CommandBarFrame:SetFrameStrata("MEDIUM") +CommandBarFrame:SetClampedToScreen(true) +CommandBarFrame:SetBackdrop(AprRC.Backdrop.defaut) +CommandBarFrame:SetBackdropColor(unpack(AprRC.Backdrop.defaultBackdrop)) + + +local function CreateButton(parent, texture, tooltipText, onClick) + local btn = CreateFrame("Button", nil, parent) + btn:SetSize(24, 24) + btn:SetPoint("TOPLEFT", 0, 0) + btn.icon = btn:CreateTexture(nil, "BACKGROUND") + btn.icon:SetAllPoints(btn) + btn.icon:SetTexture(texture) + btn:SetHighlightTexture("Interface\\Buttons\\UI-Common-MouseHilight") + + btn:SetScript("OnEnter", function(self) + GameTooltip:SetOwner(self, "ANCHOR_BOTTOM") + GameTooltip:AddLine(tooltipText, unpack(AprRC.Color.darkblue)) + GameTooltip:Show() + end) + btn:SetScript("OnLeave", function(self) GameTooltip:Hide() end) + btn:SetScript("OnClick", onClick) + + return btn +end + +function AprRC.CommandBar:UpdateFrame() + for _, child in ipairs({ CommandBarFrame:GetChildren() }) do + child:Hide() + end + AprRC.CommandBar.btnList = {} + + for _, commandData in ipairs(AprRCData.CommandBarCommands) do + local btn = CreateButton(CommandBarFrame, commandData.texture, commandData.label, function() + AprRC.command:SlashCmd(commandData.command) + end) + tinsert(AprRC.CommandBar.btnList, btn) + end + + -- Create rotation button + local rotationBtn = CreateButton(CommandBarFrame, "Interface\\AddOns\\APR-Recorder\\assets\\icons\\rotate", "Rotate", + function() + AprRC.settings.profile.commandBarFrame.rotation = AprRC.settings.profile.commandBarFrame.rotation == + "HORIZONTAL" and + "VERTICAL" or "HORIZONTAL" + AprRC.CommandBar:AdjustBarRotation(CommandBarFrame) + end) + + -- Create settings button + local settingsBtn = CreateButton(CommandBarFrame, "Interface\\AddOns\\APR-Recorder\\assets\\icons\\settings", + "Settings", function() + AprRC.CommandBarSetting:Show() + end) + + tinsert(AprRC.CommandBar.btnList, rotationBtn) + tinsert(AprRC.CommandBar.btnList, settingsBtn) + AprRC.CommandBar:AdjustBarRotation(CommandBarFrame) +end + +--------------------------------------------------------------------------------------- +----------------------------- Function CommandBar Frames ----------------------------- +--------------------------------------------------------------------------------------- + +function AprRC.CommandBar:OnInit() + LibWindow.RegisterConfig(CommandBarFrame, AprRC.settings.profile.commandBarFrame.position) + CommandBarFrame.RegisteredForLibWindow = true + LibWindow.MakeDraggable(CommandBarFrame) + LibWindow.RestorePosition(CommandBarFrame) + CommandBarFrame:SetPoint("CENTER", UIParent, "CENTER", 0, 0) + + + self:RefreshFrameAnchor() +end + +function AprRC.CommandBar:RefreshFrameAnchor() + if not AprRC.settings.profile.enableAddon or not AprRC.settings.profile.recordBarFrame.isRecording or C_PetBattles.IsInBattle() then + CommandBarFrame:Hide() + return + end + CommandBarFrame:Show() + CommandBarFrame:EnableMouse(true) + LibWindow.RestorePosition(CommandBarFrame) + self:UpdateFrame() +end + +function AprRC.CommandBar:AdjustBarRotation(bar) + local buttons = AprRC.CommandBar.btnList + local spacing = 10 + local offsetX, offsetY = 5, -5 + local rotation = AprRC.settings.profile.commandBarFrame.rotation + + for i, btn in ipairs(buttons) do + if rotation == "HORIZONTAL" then + btn:SetPoint("TOPLEFT", offsetX, offsetY) + offsetX = offsetX + btn:GetWidth() + spacing + else -- VERTICAL + btn:SetPoint("TOPLEFT", offsetX, offsetY) + offsetY = offsetY - btn:GetHeight() - spacing + end + end + + local totalButtonWidth = (#buttons * (buttons[1]:GetWidth() + spacing)) - spacing + local totalButtonHeight = (#buttons * (buttons[1]:GetHeight() + spacing)) - spacing + + if rotation == "HORIZONTAL" then + bar:SetHeight(FRAME_HEIGHT) + bar:SetWidth(totalButtonWidth + 10) + else + bar:SetWidth(FRAME_HEIGHT) + bar:SetHeight(totalButtonHeight + 10) + end +end diff --git a/frames/CommandsBarSetting.lua b/frames/CommandsBarSetting.lua new file mode 100644 index 0000000..c55f1de --- /dev/null +++ b/frames/CommandsBarSetting.lua @@ -0,0 +1,159 @@ +local AceGUI = LibStub("AceGUI-3.0") +local LibWindow = LibStub("LibWindow-1.1") + +AprRC.CommandBarSetting = AprRC:NewModule('CommandBarSetting') + +-- Liste des commandes disponibles +local allCommands = { + { command = "btn", label = "Button", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\Button" }, + { command = "class", label = "Class", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\Class" }, + { command = "coord", label = "Coord", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\Coord" }, + { command = "donedb", label = "DoneDB", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\DoneDB" }, + { command = "noachievement", label = "DontHaveAchievement", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\DontHaveAchievement" }, + { command = "notskipvid", label = "Dontskipvid", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\Dontskipvid" }, + { command = "eta", label = "ETA", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\ETA" }, + { command = "text", label = "ExtraLineText", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\ExtraLineText" }, + { command = "faction", label = "Faction", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\Faction" }, + { command = "filler", label = "Fillers", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\Fillers" }, + { command = "gender", label = "Gender", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\Gender" }, + { command = "grind", label = "Grind", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\Grind" }, + { command = "achievement", label = "HasAchievement", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\HasAchievement" }, + { command = "instance", label = "InstanceQuest", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\InstanceQuest" }, + { command = "addjob", label = "LearnProfession", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\LearnProfession" }, + { command = "noarrow", label = "NoArrow", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\NoArrow" }, + { command = "pickupdb", label = "PickUpDB", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\PickUpDB" }, + { command = "qpartdb", label = "QpartDB", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\QpartDB" }, + { command = "qpartpart", label = "QpartPart", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\QpartPart" }, + { command = "race", label = "Race", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\Race" }, + { command = "range", label = "Range", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\Range" }, + { command = "spelltrigger", label = "SpellTrigger", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\SpellTrigger" }, + { command = "vehicle", label = "VehicleExit", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\VehicleExit" }, + { command = "warmode", label = "WarMode", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\WarMode" }, + { command = "waypoint", label = "Waypoint", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\Waypoint" }, + { command = "zonetrigger", label = "ZoneStepTrigger", texture = "Interface\\AddOns\\APR-Recorder\\assets\\icons\\ZoneStepTrigger" }, +} + +function AprRC.CommandBarSetting:Show() + if self.frame and self.frame:IsShown() then + self.frame:Hide() + return + end + + local frame = AceGUI:Create("Frame") + frame:SetTitle("Command Bar Settings") + frame:SetStatusText("Manage your command bar, click to move") + frame:SetCallback("OnClose", function(widget) AceGUI:Release(widget) end) + frame:SetWidth(650) + frame:SetHeight(400) + frame:EnableResize(false) + frame:SetLayout("Flow") + self.frame = frame + + LibWindow.RegisterConfig(frame.frame, AprRC.settings.profile.commandBarSettingFrame) + LibWindow.RestorePosition(frame.frame) + LibWindow.MakeDraggable(frame.frame) + + -- Create container for left list + local leftContainer = AceGUI:Create("InlineGroup") + leftContainer:SetHeight(300) + leftContainer:SetTitle("Command List") + leftContainer:SetLayout("Fill") + + local leftList = AceGUI:Create("ScrollFrame") + leftList:SetLayout("List") + leftContainer:AddChild(leftList) + + -- Create container for right list + local rightContainer = AceGUI:Create("InlineGroup") + rightContainer:SetTitle("Commands in Command Bar") + rightContainer:SetHeight(300) + rightContainer:SetLayout("Fill") + + local rightList = AceGUI:Create("ScrollFrame") + rightList:SetLayout("List") + rightContainer:AddChild(rightList) + + -- Initialize lists from saved data + local leftCommands = {} + + for _, commandData in ipairs(allCommands) do + local found = false + for _, selectedCommand in ipairs(AprRCData.CommandBarCommands) do + if commandData.label == selectedCommand.label then + found = true + break + end + end + if not found then + table.insert(leftCommands, commandData) + end + end + + -- Sort leftCommands alphabetically by label + table.sort(leftCommands, function(a, b) return a.label < b.label end) + + -- Helper function to create an interactive label + local function CreateInteractiveLabel(commandData, isSelected, onClick) + local interacLabel = AceGUI:Create("InteractiveLabel") + interacLabel:SetText(commandData.label) + interacLabel:SetColor(255, 255, 255) + interacLabel:SetFullWidth(true) + if isSelected then + interacLabel:SetJustifyH("RIGHT") + end + interacLabel:SetCallback("OnClick", onClick) + interacLabel:SetCallback("OnEnter", function(widget) + widget:SetHighlight("Interface\\QuestFrame\\UI-QuestTitleHighlight") + end) + interacLabel:SetCallback("OnLeave", function(widget) + widget:SetHighlight(nil) + end) + return interacLabel + end + + -- Refresh function to update both lists + local function RefreshLists() + leftList:ReleaseChildren() + rightList:ReleaseChildren() + + for _, commandData in ipairs(leftCommands) do + leftList:AddChild(CreateInteractiveLabel(commandData, false, function() + table.insert(AprRCData.CommandBarCommands, commandData) + for i, cmd in ipairs(leftCommands) do + if cmd.label == commandData.label then + table.remove(leftCommands, i) + break + end + end + table.sort(leftCommands, function(a, b) return a.label < b.label end) + RefreshLists() + AprRC.CommandBar:RefreshFrameAnchor() + end)) + end + + for _, selectedCommand in ipairs(AprRCData.CommandBarCommands) do + for _, commandData in ipairs(allCommands) do + if commandData.label == selectedCommand.label then + rightList:AddChild(CreateInteractiveLabel(commandData, true, function() + table.insert(leftCommands, commandData) + for i, cmd in ipairs(AprRCData.CommandBarCommands) do + if cmd.label == selectedCommand.label then + table.remove(AprRCData.CommandBarCommands, i) + break + end + end + table.sort(leftCommands, function(a, b) return a.label < b.label end) + RefreshLists() + AprRC.CommandBar:RefreshFrameAnchor() + end)) + break + end + end + end + end + + frame:AddChild(leftContainer) + frame:AddChild(rightContainer) + + RefreshLists() +end diff --git a/frames/QuestObjectiveSelector.lua b/frames/QuestObjectiveSelector.lua new file mode 100644 index 0000000..5b04252 --- /dev/null +++ b/frames/QuestObjectiveSelector.lua @@ -0,0 +1,138 @@ +local _G = _G +local AceGUI = LibStub("AceGUI-3.0") + +AprRC.QuestObjectiveSelector = AprRC:NewModule('QuestObjectiveSelector') + + +function AprRC.QuestObjectiveSelector:Show(config) + local frame = AceGUI:Create("Frame") + frame:SetTitle(config.title or "Quest Objective Selector") + frame:SetStatusText(config.statusText or "Select a quest objective") + frame:SetCallback("OnClose", function(widget) AceGUI:Release(widget) end) + frame:SetWidth(800) + frame:SetHeight(600) + frame:EnableResize(true) + frame:SetLayout("Fill") + + local scrollFrame = AceGUI:Create("ScrollFrame") + scrollFrame:SetFullWidth(true) + scrollFrame:SetFullHeight(true) + scrollFrame:SetLayout("Flow") + + + for _, quest in ipairs(config.questList) do + if #quest.objectives > 0 then + local questGroup = AceGUI:Create("InlineGroup") + questGroup:SetFullWidth(true) + questGroup:SetTitle(quest.title) + questGroup:SetLayout("List") + + for i, objective in ipairs(quest.objectives) do + local objectiveLabel = AceGUI:Create("InteractiveLabel") + objectiveLabel:SetText("[" .. objective.objectiveID .. "]" .. " - " .. objective.text) + objectiveLabel:SetFullWidth(true) + objectiveLabel:SetCallback("OnClick", function() + if config.onClick then + config.onClick(quest.questID, objective.objectiveID) + end + AceGUI:Release(frame) + end) + objectiveLabel:SetCallback("OnEnter", function(widget) + widget:SetHighlight("Interface\\QuestFrame\\UI-QuestTitleHighlight") + end) + objectiveLabel:SetCallback("OnLeave", function(widget) + widget:SetHighlight(nil) + end) + questGroup:AddChild(objectiveLabel) + if i < #quest.objectives then + local spacer = AceGUI:Create("Label") + spacer:SetText("") + spacer:SetFullWidth(true) + spacer:SetHeight(10) + questGroup:AddChild(spacer) + end + end + + scrollFrame:AddChild(questGroup) + end + end + + frame:AddChild(scrollFrame) +end + +local function GetFormattedQuestObjectives(questID, objectiveIDs) + local formattedObjectives = {} + local objectivesInfo = C_QuestLog.GetQuestObjectives(questID) + + if objectivesInfo then + for _, objectiveID in ipairs(objectiveIDs) do + local objective = objectivesInfo[objectiveID] + if objective then + table.insert(formattedObjectives, { + objectiveID = objectiveID, + text = objective.text + }) + end + end + end + + return formattedObjectives +end + +local function AddQuestsToList(questList, questsTable) + for questID, objectives in pairs(questsTable) do + local title = C_QuestLog.GetTitleForQuestID(questID) + if title then + local formattedObjectives = GetFormattedQuestObjectives(questID, objectives) + table.insert(questList, { + title = questID .. " - " .. title, + questID = questID, + objectives = formattedObjectives + }) + end + end +end + +function AprRC.QuestObjectiveSelector:GetQuestList() + local questList = {} + + for i = 1, C_QuestLog.GetNumQuestLogEntries() do + local info = C_QuestLog.GetInfo(i) + if info and not info.isHeader then + local questID = info.questID + local title = C_QuestLog.GetTitleForQuestID(questID) + if title and not C_QuestLog.IsComplete(questID) then + local objectives = {} + local objectivesInfo = C_QuestLog.GetQuestObjectives(questID) + for j, objective in ipairs(objectivesInfo) do + table.insert(objectives, j) + end + local formattedObjectives = GetFormattedQuestObjectives(questID, objectives) + table.insert(questList, { + title = questID .. " - " .. title, + questID = questID, + objectives = formattedObjectives + }) + end + end + end + + return questList +end + +function AprRC.QuestObjectiveSelector:GetQuestListFromLastStep() + local questList = {} + local lastStep = AprRC:GetLastStep() + + if lastStep then + if lastStep.Qpart then + AddQuestsToList(questList, lastStep.Qpart) + end + + if lastStep.Fillers then + AddQuestsToList(questList, lastStep.Fillers) + end + end + + return questList +end diff --git a/frames/QuestionPopUp.lua b/frames/QuestionPopUp.lua index 7b8f674..2321f28 100644 --- a/frames/QuestionPopUp.lua +++ b/frames/QuestionPopUp.lua @@ -8,6 +8,7 @@ function AprRC.questionDialog:CreateEditBoxPopupWithCallback(text, onAcceptCallb text = text or "General Kenobi", hasEditBox = true, button1 = CONTINUE, + button2 = CANCEL, OnShow = function(self) local box = _G[self:GetName() .. "EditBox"] local button = _G[self:GetName() .. "Button1"] -- Récupère le bouton OK diff --git a/frames/RecorderBar.lua b/frames/RecorderBar.lua index cd4059c..f27ccc6 100644 --- a/frames/RecorderBar.lua +++ b/frames/RecorderBar.lua @@ -19,20 +19,21 @@ RecordBarFrame:SetBackdropColor(unpack(AprRC.Backdrop.defaultBackdrop)) local function UpdateRecordButton(button) if AprRC.settings.profile.recordBarFrame.isRecording then - button.icon:SetTexture("interface/buttons/ui-stopbutton") + button.icon:SetTexture("Interface\\AddOns\\APR-Recorder\\assets\\icons\\stop") else - button.icon:SetTexture("interface/timemanager/resetbutton") + button.icon:SetTexture("Interface\\AddOns\\APR-Recorder\\assets\\icons\\rec") end + AprRC.CommandBar:RefreshFrameAnchor() end -local function CreateButton(parent, iconPath, color, message) +local function CreateButton(parent, iconPath, message) local btn = CreateFrame("Button", nil, parent) btn:SetSize(24, 24) btn:SetPoint("TOPLEFT", 0, 0) btn.icon = btn:CreateTexture(nil, "BACKGROUND") btn.icon:SetAllPoints(btn) btn.icon:SetTexture(iconPath) - btn.icon:SetVertexColor(unpack(color)) + btn.icon:SetVertexColor(unpack(AprRC.Color.white)) btn:SetScript("OnEnter", function(self) GameTooltip:SetOwner(self, "ANCHOR_BOTTOM") GameTooltip:AddLine(message, unpack(AprRC.Color.darkblue)) @@ -40,16 +41,13 @@ local function CreateButton(parent, iconPath, color, message) end) btn:SetScript("OnLeave", function(self) GameTooltip:Hide() end) - - - btn:SetPushedTexture([[Interface\Buttons\heckbuttonglow]]) - btn:SetHighlightTexture([[Interface\Buttons\UI-Panel-MinimizeButton-Highlight]]) - btn:SetDisabledTexture([[Interface\Buttons\UI-Panel-QuestHideButton-disabled]]) + btn:SetHighlightTexture("Interface\\Buttons\\UI-Common-MouseHilight") + btn:SetDisabledTexture("Interface\\Buttons\\UI-Panel-QuestHideButton-disabled") return btn end -local recordBtn = CreateButton(RecordBarFrame, "interface/timemanager/resetbutton", AprRC.Color.red, "Record/Stop") +local recordBtn = CreateButton(RecordBarFrame, "Interface\\AddOns\\APR-Recorder\\assets\\icons\\rec", "Record/Stop") recordBtn:SetScript("OnClick", function() AprRC.settings.profile.recordBarFrame.isRecording = not AprRC.settings.profile.recordBarFrame.isRecording if AprRC.settings.profile.recordBarFrame.isRecording then @@ -77,18 +75,19 @@ recordBtn:SetScript("OnClick", function() end end) -local updateBtn = CreateButton(RecordBarFrame, "interface/buttons/ui-optionsbutton", AprRC.Color.white, "Setting") -updateBtn:SetScript("OnClick", function() - AprRC.settings:OpenSettings(AprRC.title) -end) - -local rotationBtn = CreateButton(RecordBarFrame, "interface/buttons/ui-rotationleft-button-up", - AprRC.Color.white, "Rotate") +local rotationBtn = CreateButton(RecordBarFrame, "Interface\\AddOns\\APR-Recorder\\assets\\icons\\rotate", "Rotate") rotationBtn:SetScript("OnClick", function() AprRC.settings.profile.recordBarFrame.rotation = AprRC.settings.profile.recordBarFrame.rotation == "HORIZONTAL" and "VERTICAL" or "HORIZONTAL" AprRC.record:AdjustBarRotation(RecordBarFrame) end) + +local updateBtn = CreateButton(RecordBarFrame, "Interface\\AddOns\\APR-Recorder\\assets\\icons\\settings", "Settings") +updateBtn:SetScript("OnClick", function() + AprRC.settings:OpenSettings(AprRC.title) +end) + + --------------------------------------------------------------------------------------- ----------------------------- Function Recorder Frames -------------------------------- --------------------------------------------------------------------------------------- diff --git a/frames/autocomplete.lua b/frames/autocomplete.lua new file mode 100644 index 0000000..0946efc --- /dev/null +++ b/frames/autocomplete.lua @@ -0,0 +1,228 @@ +local _G = _G +local AceGUI = LibStub("AceGUI-3.0") +local L_APR = LibStub("AceLocale-3.0"):GetLocale("APR") + +AprRC.autocomplete = AprRC:NewModule('AutoComplete') + +function AprRC.autocomplete:ShowAutoComplete(title, list, onConfirm, formatItem, width, height, showAllOnEmpty) + showAllOnEmpty = showAllOnEmpty or false + local frame = AceGUI:Create("Frame") + frame:SetTitle(title) + frame.statustext:GetParent():Hide() + frame:SetCallback("OnClose", function(widget) AceGUI:Release(widget) end) + frame:SetWidth(width or 1000) + frame:SetHeight(height or 450) + frame:EnableResize(false) + frame:SetLayout("Flow") + + local editbox = AceGUI:Create("EditBox") + editbox:SetLabel("Enter text") + editbox:SetFullWidth(true) + editbox:DisableButton(true) + + local scrollFrame = AceGUI:Create("ScrollFrame") + scrollFrame:SetFullWidth(true) + scrollFrame:SetLayout("Flow") + scrollFrame:SetHeight(300) + scrollFrame.frame:Hide() + + local btnConfirm = AceGUI:Create("Button") + btnConfirm:SetText("Confirm") + btnConfirm:SetWidth(100) + btnConfirm:SetDisabled(false) + btnConfirm:SetCallback("OnClick", function() + onConfirm(editbox:GetText(), editbox.key, frame) + end) + + local debounceTimer = nil + local function UpdateAutoCompleteList(text) + if debounceTimer then + debounceTimer:Cancel() + end + debounceTimer = C_Timer.NewTimer(0.3, function() + scrollFrame:ReleaseChildren() -- Clear current list + editbox.key = nil + local matches = {} + + if text ~= "" or showAllOnEmpty then + for key, value in pairs(list) do + if text == "" or string.match(value:lower(), text:lower()) then + table.insert(matches, { key = key, value = value }) + end + end + end + + -- Render items in chunks to avoid lag + local function RenderMatches(startIndex, endIndex) + for i = startIndex, endIndex do + local match = matches[i] + if match then + local interacLabel = AceGUI:Create("InteractiveLabel") + interacLabel:SetText(formatItem and formatItem(match) or match.value) + interacLabel:SetColor(255, 255, 255) + interacLabel:SetFullWidth(true) + interacLabel:SetCallback("OnClick", function() + editbox:SetText(match.value) + editbox.key = match.key + scrollFrame:ReleaseChildren() -- Clear list after selection + scrollFrame.frame:Hide() + end) + interacLabel:SetCallback("OnEnter", function(widget) + widget:SetHighlight("Interface\\QuestFrame\\UI-QuestTitleHighlight") + end) + interacLabel:SetCallback("OnLeave", function(widget) + widget:SetHighlight(nil) + end) + scrollFrame:AddChild(interacLabel) + end + end + if endIndex < #matches then + C_Timer.After(0.01, function() + RenderMatches(endIndex + 1, math.min(endIndex + 10, #matches)) + end) + end + end + + if #matches > 0 then + scrollFrame.frame:Show() + RenderMatches(1, math.min(10, #matches)) -- Start rendering first 10 matches for lazy rendering + else + scrollFrame.frame:Hide() + end + + debounceTimer = nil + end) + end + + editbox:SetCallback("OnTextChanged", function(widget, event, text) + UpdateAutoCompleteList(text) + end) + + frame:AddChild(editbox) + frame:AddChild(scrollFrame) + frame:AddChild(btnConfirm) + + -- Initial call to show all items if the text is empty and showAllOnEmpty is true + if showAllOnEmpty then + UpdateAutoCompleteList("") + end +end + +function AprRC.autocomplete:ShowLocaleAutoComplete() + self:ShowAutoComplete( + "Extra Line Text", + L_APR, + function(text, key, frame) + if not key then + key = AprRC:ExtraLineTextToKey(text) + AprRCData.ExtraLineTexts[key] = text + end + local currentStep = AprRC:GetLastStep() + + local baseName = "ExtraLineText" + local index = 2 + local propertyName = baseName + + if currentStep[baseName] then + while currentStep[baseName .. index] do + index = index + 1 + end + propertyName = baseName .. index + end + + currentStep[propertyName] = key + + print("|cff00bfffExtraLineTexts|r Added") + AceGUI:Release(frame) + end + ) +end + +function AprRC.autocomplete:ShowItemAutoComplete(questID, objectiveID) + local itemList = {} + for bag = 0, 4 do + for slot = 1, C_Container.GetContainerNumSlots(bag) do + local itemID = C_Container.GetContainerItemID(bag, slot) + if itemID then + local itemName, _, _, _, _, _, _, _, _, itemIcon = C_Item.GetItemInfo(itemID) + if itemName then + itemList[itemID] = itemName + end + end + end + end + + self:ShowAutoComplete( + "Select Item", + itemList, + function(_, itemID, frame) + local currentStep = AprRC:GetLastStep() + if not currentStep.Button then + currentStep.Button = {} + end + currentStep.Button[questID .. "-" .. objectiveID] = tonumber(itemID, 10) + print("|cff00bfff Button |r Added") + AceGUI:Release(frame) + end, + function(match) + local itemName, _, _, _, _, _, _, _, _, itemIcon = C_Item.GetItemInfo(match.key) + return "|T" .. itemIcon .. ":35:35|t " .. itemName + end, + 500, + 450, + true + ) +end + +function AprRC.autocomplete:ShowSpellAutoComplete(questID, objectiveID, onConfirm) + local spellList = {} + for i = 1, C_SpellBook.GetNumSpellBookSkillLines() do + local skillLineInfo = C_SpellBook.GetSpellBookSkillLineInfo(i) + local offset, numSlots = skillLineInfo.itemIndexOffset, skillLineInfo.numSpellBookItems + for j = offset + 1, offset + numSlots do + local name, subName = C_SpellBook.GetSpellBookItemName(j, Enum.SpellBookSpellBank.Player) + local spellID = select(2, C_SpellBook.GetSpellBookItemType(j, Enum.SpellBookSpellBank.Player)) + spellList[spellID] = name + end + end + + self:ShowAutoComplete( + "Select Spell", + spellList, + onConfirm, + function(match) + local spellInfo = C_Spell.GetSpellInfo(match.key) + if spellInfo then + return "|T" .. spellInfo.iconID .. ":35:35|t " .. spellInfo.name + end + end, + 500, + 450, + true + ) +end + +function AprRC.autocomplete:ShowAchievementAutoComplete(onConfirm) + local achievementList = {} + for _, catId in ipairs(GetCategoryList()) do + for i = 1, GetCategoryNumAchievements(catId) do + local id, name = GetAchievementInfo(catId, i) + if id and name then + achievementList[id] = name + end + end + end + + self:ShowAutoComplete( + "Select Achievement", + achievementList, + onConfirm, + function(match) + local id, name, _, _, _, _, _, _, _, icon = GetAchievementInfo(match.key) + return "|T" .. icon .. ":35:35|t " .. name + end, + 500, + 450, + true + ) +end diff --git a/frames/autocompleteLocales.lua b/frames/autocompleteLocales.lua deleted file mode 100644 index 1f96f5c..0000000 --- a/frames/autocompleteLocales.lua +++ /dev/null @@ -1,104 +0,0 @@ -local _G = _G -local AceGUI = LibStub("AceGUI-3.0") -local L_APR = LibStub("AceLocale-3.0"):GetLocale("APR") - -AprRC.autocomplete = AprRC:NewModule('AutoComplete') - -function AprRC.autocomplete:Show() - local frame = AceGUI:Create("Frame") - frame:SetTitle("Extra Line Text") - frame.statustext:GetParent():Hide() - frame:SetCallback("OnClose", function(widget) AceGUI:Release(widget) end) - frame:SetWidth(1000) - frame:SetHeight(450) - frame:EnableResize(false) - frame:SetLayout("Flow") - - - local editbox = AceGUI:Create("EditBox") - editbox:SetLabel("Enter text") - editbox:SetFullWidth(true) - editbox:DisableButton(true) - - local scrollFrame = AceGUI:Create("ScrollFrame") - scrollFrame:SetFullWidth(true) - scrollFrame:SetLayout("Flow") - scrollFrame:SetHeight(300) - scrollFrame.frame:Hide() - - local btnConfirm = AceGUI:Create("Button") - btnConfirm:SetText("Confirm") - btnConfirm:SetWidth(100) - btnConfirm:SetDisabled(false) - btnConfirm:SetCallback("OnClick", function() - local key = editbox.key - if editbox.newKey then - key = AprRC:ExtraLineTextToKey(editbox:GetText()) - AprRCData.ExtraLineTexts[key] = editbox:GetText() - end - AprRC.autocomplete:SetExtraLineText(key) - AceGUI:Release(frame) - end) - - local debounceTimer = nil - local function UpdateAutoCompleteList(text) - if not debounceTimer then - debounceTimer = C_Timer.NewTimer(0.3, function() - scrollFrame:ReleaseChildren() -- Clear current list - scrollFrame.frame:Hide() - editbox.key = '' - editbox.newKey = true - if text ~= "" then - btnConfirm:SetDisabled(false) - for key, value in pairs(L_APR) do - if string.match(value:lower(), text:lower()) then - local interacLabel = AceGUI:Create("InteractiveLabel") - interacLabel:SetText(value) - interacLabel:SetColor(255, 255, 255) - interacLabel:SetFullWidth(true) - interacLabel:SetCallback("OnClick", function() - editbox:SetText(value) - editbox.key = key - editbox.newKey = false - scrollFrame:ReleaseChildren() -- Clear list after selection - scrollFrame.frame:Hide() - end) - scrollFrame.frame:Show() - scrollFrame:AddChild(interacLabel) - end - end - else - btnConfirm:SetDisabled(true) - end - debounceTimer = nil - end) - end - end - - editbox:SetCallback("OnTextChanged", function(widget, event, text) - UpdateAutoCompleteList(text) - end) - - frame:AddChild(editbox) - frame:AddChild(scrollFrame) - frame:AddChild(btnConfirm) -end - -function AprRC.autocomplete:SetExtraLineText(key) - local currentStep = AprRC:GetLastStep() - - local baseName = "ExtraLineText" - local index = 2 - local propertyName = baseName - - if currentStep[baseName] then - while currentStep[baseName .. index] do - index = index + 1 - end - propertyName = baseName .. index - end - - currentStep[propertyName] = key - - print("|cff00bfffExtraLineTexts|r Added") -end diff --git a/frames/exportRoute.lua b/frames/exportRoute.lua index e637c68..1278b02 100644 --- a/frames/exportRoute.lua +++ b/frames/exportRoute.lua @@ -17,28 +17,6 @@ end function AprRC.export:Show() local refreshTimer - local function StartAutoRefresh(dropdown, editbox) - if not refreshTimer then - refreshTimer = AceTimer:ScheduleRepeatingTimer(function() - if dropdown:GetValue() then - if AprRCData.CurrentRoute.name ~= "" then - AprRC:UpdateRouteByName(AprRCData.CurrentRoute.name, AprRCData.CurrentRoute) - local route = AprRCData.Routes[dropdown:GetValue()] - if route then - editbox:SetText(AprRC:RouteToString(route.steps)) - end - end - end - end, 5) - end - end - - local function StopAutoRefresh() - if refreshTimer then - AceTimer:CancelTimer(refreshTimer) - refreshTimer = nil - end - end frame = AceGUI:Create("Frame") frame:SetTitle("Export") @@ -77,6 +55,39 @@ function AprRC.export:Show() editbox:SetFullHeight(true) editbox:DisableButton(true) scrollContainer:AddChild(editbox) + AprRC.export.editbox = editbox + + local function AutoScrollToBottom() + C_Timer.After(0.1, function() + local scrollFrame = editbox.scrollFrame + scrollFrame:SetVerticalScroll(scrollFrame:GetVerticalScrollRange()) + end) + end + + local function StartAutoRefresh(dropdown, editbox) + if not refreshTimer then + refreshTimer = AceTimer:ScheduleRepeatingTimer(function() + if dropdown:GetValue() then + if AprRCData.CurrentRoute.name ~= "" then + AprRC:UpdateRouteByName(AprRCData.CurrentRoute.name, AprRCData.CurrentRoute) + local route = AprRCData.Routes[dropdown:GetValue()] + if route then + editbox:SetText(AprRC:RouteToString(route.steps)) + AutoScrollToBottom() + end + end + end + end, 5) + end + end + + local function StopAutoRefresh() + if refreshTimer then + AceTimer:CancelTimer(refreshTimer) + refreshTimer = nil + end + end + local btnExportELT = AceGUI:Create("Button") btnExportELT:SetText("Export Extra Line Text") @@ -95,7 +106,6 @@ function AprRC.export:Show() local routeText = editbox:GetText() local newStepRouteTable = AprRC:stringToTable(routeText) if not newStepRouteTable then - UIErrorsFrame:AddMessage("Route not saved, incorrect format", 1, 0, 0, 1, 5) AprRC:Error("Route not saved, incorrect format") return end @@ -104,6 +114,7 @@ function AprRC.export:Show() if AprRCData.CurrentRoute.name == selectedRouteName then AprRCData.CurrentRoute = newRoute end + AutoScrollToBottom() end) frame:AddChild(btnSave) @@ -116,6 +127,7 @@ function AprRC.export:Show() APRData.CustomRoute[name] = route.steps APR.RouteQuestStepList[name] = route.steps APR.RouteList.Custom[name] = name:match("%d+-(.*)") + AutoScrollToBottom() end) frame:AddChild(exportToAPRBtn) @@ -139,6 +151,7 @@ function AprRC.export:Show() end dropdown:SetValue(defaultIndex) editbox:SetText(AprRC:RouteToString(AprRCData.Routes[defaultIndex].steps)) + AutoScrollToBottom() end dropdown:SetCallback("OnValueChanged", function(widget, event, index) @@ -153,6 +166,7 @@ function AprRC.export:Show() checkbox:SetValue(false) StopAutoRefresh() end + AutoScrollToBottom() end end) diff --git a/frames/fillersSelection.lua b/frames/fillersSelection.lua deleted file mode 100644 index afe9fab..0000000 --- a/frames/fillersSelection.lua +++ /dev/null @@ -1,93 +0,0 @@ -local _G = _G -local AceGUI = LibStub("AceGUI-3.0") - -AprRC.fillers = AprRC:NewModule('Fillers') - -function AprRC.fillers:Show() - local frame = AceGUI:Create("Frame") - frame:SetTitle("Fillers quest list") - frame:SetStatusText("Click on an objective to add it as a filler") - frame:SetCallback("OnClose", function(widget) AceGUI:Release(widget) end) - frame:SetWidth(800) - frame:SetHeight(600) - frame:EnableResize(true) - frame:SetLayout("Fill") - - local scrollFrame = AceGUI:Create("ScrollFrame") - scrollFrame:SetFullWidth(true) - scrollFrame:SetFullHeight(true) - scrollFrame:SetLayout("Flow") - - local questList = AprRC.fillers:GetQuestList() - - for _, quest in ipairs(questList) do - local questGroup = AceGUI:Create("InlineGroup") - questGroup:SetFullWidth(true) - questGroup:SetTitle(quest.title) - questGroup:SetLayout("List") - - for i, objective in ipairs(quest.objectives) do - local objectiveLabel = AceGUI:Create("InteractiveLabel") - objectiveLabel:SetText("[" .. objective.objectiveID .. "]" .. " - " .. objective.text) - objectiveLabel:SetFullWidth(true) - objectiveLabel:SetCallback("OnClick", function() - local currentStep = AprRC:GetLastStep() - if not currentStep.Fillers then - currentStep.Fillers = {} - end - if not currentStep.Fillers[quest.questID] then - currentStep.Fillers[quest.questID] = {} - end - tinsert(currentStep.Fillers[quest.questID], objective.objectiveID) - print("|cff00bfffFillers - [" .. quest.title .. "] - " .. objective.objectiveID .. "|r Added") - AceGUI:Release(frame) - end) - questGroup:AddChild(objectiveLabel) - if i < #quest.objectives then - local spacer = AceGUI:Create("Label") - spacer:SetText("") - spacer:SetFullWidth(true) - spacer:SetHeight(10) - questGroup:AddChild(spacer) - end - end - - scrollFrame:AddChild(questGroup) - end - - frame:AddChild(scrollFrame) -end - -function AprRC.fillers:GetQuestList() - local questList = {} - - for i = 1, C_QuestLog.GetNumQuestLogEntries() do - local info = C_QuestLog.GetInfo(i) - if info and not info.isHeader then - local questID = info.questID - local title = C_QuestLog.GetTitleForQuestID(questID) - local isComplete = C_QuestLog.IsComplete(questID) - if not isComplete then - local objectives = C_QuestLog.GetQuestObjectives(questID) - - local formattedObjectives = {} - for j, objective in ipairs(objectives) do - local formattedObjective = { - objectiveID = j, - text = objective.text - } - table.insert(formattedObjectives, formattedObjective) - end - - local questData = { - title = questID .. " - " .. title, - questID = questID, - objectives = formattedObjectives - } - table.insert(questList, questData) - end - end - end - - return questList -end diff --git a/frames/selectButton.lua b/frames/selectButton.lua index 857e006..632677d 100644 --- a/frames/selectButton.lua +++ b/frames/selectButton.lua @@ -17,51 +17,49 @@ function AprRC.SelectButton:Show() buttonGroup:SetLayout("Flow") frame:AddChild(buttonGroup) - local btnItem = AceGUI:Create("Button") - btnItem:SetText("Item") - btnItem:SetFullWidth(true) - btnItem:SetCallback("OnClick", function() - AceGUI:Release(frame) - AprRC.questionDialog:CreateEditBoxPopupWithCallback("QuestID for the button (ID)", function(questID) - C_Timer.After(0.2, function() - AprRC.questionDialog:CreateEditBoxPopupWithCallback("Objective index of the quest", function(index) - C_Timer.After(0.2, function() - AprRC.questionDialog:CreateEditBoxPopupWithCallback("Item Button (ID)", function(itemID) - local currentStep = AprRC:GetLastStep() - if not currentStep.Button then - currentStep.Button = {} - end - currentStep.Button[questID .. "-" .. index] = tonumber(itemID, 10) - print("|cff00bfff Button |r Added") - end) - end) - end) - end) + local function AddButton(text, callback) + local button = AceGUI:Create("Button") + button:SetText(text) + button:SetFullWidth(true) + button:SetCallback("OnClick", function() + AceGUI:Release(frame) + callback() end) - end) - buttonGroup:AddChild(btnItem) + buttonGroup:AddChild(button) + end + + AddButton("Item", function() self:ShowQuestSelector("Item") end) + AddButton("Spell", function() self:ShowQuestSelector("Spell") end) +end - local btnSpell = AceGUI:Create("Button") - btnSpell:SetText("Spell") - btnSpell:SetFullWidth(true) - btnSpell:SetCallback("OnClick", function() - AceGUI:Release(frame) - AprRC.questionDialog:CreateEditBoxPopupWithCallback("QuestID for the button (ID)", function(questID) - C_Timer.After(0.2, function() - AprRC.questionDialog:CreateEditBoxPopupWithCallback("Objective index of the quest", function(index) - C_Timer.After(0.2, function() - AprRC.questionDialog:CreateEditBoxPopupWithCallback("Spell Button (ID)", function(spellID) - local currentStep = AprRC:GetLastStep() - if not currentStep.SpellButton then - currentStep.SpellButton = {} - end - currentStep.SpellButton[questID .. "-" .. index] = tonumber(spellID, 10) - print("|cff00bfff SpellButton |r Added") - end) - end) - end) +function AprRC.SelectButton:ShowQuestSelector(type) + local questList = AprRC.QuestObjectiveSelector:GetQuestListFromLastStep() + if #questList == 0 then + AprRC:Error("No Qpart or Filler quests available on your last step") + return + end + + local callback + if type == "Item" then + callback = function(questID, objectiveID) + AprRC.autocomplete:ShowItemAutoComplete(questID, objectiveID) + end + elseif type == "Spell" then + callback = function(questID, objectiveID) + AprRC.autocomplete:ShowSpellAutoComplete(questID, objectiveID, function(_, spellID, frame) + local currentStep = AprRC:GetLastStep() + if not currentStep.SpellButton then + currentStep.SpellButton = {} + end + currentStep.SpellButton[questID .. "-" .. objectiveID] = tonumber(spellID, 10) + print("|cff00bfff SpellButton |r Added") + AceGUI:Release(frame) end) - end) - end) - buttonGroup:AddChild(btnSpell) + end + end + + AprRC.QuestObjectiveSelector:Show({ + questList = questList, + onClick = callback + }) end diff --git a/helper/RouteManagement.lua b/helper/RouteManagement.lua index cee3bce..5b84c17 100644 --- a/helper/RouteManagement.lua +++ b/helper/RouteManagement.lua @@ -28,6 +28,7 @@ function AprRC:NewStep(step) if AprRC:IsTableEmpty(lastStep) then AprRCData.CurrentRoute.steps = {} end + step._index = #AprRCData.CurrentRoute.steps + 1 tinsert(AprRCData.CurrentRoute.steps, step) end diff --git a/helper/Utils.lua b/helper/Utils.lua index ceba924..210755d 100644 --- a/helper/Utils.lua +++ b/helper/Utils.lua @@ -21,6 +21,7 @@ function AprRC:Error(errorMessage, data) else DEFAULT_CHAT_FRAME:AddMessage(redColorCode .. L["ERROR"] .. ": " .. errorMessage .. "|r") end + UIErrorsFrame:AddMessage(errorMessage, 1, 0, 0, 1, 5) end end @@ -39,6 +40,19 @@ function AprRC:Contains(list, x) return false end +function AprRC:DeepCompare(t1, t2) + if t1 == t2 then return true end + if type(t1) ~= "table" or type(t2) ~= "table" then return false end + for k1, v1 in pairs(t1) do + local v2 = t2[k1] + if v2 == nil or not deepCompare(v1, v2) then return false end + end + for k2, v2 in pairs(t2) do + if t1[k2] == nil then return false end + end + return true +end + function AprRC:IsTableEmpty(table) if (table) then return next(table) == nil @@ -124,7 +138,7 @@ local function qpartTableToString(tbl, level, parrentKey) for _, k in ipairs(keys) do local v = tbl[k] local keyStr = '' - if parrentKey == "Button" then + if parrentKey == "Button" or parrentKey == "SpellButton" then keyStr = '["' .. tostring(k) .. '"] = ' else keyStr = "[" .. k .. "] = " @@ -162,7 +176,7 @@ function AprRC:RouteToString(tbl, level) str = str .. itemIndent .. keyStr .. "{}" .. ",\n" else local valueStr - if k == "Qpart" or k == "Fillers" or k == "Button" then + if k == "Qpart" or k == "Fillers" or k == "Button" or k == "SpellButton" then valueStr = qpartTableToString(v, level + 1, k) else valueStr = self:RouteToString(v, level + 1) diff --git a/libs/AceComm-3.0/AceComm-3.0.lua b/libs/AceComm-3.0/AceComm-3.0.lua index 3f21f5b..1fc7a37 100644 --- a/libs/AceComm-3.0/AceComm-3.0.lua +++ b/libs/AceComm-3.0/AceComm-3.0.lua @@ -9,7 +9,7 @@ -- make into AceComm. -- @class file -- @name AceComm-3.0 --- @release $Id: AceComm-3.0.lua 1284 2022-09-25 09:15:30Z nevcairiel $ +-- @release $Id: AceComm-3.0.lua 1333 2024-05-05 16:24:39Z nevcairiel $ --[[ AceComm-3.0 @@ -20,7 +20,7 @@ TODO: Time out old data rotting around from dead senders? Not a HUGE deal since local CallbackHandler = LibStub("CallbackHandler-1.0") local CTL = assert(ChatThrottleLib, "AceComm-3.0 requires ChatThrottleLib") -local MAJOR, MINOR = "AceComm-3.0", 12 +local MAJOR, MINOR = "AceComm-3.0", 14 local AceComm,oldminor = LibStub:NewLibrary(MAJOR, MINOR) if not AceComm then return end @@ -93,12 +93,12 @@ function AceComm:SendCommMessage(prefix, text, distribution, target, prio, callb local textlen = #text local maxtextlen = 255 -- Yes, the max is 255 even if the dev post said 256. I tested. Char 256+ get silently truncated. /Mikk, 20110327 - local queueName = prefix..distribution..(target or "") + local queueName = prefix local ctlCallback = nil if callbackFn then - ctlCallback = function(sent) - return callbackFn(callbackArg, sent, textlen) + ctlCallback = function(sent, sendResult) + return callbackFn(callbackArg, sent, textlen, sendResult) end end diff --git a/libs/AceComm-3.0/ChatThrottleLib.lua b/libs/AceComm-3.0/ChatThrottleLib.lua index d1dd8a0..688d318 100644 --- a/libs/AceComm-3.0/ChatThrottleLib.lua +++ b/libs/AceComm-3.0/ChatThrottleLib.lua @@ -23,7 +23,7 @@ -- LICENSE: ChatThrottleLib is released into the Public Domain -- -local CTL_VERSION = 24 +local CTL_VERSION = 29 local _G = _G @@ -74,9 +74,7 @@ local math_max = math.max local next = next local strlen = string.len local GetFramerate = GetFramerate -local strlower = string.lower local unpack,type,pairs,wipe = unpack,type,pairs,table.wipe -local UnitInRaid,UnitInParty = UnitInRaid,UnitInParty ----------------------------------------------------------------------- @@ -115,6 +113,23 @@ function Ring:Remove(obj) end end +-- Note that this is local because there's no upgrade logic for existing ring +-- metatables, and this isn't present on rings created in versions older than +-- v25. +local function Ring_Link(self, other) -- Move and append all contents of another ring to this ring + if not self.pos then + -- This ring is empty, so just transfer ownership. + self.pos = other.pos + other.pos = nil + elseif other.pos then + -- Our tail should point to their head, and their tail to our head. + self.pos.prev.next, other.pos.prev.next = other.pos, self.pos + -- Our head should point to their tail, and their head to our tail. + self.pos.prev, other.pos.prev = other.pos.prev, self.pos.prev + other.pos = nil + end +end + ----------------------------------------------------------------------- @@ -179,6 +194,13 @@ function ChatThrottleLib:Init() self.Prio["BULK"] = { ByName = {}, Ring = Ring:New(), avail = 0 } end + if not self.BlockedQueuesDelay then + -- v25: Add blocked queues to rings to handle new client throttles. + for _, Prio in pairs(self.Prio) do + Prio.Blocked = Ring:New() + end + end + -- v4: total send counters per priority for _, Prio in pairs(self.Prio) do Prio.nTotalSent = Prio.nTotalSent or 0 @@ -201,6 +223,7 @@ function ChatThrottleLib:Init() self.Frame:SetScript("OnEvent", self.OnEvent) -- v11: Monitor P_E_W so we can throttle hard for a few seconds self.Frame:RegisterEvent("PLAYER_ENTERING_WORLD") self.OnUpdateDelay = 0 + self.BlockedQueuesDelay = 0 self.LastAvailUpdate = GetTime() self.HardThrottlingBeginTime = GetTime() -- v11: Throttle hard for a few seconds after startup @@ -213,16 +236,27 @@ function ChatThrottleLib:Init() return ChatThrottleLib.Hook_SendChatMessage(...) end) --SendAddonMessage - if _G.C_ChatInfo then - hooksecurefunc(_G.C_ChatInfo, "SendAddonMessage", function(...) - return ChatThrottleLib.Hook_SendAddonMessage(...) - end) - else - hooksecurefunc("SendAddonMessage", function(...) - return ChatThrottleLib.Hook_SendAddonMessage(...) - end) - end + hooksecurefunc(_G.C_ChatInfo, "SendAddonMessage", function(...) + return ChatThrottleLib.Hook_SendAddonMessage(...) + end) + end + + -- v26: Hook SendAddonMessageLogged for traffic logging + if not self.securelyHookedLogged then + self.securelyHookedLogged = true + hooksecurefunc(_G.C_ChatInfo, "SendAddonMessageLogged", function(...) + return ChatThrottleLib.Hook_SendAddonMessageLogged(...) + end) + end + + -- v29: Hook BNSendGameData for traffic logging + if not self.securelyHookedBNGameData then + self.securelyHookedBNGameData = true + hooksecurefunc("BNSendGameData", function(...) + return ChatThrottleLib.Hook_BNSendGameData(...) + end) end + self.nBypass = 0 end @@ -251,6 +285,12 @@ function ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype, destinati self.avail = self.avail - size self.nBypass = self.nBypass + size -- just a statistic end +function ChatThrottleLib.Hook_SendAddonMessageLogged(prefix, text, chattype, destination, ...) + ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype, destination, ...) +end +function ChatThrottleLib.Hook_BNSendGameData(destination, prefix, text) + ChatThrottleLib.Hook_SendAddonMessage(prefix, text, "WHISPER", destination) +end @@ -292,38 +332,89 @@ end -- - ... made up of N "Pipe"s (1 for each destination/pipename) -- - and each pipe contains messages +local SendAddonMessageResult = Enum.SendAddonMessageResult or { + Success = 0, + AddonMessageThrottle = 3, + NotInGroup = 5, + ChannelThrottle = 8, + GeneralError = 9, +} + +local function MapToSendResult(ok, ...) + local result + + if not ok then + -- The send function itself errored; don't look at anything else. + result = SendAddonMessageResult.GeneralError + else + -- Grab the last return value from the send function and remap + -- it from a boolean to an enum code. If there are no results, + -- assume success (true). + + result = select(-1, true, ...) + + if result == true then + result = SendAddonMessageResult.Success + elseif result == false then + result = SendAddonMessageResult.GeneralError + end + end + + return result +end + +local function IsThrottledSendResult(result) + return result == SendAddonMessageResult.AddonMessageThrottle +end + +-- A copy of this function exists in FrameXML, but for clarity it's here too. +local function CallErrorHandler(...) + return geterrorhandler()(...) +end + +local function PerformSend(sendFunction, ...) + bMyTraffic = true + local sendResult = MapToSendResult(xpcall(sendFunction, CallErrorHandler, ...)) + bMyTraffic = false + return sendResult +end + function ChatThrottleLib:Despool(Prio) local ring = Prio.Ring while ring.pos and Prio.avail > ring.pos[1].nSize do - local msg = table_remove(ring.pos, 1) - if not ring.pos[1] then -- did we remove last msg in this pipe? - local pipe = Prio.Ring.pos + local pipe = ring.pos + local msg = pipe[1] + local sendResult = PerformSend(msg.f, unpack(msg, 1, msg.n)) + + if IsThrottledSendResult(sendResult) then + -- Message was throttled; move the pipe into the blocked ring. Prio.Ring:Remove(pipe) - Prio.ByName[pipe.name] = nil - DelPipe(pipe) - else - Prio.Ring.pos = Prio.Ring.pos.next - end - local didSend=false - local lowerDest = strlower(msg[3] or "") - if lowerDest == "raid" and not UnitInRaid("player") then - -- do nothing - elseif lowerDest == "party" and not UnitInParty("player") then - -- do nothing + Prio.Blocked:Add(pipe) else - Prio.avail = Prio.avail - msg.nSize - bMyTraffic = true - msg.f(unpack(msg, 1, msg.n)) - bMyTraffic = false - Prio.nTotalSent = Prio.nTotalSent + msg.nSize + -- Dequeue message after submission. + table_remove(pipe, 1) DelMsg(msg) - didSend = true - end - -- notify caller of delivery (even if we didn't send it) - if msg.callbackFn then - msg.callbackFn (msg.callbackArg, didSend) + + if not pipe[1] then -- did we remove last msg in this pipe? + Prio.Ring:Remove(pipe) + Prio.ByName[pipe.name] = nil + DelPipe(pipe) + else + ring.pos = ring.pos.next + end + + -- Update bandwidth counters on successful sends. + local didSend = (sendResult == SendAddonMessageResult.Success) + if didSend then + Prio.avail = Prio.avail - msg.nSize + Prio.nTotalSent = Prio.nTotalSent + msg.nSize + end + + -- Notify caller of message submission. + if msg.callbackFn then + securecallfunction(msg.callbackFn, msg.callbackArg, didSend, sendResult) + end end - -- USER CALLBACK MAY ERROR end end @@ -342,6 +433,7 @@ function ChatThrottleLib.OnUpdate(this,delay) local self = ChatThrottleLib self.OnUpdateDelay = self.OnUpdateDelay + delay + self.BlockedQueuesDelay = self.BlockedQueuesDelay + delay if self.OnUpdateDelay < 0.08 then return end @@ -353,40 +445,60 @@ function ChatThrottleLib.OnUpdate(this,delay) return -- argh. some bastard is spewing stuff past the lib. just bail early to save cpu. end - -- See how many of our priorities have queued messages (we only have 3, don't worry about the loop) - local n = 0 - for prioname,Prio in pairs(self.Prio) do - if Prio.Ring.pos or Prio.avail < 0 then - n = n + 1 + -- Integrate blocked queues back into their rings periodically. + if self.BlockedQueuesDelay >= 0.35 then + for _, Prio in pairs(self.Prio) do + Ring_Link(Prio.Ring, Prio.Blocked) end + + self.BlockedQueuesDelay = 0 end - -- Anything queued still? - if n<1 then - -- Nope. Move spillover bandwidth to global availability gauge and clear self.bQueueing - for prioname, Prio in pairs(self.Prio) do + -- See how many of our priorities have queued messages. This is split + -- into two counters because priorities that consist only of blocked + -- queues must keep our OnUpdate alive, but shouldn't count toward + -- bandwidth distribution. + local nSendablePrios = 0 + local nBlockedPrios = 0 + + for prioname, Prio in pairs(self.Prio) do + if Prio.Ring.pos then + nSendablePrios = nSendablePrios + 1 + elseif Prio.Blocked.pos then + nBlockedPrios = nBlockedPrios + 1 + end + + -- Collect unused bandwidth from priorities with nothing to send. + if not Prio.Ring.pos then self.avail = self.avail + Prio.avail Prio.avail = 0 end - self.bQueueing = false - self.Frame:Hide() + end + + -- Bandwidth reclamation may take us back over the burst cap. + self.avail = math_min(self.avail, self.BURST) + + -- If we can't currently send on any priorities, stop processing early. + if nSendablePrios == 0 then + -- If we're completely out of data to send, disable queue processing. + if nBlockedPrios == 0 then + self.bQueueing = false + self.Frame:Hide() + end + return end -- There's stuff queued. Hand out available bandwidth to priorities as needed and despool their queues - local avail = self.avail/n + local avail = self.avail / nSendablePrios self.avail = 0 for prioname, Prio in pairs(self.Prio) do - if Prio.Ring.pos or Prio.avail < 0 then + if Prio.Ring.pos then Prio.avail = Prio.avail + avail - if Prio.Ring.pos and Prio.avail > Prio.Ring.pos[1].nSize then - self:Despool(Prio) - -- Note: We might not get here if the user-supplied callback function errors out! Take care! - end + self:Despool(Prio) end end - end @@ -429,16 +541,22 @@ function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, languag -- Check if there's room in the global available bandwidth gauge to send directly if not self.bQueueing and nSize < self:UpdateAvail() then - self.avail = self.avail - nSize - bMyTraffic = true - _G.SendChatMessage(text, chattype, language, destination) - bMyTraffic = false - self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize - if callbackFn then - callbackFn (callbackArg, true) + local sendResult = PerformSend(_G.SendChatMessage, text, chattype, language, destination) + + if not IsThrottledSendResult(sendResult) then + local didSend = (sendResult == SendAddonMessageResult.Success) + + if didSend then + self.avail = self.avail - nSize + self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize + end + + if callbackFn then + securecallfunction(callbackFn, callbackArg, didSend, sendResult) + end + + return end - -- USER CALLBACK MAY ERROR - return end -- Message needs to be queued @@ -453,54 +571,36 @@ function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, languag msg.callbackFn = callbackFn msg.callbackArg = callbackArg - self:Enqueue(prio, queueName or (prefix..(chattype or "SAY")..(destination or "")), msg) + self:Enqueue(prio, queueName or prefix, msg) end -function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) - if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then - error('Usage: ChatThrottleLib:SendAddonMessage("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2) - end - if callbackFn and type(callbackFn)~="function" then - error('ChatThrottleLib:SendAddonMessage(): callbackFn: expected function, got '..type(callbackFn), 2) - end +local function SendAddonMessageInternal(self, sendFunction, prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) + local nSize = #text + self.MSG_OVERHEAD - local nSize = text:len(); + -- Check if there's room in the global available bandwidth gauge to send directly + if not self.bQueueing and nSize < self:UpdateAvail() then + local sendResult = PerformSend(sendFunction, prefix, text, chattype, target) - if C_ChatInfo or RegisterAddonMessagePrefix then - if nSize>255 then - error("ChatThrottleLib:SendAddonMessage(): message length cannot exceed 255 bytes", 2) - end - else - nSize = nSize + prefix:len() + 1 - if nSize>255 then - error("ChatThrottleLib:SendAddonMessage(): prefix + message length cannot exceed 254 bytes", 2) - end - end + if not IsThrottledSendResult(sendResult) then + local didSend = (sendResult == SendAddonMessageResult.Success) + + if didSend then + self.avail = self.avail - nSize + self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize + end - nSize = nSize + self.MSG_OVERHEAD; + if callbackFn then + securecallfunction(callbackFn, callbackArg, didSend, sendResult) + end - -- Check if there's room in the global available bandwidth gauge to send directly - if not self.bQueueing and nSize < self:UpdateAvail() then - self.avail = self.avail - nSize - bMyTraffic = true - if _G.C_ChatInfo then - _G.C_ChatInfo.SendAddonMessage(prefix, text, chattype, target) - else - _G.SendAddonMessage(prefix, text, chattype, target) - end - bMyTraffic = false - self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize - if callbackFn then - callbackFn (callbackArg, true) + return end - -- USER CALLBACK MAY ERROR - return end -- Message needs to be queued local msg = NewMsg() - msg.f = _G.C_ChatInfo and _G.C_ChatInfo.SendAddonMessage or _G.SendAddonMessage + msg.f = sendFunction msg[1] = prefix msg[2] = text msg[3] = chattype @@ -510,10 +610,64 @@ function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target, msg.callbackFn = callbackFn msg.callbackArg = callbackArg - self:Enqueue(prio, queueName or (prefix..chattype..(target or "")), msg) + self:Enqueue(prio, queueName or prefix, msg) +end + + +function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) + if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then + error('Usage: ChatThrottleLib:SendAddonMessage("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2) + elseif callbackFn and type(callbackFn)~="function" then + error('ChatThrottleLib:SendAddonMessage(): callbackFn: expected function, got '..type(callbackFn), 2) + elseif #text>255 then + error("ChatThrottleLib:SendAddonMessage(): message length cannot exceed 255 bytes", 2) + end + + local sendFunction = _G.C_ChatInfo.SendAddonMessage + SendAddonMessageInternal(self, sendFunction, prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) end +function ChatThrottleLib:SendAddonMessageLogged(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) + if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then + error('Usage: ChatThrottleLib:SendAddonMessageLogged("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2) + elseif callbackFn and type(callbackFn)~="function" then + error('ChatThrottleLib:SendAddonMessageLogged(): callbackFn: expected function, got '..type(callbackFn), 2) + elseif #text>255 then + error("ChatThrottleLib:SendAddonMessageLogged(): message length cannot exceed 255 bytes", 2) + end + + local sendFunction = _G.C_ChatInfo.SendAddonMessageLogged + SendAddonMessageInternal(self, sendFunction, prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) +end + +local function BNSendGameDataReordered(prefix, text, _, gameAccountID) + return _G.BNSendGameData(gameAccountID, prefix, text) +end + +function ChatThrottleLib:BNSendGameData(prio, prefix, text, chattype, gameAccountID, queueName, callbackFn, callbackArg) + -- Note that this API is intentionally limited to 255 bytes of data + -- for reasons of traffic fairness, which is less than the 4078 bytes + -- BNSendGameData natively supports. Additionally, a chat type is required + -- but must always be set to 'WHISPER' to match what is exposed by the + -- receipt event. + -- + -- If splitting messages, callers must also be aware that message + -- delivery over BNSendGameData is unordered. + + if not self or not prio or not prefix or not text or not gameAccountID or not chattype or not self.Prio[prio] then + error('Usage: ChatThrottleLib:BNSendGameData("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype", gameAccountID)', 2) + elseif callbackFn and type(callbackFn)~="function" then + error('ChatThrottleLib:BNSendGameData(): callbackFn: expected function, got '..type(callbackFn), 2) + elseif #text>255 then + error("ChatThrottleLib:BNSendGameData(): message length cannot exceed 255 bytes", 2) + elseif chattype ~= "WHISPER" then + error("ChatThrottleLib:BNSendGameData(): chat type must be 'WHISPER'", 2) + end + + local sendFunction = BNSendGameDataReordered + SendAddonMessageInternal(self, sendFunction, prio, prefix, text, chattype, gameAccountID, queueName, callbackFn, callbackArg) +end ----------------------------------------------------------------------- diff --git a/libs/AceConfig-3.0/AceConfig-3.0.lua b/libs/AceConfig-3.0/AceConfig-3.0.lua index 5071cdc..ab91c9e 100644 --- a/libs/AceConfig-3.0/AceConfig-3.0.lua +++ b/libs/AceConfig-3.0/AceConfig-3.0.lua @@ -3,7 +3,7 @@ -- as well as associate it with a slash command. -- @class file -- @name AceConfig-3.0 --- @release $Id: AceConfig-3.0.lua 1202 2019-05-15 23:11:22Z nevcairiel $ +-- @release $Id: AceConfig-3.0.lua 1335 2024-05-05 19:35:16Z nevcairiel $ --[[ AceConfig-3.0 @@ -27,7 +27,7 @@ if not AceConfig then return end local pcall, error, type, pairs = pcall, error, type, pairs -- ------------------------------------------------------------------- --- :RegisterOptionsTable(appName, options, slashcmd, persist) +-- :RegisterOptionsTable(appName, options, slashcmd) -- -- - appName - (string) application name -- - options - table or function ref, see AceConfigRegistry diff --git a/libs/AceDB-3.0/AceDB-3.0.lua b/libs/AceDB-3.0/AceDB-3.0.lua index a8e306a..1e25429 100644 --- a/libs/AceDB-3.0/AceDB-3.0.lua +++ b/libs/AceDB-3.0/AceDB-3.0.lua @@ -40,8 +40,8 @@ -- end -- @class file -- @name AceDB-3.0.lua --- @release $Id: AceDB-3.0.lua 1306 2023-06-23 14:55:09Z nevcairiel $ -local ACEDB_MAJOR, ACEDB_MINOR = "AceDB-3.0", 28 +-- @release $Id: AceDB-3.0.lua 1328 2024-03-20 22:36:27Z nevcairiel $ +local ACEDB_MAJOR, ACEDB_MINOR = "AceDB-3.0", 29 local AceDB = LibStub:NewLibrary(ACEDB_MAJOR, ACEDB_MINOR) if not AceDB then return end -- No upgrade needed @@ -606,8 +606,8 @@ end -- profile. -- @param defaultProfile The profile name to use as the default function DBObjectLib:ResetDB(defaultProfile) - if defaultProfile and type(defaultProfile) ~= "string" then - error(("Usage: AceDBObject:ResetDB(defaultProfile): 'defaultProfile' - string or nil expected, got %q."):format(type(defaultProfile)), 2) + if defaultProfile and type(defaultProfile) ~= "string" and defaultProfile ~= true then + error(("Usage: AceDBObject:ResetDB(defaultProfile): 'defaultProfile' - string or true expected, got %q."):format(type(defaultProfile)), 2) end local sv = self.sv diff --git a/libs/AceGUI-3.0/widgets/AceGUIWidget-ColorPicker.lua b/libs/AceGUI-3.0/widgets/AceGUIWidget-ColorPicker.lua index 2d12ab7..ec811d0 100644 --- a/libs/AceGUI-3.0/widgets/AceGUIWidget-ColorPicker.lua +++ b/libs/AceGUI-3.0/widgets/AceGUIWidget-ColorPicker.lua @@ -1,7 +1,7 @@ --[[----------------------------------------------------------------------------- ColorPicker Widget -------------------------------------------------------------------------------]] -local Type, Version = "ColorPicker", 27 +local Type, Version = "ColorPicker", 28 local AceGUI = LibStub and LibStub("AceGUI-3.0", true) if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end @@ -11,10 +11,17 @@ local pairs = pairs -- WoW APIs local CreateFrame, UIParent = CreateFrame, UIParent +-- Unfortunately we have no way to realistically detect if a client uses inverted alpha +-- as no API will tell you. Wrath uses the old colorpicker, era uses the new one, both are inverted +local INVERTED_ALPHA = (WOW_PROJECT_ID ~= WOW_PROJECT_MAINLINE) + --[[----------------------------------------------------------------------------- Support functions -------------------------------------------------------------------------------]] local function ColorCallback(self, r, g, b, a, isAlpha) + if INVERTED_ALPHA and a then + a = 1 - a + end if not self.HasAlpha then a = 1 end @@ -53,10 +60,12 @@ local function ColorSwatch_OnClick(frame) ColorPickerFrame:SetFrameStrata("FULLSCREEN_DIALOG") ColorPickerFrame:SetFrameLevel(frame:GetFrameLevel() + 10) ColorPickerFrame:SetClampedToScreen(true) - ColorPickerFrame:EnableMouse(true) -- Make sure the background isn't click-through if ColorPickerFrame.SetupColorPickerAndShow then -- 10.2.5 color picker overhaul - local r2, g2, b2, a2 = self.r, self.g, self.b, self.a + local r2, g2, b2, a2 = self.r, self.g, self.b, (self.a or 1) + if INVERTED_ALPHA then + a2 = 1 - a2 + end local info = { swatchFunc = function() @@ -71,7 +80,7 @@ local function ColorSwatch_OnClick(frame) local a = ColorPickerFrame:GetColorAlpha() ColorCallback(self, r, g, b, a, true) end, - opacity = (a2 or 1), + opacity = a2, cancelFunc = function() ColorCallback(self, r2, g2, b2, a2, true) @@ -86,20 +95,20 @@ local function ColorSwatch_OnClick(frame) else ColorPickerFrame.func = function() local r, g, b = ColorPickerFrame:GetColorRGB() - local a = 1 - OpacitySliderFrame:GetValue() + local a = OpacitySliderFrame:GetValue() ColorCallback(self, r, g, b, a) end ColorPickerFrame.hasOpacity = self.HasAlpha ColorPickerFrame.opacityFunc = function() local r, g, b = ColorPickerFrame:GetColorRGB() - local a = 1 - OpacitySliderFrame:GetValue() + local a = OpacitySliderFrame:GetValue() ColorCallback(self, r, g, b, a, true) end - local r, g, b, a = self.r, self.g, self.b, self.a + local r, g, b, a = self.r, self.g, self.b, 1 - (self.a or 1) if self.HasAlpha then - ColorPickerFrame.opacity = 1 - (a or 0) + ColorPickerFrame.opacity = a end ColorPickerFrame:SetColorRGB(r, g, b) diff --git a/libs/AceTimer-3.0/AceTimer-3.0.lua b/libs/AceTimer-3.0/AceTimer-3.0.lua index 8776da2..78fb4ed 100644 --- a/libs/AceTimer-3.0/AceTimer-3.0.lua +++ b/libs/AceTimer-3.0/AceTimer-3.0.lua @@ -15,7 +15,7 @@ -- make into AceTimer. -- @class file -- @name AceTimer-3.0 --- @release $Id: AceTimer-3.0.lua 1284 2022-09-25 09:15:30Z nevcairiel $ +-- @release $Id: AceTimer-3.0.lua 1342 2024-05-26 11:49:35Z nevcairiel $ local MAJOR, MINOR = "AceTimer-3.0", 17 -- Bump minor on changes local AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR) @@ -78,7 +78,7 @@ end --- Schedule a new one-shot timer. -- The timer will fire once in `delay` seconds, unless canceled before. --- @param callback Callback function for the timer pulse (funcref or method name). +-- @param func Callback function for the timer pulse (funcref or method name). -- @param delay Delay for the timer, in seconds. -- @param ... An optional, unlimited amount of arguments to pass to the callback function. -- @usage @@ -107,7 +107,7 @@ end --- Schedule a repeating timer. -- The timer will fire every `delay` seconds, until canceled. --- @param callback Callback function for the timer pulse (funcref or method name). +-- @param func Callback function for the timer pulse (funcref or method name). -- @param delay Delay for the timer, in seconds. -- @param ... An optional, unlimited amount of arguments to pass to the callback function. -- @usage