diff --git a/src/Classes/BuildListControl.lua b/src/Classes/BuildListControl.lua index a09fa00a683..ae4411a5516 100644 --- a/src/Classes/BuildListControl.lua +++ b/src/Classes/BuildListControl.lua @@ -7,12 +7,28 @@ local ipairs = ipairs local s_format = string.format local buildListHelpers = require("Modules.BuildListHelpers") +---@class BuildListEntry +---@field subPath string +---@field fullFileName string +---@field fileName? string +---@field folderName? string +---@field buildName? string +---@field className? string +---@field ascendClassName? string +---@field level? integer + +---@class BuildListMode: ControlHost +---@field subPath string +---@field list BuildListEntry[] + ---@class BuildListControl: ListControl +---@field listMode BuildListMode local BuildListClass = newClass("BuildListControl", "ListControl") ----@param anchor Anchor? ----@param rect Rect? ----@param listMode any +---@param anchor? Anchor +---@param rect? Rect +---@param listMode BuildListMode +---@return BuildListControl function BuildListClass:BuildListControl(anchor, rect, listMode) self:ListControl(anchor, rect, 20, "VERTICAL", false, listMode.list) self.listMode = listMode @@ -30,9 +46,15 @@ function BuildListClass:BuildListControl(anchor, rect, listMode) self.selDragActive = false self.otherDragSource = false end) + ---@param type string + ---@param build BuildListEntry + ---@return boolean function self.controls.path:CanReceiveDrag(type, build) return type == "Build" and #self.folderList > 1 end + ---@param type string + ---@param build BuildListEntry + ---@param source ListControl function self.controls.path:ReceiveDrag(type, build, source) if type == "Build" then for index, folder in ipairs(self.folderList) do @@ -62,6 +84,7 @@ function BuildListClass:BuildListControl(anchor, rect, listMode) return self end +---@param fullFileName string function BuildListClass:SelByFullFileName(fullFileName) if fullFileName then for index, build in ipairs(self.list) do @@ -75,6 +98,7 @@ function BuildListClass:SelByFullFileName(fullFileName) self.selValue = nil end +---@param build BuildListEntry function BuildListClass:LoadBuild(build) if build.folderName then self.controls.path:SetSubPath(build.subPath .. build.folderName .. "/") @@ -92,6 +116,8 @@ function BuildListClass:NewFolder() end) end +---@param build BuildListEntry +---@param copyOnName? boolean function BuildListClass:RenameBuild(build, copyOnName) local controls = { } controls.label = new("LabelControl"):LabelControl(nil, {0, 20, 0, 16}, "^7Enter the new name for this "..(build.folderName and "folder:" or "build:")) @@ -159,6 +185,7 @@ function BuildListClass:RenameBuild(build, copyOnName) main:OpenPopup(370, 100, (copyOnName and "Copy " or "Rename ")..(build.folderName and "Folder" or "Build"), controls, "save", "edit") end +---@param build BuildListEntry function BuildListClass:DeleteBuild(build) if build.folderName then if NewFileSearch(build.fullFileName.."/*") or NewFileSearch(build.fullFileName.."/*", true) then @@ -188,6 +215,10 @@ function BuildListClass:DeleteBuild(build) end end +---@param column integer +---@param index integer +---@param build BuildListEntry +---@return string? function BuildListClass:GetRowValue(column, index, build) if column == 1 then local label @@ -223,14 +254,24 @@ function BuildListClass:GetRowValue(column, index, build) end end +---@param index integer +---@param build BuildListEntry +---@return string +---@return BuildListEntry function BuildListClass:GetDragValue(index, build) return "Build", build end +---@param type string +---@param build BuildListEntry +---@return boolean function BuildListClass:CanReceiveDrag(type, build) return type == "Build" end +---@param type string +---@param build BuildListEntry +---@param source? ListControl function BuildListClass:ReceiveDrag(type, build, source) if type == "Build" then if self.hoverValue and self.hoverValue.folderName then @@ -252,10 +293,17 @@ function BuildListClass:ReceiveDrag(type, build, source) end end +---@param index integer +---@param build BuildListEntry +---@param source? ListControl +---@return boolean function BuildListClass:CanDragToValue(index, build, source) return build.folderName and source.selValue ~= build and buildListHelpers.CanMoveToSubPath(source.selValue, build.subPath .. build.folderName .. "/") end +---@param index integer +---@param build BuildListEntry +---@param doubleClick? boolean function BuildListClass:OnSelClick(index, build, doubleClick) if doubleClick then self:LoadBuild(build) @@ -264,20 +312,29 @@ function BuildListClass:OnSelClick(index, build, doubleClick) end end +---@param index integer +---@param build BuildListEntry function BuildListClass:OnSelCopy(index, build) self.copyBuild = build self.cutBuild = nil end +---@param index integer +---@param build BuildListEntry function BuildListClass:OnSelCut(index, build) self.copyBuild = nil self.cutBuild = build end +---@param index integer +---@param build BuildListEntry function BuildListClass:OnSelDelete(index, build) self:DeleteBuild(build) end +---@param index integer +---@param build BuildListEntry +---@param key string function BuildListClass:OnSelKeyDown(index, build, key) if key == "RETURN" then self:LoadBuild(build) diff --git a/src/Classes/ButtonControl.lua b/src/Classes/ButtonControl.lua index e231280f5ec..04f642dedd4 100644 --- a/src/Classes/ButtonControl.lua +++ b/src/Classes/ButtonControl.lua @@ -4,8 +4,23 @@ -- Basic button control. -- ---@class ButtonControl: Control, TooltipHost +---@field label Prop +---@field onClick fun(): Control? +---@field onHover? fun(): Control? +---@field forceTooltip? boolean +---@field image? ImageHandle +---@field clicked? boolean +---@field locked? Prop +---@field enterFunc? fun() local ButtonClass = newClass("ButtonControl", "Control", "TooltipHost") +---@param anchor? Anchor +---@param rect? Rect +---@param label Prop +---@param onClick fun(): Control? +---@param onHover? fun(): Control? +---@param forceTooltip? boolean +---@return ButtonControl function ButtonClass:ButtonControl(anchor, rect, label, onClick, onHover, forceTooltip) self:Control(anchor, rect) self:TooltipHost() @@ -22,6 +37,7 @@ function ButtonClass:Click() end end +---@param path? string function ButtonClass:SetImage(path) if path then self.image = NewImageHandle() @@ -31,6 +47,7 @@ function ButtonClass:SetImage(path) end end +---@return boolean function ButtonClass:IsMouseOver() if not self:IsShown() then return false @@ -38,6 +55,9 @@ function ButtonClass:IsMouseOver() return self:IsMouseInBounds() end +---@param viewPort Rect +---@param noTooltip? boolean +---@return Control? function ButtonClass:Draw(viewPort, noTooltip) local x, y = self:GetPos() local width, height = self:GetSize() @@ -104,6 +124,8 @@ function ButtonClass:Draw(viewPort, noTooltip) end end +---@param key string +---@return ButtonControl? function ButtonClass:OnKeyDown(key) if not self:IsShown() or not self:IsEnabled() then return @@ -116,6 +138,8 @@ function ButtonClass:OnKeyDown(key) return self end +---@param key string +---@return Control? function ButtonClass:OnKeyUp(key) if not self:IsShown() or not self:IsEnabled() then return diff --git a/src/Classes/CalcBreakdownControl.lua b/src/Classes/CalcBreakdownControl.lua index 6db2299accb..09ca478c3dc 100644 --- a/src/Classes/CalcBreakdownControl.lua +++ b/src/Classes/CalcBreakdownControl.lua @@ -14,9 +14,26 @@ local m_pi = math.pi local band = bit.band ---@class CalcBreakdownControl: Control, ControlHost +---@field calcsTab CalcsTab +---@field sourceData? CalcSectionEntry[] +---@field pinned boolean +---@field forceActor? "player"|"minion" +---@field tooltip Tooltip +---@field nodeViewer PassiveTreeView +---@field rangeGuide ImageHandle +---@field uiOverlay ImageHandle +---@field borderThickness integer +---@field pinnedColour number[] +---@field borderColour number[] +---@field sectionList table[] +---@field contentWidth number +---@field contentHeight number +---@field clearDisplayFunc? fun() +---@field envName? string local CalcBreakdownClass = newClass("CalcBreakdownControl", "Control", "ControlHost") ---@param calcsTab CalcsTab +---@return CalcBreakdownControl function CalcBreakdownClass:CalcBreakdownControl(calcsTab) self:Control() self:ControlHost() @@ -39,6 +56,7 @@ function CalcBreakdownClass:CalcBreakdownControl(calcsTab) return self end +---@return boolean|Control? function CalcBreakdownClass:IsMouseOver() if not self:IsShown() then return @@ -46,6 +64,8 @@ function CalcBreakdownClass:IsMouseOver() return self:IsMouseInBounds() or self:GetMouseOverControl() end +---@return Actor? actor +---@return table env function CalcBreakdownClass:GetActor() local env = self.calcsTab[self.envName or "calcsEnv"] local actor = self.calcsTab.input.showMinion and env.minion or env.player @@ -54,9 +74,9 @@ function CalcBreakdownClass:GetActor() end return actor, env end ----@param displayData any ----@param pinned any ----@param forceActor "player"|"minion"|nil +---@param displayData? CalcSectionEntry[] +---@param pinned boolean +---@param forceActor? "player"|"minion" function CalcBreakdownClass:SetBreakdownData(displayData, pinned, forceActor) self.pinned = pinned if displayData == self.sourceData then @@ -144,6 +164,7 @@ function CalcBreakdownClass:SetBreakdownData(displayData, pinned, forceActor) end -- Add sections based on the breakdown data generated by the Calcs module +---@param sectionData CalcSectionEntry function CalcBreakdownClass:AddBreakdownSection(sectionData) local actor = self:GetActor() local breakdown @@ -297,6 +318,8 @@ function CalcBreakdownClass:AddBreakdownSection(sectionData) end -- Add a table section showing a list of modifiers +---@param sectionData CalcSectionEntry +---@param modList? { value: unknown, mod: Mod }[] function CalcBreakdownClass:AddModSection(sectionData, modList) local actor = self:GetActor() local build = self.calcsTab.build @@ -526,18 +549,29 @@ function CalcBreakdownClass:AddModSection(sectionData, modList) end end +---@param modName string +---@return string function CalcBreakdownClass:FormatModName(modName) return modName:gsub("([%l%d]:?)(%u)","%1 %2"):gsub("(%l)(%d)","%1 %2") end +---@param var? string +---@param varList? string[] +---@return string function CalcBreakdownClass:FormatVarNameOrList(var, varList) return var and self:FormatModName(var) or self:FormatModName(table.concat(varList, " / ")) end +---@param mod Mod +---@param base number +---@return string function CalcBreakdownClass:FormatModBase(mod, base) return mod.type == "BASE" and string.format("%+g", math.abs(base)) or math.abs(base).."%" end +---@param value unknown +---@param modType NumericModTypes|string +---@return string function CalcBreakdownClass:FormatModValue(value, modType) if modType == "BASE" then return string.format("%+g base", value) @@ -568,6 +602,10 @@ function CalcBreakdownClass:FormatModValue(value, modType) end end +---@param viewPort Rect +---@param x number +---@param y number +---@param section table function CalcBreakdownClass:DrawBreakdownTable(viewPort, x, y, section) local cursorX, cursorY = GetCursorPos() if section.label then @@ -652,6 +690,11 @@ function CalcBreakdownClass:DrawBreakdownTable(viewPort, x, y, section) end end +---@param x number +---@param y number +---@param width number +---@param height number +---@param radius number function CalcBreakdownClass:DrawRadiusVisual(x, y, width, height, radius) SetDrawColor(0.75, 0.75, 0.75) DrawImage(self.rangeGuide, x, y, width, height) @@ -687,6 +730,7 @@ function CalcBreakdownClass:DrawRadiusVisual(x, y, width, height, radius) DrawImage(self.uiOverlay, x, y, width, height) end +---@param viewPort Rect function CalcBreakdownClass:Draw(viewPort) local sourceData = self.sourceData local scrollBar = self.controls.scrollBar @@ -752,6 +796,9 @@ function CalcBreakdownClass:Draw(viewPort) SetDrawLayer(nil, 0) end +---@param key string +---@param doubleClick? boolean +---@return Control? function CalcBreakdownClass:OnKeyDown(key, doubleClick) if not self:IsShown() or not self:IsEnabled() then return @@ -776,6 +823,8 @@ function CalcBreakdownClass:OnKeyDown(key, doubleClick) return self end +---@param key string +---@return CalcBreakdownControl? function CalcBreakdownClass:OnKeyUp(key) if not self:IsShown() or not self:IsEnabled() then return diff --git a/src/Classes/CalcSectionControl.lua b/src/Classes/CalcSectionControl.lua index 668e742d437..4f51ad08d77 100644 --- a/src/Classes/CalcSectionControl.lua +++ b/src/Classes/CalcSectionControl.lua @@ -8,15 +8,33 @@ local m_max = math.max local m_min = math.min ---@class CalcSectionControl: Control, ControlHost +---@field calcsTab CalcsTab +---@field id string +---@field group integer +---@field colour string +---@field subSection CalcSectionSubsection[] +---@field flag? string +---@field notFlag? string +---@field updateFunc? fun(self: CalcSectionControl) +---@field hasControls? boolean +---@field isOverlay boolean +---@field overlayX number +---@field overlayY number +---@field overlayRevision? integer +---@field overlayBreakdownCell? boolean +---@field dragging boolean +---@field dragOffX number +---@field dragOffY number local CalcSectionClass = newClass("CalcSectionControl", "Control", "ControlHost") ---@param calcsTab CalcsTab ----@param width any ----@param id any ----@param group any ----@param colour any ----@param subSection any ----@param updateFunc any +---@param width number +---@param id string +---@param group integer +---@param colour string +---@param subSection CalcSectionSubsection[] +---@param updateFunc? fun(self: CalcSectionControl) +---@return CalcSectionControl function CalcSectionClass:CalcSectionControl(calcsTab, width, id, group, colour, subSection, updateFunc) self:Control(calcsTab, {0, 0, width, 0}) self:ControlHost() @@ -81,6 +99,8 @@ function CalcSectionClass:CalcSectionControl(calcsTab, width, id, group, colour, return self end +---@return boolean? mouseOver +---@return CalcSectionColumn? hoveredCell function CalcSectionClass:IsMouseOver() if not self:IsShown() then return @@ -184,6 +204,8 @@ function CalcSectionClass:UpdatePos() end end +---@param viewPort Rect +---@param noTooltip? boolean function CalcSectionClass:Draw(viewPort, noTooltip) local x, y = self:GetPos() local width, height = self:GetSize() @@ -198,6 +220,9 @@ function CalcSectionClass:Draw(viewPort, noTooltip) self:DrawContent(x, y, width, actor, viewPort, false, noTooltip) end +---@param key string +---@param doubleClick? boolean +---@return Control? function CalcSectionClass:OnKeyDown(key, doubleClick) if not self:IsShown() or not self:IsEnabled() then return @@ -220,6 +245,8 @@ function CalcSectionClass:OnKeyDown(key, doubleClick) return end +---@param key string +---@return Control? function CalcSectionClass:OnKeyUp(key) if not self:IsShown() or not self:IsEnabled() then return @@ -261,6 +288,9 @@ function CalcSectionClass:RaiseOverlay() end end +---@param cursorX number +---@param cursorY number +---@return boolean function CalcSectionClass:IsMouseInOverlay(cursorX, cursorY) if not self.isOverlay or not self.calcsTab.calcsEnv then return false end local x = self.overlayX @@ -269,6 +299,7 @@ function CalcSectionClass:IsMouseInOverlay(cursorX, cursorY) return cursorY < y + self:GetOverlayHeight() end +---@return number function CalcSectionClass:GetOverlayHeight() local height = 28 local enabled = self.calcsTab.calcsEnv and self.calcsTab:CheckFlag(self) @@ -288,6 +319,9 @@ function CalcSectionClass:GetOverlayHeight() return height end +---@param key string +---@param cursorX number +---@param cursorY number function CalcSectionClass:HandleOverlayClick(key, cursorX, cursorY) if key ~= "LEFTBUTTON" then return end @@ -359,12 +393,15 @@ function CalcSectionClass:HandleOverlayClick(key, cursorX, cursorY) end end +---@param key string function CalcSectionClass:HandleOverlayRelease(key) if key == "LEFTBUTTON" then self.dragging = false end end +---@param viewPort Rect +---@param inputEvents InputEvent[] function CalcSectionClass:DrawOverlay(viewPort, inputEvents) local cursorX, cursorY = GetCursorPos() self.overlayBreakdownCell = nil @@ -434,6 +471,13 @@ function CalcSectionClass:DrawOverlay(viewPort, inputEvents) SetDrawLayer(0) end +---@param drawX number +---@param startLineY number +---@param drawWidth number +---@param actor Actor +---@param viewPort Rect +---@param isOverlay boolean +---@param noTooltip? boolean function CalcSectionClass:DrawContent(drawX, startLineY, drawWidth, actor, viewPort, isOverlay, noTooltip) local cursorX, cursorY = GetCursorPos() local lineY = startLineY @@ -548,4 +592,4 @@ function CalcSectionClass:DrawContent(drawX, startLineY, drawWidth, actor, viewP end end end -end \ No newline at end of file +end diff --git a/src/Classes/CalcsTab.lua b/src/Classes/CalcsTab.lua index 7d79e865d98..d56d7ee283b 100644 --- a/src/Classes/CalcsTab.lua +++ b/src/Classes/CalcsTab.lua @@ -17,11 +17,15 @@ local buffModeDropList = { } ---@class CalcsTab: UndoHandler, ControlHost, Control +---@field build Build +---@field modFlag boolean +---@field displayData? table ---@field powerStat PowerStat? ---@field nodePowerMaxDepth integer? Maximum distance for power report local CalcsTabClass = newClass("CalcsTab", "UndoHandler", "ControlHost", "Control") ---@param build Build +---@return CalcsTab function CalcsTabClass:CalcsTab(build) self:UndoHandler() self:ControlHost() @@ -160,6 +164,9 @@ Effective DPS: Curses and enemy properties (such as resistances and status condi return self end +---@param xml table +---@param dbFileName string +---@return boolean? loadError @True if loading failed. function CalcsTabClass:Load(xml, dbFileName) for _, node in ipairs(xml) do if type(node) == "table" then @@ -200,6 +207,7 @@ function CalcsTabClass:Load(xml, dbFileName) self:ResetUndo() end +---@param xml table function CalcsTabClass:Save(xml) for k, v in pairs(self.input) do local child = { elem = "Input", attrib = {name = k} } @@ -223,6 +231,8 @@ function CalcsTabClass:Save(xml) end end +---@param viewPort Rect +---@param inputEvents InputEvent[] function CalcsTabClass:Draw(viewPort, inputEvents) self.x = viewPort.x self.y = viewPort.y @@ -367,6 +377,8 @@ function CalcsTabClass:Draw(viewPort, inputEvents) end end +---@param width number +---@param ... unknown function CalcsTabClass:NewSection(width, ...) local section = new("CalcSectionControl"):CalcSectionControl(self, width * self.colWidth + 8 * (width - 1), ...) section.widthCols = width @@ -380,6 +392,8 @@ function CalcsTabClass:ClearDisplayStat() self.controls.breakdown:SetBreakdownData() end +---@param displayData table +---@param pin? boolean function CalcsTabClass:SetDisplayStat(displayData, pin) if not displayData or (not pin and self.displayPinned) then return @@ -393,6 +407,10 @@ function CalcsTabClass:SetDisplayStat(displayData, pin) self.controls.breakdown:SetBreakdownData(displayData, pin) end +---@param obj table +---@param actor Actor +---@param player Actor +---@return boolean function CalcsTabClass:CheckFlag(obj, actor, player) actor = actor or (self.input.showMinion and self.calcsEnv.minion or self.calcsEnv.player) local skillFlags = actor.mainSkill.skillFlags @@ -432,6 +450,8 @@ function CalcsTabClass:CheckFlag(obj, actor, player) return true end +---@param txt string +---@return boolean function CalcsTabClass:SearchMatch(txt) local searchStr = self.controls.search.buf:lower() return string.len(searchStr) > 0 and txt:lower():find(searchStr) @@ -515,6 +535,9 @@ function CalcsTabClass:PowerBuilder() coroutine.yield() end + ---@param node Node + ---@param effect table + ---@return Node local function buildMasteryEffectNode(node, effect) local effectNode = { id = node.id, @@ -529,11 +552,19 @@ function CalcsTabClass:PowerBuilder() return effectNode end + ---@param node Node + ---@param masteryEffect table + ---@return boolean local function masteryEffectCanBeAssignedToNode(node, masteryEffect) local assignedNodeId = isValueInTable(self.build.spec.masterySelections, masteryEffect.effect) return not assignedNodeId or assignedNodeId == node.id end + ---@param power number + ---@param distance number + ---@param node Node + ---@param output Output + ---@param buildPathNodes table local function calculateAddNodePower(power, distance, node, output, buildPathNodes) if self.powerStat and self.powerStat.stat and not self.powerStat.ignoreForNodes then power.singleStat = self:CalculatePowerStat(self.powerStat, output, calcBase) @@ -739,12 +770,20 @@ function CalcsTabClass:PowerBuilder() -- ConPrintf("Power Build time: %d ms", GetTime() - timer_start) end +---@param selection table +---@param original Output +---@param modified Output +---@return number function CalcsTabClass:CalculatePowerStat(selection, original, modified) local originalValue = data.powerStatList.GetFromOutput(original, selection) local modifiedValue = data.powerStatList.GetFromOutput(modified, selection) return originalValue - modifiedValue end +---@param original Output +---@param modified Output +---@return number offence +---@return number defence function CalcsTabClass:CalculateCombinedOffDefStat(original, modified) local defence = (original.LifeUnreserved - modified.LifeUnreserved) / m_max(3000, modified.Life) + (original.Armour - modified.Armour) / m_max(10000, modified.Armour) + @@ -757,14 +796,18 @@ function CalcsTabClass:CalculateCombinedOffDefStat(original, modified) return dpsIncr / modifiedDps, defence end +---@return fun(adjustments?: table, useFullDPS?: boolean): Output calcFunc +---@return Output calcBase function CalcsTabClass:GetMiscCalculator() return self.miscCalculator[1], self.miscCalculator[2] end +---@return table function CalcsTabClass:CreateUndoState() return copyTable(self.input) end +---@param state table function CalcsTabClass:RestoreUndoState(state) wipeTable(self.input) for k, v in pairs(state) do diff --git a/src/Classes/CheckBoxControl.lua b/src/Classes/CheckBoxControl.lua index 048d2582a2b..91e3dd936a5 100644 --- a/src/Classes/CheckBoxControl.lua +++ b/src/Classes/CheckBoxControl.lua @@ -4,8 +4,22 @@ -- Basic check box control. -- ---@class CheckBoxControl: Control, TooltipHost +---@field label? Prop +---@field labelWidth number +---@field labelRight boolean +---@field changeFunc? fun(state: boolean) +---@field state boolean +---@field clicked? boolean +---@field borderFunc? fun(): number, number, number local CheckBoxClass = newClass("CheckBoxControl", "Control", "TooltipHost") +---@param anchor Anchor +---@param rect Rect +---@param label? Prop +---@param changeFunc? fun(state: boolean) +---@param tooltipText? Prop +---@param initialState? boolean +---@return CheckBoxControl function CheckBoxClass:CheckBoxControl(anchor, rect, label, changeFunc, tooltipText, initialState) rect[4] = rect[3] or 0 self:Control(anchor, rect) @@ -18,6 +32,7 @@ function CheckBoxClass:CheckBoxControl(anchor, rect, label, changeFunc, tooltipT return self end +---@return boolean function CheckBoxClass:IsMouseOver() if not self:IsShown() then return false @@ -37,6 +52,8 @@ function CheckBoxClass:IsMouseOver() return cursorX >= x and cursorY >= y and cursorX < x + width and cursorY < y + height end +---@param viewPort Rect +---@param noTooltip? boolean function CheckBoxClass:Draw(viewPort, noTooltip) local x, y = self:GetPos() local size = self.width @@ -91,6 +108,8 @@ function CheckBoxClass:Draw(viewPort, noTooltip) end end +---@param key string +---@return CheckBoxControl? function CheckBoxClass:OnKeyDown(key) if not self:IsShown() or not self:IsEnabled() then return @@ -101,6 +120,7 @@ function CheckBoxClass:OnKeyDown(key) return self end +---@param key string function CheckBoxClass:OnKeyUp(key) if not self:IsShown() or not self:IsEnabled() then return diff --git a/src/Classes/CompareBuySimilar.lua b/src/Classes/CompareBuySimilar.lua index 55445eae65a..5030832a115 100644 --- a/src/Classes/CompareBuySimilar.lua +++ b/src/Classes/CompareBuySimilar.lua @@ -10,6 +10,7 @@ local tradeHelpers = require("Classes.TradeHelpers") -- used to check what stats actually exist on the trade site local _existingStats +---@return table local function getStats() if _existingStats then return _existingStats end _existingStats = {} @@ -21,8 +22,36 @@ local function getStats() return _existingStats end +---@class CompareBuySimilar local M = {} +---@class CompareBuySimilarTradeFilterValue +---@field min? number +---@field max? number +---@field option? string|number + +---@class CompareBuySimilarTradeFilter +---@field id string +---@field value? CompareBuySimilarTradeFilterValue + +---@class CompareBuySimilarDefenceEntry +---@field label string +---@field value number +---@field tradeKey string + +---@class CompareBuySimilarModTypeSource +---@field list? ModLine[] +---@field type string + +---@class CompareBuySimilarModEntry +---@field formattedLines string[] +---@field type string +---@field isOption boolean +---@field invert boolean +---@field tradeIds string[] +---@field value? string|number +---@field needsExactValue? boolean + -- Realm display name to API id mapping local REALM_API_IDS = { ["PC"] = "pc", @@ -44,6 +73,13 @@ end -- Build the trade search URL based on popup selections +---@param item Item +---@param slotName string +---@param controls table +---@param modEntries CompareBuySimilarModEntry[] +---@param defenceEntries CompareBuySimilarDefenceEntry[] +---@param isUnique boolean +---@return string local function buildURL(item, slotName, controls, modEntries, defenceEntries, isUnique) -- Determine realm and league from the popup's dropdowns local realmDisplayValue = controls.realmDrop and controls.realmDrop:GetSelValue() or "PC" @@ -143,6 +179,8 @@ local function buildURL(item, slotName, controls, modEntries, defenceEntries, is -- Mod filters for i, entry in ipairs(modEntries) do local prefix = "mod" .. i + ---@param tradeId string + ---@return CompareBuySimilarTradeFilter local function getFilter(tradeId) local filter = { id = tradeId } if entry.isOption then @@ -205,15 +243,16 @@ local function buildURL(item, slotName, controls, modEntries, defenceEntries, is return url end ----@param item any ----@param modTypeSources ModTypeSources ----@return table[] entries mod entries used in buy similar popup +---@param item Item +---@param modTypeSources CompareBuySimilarModTypeSource[] +---@return CompareBuySimilarModEntry[] entries mod entries used in buy similar popup function M.addModEntries(item, modTypeSources) local modEntries = {} -- this adds a single aggregated entry for matching stats (e.g. transformed flat dmg mods) which -- avoids issues with confusing results. mods with different types are not summed as e.g. -- implicit and explicit mods are separate in the search. options are also avoided as they don't -- represent values that can be added combined + ---@param entry CompareBuySimilarModEntry local function insertOrAddToExisting(entry) for _, existingFilter in ipairs(modEntries) do -- check if all result trade ids are equal @@ -282,6 +321,9 @@ function M.addModEntries(item, modTypeSources) return modEntries end -- Open the Buy Similar popup for a compared item +---@param item Item +---@param slotName string +---@param primaryBuild Build function M.openPopup(item, slotName, primaryBuild) if not item then return end @@ -297,7 +339,7 @@ function M.openPopup(item, slotName, primaryBuild) local fieldH = 20 local checkboxSize = 20 - ---@class ModTypeSources + ---@type CompareBuySimilarModTypeSource[] local modTypeSources = { { list = item.enchantModLines, type = "enchant" }, { list = item.implicitModLines, type = "implicit" }, @@ -347,6 +389,7 @@ function M.openPopup(item, slotName, primaryBuild) uri = result end -- Helper to fetch and populate leagues for a given realm API id + ---@param realmApiId string local function fetchLeaguesForRealm(realmApiId) local lastLeague = M.lastLeagueByRealm and M.lastLeagueByRealm[realmApiId] controls.leagueDrop:SetList({"Loading..."}) diff --git a/src/Classes/CompareCalcsHelpers.lua b/src/Classes/CompareCalcsHelpers.lua index 1a5254e8d01..9048788798c 100644 --- a/src/Classes/CompareCalcsHelpers.lua +++ b/src/Classes/CompareCalcsHelpers.lua @@ -9,7 +9,16 @@ local s_format = string.format local M = {} +---@class CompareCalcsHelpers + +---@class CompareCalcModRow +---@field value number|boolean +---@field mod Mod + -- Format a modifier value with its type for display +---@param value number|boolean +---@param modType NumericModTypes|string +---@return string function M.FormatCalcModValue(value, modType) if modType == "BASE" then return s_format("%+g base", value) @@ -35,6 +44,8 @@ function M.FormatCalcModValue(value, modType) end -- Format CamelCase mod name to spaced words +---@param modName string +---@return string function M.FormatCalcModName(modName) return modName:gsub("([%l%d]:?)(%u)", "%1 %2"):gsub("(%l)(%d)", "%1 %2") end @@ -42,6 +53,7 @@ end -- Resolve a modifier's source to a human-readable name ---@param mod Mod ---@param build Build +---@return string function M.ResolveSourceName(mod, build) if not mod.source then return "" end local sourceType = mod.source:match("[^:]+") or "" @@ -76,6 +88,10 @@ function M.ResolveSourceName(mod, build) end -- Get the modDB and config for a sectionData entry and actor +---@param sectionData CalcSectionData +---@param actor Actor +---@return ModStore? +---@return ModCfg? function M.GetModStoreAndCfg(sectionData, actor) local cfg = {} if sectionData.cfg and actor.mainSkill and actor.mainSkill[sectionData.cfg .. "Cfg"] then @@ -96,6 +112,9 @@ function M.GetModStoreAndCfg(sectionData, actor) end -- Tabulate modifiers for a sectionData entry and actor +---@param sectionData CalcSectionData +---@param actor Actor +---@return CompareCalcModRow[] function M.TabulateMods(sectionData, actor) local modStore, cfg = M.GetModStoreAndCfg(sectionData, actor) if not modStore then return {} end @@ -110,6 +129,8 @@ function M.TabulateMods(sectionData, actor) end -- Build a unique key for a modifier row to match between builds +---@param row { mod: Mod } +---@return string function M.ModRowKey(row) local src = row.mod.source or "" local name = row.mod.name or "" @@ -121,6 +142,13 @@ function M.ModRowKey(row) end -- Format a single modifier row as a tooltip line +---@param row CompareCalcModRow +---@param sectionData CalcSectionData +---@param build Build +---@return string displayValue +---@return string sourceType +---@return string sourceName +---@return string modName function M.FormatModRow(row, sectionData, build) local displayValue if not sectionData.modType then @@ -140,8 +168,9 @@ function M.FormatModRow(row, sectionData, build) end -- Get breakdown text lines for a build's actor ----@param sectionData any +---@param sectionData CalcSectionData ---@param build Build +---@return string[]? function M.GetBreakdownLines(sectionData, build) if not sectionData.breakdown then return nil end local calcsActor = build.calcsTab and build.calcsTab.calcsEnv and build.calcsTab.calcsEnv.player @@ -170,6 +199,15 @@ end -- tooltip, primaryBuild, primaryLabel passed as args instead of self ---@param tooltip Tooltip ---@param primaryBuild Build +---@param primaryLabel string +---@param colData CalcSectionData[] +---@param rowLabel string? +---@param rowX number +---@param rowY number +---@param rowW number +---@param rowH number +---@param vp Rect +---@param compareEntry CompareEntry function M.DrawCalcsTooltip(tooltip, primaryBuild, primaryLabel, colData, rowLabel, rowX, rowY, rowW, rowH, vp, compareEntry) if tooltip:CheckForUpdate(colData, rowLabel) then -- Get calcsEnv actors (these have breakdown data populated) @@ -326,6 +364,8 @@ end -- Resolve a modifier's source name for breakdown panel display ---@param mod Mod ---@param build Build +---@return string sourceType +---@return string sourceName local function resolveModSource(mod, build) local sourceType = mod.source and mod.source:match("[^:]+") or "?" local sourceName = "" @@ -361,6 +401,13 @@ end -- Draw a breakdown panel for a single build's SkillBuffs or SkillDebuffs, ---@param build Build +---@param breakdownKey string +---@param label string +---@param cellX number +---@param cellY number +---@param cellW number +---@param cellH number +---@param vp Rect function M.DrawSkillBreakdownPanel(build, breakdownKey, label, cellX, cellY, cellW, cellH, vp) local player = build.calcsTab and build.calcsTab.calcsEnv and build.calcsTab.calcsEnv.player diff --git a/src/Classes/CompareEntry.lua b/src/Classes/CompareEntry.lua index 58529fb285d..fcecd748da3 100644 --- a/src/Classes/CompareEntry.lua +++ b/src/Classes/CompareEntry.lua @@ -10,8 +10,39 @@ local m_min = math.min local m_max = math.max ---@class CompareEntry: ControlHost +---@field label string +---@field buildName string +---@field xmlText string +---@field viewMode string +---@field characterLevel integer +---@field targetVersion string +---@field bandit string +---@field pantheonMajorGod string +---@field pantheonMinorGod string +---@field characterLevelAutoMode boolean +---@field mainSocketGroup integer +---@field notesText string +---@field spectreList table +---@field timelessData table +---@field latestTree PassiveTree +---@field data table +---@field buildFlag boolean +---@field outputRevision integer +---@field displayStats table +---@field minionDisplayStats table +---@field extraSaveStats table +---@field importLink? string +---@field xmlSectionList table[] +---@field partyTab table +---@field configTab ConfigTab +---@field itemsTab ItemsTab +---@field treeTab TreeTab +---@field calcsTab CalcsTab local CompareEntryClass = newClass("CompareEntry", "ControlHost") +---@param xmlText string +---@param label? string +---@return CompareEntry function CompareEntryClass:CompareEntry(xmlText, label) self:ControlHost() @@ -60,6 +91,8 @@ function CompareEntryClass:CompareEntry(xmlText, label) return self end +---@param xmlText string +---@return boolean? function CompareEntryClass:LoadFromXML(xmlText) -- Parse the XML local dbXML, errMsg = common.xml.ParseXML(xmlText) @@ -186,6 +219,7 @@ function CompareEntryClass:LoadFromXML(xmlText) end -- Load build section attributes +---@param xml table function CompareEntryClass:LoadBuildSection(xml) self.targetVersion = xml.attrib.targetVersion or legacyTargetVersion if xml.attrib.viewMode then @@ -218,10 +252,12 @@ function CompareEntryClass:LoadBuildSection(xml) end end +---@return Output? function CompareEntryClass:GetOutput() return self.calcsTab.mainOutput end +---@return PassiveSpec? function CompareEntryClass:GetSpec() return self.spec end @@ -255,6 +291,7 @@ function CompareEntryClass:Rebuild() self.buildFlag = false end +---@param index integer function CompareEntryClass:SetActiveSpec(index) if self.treeTab and self.treeTab.SetActiveSpec then self.treeTab:SetActiveSpec(index) @@ -262,6 +299,7 @@ function CompareEntryClass:SetActiveSpec(index) end end +---@param id integer function CompareEntryClass:SetActiveItemSet(id) if self.itemsTab and self.itemsTab.SetActiveItemSet then self.itemsTab:SetActiveItemSet(id) @@ -269,6 +307,7 @@ function CompareEntryClass:SetActiveItemSet(id) end end +---@param id integer function CompareEntryClass:SetActiveSkillSet(id) if self.skillsTab and self.skillsTab.SetActiveSkillSet then self.skillsTab:SetActiveSkillSet(id) @@ -281,11 +320,15 @@ function CompareEntryClass:RefreshStatList() -- No sidebar to refresh in comparison entry end +---@param index integer function CompareEntryClass:SetMainSocketGroup(index) self.mainSocketGroup = index self.buildFlag = true end +---@param controls table +---@param mainGroup? table +---@param suffix string function CompareEntryClass:RefreshSkillSelectControls(controls, mainGroup, suffix) -- Populate skill select controls if not controls or not controls.mainSocketGroup then return end @@ -376,6 +419,10 @@ function CompareEntryClass:RefreshSkillSelectControls(controls, mainGroup, suffi end end +---@param controls table +---@param activeSkill? table +---@param activeEffect? table +---@param suffix string function CompareEntryClass:RefreshMinionControls(controls, activeSkill, activeEffect, suffix) wipeTable(controls.mainSkillMinion.list) if activeEffect.grantedEffect.minionHasItemSet then @@ -424,6 +471,12 @@ function CompareEntryClass:OpenSpectreLibrary() -- No spectre library in comparison entry end +---@param tooltip Tooltip +---@param baseOutput Output +---@param compareOutput Output +---@param header string +---@param nodeCount integer +---@return integer function CompareEntryClass:AddStatComparesToTooltip(tooltip, baseOutput, compareOutput, header, nodeCount) -- Reuse the stat comparison logic local count = 0 @@ -442,6 +495,14 @@ function CompareEntryClass:AddStatComparesToTooltip(tooltip, baseOutput, compare end -- Stat comparison +---@param tooltip Tooltip +---@param statList table +---@param actor Actor +---@param baseOutput Output +---@param compareOutput Output +---@param header string +---@param nodeCount integer +---@return integer function CompareEntryClass:CompareStatList(tooltip, statList, actor, baseOutput, compareOutput, header, nodeCount) local s_format = string.format local count = 0 @@ -513,6 +574,14 @@ end -- Add requirements to tooltip do local req = { } + ---@param tooltip Tooltip + ---@param level number + ---@param str number + ---@param dex number + ---@param int number + ---@param strBase number + ---@param dexBase number + ---@param intBase number function CompareEntryClass:AddRequirementsToTooltip(tooltip, level, str, dex, int, strBase, dexBase, intBase) if level and level > 0 then t_insert(req, s_format("^x7F7F7FLevel %s%d", main:StatColor(level, nil, self.characterLevel), level)) diff --git a/src/Classes/ComparePowerReportListControl.lua b/src/Classes/ComparePowerReportListControl.lua index 49cdb507999..8a1ada07789 100644 --- a/src/Classes/ComparePowerReportListControl.lua +++ b/src/Classes/ComparePowerReportListControl.lua @@ -8,8 +8,15 @@ local t_insert = table.insert local t_sort = table.sort ---@class ComparePowerReportListControl: ListControl +---@field reportData table +---@field impactColumn? ListColumn +---@field lastTooltipIndex? integer +---@field statusText? string local ComparePowerReportListClass = newClass("ComparePowerReportListControl", "ListControl") +---@param anchor? Anchor +---@param rect? Rect +---@return ComparePowerReportListControl function ComparePowerReportListClass:ComparePowerReportListControl(anchor, rect) self:ListControl(anchor, rect, 18, "VERTICAL", false) @@ -28,6 +35,8 @@ function ComparePowerReportListClass:ComparePowerReportListControl(anchor, rect) return self end +---@param stat? { label: string } +---@param report? table function ComparePowerReportListClass:SetReport(stat, report) self.impactColumn.label = stat and stat.label or "" self.reportData = report or {} @@ -46,6 +55,7 @@ function ComparePowerReportListClass:SetReport(stat, report) self:ReSort(3) end +---@param progress number function ComparePowerReportListClass:SetProgress(progress) if progress < 100 then self.statusText = "Calculating... " .. progress .. "%" @@ -53,6 +63,8 @@ function ComparePowerReportListClass:SetProgress(progress) end end +---@param viewPort Rect +---@param noTooltip? boolean function ComparePowerReportListClass:Draw(viewPort, noTooltip) if self.hoverIndex ~= self.lastTooltipIndex then self.tooltip.updateParams = nil @@ -71,6 +83,7 @@ function ComparePowerReportListClass:Draw(viewPort, noTooltip) end end +---@param colIndex integer function ComparePowerReportListClass:ReSort(colIndex) local compare = function(a, b) return a > b end @@ -113,6 +126,9 @@ function ComparePowerReportListClass:ReList() end end +---@param tooltip Tooltip +---@param index integer +---@param entry table function ComparePowerReportListClass:AddValueTooltip(tooltip, index, entry) if main.popups[1] then tooltip:Clear() @@ -146,6 +162,10 @@ function ComparePowerReportListClass:AddValueTooltip(tooltip, index, entry) end end +---@param column integer +---@param index integer +---@param entry table +---@return string? function ComparePowerReportListClass:GetRowValue(column, index, entry) if column == 1 then return (entry.categoryColor or "^7") .. entry.category diff --git a/src/Classes/CompareTab.lua b/src/Classes/CompareTab.lua index bcd470eab79..6f0ad9b9fef 100644 --- a/src/Classes/CompareTab.lua +++ b/src/Classes/CompareTab.lua @@ -22,6 +22,9 @@ local CLUSTER_NODE_OFFSET = 65536 -- Wrap a string into lines for a given pixel width at font height 14 ("VAR"). -- Breaks BEFORE a word that would exceed the width, so rendered lines never -- overshoot into the next column. +---@param str string +---@param width number +---@return string[] lines local function wrapInfoLine(str, width) local lines = {} if not str or str == "" or width <= 0 then @@ -98,6 +101,10 @@ local LAYOUT = { } -- Flag matching for stat filtering +---@param reqFlags string|string[]? +---@param notFlags string|string[]? +---@param flags table +---@return boolean? local function matchFlags(reqFlags, notFlags, flags) if type(reqFlags) == "string" then reqFlags = { reqFlags } @@ -122,10 +129,131 @@ local function matchFlags(reqFlags, notFlags, flags) return true end +---@class ComparePowerCategories +---@field treeNodes boolean +---@field items boolean +---@field skillGems boolean +---@field supportGems boolean +---@field config boolean + +---@class ComparePowerResult +---@field category string +---@field categoryColor string +---@field nameColor string +---@field name string +---@field impact number +---@field impactStr string +---@field impactPercent number +---@field combinedImpactStr string +---@field pathDist? number +---@field perPoint? number +---@field perPointStr? string +---@field nodeId? integer +---@field itemObj? Item +---@field slotName? string + +---@class CompareJewelComparisonSlot +---@field label string +---@field nodeId integer +---@field pItem Item? +---@field cItem Item? +---@field pSlotName string +---@field cSlotName string +---@field pNodeAllocated boolean +---@field cNodeAllocated boolean + +---@class CompareTabConfigControlInfo +---@field primaryControl Control +---@field compareControl Control +---@field varData table +---@field visible boolean + +---@class CompareTabConfigSection +---@field name string +---@field col? integer +---@field items CompareTabConfigControlInfo[] +---@field rows? { ctrlInfo: CompareTabConfigControlInfo, isDiff: boolean }[] +---@field height? number +---@field diffCount? integer +---@field x? number +---@field y? number + +---@class CompareGem +---@field grantedEffect? table +---@field gemData? table +---@field nameSpec? string +---@field level? integer +---@field quality? integer +---@field color? string +---@field isImbuedSupport? boolean +---@field supportEffect? table + +---@class CompareSocketGroup +---@field gemList CompareGem[] +---@field displayLabel? string +---@field label? string +---@field slot? string +---@field imbuedSupport? string + +---@class CompareGemDisplayEntry +---@field gem? CompareGem +---@field name string +---@field status "common"|"additional"|"missing" + +---@class CompareGemHoverEntry +---@field gem CompareGem +---@field x number +---@field y number +---@field group? CompareSocketGroup + ---@class CompareTab: ControlHost, Control +---@field build Build +---@field primaryBuild Build +---@field compareEntries CompareEntry[] +---@field activeCompareIndex integer +---@field compareViewMode string +---@field scrollY number +---@field itemsScrollX number +---@field skillsScrollX number +---@field summaryTotalContentHeight number +---@field itemsTotalContentHeight number +---@field skillsTotalContentHeight number +---@field treeLayout? table +---@field treeSearchNeedsSync boolean +---@field treeOverlayMode boolean +---@field itemTooltip Tooltip +---@field itemsExpandedMode boolean +---@field calcsTooltip Tooltip +---@field calcsShowOnlyDifferences boolean +---@field configControls table +---@field configControlList CompareTabConfigControlInfo[] +---@field configNeedsRebuild boolean +---@field configCompareId? integer +---@field configToggle boolean +---@field configSections CompareTabConfigSection[] +---@field configSectionLayout CompareTabConfigSection[] +---@field configTotalContentHeight number +---@field comparePowerStat? PowerStat +---@field comparePowerCategories ComparePowerCategories +---@field comparePowerResults? ComparePowerResult[] +---@field comparePowerCoroutine? thread +---@field comparePowerProgress number +---@field comparePowerDirty boolean +---@field comparePowerCompareId? CompareEntry +---@field configOptions table[] +---@field calcSections CalcSection[] +---@field calcs table +---@field treeVersionDropdownList table[] +---@field comparePowerListSynced boolean +---@field calcsSkillHeaderHeight? number +---@field itemsColWidth? number +---@field calcsSkillHeaderHover? table +---@field modFlag boolean +---@field [string] unknown local CompareTabClass = newClass("CompareTab", "ControlHost", "Control") ---@param primaryBuild Build +---@return CompareTab function CompareTabClass:CompareTab(primaryBuild) self:ControlHost() self:Control() @@ -1071,6 +1199,8 @@ function CompareTabClass:InitControls() end -- Get a short display name from a build name (strips "AccountName - " prefix) +---@param fullName string? +---@return string function CompareTabClass:GetShortBuildName(fullName) if not fullName then return "Your Build" end local dashPos = fullName:find(" %- ") @@ -1084,6 +1214,11 @@ end -- tab: the tab object (e.g. itemsTab, skillsTab, configTab) -- orderListField/setsField/activeIdField: string keys on tab -- control: the DropDownControl to populate +---@param tab SkillsTab|ItemsTab|ConfigTab +---@param orderListField string +---@param setsField string +---@param activeIdField string +---@param control DropDownControl function CompareTabClass:PopulateSetDropdown(tab, orderListField, setsField, activeIdField, control) local list = {} local orderList = tab[orderListField] @@ -1102,6 +1237,9 @@ function CompareTabClass:PopulateSetDropdown(tab, orderListField, setsField, act end -- Format a config value for read-only display +---@param varData table +---@param val ConfigValue? +---@return string function CompareTabClass:FormatConfigValue(varData, val) if val == nil then return "^8(not set)" end if varData.type == "check" then @@ -1120,6 +1258,11 @@ end -- Normalize config values so that functionally equivalent states compare equal -- (nil/false for checks, nil/0 for counts/integers/floats) +---@param varData table +---@param pVal ConfigValue? +---@param cVal ConfigValue? +---@return ConfigValue? primaryValue +---@return ConfigValue? compareValue function CompareTabClass:NormalizeConfigVals(varData, pVal, cVal) if varData.type == "check" then return pVal or false, cVal or false @@ -1130,6 +1273,12 @@ function CompareTabClass:NormalizeConfigVals(varData, pVal, cVal) end -- Create a single config control for a given varData, writing to the specified input/configTab/build +---@param varData table +---@param inputTable table +---@param configTab ConfigTab +---@param buildObj Build|CompareEntry +---@param sourceControl Control? +---@return Control? local function makeConfigControl(varData, inputTable, configTab, buildObj, sourceControl) local control local pVal = inputTable[varData.var] @@ -1176,6 +1325,7 @@ local function makeConfigControl(varData, inputTable, configTab, buildObj, sourc end -- Rebuild interactive config controls for all config options (both primary and compare builds) +---@param compareEntry CompareEntry function CompareTabClass:RebuildConfigControls(compareEntry) -- Remove old config controls for var, _ in pairs(self.configControls) do @@ -1242,6 +1392,9 @@ function CompareTabClass:CopyCompareConfig() end -- Import a comparison build from XML text +---@param xmlText string +---@param label string? +---@return boolean function CompareTabClass:ImportBuild(xmlText, label) local entry = new("CompareEntry"):CompareEntry(xmlText, label) if entry and entry.calcsTab and entry.calcsTab.mainOutput then @@ -1258,6 +1411,8 @@ function CompareTabClass:ImportBuild(xmlText, label) end -- Import a comparison build from a build code (base64-encoded) +---@param code string +---@return boolean function CompareTabClass:ImportFromCode(code) local xmlText = Inflate(common.base64.decode(code:gsub("-","+"):gsub("_","/"))) if not xmlText then @@ -1270,6 +1425,7 @@ function CompareTabClass:ImportFromCode(code) end -- Remove a comparison build +---@param index integer function CompareTabClass:RemoveBuild(index) if index >= 1 and index <= #self.compareEntries then t_remove(self.compareEntries, index) @@ -1325,6 +1481,7 @@ function CompareTabClass:UpdateBuildSelector() end -- Get the active comparison entry +---@return CompareEntry? function CompareTabClass:GetActiveCompare() if self.activeCompareIndex > 0 and self.activeCompareIndex <= #self.compareEntries then return self.compareEntries[self.activeCompareIndex] @@ -1333,6 +1490,7 @@ function CompareTabClass:GetActiveCompare() end -- Copy the compared build's currently selected tree spec into the primary build +---@param andUse boolean? function CompareTabClass:CopyCompareSpecToPrimary(andUse) local entry = self:GetActiveCompare() if not entry or not entry.treeTab then return end @@ -1374,6 +1532,8 @@ end -- Build a list of jewel comparison entries between the primary and compare builds. -- Returns a sorted list of { label, nodeId, pItem, cItem, pSlotName, cSlotName } records. +---@param compareEntry CompareEntry +---@return CompareJewelComparisonSlot[] function CompareTabClass:GetJewelComparisonSlots(compareEntry) local pSpec = self.primaryBuild.spec local cSpec = compareEntry.spec @@ -1441,6 +1601,9 @@ function CompareTabClass:GetJewelComparisonSlots(compareEntry) end -- Copy a compared build's item into the primary build +---@param slotName string +---@param compareEntry CompareEntry +---@param andUse boolean? function CompareTabClass:CopyCompareItemToPrimary(slotName, compareEntry, andUse) local cSlot = compareEntry.itemsTab and compareEntry.itemsTab.slots and compareEntry.itemsTab.slots[slotName] local cItem = cSlot and compareEntry.itemsTab.items and compareEntry.itemsTab.items[cSlot.selItemId] @@ -1560,12 +1723,14 @@ function CompareTabClass:OpenImportFolderPopup() controls.buildList:SelByFullFileName(selectedFullFileName) end end - function listHost:SelectControl(control) +---@param control Control +function listHost:SelectControl(control) -- Focus is managed by the popup's ControlHost; this is a no-op for the popup list. end -- Import the given build entry (xml file on disk) as a comparison. - local function importBuildEntry(build) +---@param build BuildListEntry +local function importBuildEntry(build) local fileHnd = io.open(build.fullFileName, "r") if not fileHnd then main:OpenMessagePopup("Import Error", "Couldn't open '"..build.fullFileName.."'.") @@ -1604,28 +1769,35 @@ function CompareTabClass:OpenImportFolderPopup() -- Override instance methods on the BuildListControl to tailor it for the popup: -- navigate folders, import builds, and suppress rename/delete/drag behaviors. - function controls.buildList:LoadBuild(build) +---@param build BuildListEntry +function controls.buildList:LoadBuild(build) if build.folderName then self.controls.path:SetSubPath(build.subPath .. build.folderName .. "/") else importBuildEntry(build) end end - function controls.buildList:OnSelKeyDown(index, build, key) +---@param index integer +---@param build BuildListEntry +---@param key string +function controls.buildList:OnSelKeyDown(index, build, key) if key == "RETURN" then self:LoadBuild(build) end end - function controls.buildList:OnHoverKeyUp(key) +---@param key string +function controls.buildList:OnHoverKeyUp(key) if self.controls.scrollBarV:IsScrollDownKey(key) or self.controls.scrollBarV:IsScrollUpKey(key) then self:OnKeyUp(key) end end - function controls.buildList:CanReceiveDrag() return false end +---@return boolean +function controls.buildList:CanReceiveDrag() return false end function controls.buildList:OnSelCopy() end function controls.buildList:OnSelCut() end function controls.buildList:OnSelDelete() end - function controls.buildList.controls.path:CanReceiveDrag() return false end +---@return boolean +function controls.buildList.controls.path:CanReceiveDrag() return false end -- Populate the initial list now that the control (and its path control) exist. listHost:BuildList() @@ -1647,6 +1819,8 @@ end -- ============================================================ -- DRAW - Main render method -- ============================================================ +---@param viewPort Rect +---@param inputEvents InputEvent[] function CompareTabClass:Draw(viewPort, inputEvents) main:DrawBackground(viewPort) -- Position top-bar controls @@ -1869,6 +2043,8 @@ end -- DRAW HELPERS -- ============================================================ +---@param viewPort Rect +---@param controls Control[] function CompareTabClass:DrawControlList(viewPort, controls) local noTooltip = function(control) return self.selControl and self.selControl.hasFocus and self.selControl ~= control @@ -1882,6 +2058,8 @@ end -- Pre-draw tree header/footer backgrounds and position tree controls. -- Must run before ProcessControlsInput so controls render on top of backgrounds. +---@param contentVP Rect +---@param compareEntry CompareEntry function CompareTabClass:LayoutTreeView(contentVP, compareEntry) self.treeLayout = nil if self.compareViewMode ~= "TREE" or not compareEntry then return end @@ -2028,6 +2206,9 @@ function CompareTabClass:LayoutTreeView(contentVP, compareEntry) end -- Sync a single control's displayed value with the actual input value +---@param ctrl Control +---@param varData table +---@param val ConfigValue? local function syncControlValue(ctrl, varData, val) if varData.type == "check" then ctrl.state = val or false @@ -2044,6 +2225,8 @@ local function syncControlValue(ctrl, varData, val) end -- Position config controls and build section-grouped display when in CONFIG view. +---@param contentVP Rect +---@param compareEntry CompareEntry function CompareTabClass:LayoutConfigView(contentVP, compareEntry) if self.compareViewMode ~= "CONFIG" or not compareEntry then return end @@ -2116,7 +2299,9 @@ function CompareTabClass:LayoutConfigView(contentVP, compareEntry) -- Search filter: match config labels against search text local searchStr = self.controls.configSearchEdit.buf:lower():gsub("[%-%.%+%[%]%$%^%%%?%*]", "%%%0") local hasSearch = searchStr and searchStr:match("%S") - local function searchMatch(varData) +---@param varData table +---@return boolean +local function searchMatch(varData) if not hasSearch then return true end local err, match = PCall(string.matchOrPattern, (varData.label or ""):lower(), searchStr) return not err and match @@ -2225,6 +2410,7 @@ function CompareTabClass:LayoutConfigView(contentVP, compareEntry) end -- Update comparison build set selectors (spec, skill set, item set, skill controls). +---@param compareEntry CompareEntry function CompareTabClass:UpdateSetSelectors(compareEntry) -- Tree spec list (reuse GetSpecList from TreeTab) if compareEntry.treeTab then @@ -2259,6 +2445,7 @@ function CompareTabClass:UpdateSetSelectors(compareEntry) end -- Refresh calcs skill detail controls for both builds. +---@param compareEntry CompareEntry function CompareTabClass:RefreshCalcsSkillControls(compareEntry) -- Build control maps for RefreshSkillSelectControls local primControls = { @@ -2318,6 +2505,9 @@ function CompareTabClass:RefreshCalcsSkillControls(compareEntry) end -- Layout calcs skill detail controls into a two-column header area +---@param vp Rect +---@param compareEntry CompareEntry +---@return number headerHeight function CompareTabClass:LayoutCalcsSkillControls(vp, compareEntry) if self.compareViewMode ~= "CALCS" or not compareEntry then return 0 end @@ -2332,7 +2522,12 @@ function CompareTabClass:LayoutCalcsSkillControls(vp, compareEntry) local y = vp.y + 4 -- Helper to position a row of label + control - local function layoutRow(control, x, currentY, width) +---@param control Control +---@param x number +---@param currentY number +---@param width number? +---@return boolean +local function layoutRow(control, x, currentY, width) if control.shown == false or (type(control.shown) == "function" and not control:IsShown()) then return false end @@ -2409,6 +2604,8 @@ function CompareTabClass:LayoutCalcsSkillControls(vp, compareEntry) end -- Handle scroll events for scrollable views. +---@param contentVP Rect +---@param inputEvents InputEvent[] function CompareTabClass:HandleScrollInput(contentVP, inputEvents) local cursorX, cursorY = GetCursorPos() local mouseInContent = cursorX >= contentVP.x and cursorX < contentVP.x + contentVP.width @@ -2465,6 +2662,8 @@ end -- ============================================================ -- Resolve the granted effect for a gem instance +---@param gem CompareGem +---@return table? function CompareTabClass:GetGemGrantedEffect(gem) if gem.gemData and gem.gemData.grantedEffect then return gem.gemData.grantedEffect @@ -2473,6 +2672,8 @@ function CompareTabClass:GetGemGrantedEffect(gem) end -- Build a signature string for a socket group (sorted gem names) +---@param group CompareSocketGroup +---@return string function CompareTabClass:GetSocketGroupSignature(group) local names = {} for _, gem in ipairs(group.gemList or {}) do @@ -2486,6 +2687,8 @@ function CompareTabClass:GetSocketGroupSignature(group) end -- Get a display label for a socket group (active skills only) +---@param group CompareSocketGroup +---@return string function CompareTabClass:GetSocketGroupLabel(group) local names = {} for _, gem in ipairs(group.gemList or {}) do @@ -2513,6 +2716,9 @@ function CompareTabClass:GetSocketGroupLabel(group) end -- Coroutine: calculate power of compared build elements against primary build +---@param compareEntry CompareEntry +---@param powerStat PowerStat +---@param categories ComparePowerCategories function CompareTabClass:ComparePowerBuilder(compareEntry, powerStat, categories) local results = {} local useFullDPS = powerStat.stat == "FullDPS" @@ -2624,7 +2830,13 @@ function CompareTabClass:ComparePowerBuilder(compareEntry, powerStat, categories local baseStatValue = data.powerStatList.GetFromOutput(calcBase, powerStat) -- Helper to format an impact value and compute percentage - local function formatImpact(impact) +---@param impact number +---@return string impactStr +---@return number impactValue +---@return string combinedImpactStr +---@return number impactPercent +---@return boolean impactIsZero +local function formatImpact(impact) local displayVal = impact * ((displayStat.pc or displayStat.mod) and 100 or 1) local rawNumStr = s_format("%" .. displayStat.fmt, displayVal) local isZero = (tonumber(rawNumStr) == 0) @@ -3049,7 +3261,9 @@ function CompareTabClass:ComparePowerBuilder(compareEntry, powerStat, categories local pInput = self.primaryBuild.configTab.input local cInput = compareEntry.configTab.input or {} - local function stripColors(s) +---@param s string +---@return string +local function stripColors(s) return s:gsub("%^%x", ""):gsub("%^x%x%x%x%x%x%x", "") end @@ -3126,6 +3340,7 @@ function CompareTabClass:ComparePowerBuilder(compareEntry, powerStat, categories end -- Drive the compare power report coroutine +---@param compareEntry CompareEntry function CompareTabClass:RunComparePowerReport(compareEntry) -- Invalidate if compare entry changed if self.comparePowerCompareId ~= compareEntry then @@ -3159,6 +3374,8 @@ end -- ============================================================ -- SUMMARY VIEW -- ============================================================ +---@param vp Rect +---@param compareEntry CompareEntry function CompareTabClass:DrawSummary(vp, compareEntry) local primaryCalcs = self.primaryBuild.calcsTab local compareCalcs = compareEntry.calcsTab @@ -3306,6 +3523,17 @@ function CompareTabClass:DrawSummary(vp, compareEntry) end +---@param drawY number +---@param displayStats DisplayStat[] +---@param primaryOutput Output +---@param compareOutput Output +---@param primaryActor Actor +---@param compareActor Actor +---@param col1 number +---@param col4 number +---@param col2R number +---@param col3R number +---@return number drawY function CompareTabClass:DrawStatList(drawY, displayStats, primaryOutput, compareOutput, primaryActor, compareActor, col1, col4, col2R, col3R) local lineHeight = 16 @@ -3400,6 +3628,9 @@ end -- ============================================================ -- TREE VIEW (overlay + side-by-side) -- ============================================================ +---@param vp Rect +---@param inputEvents InputEvent[] +---@param compareEntry CompareEntry function CompareTabClass:DrawTree(vp, inputEvents, compareEntry) local layout = self.treeLayout if not layout then return end @@ -3492,12 +3723,25 @@ end -- Draw a single item's full details at (x, startY) within colWidth. -- otherModMap: optional table from buildModMap() of the other item for diff highlighting. -- Returns the total height consumed. +---@param item Item? +---@param x number +---@param startY number +---@param colWidth number +---@param otherModMap table? +---@param measureMode boolean? +---@return number function CompareTabClass:DrawItemExpanded(item, x, startY, colWidth, otherModMap, measureMode) local lineHeight = 16 local fontSize = 14 local drawY = startY local maxLineW = 0 - local function emit(lx, ly, align, fs, fStyle, str) +---@param lx number +---@param ly number +---@param align string +---@param fs number +---@param fStyle string +---@param str string +local function emit(lx, ly, align, fs, fStyle, str) if measureMode then local w = DrawStringWidth(fs, fStyle, str) if w > maxLineW then maxLineW = w end @@ -3665,6 +3909,8 @@ function CompareTabClass:DrawItemExpanded(item, x, startY, colWidth, otherModMap return drawY - startY end +---@param compareEntry CompareEntry +---@return boolean? function CompareTabClass:ShouldShowRing3(compareEntry) local primaryEnv = self.primaryBuild.calcsTab and self.primaryBuild.calcsTab.mainEnv local compareEnv = compareEntry.calcsTab and compareEntry.calcsTab.mainEnv @@ -3673,9 +3919,9 @@ function CompareTabClass:ShouldShowRing3(compareEntry) return primaryHas or compareHas end ---- @param comparison table ---- @param destTable string[] ---- @param requireBothSides boolean +---@param comparison CompareEntry +---@param destTable string[] +---@param requireBothSides boolean function CompareTabClass:AddAbyssSockets(comparison, destTable, requireBothSides) local equipmentSlots = { "Weapon 1", "Weapon 2", "Weapon 1 Swap", "Weapon 2 Swap", "Helmet", "Body Armour", "Gloves", "Boots", "Belt" } @@ -3692,6 +3938,9 @@ function CompareTabClass:AddAbyssSockets(comparison, destTable, requireBothSides end end +---@param vp Rect +---@param compareEntry CompareEntry +---@param inputEvents InputEvent[] function CompareTabClass:DrawItems(vp, compareEntry, inputEvents) local baseSlots = { "Weapon 1", "Weapon 2", "Weapon 1 Swap", "Weapon 2 Swap", "Helmet", "Body Armour", "Gloves", "Boots", "Amulet", "Ring 1", "Ring 2", "Belt", "Flask 1", "Flask 2", "Flask 3", "Flask 4", "Flask 5" } @@ -3752,7 +4001,9 @@ function CompareTabClass:DrawItems(vp, compareEntry, inputEvents) local pSlots = self.primaryBuild.itemsTab and self.primaryBuild.itemsTab.slots local cItems = compareEntry.itemsTab and compareEntry.itemsTab.items local cSlots = compareEntry.itemsTab and compareEntry.itemsTab.slots - local function measureDiff(pItem, cItem) +---@param pItem Item? +---@param cItem Item? +local function measureDiff(pItem, cItem) local lbl = tradeHelpers.getSlotDiffLabel(pItem, cItem) if lbl and lbl ~= "" then local w = DrawStringWidth(14, "VAR", lbl) @@ -3816,7 +4067,17 @@ function CompareTabClass:DrawItems(vp, compareEntry, inputEvents) -- Helper: process copy/buy button hover state and click events for a slot. -- Closes over hoverEquip*/clicked* locals above. - local function processSlotButtons(b1Hover, b2Hover, b3Hover, b2X, b2Y, b2W, b2H, cItem, copySlotName, equipSlotName) +---@param b1Hover boolean +---@param b2Hover boolean +---@param b3Hover boolean +---@param b2X number +---@param b2Y number +---@param b2W number +---@param b2H number +---@param cItem Item? +---@param copySlotName string +---@param equipSlotName string +local function processSlotButtons(b1Hover, b2Hover, b3Hover, b2X, b2Y, b2W, b2H, cItem, copySlotName, equipSlotName) if b2Hover and cItem then hoverEquipItem = cItem hoverEquipSlotName = equipSlotName @@ -3844,7 +4105,16 @@ function CompareTabClass:DrawItems(vp, compareEntry, inputEvents) -- Helper: draw a single slot entry (expanded or compact mode). -- Closes over drawY, colWidth, cursorX/Y, vp, self, compareEntry, hoverItem/hoverX/Y/W/H/hoverItemsTab. - local function drawSlotEntry(label, pItem, cItem, copySlotName, equipSlotName, labelW, pWarn, cWarn, slotMissing) +---@param label string +---@param pItem Item? +---@param cItem Item? +---@param copySlotName string +---@param equipSlotName string +---@param labelW number +---@param pWarn string? +---@param cWarn string? +---@param slotMissing boolean? +local function drawSlotEntry(label, pItem, cItem, copySlotName, equipSlotName, labelW, pWarn, cWarn, slotMissing) if self.itemsExpandedMode then -- === EXPANDED MODE === SetDrawColor(1, 1, 1) @@ -4046,6 +4316,8 @@ end -- ============================================================ -- SKILLS VIEW -- ============================================================ +---@param vp Rect +---@param compareEntry CompareEntry function CompareTabClass:DrawSkills(vp, compareEntry) local lineHeight = 18 @@ -4058,7 +4330,10 @@ function CompareTabClass:DrawSkills(vp, compareEntry) -- Imbued supports live on socketGroup.imbuedSupport (a name string) rather than in gemList, -- so synthesize a minimal gem-like entry that the rendering can treat like any other gem local imbuedGemCache = {} - local function getImbuedGem(group, skillsTab) +---@param group CompareSocketGroup? +---@param skillsTab SkillsTab? +---@return CompareGem? +local function getImbuedGem(group, skillsTab) if not group or not group.imbuedSupport then return nil end if imbuedGemCache[group] then return imbuedGemCache[group] end -- Prefer the grantedEffect cached on skillsTab; fall back to a direct data lookup @@ -4081,7 +4356,10 @@ function CompareTabClass:DrawSkills(vp, compareEntry) end -- Helper: get the set of gem names in a socket group - local function getGemNameSet(group, skillsTab) +---@param group CompareSocketGroup +---@param skillsTab SkillsTab? +---@return table +local function getGemNameSet(group, skillsTab) local set = {} for _, gem in ipairs(group.gemList or {}) do local name = gem.grantedEffect and gem.grantedEffect.name or gem.nameSpec @@ -4100,7 +4378,10 @@ function CompareTabClass:DrawSkills(vp, compareEntry) end -- Helper: compute Jaccard similarity between two gem name sets - local function groupSimilarity(setA, setB) +---@param setA table +---@param setB table +---@return number +local function groupSimilarity(setA, setB) local intersection = 0 local union = 0 local allKeys = {} @@ -4166,7 +4447,10 @@ function CompareTabClass:DrawSkills(vp, compareEntry) end -- Helper: check if gemA supports gemB (mirrors GemSelectControl:CheckSupporting) - local function checkSupporting(gemA, gemB) +---@param gemA CompareGem +---@param gemB CompareGem +---@return boolean +local function checkSupporting(gemA, gemB) -- Synthesized imbued-support entries lack gemData/supportEffect wiring, but by definition -- they support any active (non-support) gem in the group. if gemA.isImbuedSupport then @@ -4189,7 +4473,9 @@ function CompareTabClass:DrawSkills(vp, compareEntry) local gemLineHeight = 18 -- Helper: build the exact string drawGemList will render (used for both drawing and width measurement) - local function buildGemDisplayString(entry) +---@param entry CompareGemDisplayEntry +---@return string +local function buildGemDisplayString(entry) if entry.status == "missing" then return colorCodes.NEGATIVE .. "- " .. entry.name .. "^7" elseif entry.gem then @@ -4208,12 +4494,17 @@ function CompareTabClass:DrawSkills(vp, compareEntry) -- Helper: build aligned display lists for a matched pair of groups -- Common gems appear first, then additional, then missing - local function getGemName(gem) +---@param gem CompareGem +---@return string? +local function getGemName(gem) return gem.grantedEffect and gem.grantedEffect.name or gem.nameSpec end -- Helper: build an iterable gem list for a group that appends its imbued support (if any) - local function getGemsWithImbued(group, skillsTab) +---@param group CompareSocketGroup? +---@param skillsTab SkillsTab? +---@return CompareGem[] +local function getGemsWithImbued(group, skillsTab) if not group then return {} end local gems = {} for _, gem in ipairs(group.gemList or {}) do @@ -4226,7 +4517,13 @@ function CompareTabClass:DrawSkills(vp, compareEntry) return gems end - local function buildAlignedGemLists(pGroup, cGroup, pSet, cSet) +---@param pGroup CompareSocketGroup? +---@param cGroup CompareSocketGroup? +---@param pSet table +---@param cSet table +---@return CompareGemDisplayEntry[] primaryDisplay +---@return CompareGemDisplayEntry[] compareDisplay +local function buildAlignedGemLists(pGroup, cGroup, pSet, cSet) local pDisplay = {} local cDisplay = {} @@ -4291,7 +4588,13 @@ function CompareTabClass:DrawSkills(vp, compareEntry) end -- Helper: collect gem positions from a display list into gemEntries for hit-testing - local function collectGemEntries(gemEntries, displayList, xOffset, startY, group) +---@param gemEntries CompareGemHoverEntry[] +---@param displayList CompareGemDisplayEntry[] +---@param xOffset number +---@param startY number +---@param group CompareSocketGroup? +---@return number y +local function collectGemEntries(gemEntries, displayList, xOffset, startY, group) local y = startY for _, entry in ipairs(displayList) do if entry.gem then @@ -4303,7 +4606,13 @@ function CompareTabClass:DrawSkills(vp, compareEntry) end -- Helper: draw a list of gems (common, additional, missing) at a given x offset - local function drawGemList(displayList, xOffset, startY, highlightSet, gemTextWidth) +---@param displayList CompareGemDisplayEntry[] +---@param xOffset number +---@param startY number +---@param highlightSet table +---@param gemTextWidth number +---@return number y +local function drawGemList(displayList, xOffset, startY, highlightSet, gemTextWidth) local y = startY for _, entry in ipairs(displayList) do if entry.gem and highlightSet[entry.gem] then @@ -4320,14 +4629,21 @@ function CompareTabClass:DrawSkills(vp, compareEntry) end -- Build display lists once and measure widest primary-side content - local function getGroupLabel(group, idx) +---@param group CompareSocketGroup +---@param idx integer +---@return string +local function getGroupLabel(group, idx) local groupLabel = group.displayLabel or group.label or ("Group " .. idx) if group.slot then groupLabel = groupLabel .. " (" .. group.slot .. ")" end return groupLabel end - local function getGroupSlotIcon(skillsTab, idx, group) +---@param skillsTab SkillsTab? +---@param idx integer +---@param group CompareSocketGroup? +---@return unknown +local function getGroupSlotIcon(skillsTab, idx, group) local groupList = skillsTab and skillsTab.controls and skillsTab.controls.groupList if not groupList or not groupList.GetRowIcon or not group then return nil @@ -4339,10 +4655,18 @@ function CompareTabClass:DrawSkills(vp, compareEntry) local groupHeaderIconGap = 2 local groupHeaderTextIndent = groupHeaderIconSize + groupHeaderIconGap local groupHeaderTextX = groupHeaderX + groupHeaderTextIndent - local function getGroupHeaderWidth(group, idx) +---@param group CompareSocketGroup +---@param idx integer +---@return number +local function getGroupHeaderWidth(group, idx) return groupHeaderTextX + DrawStringWidth(18, "VAR", "^7" .. getGroupLabel(group, idx)) end - local function drawGroupHeader(skillsTab, group, idx, x, y) +---@param skillsTab SkillsTab? +---@param group CompareSocketGroup +---@param idx integer +---@param x number +---@param y number +local function drawGroupHeader(skillsTab, group, idx, x, y) local icon = getGroupSlotIcon(skillsTab, idx, group) local textX = x + groupHeaderTextIndent if icon then @@ -4493,6 +4817,14 @@ end -- ============================================================ -- CALCS TOOLTIP HELPERS (delegated to CompareCalcsHelpers) -- ============================================================ +---@param colData CalcSectionColumn +---@param rowLabel string? +---@param rowX number +---@param rowY number +---@param rowW number +---@param rowH number +---@param vp Rect +---@param compareEntry CompareEntry function CompareTabClass:DrawCalcsTooltip(colData, rowLabel, rowX, rowY, rowW, rowH, vp, compareEntry) local primaryLabel = self:GetShortBuildName(self.primaryBuild.buildName) calcsHelpers.DrawCalcsTooltip( @@ -4506,6 +4838,11 @@ end -- ============================================================ -- Draw the skill detail header area with labels for controls and text info lines +---@param vp Rect +---@param compareEntry CompareEntry +---@param headerHeight number +---@param primaryEnv table +---@param compareEnv table function CompareTabClass:DrawCalcsSkillHeader(vp, compareEntry, headerHeight, primaryEnv, compareEnv) local colWidth = m_floor((vp.width - 20) / 2) local leftX = vp.x + 4 @@ -4523,7 +4860,12 @@ function CompareTabClass:DrawCalcsSkillHeader(vp, compareEntry, headerHeight, pr y = y + rowH -- Draw labels next to each control row - local function drawLabel(label, x, cy, control) +---@param label string +---@param x number +---@param cy number +---@param control Control +---@return boolean +local function drawLabel(label, x, cy, control) if control.shown == false or (type(control.shown) == "function" and not control:IsShown()) then return false end @@ -4636,6 +4978,12 @@ function CompareTabClass:DrawCalcsSkillHeader(vp, compareEntry, headerHeight, pr DrawImage(nil, vp.x + 2, vp.y + headerHeight - 2, vp.width - 4, 2) end +---@param self CompareTab +---@param colData CalcSectionColumn? +---@param primaryActor Actor +---@param compareActor Actor +---@param compareEntry CompareEntry +---@return boolean local function calcRowMatchesBetweenBuilds(self, colData, primaryActor, compareActor, compareEntry) if not colData or not colData.format then return false end local primaryFormatOk, primaryFormattedValue = pcall(formatCalcStr, colData.format, primaryActor, colData) @@ -4674,6 +5022,10 @@ local function calcRowMatchesBetweenBuilds(self, colData, primaryActor, compareA return true end +---@param subSecData CalcSectionData? +---@param primaryActor Actor +---@param compareActor Actor +---@return boolean local function subSectionExtraMatches(subSecData, primaryActor, compareActor) if not subSecData or not subSecData.extra then return true end local primaryExtraOk, primaryExtraText = pcall(formatCalcStr, subSecData.extra, primaryActor) @@ -4681,6 +5033,8 @@ local function subSectionExtraMatches(subSecData, primaryActor, compareActor) return primaryExtraOk and compareExtraOk and tostring(primaryExtraText or "") == tostring(compareExtraText or "") end +---@param vp Rect +---@param compareEntry CompareEntry function CompareTabClass:DrawCalcs(vp, compareEntry) -- Use calcsEnv for both values and tooltips (has breakdown data + respects Calcs skill selection) local primaryEnv = self.primaryBuild.calcsTab.calcsEnv @@ -4933,6 +5287,9 @@ end -- ============================================================ -- CONFIG VIEW -- ============================================================ +---@param vp Rect +---@param compareEntry CompareEntry +---@param headerOnly boolean? function CompareTabClass:DrawConfig(vp, compareEntry, headerOnly) local rowHeight = LAYOUT.configRowHeight local columnHeaderHeight = LAYOUT.configColumnHeaderHeight diff --git a/src/Classes/ConfigSetListControl.lua b/src/Classes/ConfigSetListControl.lua index f9440970822..3113cdd44ab 100644 --- a/src/Classes/ConfigSetListControl.lua +++ b/src/Classes/ConfigSetListControl.lua @@ -8,8 +8,15 @@ local t_remove = table.remove local m_max = math.max ---@class ConfigSetListControl: ListControl +---@field configTab ConfigTab +---@field selIndex? integer +---@field selValue? integer local ConfigSetListClass = newClass("ConfigSetListControl", "ListControl") +---@param anchor? Anchor +---@param rect? Rect +---@param configTab ConfigTab +---@return ConfigSetListControl function ConfigSetListClass:ConfigSetListControl(anchor, rect, configTab) self:ListControl(anchor, rect, 16, "VERTICAL", true, configTab.configSetOrderList) self.configTab = configTab @@ -44,6 +51,8 @@ function ConfigSetListClass:ConfigSetListControl(anchor, rect, configTab) return self end +---@param configSet ConfigSet +---@param addOnName? boolean function ConfigSetListClass:RenameSet(configSet, addOnName) local controls = { } controls.label = new("LabelControl"):LabelControl(nil, {0, 20, 0, 16}, "^7Enter name for this config set:") @@ -72,6 +81,10 @@ function ConfigSetListClass:RenameSet(configSet, addOnName) main:OpenPopup(370, 100, configSet.title and "Rename" or "Set Name", controls, "save", "edit", "cancel") end +---@param column integer +---@param index integer +---@param configSetId integer +---@return string? function ConfigSetListClass:GetRowValue(column, index, configSetId) local configSet = self.configTab.configSets[configSetId] if column == 1 then @@ -83,6 +96,9 @@ function ConfigSetListClass:OnOrderChange() self.configTab.modFlag = true end +---@param index integer +---@param configSetId integer +---@param doubleClick? boolean function ConfigSetListClass:OnSelClick(index, configSetId, doubleClick) if doubleClick and configSetId ~= self.configTab.activeConfigSetId then self.configTab:SetActiveConfigSet(configSetId) @@ -90,6 +106,8 @@ function ConfigSetListClass:OnSelClick(index, configSetId, doubleClick) end end +---@param index integer +---@param configSetId integer function ConfigSetListClass:OnSelDelete(index, configSetId) local configSet = self.configTab.configSets[configSetId] if #self.list > 1 then @@ -107,6 +125,9 @@ function ConfigSetListClass:OnSelDelete(index, configSetId) end end +---@param index integer +---@param configSetId integer +---@param key string function ConfigSetListClass:OnSelKeyDown(index, configSetId, key) if key == "F2" then self:RenameSet(self.configTab.configSets[configSetId]) diff --git a/src/Classes/ConfigTab.lua b/src/Classes/ConfigTab.lua index 3d1f007cd63..40bd825ea87 100644 --- a/src/Classes/ConfigTab.lua +++ b/src/Classes/ConfigTab.lua @@ -13,14 +13,23 @@ local varList = require("Modules.ConfigOptions") local configVisibility = require("Modules.ConfigVisibility") local configModBrowser = require("Modules.ConfigModBrowser") +---@class CustomModBlockData +---@field title? string +---@field enabled? boolean +---@field text? string + ---@class CustomModBlockControl: ControlHost, Control +---@field configTab ConfigTab +---@field blockIndex integer +---@field blockData CustomModBlockData local CustomModBlockClass = newClass("CustomModBlockControl", "ControlHost", "Control") ---@param anchor Anchor? ---@param rect Rect? ---@param configTab ConfigTab ---@param blockIndex integer ----@param blockData any +---@param blockData CustomModBlockData +---@return CustomModBlockControl function CustomModBlockClass:CustomModBlockControl(anchor, rect, configTab, blockIndex, blockData) self:Control(anchor, rect) self:ControlHost() @@ -95,12 +104,15 @@ function CustomModBlockClass:CustomModBlockControl(anchor, rect, configTab, bloc return self end +---@return number width +---@return number height function CustomModBlockClass:GetSize() local textHeight = self.controls.textEdit and self.controls.textEdit.height or 80 self.height = 22 + textHeight + 4 return 344, self.height end +---@return boolean|Control? function CustomModBlockClass:IsMouseOver() if not self:IsShown() then return @@ -108,6 +120,9 @@ function CustomModBlockClass:IsMouseOver() return self:IsMouseInBounds() or self:GetMouseOverControl() end +---@param key string +---@param doubleClick? boolean +---@return Control? function CustomModBlockClass:OnKeyDown(key, doubleClick) if not self:IsShown() or not self:IsEnabled() then return @@ -118,6 +133,7 @@ function CustomModBlockClass:OnKeyDown(key, doubleClick) end end +---@param viewPort Rect function CustomModBlockClass:Draw(viewPort) if not self:IsShown() then return @@ -126,10 +142,44 @@ function CustomModBlockClass:Draw(viewPort) self:DrawControls(viewPort) end +---@alias ConfigValue string|number|boolean +---@alias ConfigConditionalOption string|integer + +---@class ConfigSet +---@field id integer +---@field title? string +---@field input table +---@field placeholder table +---@field customModsList CustomModBlockData[] + +---@class ConfigTabUndoState +---@field input table +---@field customModsList CustomModBlockData[] + ---@class ConfigTab: UndoHandler, ControlHost, Control +---@field build Build +---@field input table +---@field placeholder table +---@field defaultState table +---@field configSets table +---@field configSetOrderList integer[] +---@field activeConfigSetId integer +---@field enemyLevel integer +---@field sectionList SectionControl[] +---@field varControls table +---@field modList ModList +---@field enemyModList ModList +---@field toggleConfigs boolean +---@field calcFunc? fun(adjustments?: table, useFullDPS?: boolean): Output +---@field calcBase? Output +---@field customSection? SectionControl +---@field customModsBlockControls? CustomModBlockControl[] +---@field modFlag boolean +---@field [string] unknown local ConfigTabClass = newClass("ConfigTab", "UndoHandler", "ControlHost", "Control") ---@param build Build +---@return ConfigTab function ConfigTabClass:ConfigTab(build) self:UndoHandler() self:ControlHost() @@ -186,10 +236,14 @@ function ConfigTabClass:ConfigTab(build) self.toggleConfigs = not self.toggleConfigs end) + ---@param section SectionControl + ---@return boolean local function isCollapsed(section) return self:IsSectionCollapsed(section) end + ---@param varData table + ---@return boolean local function searchMatch(varData) local searchStr = self.controls.search.buf:lower():gsub("[%-%.%+%[%]%$%^%%%?%*]", "%%%0") if searchStr and searchStr:match("%S") then @@ -204,10 +258,14 @@ function ConfigTabClass:ConfigTab(build) end -- Override for Show All Configurations: when the toggle is on, show options that aren't on the shared exclusion list. + ---@param varData table + ---@return boolean local function isShowAllConfig(varData) return self.toggleConfigs and not configVisibility.isShowAllExcluded(varData) end + ---@param varData table + ---@return boolean local function implyCond(varData) local mainEnv = self.build.calcsTab.mainEnv if self.configSets[self.activeConfigSetId].input[varData.var] then @@ -228,6 +286,9 @@ function ConfigTabClass:ConfigTab(build) return false end + ---@param ifOption ConfigConditionalOption|ConfigConditionalOption[] + ---@param ifFunc fun(ifOption: ConfigConditionalOption): boolean + ---@return fun(): boolean local function listOrSingleIfOption(ifOption, ifFunc) return function() if type(ifOption) == "table" then @@ -241,6 +302,9 @@ function ConfigTabClass:ConfigTab(build) end end + ---@param ifOption ConfigConditionalOption|ConfigConditionalOption[] + ---@param ifFunc fun(ifOption: ConfigConditionalOption): string? + ---@return fun(): string? local function listOrSingleIfTooltip(ifOption, ifFunc) return function() if type(ifOption) == "table" then @@ -832,15 +896,22 @@ function ConfigTabClass:ConfigTab(build) end -- A collapsed section hides its contents, unless a search is active +---@param section SectionControl +---@return boolean function ConfigTabClass:IsSectionCollapsed(section) return section.collapsed and not self.controls.search.buf:match("%S") end +---@param xml table +---@param fileName string function ConfigTabClass:Load(xml, fileName) self.activeConfigSetId = 1 self.configSets = { } self.configSetOrderList = { 1 } + ---@param node table + ---@param configSetId integer + ---@return boolean? loadError local function setInputAndPlaceholder(node, configSetId) if node.elem == "Input" then if not node.attrib.name then @@ -938,6 +1009,9 @@ function ConfigTabClass:Load(xml, fileName) self:ResetUndo() end +---@param var string +---@param varType? string +---@return ConfigValue? function ConfigTabClass:GetDefaultState(var, varType) if self.configSets[self.activeConfigSetId].placeholder[var] ~= nil then return self.configSets[self.activeConfigSetId].placeholder[var] @@ -958,6 +1032,7 @@ function ConfigTabClass:GetDefaultState(var, varType) end end +---@param xml table function ConfigTabClass:Save(xml) xml.attrib = { activeConfigSet = tostring(self.activeConfigSetId) @@ -1021,6 +1096,8 @@ function ConfigTabClass:UpdateControls() self:UpdateCustomModsControls() end +---@param viewPort Rect +---@param inputEvents InputEvent[] function ConfigTabClass:Draw(viewPort, inputEvents) self.x = viewPort.x self.y = viewPort.y @@ -1205,6 +1282,8 @@ end function ConfigTabClass:ImportCalcSettings() local input = self.configSets[self.activeConfigSetId].input local calcsInput = self.build.calcsTab.input + ---@param old string + ---@param new string local function import(old, new) input[new] = calcsInput[old] calcsInput[old] = nil @@ -1239,6 +1318,7 @@ function ConfigTabClass:ImportCalcSettings() self:UpdateControls() end +---@return ConfigTabUndoState function ConfigTabClass:CreateUndoState() local configSet = self.configSets[self.activeConfigSetId] return { @@ -1247,6 +1327,7 @@ function ConfigTabClass:CreateUndoState() } end +---@param state ConfigTabUndoState|table function ConfigTabClass:RestoreUndoState(state) local configSet = self.configSets[self.activeConfigSetId] if type(state) == "table" and state.input then @@ -1277,6 +1358,9 @@ function ConfigTabClass:OpenConfigSetManagePopup() end -- Creates a new config set +---@param configSetId? integer +---@param title? string +---@return ConfigSet function ConfigTabClass:NewConfigSet(configSetId, title) local configSet = { id = configSetId, title = title, input = { }, placeholder = { }, customModsList = { { title = "Default", enabled = true, text = "" } } } if not configSetId then @@ -1334,6 +1418,8 @@ function ConfigTabClass:UpdateCustomModsControls() end -- Changes the active config set +---@param configSetId? integer +---@param init? boolean function ConfigTabClass:SetActiveConfigSet(configSetId, init) -- Initialize config sets if needed if not self.configSetOrderList[1] then diff --git a/src/Classes/Control.lua b/src/Classes/Control.lua index 22383af172c..5655ef6f619 100644 --- a/src/Classes/Control.lua +++ b/src/Classes/Control.lua @@ -35,9 +35,13 @@ local rect = { --]] ---@class Control ----@field enabled boolean | fun(...: any): boolean ----@field onFocusGained? fun() ----@field onFocusLost? fun() +---@field enabled Prop +---@field anchor AnchorState +---@field rectStart Rect +---@field hasFocus? boolean +---@field tabOrder? Control[] +---@field OnFocusGained? fun() +---@field OnFocusLost? fun() ---@field shown Prop ---@field x Prop? ---@field y Prop? @@ -48,10 +52,17 @@ local rect = { local ControlClass = newClass("Control") ---@alias Anchor [AnchorPoint, Control|ControlHost, AnchorPoint, boolean|nil] ----@alias Rect [Prop?,Prop?, Prop?, Prop?] +---@alias Rect [Prop?, Prop?, Prop?, Prop?] + +---@class AnchorState +---@field point? AnchorPoint +---@field other? Control|ControlHost +---@field otherPoint? AnchorPoint +---@field collapse? boolean ---@param anchor? Anchor ---@param rect? Rect +---@return Control function ControlClass:Control(anchor, rect) self.rectStart = rect or {0, 0, 0, 0} self.x, self.y, self.width, self.height = unpack(self.rectStart) @@ -69,7 +80,7 @@ end ---@alias Prop (fun(self: self): T) | T ---@param name string ----@return any value +---@return unknown value function ControlClass:GetProperty(name) if type(self[name]) == "function" then return self[name](self) @@ -78,6 +89,12 @@ function ControlClass:GetProperty(name) end end +---@param point AnchorPoint +---@param other Control|ControlHost +---@param otherPoint AnchorPoint +---@param x? Prop +---@param y? Prop +---@param collapse? boolean function ControlClass:SetAnchor(point, other, otherPoint, x, y, collapse) self.anchor.point = point self.anchor.other = other @@ -89,6 +106,8 @@ function ControlClass:SetAnchor(point, other, otherPoint, x, y, collapse) end end +---@return number x +---@return number y function ControlClass:GetPos() if self.anchor.collapse and self.anchor.other and not self.anchor.other:GetProperty("shown") then local x, y = self.anchor.other:GetPos() @@ -118,18 +137,23 @@ function ControlClass:GetPos() return x, y end +---@return number width +---@return number height function ControlClass:GetSize() return self:GetProperty("width"), self:GetProperty("height") end +---@return boolean function ControlClass:IsShown() return (not self.anchor.other or self.anchor.collapse or self.anchor.other:IsShown()) and self:GetProperty("shown") end +---@return boolean function ControlClass:IsEnabled() return self:GetProperty("enabled") end +---@return boolean function ControlClass:IsMouseInBounds() local x, y = self:GetPos() local width, height = self:GetSize() @@ -137,6 +161,7 @@ function ControlClass:IsMouseInBounds() return cursorX >= x and cursorY >= y and cursorX < x + width and cursorY < y + height end +---@param focus boolean function ControlClass:SetFocus(focus) if focus ~= self.hasFocus then if focus and self.OnFocusGained then @@ -148,6 +173,7 @@ function ControlClass:SetFocus(focus) end end +---@param master Control function ControlClass:AddToTabGroup(master) if master.tabOrder then t_insert(master.tabOrder, self) @@ -157,6 +183,8 @@ function ControlClass:AddToTabGroup(master) self.tabOrder = master.tabOrder end +---@param step integer +---@return Control function ControlClass:TabAdvance(step) if self.tabOrder then local index = isValueInArray(self.tabOrder, self) diff --git a/src/Classes/ControlHost.lua b/src/Classes/ControlHost.lua index 83563f758b4..ca35325c8d1 100644 --- a/src/Classes/ControlHost.lua +++ b/src/Classes/ControlHost.lua @@ -5,13 +5,17 @@ -- ---@class ControlHost +---@field controls table +---@field selControl? Control local ControlHostClass = newClass("ControlHost") +---@return ControlHost function ControlHostClass:ControlHost() self.controls = {} return self end +---@param newSelControl? Control function ControlHostClass:SelectControl(newSelControl) if self.selControl == newSelControl then return @@ -28,6 +32,7 @@ function ControlHostClass:SelectControl(newSelControl) end end +---@return Control? function ControlHostClass:GetMouseOverControl() for _, control in pairs(self.controls) do if control.IsMouseOver and control:IsMouseOver() then @@ -46,6 +51,13 @@ function ControlHostClass:GetMouseOverControl() end end +---@class InputEvent +---@field type "KeyDown"|"KeyUp"|"Char" +---@field key string +---@field doubleClick? boolean + +---@param inputEvents InputEvent[] +---@param viewPort Rect function ControlHostClass:ProcessControlsInput(inputEvents, viewPort) local processedImbuedControl for id, event in ipairs(inputEvents) do @@ -106,6 +118,8 @@ function ControlHostClass:ProcessControlsInput(inputEvents, viewPort) end end +---@param viewPort Rect +---@param selControl? Control function ControlHostClass:DrawControls(viewPort, selControl) for _, control in pairs(self.controls) do if control:IsShown() and control.Draw then diff --git a/src/Classes/DraggerControl.lua b/src/Classes/DraggerControl.lua index 3aede080850..caccbf5e85d 100644 --- a/src/Classes/DraggerControl.lua +++ b/src/Classes/DraggerControl.lua @@ -3,9 +3,33 @@ -- Class: Dragger Button Control -- Dragger button control. -- +---@class CursorPosition +---@field X number +---@field Y number + ---@class DraggerControl: Control, TooltipHost +---@field label Prop +---@field onKeyDown? fun(position: CursorPosition) +---@field onKeyUp? fun(offset: CursorPosition) +---@field onRightClick? fun(position: CursorPosition) +---@field onHover? fun(): Control? +---@field forceTooltip? boolean +---@field image? ImageHandle +---@field clicked? boolean +---@field dragging? boolean +---@field cursorX number +---@field cursorY number local DraggerClass = newClass("DraggerControl", "Control", "TooltipHost") +---@param anchor? Anchor +---@param rect? Rect +---@param label Prop +---@param onKeyDown? fun(position: CursorPosition) +---@param onKeyUp? fun(offset: CursorPosition) +---@param onRightClick? fun(position: CursorPosition) +---@param onHover? fun(): Control? +---@param forceTooltip? boolean +---@return DraggerControl function DraggerClass:DraggerControl(anchor, rect, label, onKeyDown, onKeyUp, onRightClick, onHover, forceTooltip) self:Control(anchor, rect) self:TooltipHost() @@ -20,6 +44,7 @@ function DraggerClass:DraggerControl(anchor, rect, label, onKeyDown, onKeyUp, on return self end +---@param path? string function DraggerClass:SetImage(path) if path then self.image = NewImageHandle() @@ -29,6 +54,7 @@ function DraggerClass:SetImage(path) end end +---@return boolean function DraggerClass:IsMouseOver() if not self:IsShown() then return false @@ -36,6 +62,9 @@ function DraggerClass:IsMouseOver() return self:IsMouseInBounds() end +---@param viewPort Rect +---@param noTooltip? boolean +---@return Control? function DraggerClass:Draw(viewPort, noTooltip) local x, y = self:GetPos() local width, height = self:GetSize() @@ -108,6 +137,8 @@ function DraggerClass:Draw(viewPort, noTooltip) end end +---@param key string +---@return DraggerControl? function DraggerClass:OnKeyDown(key) if not self:IsShown() or not self:IsEnabled() or self:GetProperty("locked") then return @@ -126,6 +157,9 @@ function DraggerClass:OnKeyDown(key) end return self end + +---@param key string +---@return DraggerControl? function DraggerClass:OnKeyUp(key) if not self:IsShown() or not self:IsEnabled() or self:GetProperty("locked") then return diff --git a/src/Classes/DropDownControl.lua b/src/Classes/DropDownControl.lua index bb999c49a48..ea601f783f0 100644 --- a/src/Classes/DropDownControl.lua +++ b/src/Classes/DropDownControl.lua @@ -8,9 +8,30 @@ local m_min = math.min local m_max = math.max local m_floor = math.floor ----@class DropDownControl: Control, ControlHost, TooltipHost, SearchHost +---@class DropDownControl: Control, ControlHost, TooltipHost, SearchHost +---@field list T[] +---@field selFunc? fun(index: integer, data: T, doubleClick?: boolean) +---@field selIndex? integer +---@field dropHeight number +---@field dropped boolean +---@field dropUp boolean +---@field droppedWidth? number +---@field maxDroppedWidth? number +---@field enableDroppedWidth? boolean +---@field enableChangeBoxWidth? boolean +---@field hoverSel? integer +---@field hoverSelDrop? integer +---@field tag? string local DropDownClass = newClass("DropDownControl", "Control", "ControlHost", "TooltipHost", "SearchHost") +---@generic T +---@param anchor? Anchor +---@param rect? Rect +---@param list T[] +---@param selFunc? fun(index: integer, data: T, doubleClick?: boolean) +---@param tooltipText? Prop +---@param ignoreSearchOrder? boolean +---@return DropDownControl function DropDownClass:DropDownControl(anchor, rect, list, selFunc, tooltipText, ignoreSearchOrder) self:Control(anchor, rect) self:ControlHost() @@ -58,6 +79,8 @@ function DropDownClass:DropDownControl(anchor, rect, list, selFunc, tooltipText, end -- maps the actual dropdown row index (after eventual filtering) to the original (unfiltered) list index +---@param dropIndex integer +---@return integer? function DropDownClass:DropIndexToListIndex(dropIndex) -- 1:1 if not self:IsSearchActive() then @@ -79,6 +102,9 @@ function DropDownClass:DropIndexToListIndex(dropIndex) end -- maps the original (unfiltered) list index to the actual dropdown row index (after eventual filtering) +---@param listIndex integer +---@param default? integer +---@return integer function DropDownClass:ListIndexToDropIndex(listIndex, default) -- 1:1 if not self:IsSearchActive() then @@ -102,6 +128,7 @@ function DropDownClass:ListIndexToDropIndex(listIndex, default) return default end +---@return integer function DropDownClass:GetDropCount() if self:IsSearchActive() then return self:GetMatchCount() @@ -110,6 +137,12 @@ function DropDownClass:GetDropCount() end end +---@param label string +---@param searchInfo SearchInfo +---@param x number +---@param y number +---@param width number +---@param height number function DropDownClass:DrawSearchHighlights(label, searchInfo, x, y, width, height) if searchInfo and searchInfo.matches then local startX = 0 @@ -132,7 +165,8 @@ function DropDownClass:DrawSearchHighlights(label, searchInfo, x, y, width, heig end end - +---@param value T +---@param key? string function DropDownClass:SelByValue(value, key) for index, listVal in ipairs(self.list) do if type(listVal) == "table" then @@ -149,14 +183,19 @@ function DropDownClass:SelByValue(value, key) end end +---@param key string +---@return T? function DropDownClass:GetSelValueByKey(key) return self.list[self.selIndex][key] end +---@return T? function DropDownClass:GetSelValue() return self.list[self.selIndex] end +---@param newSel integer? +---@param noCallSelFunc? boolean function DropDownClass:SetSel(newSel, noCallSelFunc) newSel = m_max(1, m_min(self:GetDropCount(), newSel)) newSel = self:DropIndexToListIndex(newSel) @@ -175,6 +214,7 @@ function DropDownClass:ScrollSelIntoView() scrollBar:ScrollIntoView((self:ListIndexToDropIndex(self.selIndex, 1) - 2) * (height - 4), 3 * (height - 4)) end +---@return boolean function DropDownClass:IsMouseOver() if not self:IsShown() then return false @@ -209,6 +249,8 @@ function DropDownClass:IsMouseOver() return mOver, mOverComp end +---@param viewPort Rect +---@param noTooltip? boolean function DropDownClass:Draw(viewPort, noTooltip) local x, y = self:GetPos() local width, height = self:GetSize() @@ -424,6 +466,8 @@ function DropDownClass:Draw(viewPort, noTooltip) end end +---@param key string +---@return DropDownControl? function DropDownClass:OnChar(key) if not self:IsShown() or not self:IsEnabled() or not self.dropped then return @@ -431,6 +475,8 @@ function DropDownClass:OnChar(key) return self:OnSearchChar(key) end +---@param key string +---@return DropDownControl? function DropDownClass:OnKeyDown(key) if not self:IsShown() or not self:IsEnabled() then return @@ -463,6 +509,8 @@ function DropDownClass:OnKeyDown(key) return self.dropped and self end +---@param key string +---@return Control? function DropDownClass:OnKeyUp(key) if not self:IsShown() or not self:IsEnabled() then return @@ -521,10 +569,13 @@ function DropDownClass:OnKeyUp(key) return self.dropped and self end +---@param key? string +---@return integer? function DropDownClass:GetHoverIndex(key) return self.hoverSel or self.selIndex end +---@param textList T[] function DropDownClass:SetList(textList) if textList then wipeTable(self.list) @@ -534,6 +585,7 @@ function DropDownClass:SetList(textList) end end +---@param enable boolean function DropDownClass:CheckDroppedWidth(enable) self.enableDroppedWidth = enable if self.enableDroppedWidth and self.list then diff --git a/src/Classes/EditControl.lua b/src/Classes/EditControl.lua index 899f5b0bcfe..5024ea4e0a8 100644 --- a/src/Classes/EditControl.lua +++ b/src/Classes/EditControl.lua @@ -9,6 +9,8 @@ local m_floor = math.floor local protected_replace = "*" local utf8 = require('lua-utf8') +---@param str string +---@return string local function lastLine(str) local lastLineIndex = 1 while true do @@ -22,6 +24,8 @@ local function lastLine(str) return str:sub(lastLineIndex, -1) end +---@param str string +---@return integer local function newlineCount(str) local count = 0 local lastLineIndex = 1 @@ -38,8 +42,34 @@ end ---@class EditControl: ControlHost, Control, UndoHandler, TooltipHost ---@field inactiveText (fun(buf: string?): string)|string +---@field buf string +---@field caret integer +---@field sel? integer +---@field prompt? string +---@field placeholder? string +---@field filter string|fun(text: string): string? +---@field filterPattern string +---@field isNumeric? boolean +---@field limit? integer +---@field changeFunc? fun(text: string, isPlaceholder?: boolean) +---@field lineHeight? number +---@field defaultLineHeight? number +---@field allowZoom? boolean +---@field blinkStart number +---@field lastUndoState? string local EditClass = newClass("EditControl", "ControlHost", "Control", "UndoHandler", "TooltipHost") +---@param anchor? Anchor +---@param rect? Rect +---@param init? string +---@param prompt? string +---@param filter? string|fun(text: string): string? +---@param limit? integer +---@param changeFunc? fun(text: string, isPlaceholder?: boolean) +---@param lineHeight? number +---@param allowZoom? boolean +---@param clearable? boolean +---@return EditControl function EditClass:EditControl(anchor, rect, init, prompt, filter, limit, changeFunc, lineHeight, allowZoom, clearable) self:ControlHost() self:Control(anchor, rect) @@ -61,6 +91,7 @@ function EditClass:EditControl(anchor, rect, init, prompt, filter, limit, change self.selBGCol = "^xBBBBBB" self.blinkStart = GetTime() self.allowZoom = allowZoom + ---@return number local function buttonSize() local _, height = self:GetSize() return height - 4 @@ -98,6 +129,8 @@ function EditClass:EditControl(anchor, rect, init, prompt, filter, limit, change return self end +---@param text string|number +---@param notify? boolean function EditClass:SetText(text, notify) self.buf = tostring(text) self.caret = #self.buf + 1 @@ -108,6 +141,8 @@ function EditClass:SetText(text, notify) self:ResetUndo() end +---@param text string|number +---@param notify? boolean function EditClass:SetPlaceholder(text, notify) self.placeholder = tostring(text) if notify and self.changeFunc then @@ -115,6 +150,7 @@ function EditClass:SetPlaceholder(text, notify) end end +---@param bool? boolean function EditClass:SetProtected(bool) self.protected = bool or true -- set the font to be fixed to prevent strange @@ -122,6 +158,7 @@ function EditClass:SetProtected(bool) self.font = "FIXED" end +---@return boolean|Control? function EditClass:IsMouseOver() if not self:IsShown() then return false @@ -135,6 +172,7 @@ function EditClass:SelectAll() self:ScrollCaretIntoView() end +---@return string function EditClass:GetSelText() local left = m_min(self.caret, self.sel) local right = m_max(self.caret, self.sel) @@ -142,6 +180,7 @@ function EditClass:GetSelText() return newBuf end +---@param text string function EditClass:ReplaceSel(text) text = text:gsub("\r","") if text:match(self.filterPattern) then @@ -164,6 +203,7 @@ function EditClass:ReplaceSel(text) self:AddUndoState() end +---@param text string function EditClass:Insert(text) text = text:gsub("\r","") -- Remove any illegal chars from the "text" variable, to stop resulting in no text when an illegal character is found. @@ -186,6 +226,7 @@ function EditClass:Insert(text) self:AddUndoState() end +---@param zoom number function EditClass:ZoomText(zoom) if not self.allowZoom or not self.lineHeight then return @@ -233,6 +274,7 @@ function EditClass:ScrollCaretIntoView() end end +---@param offset integer function EditClass:MoveCaretVertically(offset) local pre = self.buf:sub(1, self.caret - 1) local caretX = DrawStringWidth(self.lineHeight, self.font, lastLine(pre)) @@ -243,6 +285,8 @@ function EditClass:MoveCaretVertically(offset) self.blinkStart = GetTime() end +---@param viewPort Rect +---@param noTooltip? boolean function EditClass:Draw(viewPort, noTooltip) local x, y = self:GetPos() local width, height = self:GetSize() @@ -440,6 +484,9 @@ function EditClass:OnFocusGained() end end +---@param key string +---@param doubleClick? boolean +---@return EditControl? function EditClass:OnKeyDown(key, doubleClick) if not self:IsShown() or not self:IsEnabled() then return @@ -665,6 +712,8 @@ function EditClass:OnKeyDown(key, doubleClick) return self end +---@param key string +---@return EditControl? function EditClass:OnKeyUp(key) if not self:IsShown() or not self:IsEnabled() then return @@ -726,6 +775,8 @@ function EditClass:OnKeyUp(key) return self.hasFocus and self end +---@param key string +---@return EditControl? function EditClass:OnChar(key) if not self:IsShown() or not self:IsEnabled() then return @@ -740,6 +791,7 @@ function EditClass:OnChar(key) return self end +---@return string function EditClass:CreateUndoState() local state = { buf = self.buf, @@ -749,6 +801,7 @@ function EditClass:CreateUndoState() return state end +---@param state string function EditClass:RestoreUndoState(state) self.buf = state.buf self.caret = state.caret diff --git a/src/Classes/ExtBuildListControl.lua b/src/Classes/ExtBuildListControl.lua index c00323a3a23..1e97fdc8555 100644 --- a/src/Classes/ExtBuildListControl.lua +++ b/src/Classes/ExtBuildListControl.lua @@ -10,9 +10,32 @@ local m_max = math.max local m_min = math.min local dkjson = require "dkjson" +---@class ExtBuildListProviderOption +---@field name string +---@field impl ExtBuildListProvider + ---@class ExtBuildListControl: ControlHost, Control +---@field importCode? string +---@field rowHeight integer +---@field scroll string +---@field forceTooltip boolean +---@field font Font +---@field importButtons ButtonControl[] +---@field previewButtons ButtonControl[] +---@field inTransition boolean +---@field contentHeight number +---@field tabs ButtonControl[] +---@field activeListProvider? ExtBuildListProvider +---@field buildProviders ExtBuildListProviderOption[] +---@field buildProvidersList string[] +---@field providerMaxLength number +---@field scrollOffsetV? number local ExtBuildListControlClass = newClass("ExtBuildListControl", "ControlHost", "Control") +---@param anchor? Anchor +---@param rect? Rect +---@param providers ExtBuildListProviderOption[] +---@return ExtBuildListControl function ExtBuildListControlClass:ExtBuildListControl(anchor, rect, providers) self:Control(anchor, rect) self:ControlHost() @@ -38,6 +61,7 @@ function ExtBuildListControlClass:ExtBuildListControl(anchor, rect, providers) return self end +---@param providerName string function ExtBuildListControlClass:Init(providerName) wipeTable(self.controls) wipeTable(self.tabs) @@ -136,10 +160,12 @@ function ExtBuildListControlClass:Init(providerName) end end +---@param importCode string function ExtBuildListControlClass:SetImportCode(importCode) self.importCode = importCode end +---@return boolean|Control? function ExtBuildListControlClass:IsMouseOver() if not self:IsShown() then return @@ -147,6 +173,9 @@ function ExtBuildListControlClass:IsMouseOver() return self:IsMouseInBounds() or self:GetMouseOverControl() end +---@param key string +---@param doubleClick? boolean +---@return Control? function ExtBuildListControlClass:OnKeyDown(key, doubleClick) if not self:IsShown() or not self:IsEnabled() then return @@ -160,6 +189,7 @@ function ExtBuildListControlClass:OnKeyDown(key, doubleClick) end end +---@param key string function ExtBuildListControlClass:OnKeyUp(key) if not self:IsShown() or not self:IsEnabled() then return @@ -172,6 +202,7 @@ function ExtBuildListControlClass:OnKeyUp(key) end end +---@param build ExtBuildListEntry function ExtBuildListControlClass:importBuild(build) if not (build.buildLink) then print("Build link is not provided.") @@ -186,6 +217,8 @@ function ExtBuildListControlClass:importBuild(build) end) end +---@param ascendancy string +---@return ImageHandle? function ExtBuildListControlClass:GetAscendancyImageHandle(ascendancy) if ascendancy then local fileName = s_format('Assets/ascendants/%s.jpeg', (ascendancy:gsub("^%l", string.lower))) @@ -203,6 +236,10 @@ function ExtBuildListControlClass:GetAscendancyImageHandle(ascendancy) end -- splits strings by word and maxWidth +---@param str string +---@param maxWidth number +---@param font Font +---@return string[] function ExtBuildListControlClass:splitStringByWidth(str, maxWidth, font) local words = {} for word in str:gmatch("%S+") do @@ -226,6 +263,11 @@ function ExtBuildListControlClass:splitStringByWidth(str, maxWidth, font) end -- wrappers for Drawing tools to apply scrolling +---@param imgHandle ImageHandle? +---@param left number +---@param top number +---@param width number +---@param height number function ExtBuildListControlClass:DrawImage(imgHandle, left, top, width, height) local _, y = self:GetPos() if top - self.controls.scrollBarV.offset >= y and top + height - self.controls.scrollBarV.offset < self.height() + y then @@ -233,6 +275,12 @@ function ExtBuildListControlClass:DrawImage(imgHandle, left, top, width, height) end end +---@param left number +---@param top number +---@param align string +---@param height number +---@param font Font +---@param text string function ExtBuildListControlClass:DrawString(left, top, align, height, font, text) local _, y = self:GetPos() if top - self.controls.scrollBarV.offset >= y and top + height - self.controls.scrollBarV.offset < self.height() + y then @@ -240,6 +288,8 @@ function ExtBuildListControlClass:DrawString(left, top, align, height, font, tex end end +---@param viewPort Rect +---@param noTooltip? boolean function ExtBuildListControlClass:Draw(viewPort, noTooltip) if self.activeListProvider == nil then return @@ -288,6 +338,9 @@ function ExtBuildListControlClass:Draw(viewPort, noTooltip) end end +---@param y number +---@param fillH number +---@return number local function addSeparator(y, fillH) y = y + 4 SetDrawColor(0.5, 0.5, 0.5) diff --git a/src/Classes/ExtBuildListProvider.lua b/src/Classes/ExtBuildListProvider.lua index 546dc8b32c7..a057379f889 100644 --- a/src/Classes/ExtBuildListProvider.lua +++ b/src/Classes/ExtBuildListProvider.lua @@ -10,9 +10,31 @@ -- .buildList [Needs to be filled in :GetBuilds with current list. buildName and buildLink fields are required.] -- .statusMsg [This can be used to print status message on the screen. Builds will not be listed if it has a value other than nil.] +---@class ExtBuildListEntry +---@field buildName string +---@field buildLink string +---@field author? string +---@field mainSkill? string +---@field ascendancy? string +---@field class? string +---@field previewLink? string +---@field ehp? number +---@field life? number +---@field es? number +---@field dps? number +---@field version? string +---@field metadata? table[] + ---@class ExtBuildListProvider +---@field listTitles? string[] +---@field buildList ExtBuildListEntry[] +---@field activeList? string +---@field statusMsg? string +---@field importCode? string local ExtBuildListProviderClass = newClass("ExtBuildListProvider") +---@param listTitles? string[] +---@return ExtBuildListProvider function ExtBuildListProviderClass:ExtBuildListProvider(listTitles) self.listTitles = listTitles self.buildList = {} @@ -21,6 +43,7 @@ function ExtBuildListProviderClass:ExtBuildListProvider(listTitles) return self end +---@return string? function ExtBuildListProviderClass:GetPageUrl() return nil end @@ -31,6 +54,7 @@ function ExtBuildListProviderClass:Activate() end end +---@param activeList string function ExtBuildListProviderClass:SetActiveList(activeList) if self.listTitles then for _, value in ipairs(self.listTitles) do @@ -42,22 +66,27 @@ function ExtBuildListProviderClass:SetActiveList(activeList) end end +---@return string? function ExtBuildListProviderClass:GetActiveList() return self.activeList end +---@return string[]? function ExtBuildListProviderClass:GetListTitles() return self.listTitles end +---@return string? function ExtBuildListProviderClass:GetActivePageUrl() return nil end +---@return ExtBuildListEntry[] function ExtBuildListProviderClass:GetBuilds() return {} end +---@param importCode string function ExtBuildListProviderClass:SetImportCode(importCode) self.importCode = importCode end diff --git a/src/Classes/FolderListControl.lua b/src/Classes/FolderListControl.lua index 8e0abcd4066..8b87f4a8917 100644 --- a/src/Classes/FolderListControl.lua +++ b/src/Classes/FolderListControl.lua @@ -6,9 +6,24 @@ local ipairs = ipairs local t_insert = table.insert +---@class FolderListEntry +---@field name string +---@field fullFileName string +---@field modified integer + ---@class FolderListControl: ListControl +---@field subPath string +---@field sortMode "NAME"|"EDITED" +---@field onChangeCallback? fun(newSubPath: string) +---@field selIndex? integer +---@field selValue? FolderListEntry local FolderListClass = newClass("FolderListControl", "ListControl") +---@param anchor? Anchor +---@param rect? Rect +---@param subPath? string +---@param onChange? fun() +---@return FolderListControl function FolderListClass:FolderListControl(anchor, rect, subPath, onChange) self:ListControl(anchor, rect, 16, "VERTICAL", false, { }) self.subPath = subPath or "" @@ -67,22 +82,29 @@ function FolderListClass:BuildList() if self.Redraw then self:Redraw() end end +---@param folderName string function FolderListClass:OpenFolder(folderName) self.controls.path:SetSubPath(self.subPath .. folderName .. "/") end - +---@param column integer +---@param index integer +---@param folder FolderListEntry +---@return string? function FolderListClass:GetRowValue(column, index, folder) if column == 1 then return folder.name end end - +---@param index integer +---@param folder FolderListEntry +---@param doubleClick? boolean function FolderListClass:OnSelClick(index, folder, doubleClick) if doubleClick then self:OpenFolder(folder.name) end end - +---@param index integer +---@param folder FolderListEntry function FolderListClass:OnSelDelete(index, folder) if NewFileSearch(folder.fullFileName.."/*") or NewFileSearch(folder.fullFileName.."/*", true) then main:OpenMessagePopup("Delete Folder", "The folder is not empty.") diff --git a/src/Classes/GemSelectControl.lua b/src/Classes/GemSelectControl.lua index e87c36ecbc2..b423483308c 100644 --- a/src/Classes/GemSelectControl.lua +++ b/src/Classes/GemSelectControl.lua @@ -16,6 +16,16 @@ local toolTipText = "Prefix tag searches with a colon and exclude tags with a da local imbuedTooltipText = "\"Socketed in\" item must be set in order to add an imbued support.\nOnly one imbued support is allowed per item." ---@class GemSelectControl: EditControl +---@field skillsTab SkillsTab +---@field index integer +---@field imbuedSelect boolean +---@field gems table +---@field list string[] +---@field mode string +---@field forceTooltip? boolean +---@field gemChangeFunc fun(gemId: string|table?, addUndo?: boolean, focusLost?: boolean, bufMatchesGem?: boolean, slotName?: string) +---@field dpsBuildFlag boolean +---@field sortCache? table local GemSelectClass = newClass("GemSelectControl", "EditControl") ---@param anchor Anchor? @@ -23,8 +33,9 @@ local GemSelectClass = newClass("GemSelectControl", "EditControl") ---@param skillsTab SkillsTab ---@param index integer ---@param changeFunc fun(...) ----@param forceTooltip boolean ----@param imbued boolean +---@param forceTooltip? boolean +---@param imbued? boolean +---@return GemSelectControl function GemSelectClass:GemSelectControl(anchor, rect, skillsTab, index, changeFunc, forceTooltip, imbued) self:EditControl(anchor, rect, nil, nil, "^ %a':-") self.controls.scrollBar = new("ScrollBarControl"):ScrollBarControl({ "TOPRIGHT", self, "TOPRIGHT" }, {-1, 0, 18, 0}, (self.height - 4) * 4) @@ -67,6 +78,11 @@ function GemSelectClass:GemSelectControl(anchor, rect, skillsTab, index, changeF return self end +---@param calcFunc fun(adjustments?: table, useFullDPS?: boolean): Output +---@param gemData table +---@param useFullDPS? boolean +---@return Output output +---@return table gemInstance function GemSelectClass:CalcOutputWithThisGem(calcFunc, gemData, useFullDPS) local gemList = self.skillsTab.displayGroup.gemList local displayGemList = self.skillsTab.displayGroup.displayGemList @@ -145,6 +161,9 @@ function GemSelectClass:PopulateGemList() end end +---@param gemId string +---@param gemData table +---@return boolean function GemSelectClass:FilterSupport(gemId, gemData) local showSupportTypes = self.skillsTab.showSupportGemTypes local isLegacyAwakened = (gemData.grantedEffect.legacy and gemData.grantedEffect.plusVersionOf) @@ -162,6 +181,7 @@ function GemSelectClass:FilterSupport(gemId, gemData) or (showSupportTypes == "EXCEPTIONAL" and (isLegacyAwakened or gemData.tagString:match("Exceptional")))) end +---@param buf string function GemSelectClass:BuildList(buf) local searchTerm = "" local tagsList = {} @@ -372,6 +392,7 @@ function GemSelectClass:UpdateSortCache() self.dpsBuildFlag = true end +---@param gemList string[] function GemSelectClass:SortGemList(gemList) local sortCache = self.sortCache local gems = self.gems @@ -451,6 +472,9 @@ function GemSelectClass:DPSBuilder() sortCache.pendingGems = nil end +---@param setText? boolean +---@param addUndo? boolean +---@param focusLost? boolean function GemSelectClass:UpdateGem(setText, addUndo, focusLost) local gemId = self.list[m_max(self.selIndex, 1)] -- don't process unless the buffer equals an actual gem, whether typed, clicked, or navigated with arrows @@ -477,6 +501,8 @@ function GemSelectClass:ScrollSelIntoView() scrollBar:ScrollIntoView((self.selIndex - 2) * (height - 4), 3 * (height - 4)) end +---@return boolean mouseOver +---@return "BODY"|"DROP"? mouseOverComponent function GemSelectClass:IsMouseOver() if not self:IsShown() then return false @@ -500,6 +526,8 @@ function GemSelectClass:IsMouseOver() return mOver, mOverComp end +---@param viewPort Rect +---@param noTooltip? boolean function GemSelectClass:Draw(viewPort, noTooltip) self.sortPercentage = self.sortPercentage or "" if self.dpsBuildFlag then @@ -698,11 +726,15 @@ function GemSelectClass:Draw(viewPort, noTooltip) end end +---@param gemA table +---@param gemB table +---@return boolean function GemSelectClass:CheckSupporting(gemA, gemB) return (gemA.gemData.grantedEffect.support and not gemB.gemData.grantedEffect.support and gemA.supportEffect and gemA.supportEffect.isSupporting and gemA.supportEffect.isSupporting[gemB]) or (gemA.gemData.secondaryGrantedEffect and gemA.gemData.secondaryGrantedEffect.support and not gemB.gemData.grantedEffect.support and gemA.supportEffect and gemA.supportEffect.isSupporting and gemA.supportEffect.isSupporting[gemB]) end +---@param gemInstance table function GemSelectClass:AddGemTooltip(gemInstance) gemTooltip.AddGemTooltip(self.tooltip, self.skillsTab.build, gemInstance) end @@ -730,6 +762,9 @@ function GemSelectClass:OnFocusLost() end end +---@param key string +---@param doubleClick? boolean +---@return Control? function GemSelectClass:OnKeyDown(key, doubleClick) if not self:IsShown() or not self:IsEnabled() then return @@ -818,6 +853,8 @@ function GemSelectClass:OnKeyDown(key, doubleClick) return newSel == self.EditControl and self or newSel end +---@param key string +---@return Control? function GemSelectClass:OnKeyUp(key) if not self:IsShown() or not self:IsEnabled() then return diff --git a/src/Classes/GemTooltip.lua b/src/Classes/GemTooltip.lua index 4709a99827b..aa8546dcb56 100644 --- a/src/Classes/GemTooltip.lua +++ b/src/Classes/GemTooltip.lua @@ -8,6 +8,8 @@ local m_max = math.max ---@class GemTooltip local GemTooltip = { } +---@return number normal +---@return number large local function getFontSizes() return main.showFlavourText and 18 or 16, main.showFlavourText and 24 or 20 end @@ -22,11 +24,11 @@ local reservationMap = { ---@param tooltip Tooltip ---@param build Build ----@param gemInstance any ----@param grantedEffect any ----@param addLevel boolean ----@param addReq boolean ----@param mergeStatsFrom any +---@param gemInstance table +---@param grantedEffect table +---@param addLevel? boolean +---@param addReq? boolean +---@param mergeStatsFrom? table local function addCommonGemInfo(tooltip, build, gemInstance, grantedEffect, addLevel, addReq, mergeStatsFrom) local fontSizeBig = main.showFlavourText and 18 or 16 local displayInstance = gemInstance.displayEffect or gemInstance @@ -141,6 +143,9 @@ local function addCommonGemInfo(tooltip, build, gemInstance, grantedEffect, addL if gemInstance.quality > 0 then tooltip:AddLine(fontSizeBig, string.format("^x7F7F7FQuality: +%s%d%%", colorCodes.MAGIC, gemInstance.quality), "FONTIN SC") end + ---@param number number + ---@param suffix? string + ---@return string local function formatQuality(number, suffix) return colorCodes.MAGIC .. string.format("+%d%% Quality from %s", number, suffix) end @@ -206,9 +211,10 @@ end ---@class GemToolTipOptions ---@field skipRequirements? boolean + ---@param tooltip Tooltip ---@param build Build ----@param gemInstance any +---@param gemInstance table ---@param options? GemToolTipOptions function GemTooltip.AddGemTooltip(tooltip, build, gemInstance, options) options = options or { } diff --git a/src/Classes/ImportTab.lua b/src/Classes/ImportTab.lua index a44f91bc0aa..f674a8a73fc 100644 --- a/src/Classes/ImportTab.lua +++ b/src/Classes/ImportTab.lua @@ -15,12 +15,21 @@ local dkjson = require "dkjson" local influenceInfo = itemLib.influenceInfo.all +---@class RealmInfo +---@field label string +---@field id string +---@field realmCode string +---@field hostName string +---@field profileURL string + +---@type RealmInfo[] local realmList = { { label = "PC", id = "PC", realmCode = "pc", hostName = "https://www.pathofexile.com/", profileURL = "account/view-profile/" }, { label = "Xbox", id = "XBOX", realmCode = "xbox", hostName = "https://www.pathofexile.com/", profileURL = "account/view-profile/" }, { label = "Sony", id = "SONY", realmCode = "sony", hostName = "https://www.pathofexile.com/", profileURL = "account/view-profile/" }, } +---@param self ImportTab local function addOAuthControls(self) self.usingOauth = true self.isAuthorized = function() return main.api.authToken ~= nil end @@ -40,10 +49,12 @@ local function addOAuthControls(self) --- @type table self.characterList = {} + ---@return boolean local function fetchButtonEnabled() local realm = self.controls.accountRealm:GetSelValue() return not (realm and self.characterList[realm.realmCode]) end + ---@return string local function charImportStatus() if not self.isAuthorized() and not self.oauthTimer then return colorCodes.WARNING .. "Not authenticated" @@ -139,6 +150,9 @@ local function addOAuthControls(self) if not main.api.authToken then return end local realm = self.controls.accountRealm:GetSelValue() self.oauthLoading = true + ---@param body table? + ---@param err string? + ---@param timeNext integer? local function onResponse(body, err, timeNext) if not err then self.characterList[realm.realmCode] = body.characters @@ -202,6 +216,7 @@ local function addOAuthControls(self) end) self.controls.accountRealm:SelByValue(main.lastRealm or "PC", "id") + ---@return string local function fetchTextFunc() local realm = self.controls.accountRealm:GetSelValue() if realm and self.characterList[realm.realmCode] then @@ -214,7 +229,8 @@ local function addOAuthControls(self) self.controls.accountRealmFetchButton.enabled = fetchButtonEnabled -- league select - --- @param newLeague string + ---@param _ integer + ---@param newLeague string local function onLeagueChange(_, newLeague) local realm = self.controls.accountRealm:GetSelValue().realmCode if newLeague == "Any" then @@ -236,6 +252,9 @@ local function addOAuthControls(self) end -- import action controls + ---@param realmId string + ---@param league string + ---@param charName string local function saveDetails(realmId, league, charName) main.lastRealm = realmId self.lastRealm = realmId @@ -254,6 +273,8 @@ local function addOAuthControls(self) saveDetails(realm.id, league, selectedName) local deleteJewels = self.controls.charImportTreeClearJewels.state + ---@param data table? + ---@param errMsg string? local function importHandler(data, errMsg) if data and data.character then self.oauthErrCode = nil @@ -319,6 +340,7 @@ local function addOAuthControls(self) self.controls.charImportItemsIgnoreWeaponSwap = new("CheckBoxControl"):CheckBoxControl({ "LEFT", self.controls.charImportItems, "RIGHT" }, { 380, 0, 18 }, "Ignore weapon swap:", nil, "Ignore items and skills in weapon swap.", false) end +---@param self ImportTab local function addAccountNameControls(self) self.charImportMode = "GETACCOUNTNAME" self.charImportStatus = "Idle" @@ -471,6 +493,9 @@ local function addAccountNameControls(self) end ---@class ImportTab: ControlHost, Control +---@field build Build +---@field modFlag boolean +---@field [string] unknown local ImportTabClass = newClass("ImportTab", "ControlHost", "Control") ---@param build Build @@ -739,6 +764,8 @@ function ImportTabClass:TryFetchCharacterList() end end +---@param xml table +---@param fileName string function ImportTabClass:Load(xml, fileName) self.lastRealm = xml.attrib.lastRealm self.lastLeague = xml.attrib.lastLeague @@ -756,6 +783,7 @@ function ImportTabClass:Load(xml, fileName) self.lastCharacterHash = xml.attrib.lastCharacterHash end +---@param xml table function ImportTabClass:Save(xml) xml.attrib = { lastRealm = self.lastRealm, @@ -773,6 +801,8 @@ function ImportTabClass:Save(xml) xml.attrib.importLink = (xml.attrib.importLink and xml.attrib.importLink:len() < 100) and xml.attrib.importLink or nil end +---@param viewPort Rect +---@param inputEvents InputEvent[] function ImportTabClass:Draw(viewPort, inputEvents) self.x = viewPort.x self.y = viewPort.y @@ -786,6 +816,9 @@ function ImportTabClass:Draw(viewPort, inputEvents) self:DrawControls(viewPort) end +---@param json string +---@return table? data +---@return string? errMsg function ImportTabClass:ProcessSiteJSON(json) local func, errMsg = loadstring("return " .. jsonToLua(json)) if errMsg then @@ -811,6 +844,7 @@ function ImportTabClass:SaveAccountHistory() end end +---@param realm RealmInfo function ImportTabClass:DownloadPassiveTree(realm) self.charImportMode = "IMPORTING" self.charImportStatus = "Retrieving character passive tree..." @@ -849,6 +883,7 @@ function ImportTabClass:DownloadPassiveTree(realm) end) end +---@param realm RealmInfo function ImportTabClass:DownloadItems(realm) self.charImportMode = "IMPORTING" self.charImportStatus = "Retrieving character items..." @@ -884,7 +919,10 @@ function ImportTabClass:DownloadItems(realm) self:ImportItemsAndSkills(charData, clearItems, clearSkills, ignoreWeaponSwap) end) end +---@param realm RealmInfo function ImportTabClass:DownloadSiteCharacterList(realm) + ---@param league string + ---@return string function FindMatchingStandardLeague(league) -- Find a Standard league name for a given league name -- Reference https://api.pathofexile.com/league?realm=pc @@ -1166,6 +1204,9 @@ function ImportTabClass:ImportPassiveTreeAndJewels(charData, deleteJewels) -- Alternate trees don't have an identifier, so we're forced to look up something that is unique to that tree -- Hopefully this changes, because it's totally unmaintainable + ---@param className string + ---@param treeVersion string + ---@return boolean? local function isAscendancyInTree(className, treeVersion) local classes = main.tree[treeVersion].classes for _, class in pairs(classes) do @@ -1226,6 +1267,8 @@ function ImportTabClass:ImportPassiveTreeAndJewels(charData, deleteJewels) end self.build.configTab.varControls["resistancePenalty"]:SetSel(resistancePenaltyIndex) + ---@param dropdown DropDownControl + ---@param val string local function setSelByVal(dropdown, val) for i, v in ipairs(dropdown.list) do if v.val == val then @@ -1254,6 +1297,8 @@ end local SOCKET_GROUP_REIMPORT_KEY_SEPARATOR = "\31" +---@param socketGroup table +---@return string local function getSocketGroupReimportKey(socketGroup) -- Use a rarely-used separator to avoid accidental collisions when concatenating fields. local gemNameParts = { } @@ -1268,6 +1313,9 @@ local function getSocketGroupReimportKey(socketGroup) }, SOCKET_GROUP_REIMPORT_KEY_SEPARATOR) end +---@param socketGroup table +---@param isMainGroup boolean +---@return table local function snapshotSocketGroupReimportState(socketGroup, isMainGroup) local gemStates = { } for gemIndex, gem in ipairs(socketGroup.gemList) do @@ -1302,6 +1350,8 @@ local function snapshotSocketGroupReimportState(socketGroup, isMainGroup) } end +---@param gem table +---@param state table local function applyGemReimportState(gem, state) gem.enabled = state.enabled gem.count = state.count @@ -1321,6 +1371,8 @@ local function applyGemReimportState(gem, state) gem.enableGlobal2 = state.enableGlobal2 end +---@param socketGroup table +---@param state table local function applySocketGroupReimportState(socketGroup, state) socketGroup.enabled = state.enabled socketGroup.includeInFullDPS = state.includeInFullDPS @@ -1339,6 +1391,7 @@ end local GUARD_ITEM_SET = "Animate Guardian" -- Locates AG's item set from the import +---@return ItemSet function ImportTabClass:GetOrCreateGuardianItemSet() local itemsTab = self.build.itemsTab for _, itemSetId in ipairs(itemsTab.itemSetOrderList) do @@ -1354,6 +1407,7 @@ function ImportTabClass:GetOrCreateGuardianItemSet() end -- Allocates AG's item set for the AG skill gem. +---@param itemSetId integer function ImportTabClass:AssignGuardianItemSet(itemSetId) local itemsTab = self.build.itemsTab for _, socketGroup in ipairs(self.build.skillsTab.socketGroupList) do @@ -1494,6 +1548,10 @@ local rarityMap = { [0] = "NORMAL", "MAGIC", "RARE", "UNIQUE", [9] = "RELIC", [1 local slotMap = { ["Weapon"] = "Weapon 1", ["Offhand"] = "Weapon 2", ["Weapon2"] = "Weapon 1 Swap", ["Offhand2"] = "Weapon 2 Swap", ["Helm"] = "Helmet", ["BodyArmour"] = "Body Armour", ["Gloves"] = "Gloves", ["Boots"] = "Boots", ["Amulet"] = "Amulet", ["Ring"] = "Ring 1", ["Ring2"] = "Ring 2", ["Ring3"] = "Ring 3", ["Belt"] = "Belt", ["BrequelGrafts"] = "Graft 1", ["BrequelGrafts2"] = "Graft 2", } +---@param itemData GGGItem +---@param slotName? string +---@param ignoreWeaponSwap? boolean +---@param itemSetId? integer function ImportTabClass:ImportItem(itemData, slotName, ignoreWeaponSwap, itemSetId) if not slotName then if itemData.inventoryId == "PassiveJewels" then @@ -1820,6 +1878,9 @@ function ImportTabClass:ImportItem(itemData, slotName, ignoreWeaponSwap, itemSet end end +---@param item Item +---@param socketedItems GGGItem[] +---@param slotName string function ImportTabClass:ImportSocketedItems(item, socketedItems, slotName) -- Build socket group list local itemSocketGroupList = { } @@ -1898,6 +1959,7 @@ function ImportTabClass:ImportSocketedItems(item, socketedItems, slotName) end -- Return the index of the group with the most gems +---@return integer function ImportTabClass:GuessMainSocketGroup() local largestGroupSize = 0 local largestGroupIndex = 1 @@ -1910,10 +1972,14 @@ function ImportTabClass:GuessMainSocketGroup() return largestGroupIndex end +---@param x string +---@return string function HexToChar(x) return string.char(tonumber(x, 16)) end +---@param url string? +---@return string? function UrlDecode(url) if url == nil then return diff --git a/src/Classes/Item.lua b/src/Classes/Item.lua index 1618e93e018..2fb75b4e768 100644 --- a/src/Classes/Item.lua +++ b/src/Classes/Item.lua @@ -28,6 +28,10 @@ local catalystTags = { { "critical" }, } +---@param catalystId string +---@param mod Mod +---@param quality number +---@return number local function getCatalystScalar(catalystId, mod, quality) if mod.unscalable then return 1 @@ -61,6 +65,8 @@ local function getCatalystScalar(catalystId, mod, quality) return 1 end +---@param line ModLine +---@return ModLine local function normaliseModLine(line) return line:gsub("%d+%.?%d*", "#") :gsub("%(%-?#%-#%)", "#"):lower() @@ -69,6 +75,7 @@ end local uniqueModStatOrder +---@param modLines ModLine[] local function sortCraftedModLines(modLines) local sourceOrder = { } for index, modLine in ipairs(modLines) do @@ -89,8 +96,47 @@ end local influenceInfo = itemLib.influenceInfo.all ---@class Item +---@field raw string +---@field rawLines string[] +---@field name string +---@field namePrefix string +---@field nameSuffix string +---@field rarity string +---@field base? ItemBaseEntry +---@field baseName string +---@field itemLevel integer +---@field quality integer +---@field corrupted boolean +---@field crafted boolean +---@field implicit boolean +---@field fractured boolean +---@field synthesised boolean +---@field advancedCopy boolean +---@field allowDuplicateVariants boolean +---@field mutatedLines? table +---@field classRequirementModLines ModLine[] +---@field explicitModLines ModLine[] +---@field implicitModLines ModLine[] +---@field enchantModLines ModLine[] +---@field craftedModLines ModLine[] +---@field scourgeModLines ModLine[] +---@field crucibleModLines ModLine[] +---@field buffModLines ModLine[] +---@field modMagnitudeMods table +---@field variantList? table +---@field versionList? table +---@field baseModList ModList +---@field modList ModList +---@field slotModList table +---@field variantGroupSelections table +---@field sockets table[] +---@field isUnique boolean local ItemClass = newClass("Item") +---@param raw? string +---@param rarity? string +---@param highQuality? boolean +---@return Item function ItemClass:Item(raw, rarity, highQuality) if raw then self:ParseRaw(sanitiseText(raw), rarity, highQuality) @@ -139,6 +185,8 @@ local lineFlags = { -- uncommented local specialModifierFoundList = {} local inverseModifierFoundList = {} +---@param tagName string +---@param itemSlotName string local function getTagBasedModifiers(tagName, itemSlotName) local tag_name = tagName:lower() local slot_name = itemSlotName:lower():gsub(" ", "_") @@ -286,6 +334,9 @@ local function getTagBasedModifiers(tagName, itemSlotName) end -- Iterate over modifiers to see if specific substring is found (for conditional checking) +---@param substring string +---@param itemSlotName string +---@return boolean function ItemClass:FindModifierSubstring(substring, itemSlotName) local modLines = {} local substring, explicit = substring:gsub("explicit ", "") @@ -332,17 +383,25 @@ function ItemClass:FindModifierSubstring(substring, itemSlotName) return false end +---@param s string +---@return number? local function specToNumber(s) local n = s:match("^([%+%-]?[%d%.]+)") return n and tonumber(n) end +---@param groupId string +---@param variantId integer +---@return boolean function ItemClass:IsVariantGroupOptionEligible(groupId, variantId) local group = self.variantGroups and self.variantGroups[groupId] local versions = group and group[variantId] return versions and (versions[0] or self.selectedVersion and versions[self.selectedVersion]) or false end +---@param groupId string +---@param excludeSelected? boolean +---@return integer[] function ItemClass:GetVariantGroupOptions(groupId, excludeSelected) local options = { } if not self.variantGroups or not self.variantGroups[groupId] then @@ -421,6 +480,9 @@ end ---@field modId string? -- Parse raw item data and extract item name, base type, quality, and modifiers +---@param raw string +---@param rarity? string +---@param highQuality? boolean function ItemClass:ParseRaw(raw, rarity, highQuality) self.raw = raw self.name = "?" @@ -1551,12 +1613,18 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) if self.mutatedLines then -- Match both sides so the same checkbox can apply or revert the transformation. for origModId, foulModId in pairs(self.mutatedLines) do + ---@param modId string + ---@param newModId string + ---@param mutated table + ---@return boolean local function checkMod(modId, newModId, mutated) local originalMod = mutated and data.itemMods.Foulborn[modId] or data.itemMods.ItemExclusive[modId] if not originalMod then ConPrintf("mod not found while testing mutated mods %s, %s", modId, mutated) return end + ---@param lines ModLine[] + ---@return table local function findMatchingLines(lines) local matchingLines = {} local matchedLines = {} @@ -1639,10 +1707,9 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) end self.isUnique = self.rarity == "UNIQUE" or self.rarity == "RELIC" end - ---@param modId string The id which will be present on the removed mod lines ---@param newModId string Id of the new mod which is used to get the new mod lines ----@param mutatedValue boolean? Whether the new mod is a mutated line. Also determines what table the new mod is taken from. +---@param mutatedValue? boolean Whether the new mod is a mutated line. Also determines what table the new mod is taken from. function ItemClass:MutateMod(modId, newModId, mutatedValue) local newMod = mutatedValue and data.itemMods.Foulborn[newModId] or data.itemMods.ItemExclusive[newModId] if not newMod then @@ -1690,10 +1757,17 @@ function ItemClass:NormaliseQuality() end end +---@param mod Mod +---@param includeTags? table +---@param excludeTags? table +---@param baseTags? table +---@return number function ItemClass:GetModSpawnWeight(mod, includeTags, excludeTags, baseTags) local weight = 0 if self.base then baseTags = baseTags or self.base.tags + ---@param key string + ---@return boolean local function HasInfluenceTag(key) if self.base.influenceTags then for _, curInfluenceInfo in ipairs(influenceInfo) do @@ -1705,6 +1779,8 @@ function ItemClass:GetModSpawnWeight(mod, includeTags, excludeTags, baseTags) return false end + ---@param modAffix string + ---@return boolean local function HasMavenInfluence(modAffix) return modAffix:match("Elevated") end @@ -1751,6 +1827,8 @@ function ItemClass:GetModSpawnWeight(mod, includeTags, excludeTags, baseTags) return weight end +---@param mod Mod +---@return number function ItemClass:GetNecropolisModSpawnWeight(mod) local weight = 0 if self.base then @@ -1764,11 +1842,14 @@ function ItemClass:GetNecropolisModSpawnWeight(mod) return weight end +---@param mod Mod +---@return boolean function ItemClass:CheckIfModIsDelve(mod) return mod.affix == "Subterranean" or mod.affix == "of the Underground" end +---@return string function ItemClass:BuildRaw() local rawLines = { } t_insert(rawLines, "Rarity: " .. self.rarity) @@ -1839,11 +1920,16 @@ function ItemClass:BuildRaw() if self.memoryStrands then t_insert(rawLines, "Memory Strands: " .. self.memoryStrands) end + ---@param modLine ModLine + ---@return string local function writeModLine(modLine) local line = modLine.line + ---@param prefix string local function prependToAllLines(prefix) line = prefix .. line:gsub("\n", "\n" .. prefix) end + ---@param idList string[] + ---@return string local function makeIdSpec(idList) local ids = { } for id in pairsSortByKey(idList) do @@ -2117,6 +2203,8 @@ function ItemClass:Craft() self:BuildAndParseRaw() end +---@param modLine ModLine +---@return boolean function ItemClass:CheckModLineVariant(modLine) if self.usesVariantGroups then if modLine.versionList and (not self.selectedVersion or not modLine.versionList[self.selectedVersion]) then @@ -2145,6 +2233,8 @@ function ItemClass:CheckModLineVariant(modLine) or (self.hasAltVariant5 and modLine.variantList[self.variantAlt5]) end +---@param modLine ModLine +---@return integer function ItemClass:GetModLineVariantCount(modLine) if not self.allowDuplicateVariants or not modLine.variantList then return self:CheckModLineVariant(modLine) and 1 or 0 @@ -2163,6 +2253,7 @@ function ItemClass:GetModLineVariantCount(modLine) return count end -- Return the name of the slot this item is equipped in +---@return string? function ItemClass:GetPrimarySlot() if self.base.weapon then return "Weapon 1" @@ -2184,9 +2275,9 @@ end -- Calculate local modifiers, and removes them from the modifier list -- To be considered local, a modifier must be an exact flag match, and cannot have any tags (e.g. conditions, multipliers) -- Only the InSlot tag is allowed (for Adds x to x X Damage in X Hand modifiers) ----@param modList any +---@param modList ModList ---@param name string ----@param type "FLAG"|"MORE"|"BASE"|"INC" other mod types not handled +---@param type "FLAG"|"MORE"|"BASE"|"INC" ---@param flags integer ---@return boolean|number local function calcLocal(modList, name, type, flags) @@ -2219,6 +2310,9 @@ local function calcLocal(modList, name, type, flags) end -- Build list of modifiers in a given slot number while applying local modifiers and adding quality +---@param baseList ModList +---@param slotNum integer +---@return ModList function ItemClass:BuildModListForSlotNum(baseList, slotNum) local slotName = self:GetPrimarySlot() if slotNum ~= 1 then @@ -2519,6 +2613,9 @@ function ItemClass:BuildModListForSlotNum(baseList, slotNum) return { unpack(modList) } end +---@param item Item +---@param modLine ModLine +---@return Mod[] local function getRangedModList(item, modLine) if not modLine.range or not modLine.line:find("%((%-?%d+%.?%d*)%-(%-?%d+%.?%d*)%)") then return @@ -2560,6 +2657,7 @@ function ItemClass:BuildModList() end end end + ---@param modLine ModLine local function processModLine(modLine) if modLine.disabled then return @@ -2703,6 +2801,9 @@ function ItemClass:BuildModList() end end +---@param mod Mod +---@param includeTags? table +---@return boolean function ItemClass:CanHaveMod(mod, includeTags) local keyMap = { } includeTags = includeTags or { } diff --git a/src/Classes/ItemDBControl.lua b/src/Classes/ItemDBControl.lua index bc5c227bf3d..3f5dfc0fca4 100644 --- a/src/Classes/ItemDBControl.lua +++ b/src/Classes/ItemDBControl.lua @@ -11,6 +11,20 @@ local m_floor = math.floor ---@class ItemDBControl: ListControl +---@field itemsTab ItemsTab +---@field db ItemDBData +---@field dbType "RARE"|"UNIQUE" +---@field leagueList string[] +---@field typeList string[] +---@field slotList string[] +---@field sortMode string +---@field sortOrder table[] +---@field sortDropList table[] +---@field sortDetail? table +---@field listBuildFlag boolean +---@field leaguesAndTypesLoaded boolean +---@field listBuilder? thread +---@field listOutputRevision? integer local ItemDBClass = newClass("ItemDBControl", "ListControl") ---@class ItemDBData @@ -23,6 +37,7 @@ local ItemDBClass = newClass("ItemDBControl", "ListControl") ---@param itemsTab ItemsTab ---@param db ItemDBData ---@param dbType "RARE"|"UNIQUE" +---@return ItemDBControl function ItemDBClass:ItemDBControl(anchor, rect, itemsTab, db, dbType) self:ListControl(anchor, rect, 16, "VERTICAL", false) self.itemsTab = itemsTab @@ -92,6 +107,8 @@ function ItemDBClass:LoadLeaguesAndTypes() self.leaguesAndTypesLoaded = true end +---@param item Item +---@return boolean function ItemDBClass:DoesItemMatchFilters(item) if self.controls.slot.selIndex > 1 then local primarySlot = item:GetPrimarySlot() @@ -212,6 +229,7 @@ function ItemDBClass:DoesItemMatchFilters(item) return true end +---@param sortMode string function ItemDBClass:SetSortMode(sortMode) self.sortMode = sortMode self:BuildSortOrder() @@ -300,6 +318,7 @@ function ItemDBClass:ListBuilder() self.defaultText = "^7No items found that match those filters." end +---@param viewPort Rect function ItemDBClass:Draw(viewPort) if self.itemsTab.build.outputRevision ~= self.listOutputRevision then self.listBuildFlag = true @@ -327,12 +346,19 @@ function ItemDBClass:Draw(viewPort) self.ListControl.Draw(self, viewPort) end +---@param column integer +---@param index integer +---@param item Item +---@return string? function ItemDBClass:GetRowValue(column, index, item) if item and column == 1 then return colorCodes[item.rarity] .. item.name end end +---@param tooltip Tooltip +---@param index integer +---@param item Item function ItemDBClass:AddValueTooltip(tooltip, index, item) if main.popups[1] then tooltip:Clear() @@ -343,10 +369,18 @@ function ItemDBClass:AddValueTooltip(tooltip, index, item) end end +---@param index integer +---@param item Item +---@return string +---@return Item function ItemDBClass:GetDragValue(index, item) return "Item", item end +---@param index integer +---@param item Item +---@param doubleClick? boolean +---@return boolean? function ItemDBClass:OnSelClick(index, item, doubleClick) if IsKeyDown("CTRL") then -- Add item @@ -386,10 +420,13 @@ function ItemDBClass:OnSelClick(index, item, doubleClick) end end +---@param index integer +---@param item Item function ItemDBClass:OnSelCopy(index, item) Copy(item.raw:gsub("\n","\r\n")) end +---@param key string function ItemDBClass:OnHoverKeyUp(key) if itemLib.wiki.matchesKey(key) then local item = self.ListControl:GetHoverValue() diff --git a/src/Classes/ItemListControl.lua b/src/Classes/ItemListControl.lua index a38fe5fbc5e..1b9637d78ce 100644 --- a/src/Classes/ItemListControl.lua +++ b/src/Classes/ItemListControl.lua @@ -8,12 +8,19 @@ local ipairs = ipairs local t_insert = table.insert ---@class ItemListControl: ListControl +---@field itemsTab ItemsTab +---@field defaultText string +---@field dragTargetList ListControl[] +---@field isMutable boolean +---@field loadoutListKey? string +---@field lastOutputRevision? integer local ItemListClass = newClass("ItemListControl", "ListControl") ---@param anchor Anchor? ---@param rect Rect? ---@param itemsTab ItemsTab ----@param forceTooltip boolean? +---@param forceTooltip? boolean +---@return ItemListControl function ItemListClass:ItemListControl(anchor, rect, itemsTab, forceTooltip) self:ListControl(anchor, rect, 16, "VERTICAL", true, itemsTab.itemOrderList, forceTooltip) self.itemsTab = itemsTab @@ -82,6 +89,7 @@ function ItemListClass:ItemListControl(anchor, rect, itemsTab, forceTooltip) return self end +---@return boolean changed function ItemListClass:UpdateLoadoutList() local list = { "Any Loadout", "Current Loadout", "Unused Items" } local listValues = { ["Any Loadout"] = true, ["Current Loadout"] = true, ["Unused Items"] = true } @@ -193,6 +201,7 @@ function ItemListClass:UpdateList() self.selValue = self.selIndex and self.list[self.selIndex] or nil end +---@param viewPort Rect function ItemListClass:Draw(viewPort) local loadoutListChanged = self:UpdateLoadoutList() local outputRevision = self.itemsTab.build and self.itemsTab.build.outputRevision @@ -203,6 +212,9 @@ function ItemListClass:Draw(viewPort) self.ListControl.Draw(self, viewPort) end +---@param jewelId integer +---@param excludeActiveSpec? boolean +---@return string? function ItemListClass:FindSocketedJewel(jewelId, excludeActiveSpec) if not self.itemsTab.items[jewelId] or self.itemsTab.items[jewelId].type ~= "Jewel" then return nil @@ -226,6 +238,9 @@ function ItemListClass:FindSocketedJewel(jewelId, excludeActiveSpec) return equipTree end +---@param jewelId integer +---@param excludeActiveSet? boolean +---@return string? function ItemListClass:FindEquippedAbyssJewel(jewelId, excludeActiveSet) if not self.itemsTab.items[jewelId] or self.itemsTab.items[jewelId].base.subType ~= "Abyss" then return nil @@ -247,6 +262,10 @@ function ItemListClass:FindEquippedAbyssJewel(jewelId, excludeActiveSet) return equipSet end +---@param column integer +---@param index integer +---@param itemId integer +---@return string? function ItemListClass:GetRowValue(column, index, itemId) local item = self.itemsTab.items[itemId] if column == 1 then @@ -265,6 +284,9 @@ function ItemListClass:GetRowValue(column, index, itemId) end end +---@param tooltip Tooltip +---@param index integer +---@param itemId integer function ItemListClass:AddValueTooltip(tooltip, index, itemId) if main.popups[1] then tooltip:Clear() @@ -276,10 +298,17 @@ function ItemListClass:AddValueTooltip(tooltip, index, itemId) end end +---@param index integer +---@param itemId integer +---@return string +---@return Item? function ItemListClass:GetDragValue(index, itemId) return "Item", self.itemsTab.items[itemId] end +---@param type string +---@param value Item +---@param source? ListControl function ItemListClass:ReceiveDrag(type, value, source) if type == "Item" then local newItem = new("Item"):Item(value.raw) @@ -296,6 +325,10 @@ function ItemListClass:OnOrderChange() self.itemsTab:AddUndoState() end +---@param index integer +---@param itemId integer +---@param doubleClick? boolean +---@return boolean? function ItemListClass:OnSelClick(index, itemId, doubleClick) local item = self.itemsTab.items[itemId] if IsKeyDown("CTRL") then @@ -332,11 +365,15 @@ function ItemListClass:OnSelClick(index, itemId, doubleClick) end end +---@param index integer +---@param itemId integer function ItemListClass:OnSelCopy(index, itemId) local item = self.itemsTab.items[itemId] Copy(item:BuildRaw():gsub("\n", "\r\n")) end +---@param index integer +---@param itemId integer function ItemListClass:OnSelDelete(index, itemId) local item = self.itemsTab.items[itemId] local equipSlot, equipSet = self.itemsTab:GetEquippedSlotForItem(item) @@ -377,6 +414,7 @@ function ItemListClass:OnSelDelete(index, itemId) end end +---@param key string function ItemListClass:OnHoverKeyUp(key) if itemLib.wiki.matchesKey(key) then local itemId = self.ListControl:GetHoverValue() @@ -385,4 +423,4 @@ function ItemListClass:OnHoverKeyUp(key) itemLib.wiki.openItem(item) end end -end \ No newline at end of file +end diff --git a/src/Classes/ItemSetListControl.lua b/src/Classes/ItemSetListControl.lua index 4448d74ae2b..edbda643e17 100644 --- a/src/Classes/ItemSetListControl.lua +++ b/src/Classes/ItemSetListControl.lua @@ -8,9 +8,21 @@ local t_remove = table.remove local m_max = math.max local s_format = string.format +---@class ItemSet +---@field id integer +---@field title? string +---@field slots? table + ---@class ItemSetListControl: ListControl +---@field itemsTab ItemsTab +---@field selIndex? integer +---@field selValue? integer local ItemSetListClass = newClass("ItemSetListControl", "ListControl") +---@param anchor? Anchor +---@param rect? Rect +---@param itemsTab ItemsTab +---@return ItemSetListControl function ItemSetListClass:ItemSetListControl(anchor, rect, itemsTab) self:ListControl(anchor, rect, 16, "VERTICAL", true, itemsTab.itemSetOrderList) self.itemsTab = itemsTab @@ -45,6 +57,8 @@ function ItemSetListClass:ItemSetListControl(anchor, rect, itemsTab) return self end +---@param itemSet ItemSet +---@param addOnName? boolean function ItemSetListClass:RenameSet(itemSet, addOnName) local controls = { } controls.label = new("LabelControl"):LabelControl(nil, {0, 20, 0, 16}, "^7Enter name for this item set:") @@ -73,6 +87,10 @@ function ItemSetListClass:RenameSet(itemSet, addOnName) main:OpenPopup(370, 100, itemSet.title and "Rename" or "Set Name", controls, "save", "edit", "cancel") end +---@param column integer +---@param index integer +---@param itemSetId integer +---@return string? function ItemSetListClass:GetRowValue(column, index, itemSetId) local itemSet = self.itemsTab.itemSets[itemSetId] if column == 1 then @@ -80,20 +98,33 @@ function ItemSetListClass:GetRowValue(column, index, itemSetId) end end +---@param tooltip Tooltip +---@param index integer +---@param itemSetId integer function ItemSetListClass:AddValueTooltip(tooltip, index, itemSetId) local itemSet = self.itemsTab.itemSets[itemSetId] tooltip:Clear() self.itemsTab:AddItemSetTooltip(tooltip, itemSet) end +---@param index integer +---@param itemSetId integer +---@return string dragType +---@return ItemSet dragValue function ItemSetListClass:GetDragValue(index, itemSetId) return "ItemList", self.itemsTab.itemSets[itemSetId] end +---@param type string +---@param value unknown +---@return boolean function ItemSetListClass:CanReceiveDrag(type, value) return type == "SharedItemList" end +---@param type string +---@param value SharedItemSet +---@param source? ListControl function ItemSetListClass:ReceiveDrag(type, value, source) if type == "SharedItemList" then local itemSet = self.itemsTab:NewItemSet() @@ -113,6 +144,9 @@ function ItemSetListClass:OnOrderChange() self.itemsTab.modFlag = true end +---@param index integer +---@param itemSetId integer +---@param doubleClick? boolean function ItemSetListClass:OnSelClick(index, itemSetId, doubleClick) if doubleClick and itemSetId ~= self.itemsTab.activeItemSetId then self.itemsTab:SetActiveItemSet(itemSetId) @@ -120,6 +154,8 @@ function ItemSetListClass:OnSelClick(index, itemSetId, doubleClick) end end +---@param index integer +---@param itemSetId integer function ItemSetListClass:OnSelDelete(index, itemSetId) local itemSet = self.itemsTab.itemSets[itemSetId] if #self.list > 1 then @@ -137,6 +173,9 @@ function ItemSetListClass:OnSelDelete(index, itemSetId) end end +---@param index integer +---@param itemSetId integer +---@param key string function ItemSetListClass:OnSelKeyDown(index, itemSetId, key) local itemSet = self.itemsTab.itemSets[itemSetId] if key == "F2" then diff --git a/src/Classes/ItemSlotControl.lua b/src/Classes/ItemSlotControl.lua index a3f658a78e3..a71639a03e6 100644 --- a/src/Classes/ItemSlotControl.lua +++ b/src/Classes/ItemSlotControl.lua @@ -8,7 +8,14 @@ local t_insert = table.insert local m_min = math.min local itemSlotHelper = require("Modules.ItemSlotHelper") ----@class ItemSlotControl +---@class ItemSlotControl: DropDownControl +---@field itemsTab ItemsTab +---@field slotName string +---@field slotNum integer +---@field nodeId? integer +---@field selItemId integer +---@field active boolean +---@field items table local ItemSlotClass = newClass("ItemSlotControl", "DropDownControl") ---@param anchor Anchor? @@ -18,6 +25,7 @@ local ItemSlotClass = newClass("ItemSlotControl", "DropDownControl") ---@param slotName string ---@param slotLabel string ---@param nodeId integer? +---@return ItemSlotControl function ItemSlotClass:ItemSlotControl(anchor, x, y, itemsTab, slotName, slotLabel, nodeId) self:DropDownControl(anchor, { x, y, 310, 20 }, {}, function(index, value) if self.items[index] ~= self.selItemId then @@ -69,6 +77,7 @@ function ItemSlotClass:ItemSlotControl(anchor, x, y, itemsTab, slotName, slotLab return self end +---@param selItemId integer function ItemSlotClass:SetSelItemId(selItemId) if self.nodeId then if self.itemsTab.build.spec then @@ -122,10 +131,16 @@ function ItemSlotClass:Populate() end end +---@param type string +---@param value Item +---@return boolean function ItemSlotClass:CanReceiveDrag(type, value) return type == "Item" and self.itemsTab:IsItemValidForSlot(value, self.slotName) end +---@param type string +---@param value Item +---@param source? ListControl function ItemSlotClass:ReceiveDrag(type, value, source) if value.id and self.itemsTab.items[value.id] then self:SetSelItemId(value.id) @@ -141,6 +156,7 @@ function ItemSlotClass:ReceiveDrag(type, value, source) self.itemsTab.build.buildFlag = true end +---@param viewPort Rect function ItemSlotClass:Draw(viewPort) local x, y = self:GetPos() local width, height = self:GetSize() @@ -160,6 +176,8 @@ function ItemSlotClass:Draw(viewPort) end end +---@param key string +---@return ItemSlotControl? function ItemSlotClass:OnKeyDown(key) if not self:IsShown() or not self:IsEnabled() then return @@ -171,6 +189,7 @@ function ItemSlotClass:OnKeyDown(key) return self.DropDownControl:OnKeyDown(key) end +---@param key string function ItemSlotClass:OnHoverKeyUp(key) if itemLib.wiki.matchesKey(key) then local index = self.DropDownControl:GetHoverIndex() diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index 0b3852aba58..91ed7b87705 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -67,12 +67,16 @@ for _, entry in pairs(data.flavourText) do end end +---@param item? Item +---@return boolean local function isAnointable(item) return item and item.base and not item.base.cannotBeAnointed and item.base.subType ~= "Talisman" and (item.canBeAnointed or item.base.type == "Amulet") end +---@return table sortList +---@return table sortStats local function buildModSortList() local sortList = { { label = "Default", stat = nil } } local sortStats = { } @@ -86,10 +90,30 @@ local function buildModSortList() end ---@class ItemsTab: UndoHandler, ControlHost, Control +---@field build Build +---@field modFlag boolean +---@field socketViewer PassiveTreeView +---@field tradeQuery TradeQuery +---@field items table +---@field itemOrderList integer[] +---@field slots table +---@field orderedSlots ItemSlotControl[] +---@field slotOrder table +---@field slotAnchor Control +---@field sockets table +---@field itemSets table +---@field itemSetOrderList integer[] +---@field activeItemSetId integer +---@field activeItemSet ItemSet +---@field lastSlot ItemSlotControl ---@field displayItem Item? +---@field displayItemTooltip Tooltip +---@field anchorDisplayItem Control +---@field showStatDifferences boolean +---@field [string] unknown local ItemsTabClass = newClass("ItemsTab", "UndoHandler", "ControlHost", "Control") - ---@param build Build +---@return ItemsTab function ItemsTabClass:ItemsTab(build) self:UndoHandler() self:ControlHost() @@ -143,6 +167,7 @@ function ItemsTabClass:ItemsTab(build) self.slotOrder = { } self.slotAnchor = new("Control"):Control({"TOPLEFT",self,"TOPLEFT"}, {96, 76, 310, 0}) local prevSlot = self.slotAnchor + ---@param slot ItemSlotControl local function addSlot(slot) prevSlot = slot self.slots[slot.slotName] = slot @@ -583,6 +608,7 @@ holding Shift will put it in the second.]]) for i, curInfluenceInfo in ipairs(influenceInfo) do influenceDisplayList[i + 1] = curInfluenceInfo.display end + ---@param influenceIndexList integer[] local function setDisplayItemInfluence(influenceIndexList) self.displayItem:ResetInfluence() if self.displayItem.HasElderShaperAndAllConquerorInfluences then @@ -756,11 +782,24 @@ holding Shift will put it in the second.]]) for i = 1, maxModCount do local prev = self.controls["displayItemAffix"..(i-1)] or self.controls.displayItemSectionAffix local drop, slider - local function verifyRange(range, index, drop) -- flips range if it will form discontinuous values + -- flips range if it will form discontinuous values + ---@param range number + ---@param index integer + ---@param drop DropDownControl + ---@return number + local function verifyRange(range, index, drop) local priorMod = index - 1 > 0 and self.displayItem.affixes[drop.list[drop.selIndex].modList[index - 1]] or nil local nextMod = index + 1 < #drop.list[drop.selIndex].modList and self.displayItem.affixes[drop.list[drop.selIndex].modList[index + 1]] or nil - local function flipRange(modA, modB) -- assumes all pairs are ordered the same - local function getMinMax(mod) -- gets first valid range from a mod + -- assumes all pairs are ordered the same + ---@param modA table + ---@param modB table + ---@return boolean + local function flipRange(modA, modB) + -- gets first valid range from a mod + ---@param mod table + ---@return number? min + ---@return number? max + local function getMinMax(mod) for _, line in ipairs(mod) do local min, max = line:match("%((%d[%d%.]*)%-(%d[%d%.]*)%)") if min and max then return tonumber(min), tonumber(max) end @@ -1034,6 +1073,7 @@ holding Shift will put it in the second.]]) self.controls.displayItemRangeLine.shown = function() return self.displayItem and self.displayItem.rangeLineList[1] ~= nil and not (main.showAllItemAffixes and self.displayItem.rarity == "UNIQUE") end + ---@return ModLine? local function getSelectedModLine() return self.displayItem and self.displayItem.rangeLineList[self.controls.displayItemRangeLine.selIndex] or nil end @@ -1213,6 +1253,8 @@ holding Shift will put it in the second.]]) return self end +---@param xml table +---@param dbFileName string function ItemsTabClass:Load(xml, dbFileName) self.activeItemSetId = 0 self.itemSets = { } @@ -1326,6 +1368,7 @@ function ItemsTabClass:Load(xml, dbFileName) self:ResetUndo() end +---@param xml table function ItemsTabClass:Save(xml) xml.attrib = { activeItemSet = tostring(self.activeItemSetId), @@ -1416,6 +1459,8 @@ function ItemsTabClass:Save(xml) end end +---@param viewPort Rect +---@param inputEvents InputEvent[] function ItemsTabClass:Draw(viewPort, inputEvents) self.x = viewPort.x self.y = viewPort.y @@ -1570,6 +1615,8 @@ function ItemsTabClass:Draw(viewPort, inputEvents) end -- Creates a new item set +---@param itemSetId? integer +---@return ItemSet function ItemsTabClass:NewItemSet(itemSetId) local itemSet = { id = itemSetId } if not itemSetId then @@ -1588,6 +1635,7 @@ function ItemsTabClass:NewItemSet(itemSetId) end -- Changes the active item set +---@param itemSetId integer function ItemsTabClass:SetActiveItemSet(itemSetId) local prevSet = self.activeItemSet if not self.itemSets[itemSetId] then @@ -1617,6 +1665,8 @@ function ItemsTabClass:SetActiveItemSet(itemSetId) end -- Equips the given item in the given item set +---@param item Item +---@param itemSetId integer function ItemsTabClass:EquipItemInSet(item, itemSetId) local itemSet = self.itemSets[itemSetId] local slotName = item:GetPrimarySlot() @@ -1678,11 +1728,17 @@ function ItemsTabClass:UpdateSockets() end -- Returns the slot control and equipped jewel for the given node ID +---@param nodeId integer +---@return ItemSlotControl? slot +---@return Item? item function ItemsTabClass:GetSocketAndJewelForNodeID(nodeId) return self.sockets[nodeId], self.items[self.sockets[nodeId].selItemId] end -- Adds the given item to the build's item list +---@param item Item +---@param noAutoEquip? boolean +---@param index? integer function ItemsTabClass:AddItem(item, noAutoEquip, index) if not item.id then -- Find an unused item ID @@ -1725,6 +1781,8 @@ end -- Given one half of a Forbidden Flame/Flesh pair, adds the other half with the same notable -- Both jewels are generated from the same sorted class/notable list +---@param item Item +---@return Item? function ItemsTabClass:AddForbiddenJewelCounterpart(item) local otherTitle = item and item.title and forbiddenJewelCounterpart[item.title] if not otherTitle or main.uniqueDB.loading or not item.variantList or not item.variant then @@ -1771,6 +1829,8 @@ function ItemsTabClass:AddForbiddenJewelCounterpart(item) end -- Keeps the Forbidden jewels in sync: manually editing one jewel moves the other counterpart to the same notable +---@param oldItem? Item +---@param item Item function ItemsTabClass:UpdateForbiddenJewelCounterpart(oldItem, item) local otherTitle = item and item.title and forbiddenJewelCounterpart[item.title] if not otherTitle or not oldItem or oldItem.title ~= item.title then @@ -1801,6 +1861,7 @@ function ItemsTabClass:UpdateForbiddenJewelCounterpart(oldItem, item) end -- Adds the current display item to the build's item list +---@param noAutoEquip? boolean function ItemsTabClass:AddDisplayItem(noAutoEquip) local item = self.displayItem local oldItem = item and item.id and self.items[item.id] @@ -1854,6 +1915,8 @@ function ItemsTabClass:SortItemList() end -- Deletes an item +---@param item Item +---@param deferUndoState? boolean function ItemsTabClass:DeleteItem(item, deferUndoState) for slotName, slot in pairs(self.slots) do if slot.selItemId == item.id then @@ -1903,6 +1966,10 @@ function ItemsTabClass:DeleteItem(item, deferUndoState) end end +---@param newItem Item +---@param copyEldritchImplicits boolean +---@param overwrite boolean +---@param sourceSlotName? string function ItemsTabClass:CopyAnointsAndEldritchImplicits(newItem, copyEldritchImplicits, overwrite, sourceSlotName) local newItemType = sourceSlotName or (newItem.base.weapon and "Weapon 1" or newItem.base.type) if self.activeItemSet[newItemType] then @@ -1945,6 +2012,8 @@ function ItemsTabClass:CopyAnointsAndEldritchImplicits(newItem, copyEldritchImpl end -- Attempt to create a new item from the given item raw text and sets it as the new display item +---@param itemRaw string +---@param normalise? boolean function ItemsTabClass:CreateDisplayItemFromRaw(itemRaw, normalise) local newItem = new("Item"):Item(itemRaw) if newItem.base then @@ -1957,6 +2026,10 @@ function ItemsTabClass:CreateDisplayItemFromRaw(itemRaw, normalise) end end +---@param index integer +---@param value? string|table +---@param legacyField string +---@param control DropDownControl function ItemsTabClass:SelectDisplayItemVariant(index, value, legacyField, control) if self.displayItem.usesVariantGroups then if not value or not value.variantId then @@ -2031,6 +2104,7 @@ function ItemsTabClass:UpdateDisplayItemVariantControls() end -- Sets the display item to the given item +---@param item? Item function ItemsTabClass:SetDisplayItem(item) self.displayItem = item if item then @@ -2120,6 +2194,7 @@ function ItemsTabClass:UpdateDisplayItemTooltip() self.displayItemTooltip.center = true end +---@param modLine? ModLine function ItemsTabClass:ToggleDisplayItemModLine(modLine) if not self.displayItem or not modLine then return @@ -2215,6 +2290,12 @@ function ItemsTabClass:UpdateAffixControls() self:UpdateCustomControls() end +---@param control DropDownControl +---@param item Item +---@param affixType? "Prefix"|"Suffix" +---@param outputTable "prefixes"|"suffixes" +---@param outputIndex integer +---@param powerCache table function ItemsTabClass:UpdateAffixControl(control, item, affixType, outputTable, outputIndex, powerCache) local extraTags = { } local excludeGroups = { } @@ -2317,6 +2398,8 @@ function ItemsTabClass:UpdateAffixControl(control, item, affixType, outputTable, testSubject:Craft() controlPowerCache = { } end + ---@param modList string[] + ---@return string local function pickModifierFromList(modList) -- pick mid tier modifier from a group if #modList == 1 then @@ -2325,6 +2408,8 @@ function ItemsTabClass:UpdateAffixControl(control, item, affixType, outputTable, return modList[1 + round((#modList - 1) * main.defaultItemAffixQuality)] end end + ---@param modId string + ---@return number local function getPower(modId) if controlPowerCache[modId] then return controlPowerCache[modId] @@ -2390,6 +2475,7 @@ function ItemsTabClass:UpdateAffixControl(control, item, affixType, outputTable, return getPower(modIdA) > getPower(modIdB) end) end + ---@return integer local function findSelectedIdx() for i, entry in ipairs(control.list) do if entry.modList then @@ -2490,6 +2576,9 @@ function ItemsTabClass:UpdateDisplayItemRangeLines() end end +---@param line string +---@param nodes? table +---@return string local function checkLineForAllocates(line, nodes) if nodes and string.match(line, "Allocates") then local nodeId = tonumber(string.match(line, "%d+")) @@ -2500,6 +2589,9 @@ local function checkLineForAllocates(line, nodes) return line end +---@param tooltip Tooltip +---@param mod table +---@param replaceImplicits? boolean function ItemsTabClass:AddModComparisonTooltip(tooltip, mod, replaceImplicits) local slotName = self.displayItem:GetPrimarySlot() local newItem = new("Item"):Item(self.displayItem:BuildRaw()) @@ -2525,6 +2617,9 @@ function ItemsTabClass:AddModComparisonTooltip(tooltip, mod, replaceImplicits) end -- Returns the first slot in which the given item is equipped +---@param item Item +---@return ItemSlotControl? slot +---@return ItemSet? itemSet function ItemsTabClass:GetEquippedSlotForItem(item) for _, slot in ipairs(self.orderedSlots) do if not slot.inactive then @@ -2541,6 +2636,8 @@ function ItemsTabClass:GetEquippedSlotForItem(item) end end +---@param item Item +---@return string function ItemsTabClass:GetComparisonSlotNameForItem(item) local equippedSlot = self:GetEquippedSlotForItem(item) if equippedSlot then @@ -2557,6 +2654,10 @@ function ItemsTabClass:GetComparisonSlotNameForItem(item) end -- Check if the given item could be equipped in the given slot, taking into account possible conflicts with currently equipped items -- For example, a shield is not valid for Weapon 2 if Weapon 1 is a staff, and a wand is not valid for Weapon 2 if Weapon 1 is a dagger +---@param item Item +---@param slotName string +---@param itemSet? ItemSet +---@return boolean? function ItemsTabClass:IsItemValidForSlot(item, slotName, itemSet) itemSet = itemSet or self.activeItemSet local slotType, slotId = slotName:match("^([%a ]+) (%d+)$") @@ -2621,6 +2722,8 @@ end -- Opens the item crafting popup function ItemsTabClass:CraftItem() local controls = { } + ---@param base ItemBaseEntry + ---@return Item local function makeItem(base) local item = new("Item"):Item() item.name = base.name @@ -2706,8 +2809,10 @@ function ItemsTabClass:CraftItem() end -- Opens the item text editor popup +---@param alsoAddItem? boolean function ItemsTabClass:EditDisplayItemText(alsoAddItem) local controls = { } + ---@return string local function buildRaw() local editBuf = controls.edit.buf if editBuf:match("^Item Class: .*\nRarity: ") or editBuf:match("^Rarity: ") then @@ -2761,6 +2866,7 @@ function ItemsTabClass:EditDisplayItemText(alsoAddItem) end -- Opens the item enchanting popup +---@param enchantSlot? integer function ItemsTabClass:EnchantDisplayItem(enchantSlot) self.enchantSlot = enchantSlot or 1 @@ -2788,6 +2894,7 @@ function ItemsTabClass:EnchantDisplayItem(enchantSlot) end end end + ---@param onlyUsedSkills? boolean local function buildSkillList(onlyUsedSkills) wipeTable(skillList) for skillName in pairs(enchantments) do @@ -2829,6 +2936,9 @@ function ItemsTabClass:EnchantDisplayItem(enchantSlot) end buildEnchantmentSourceList() buildEnchantmentList() + ---@param idx? integer + ---@param remove? boolean + ---@return Item local function enchantItem(idx, remove) local item = new("Item"):Item(self.displayItem:BuildRaw()) local index = idx or controls.enchantment.selIndex @@ -2854,6 +2964,12 @@ function ItemsTabClass:EnchantDisplayItem(enchantSlot) item:BuildAndParseRaw() return item end + ---@param entry table + ---@param stat string + ---@param calcFunc function + ---@param slotName string + ---@param useFullDPS? boolean + ---@return number local function getSortValue(entry, stat, calcFunc, slotName, useFullDPS) entry.sortValues = entry.sortValues or { } if entry.sortValues[stat] ~= nil then @@ -2880,6 +2996,8 @@ function ItemsTabClass:EnchantDisplayItem(enchantSlot) entry.sortValues[stat] = value return value end + ---@param stat? string + ---@param selectFirst? boolean local function applySort(stat, selectFirst) if not controls.enchantment or not controls.enchantment:IsShown() then return @@ -2971,10 +3089,9 @@ function ItemsTabClass:EnchantDisplayItem(enchantSlot) end) main:OpenPopup(605, 130, "Enchant Item", controls) end - ----Gets the name of the anointed node on an item ----@param item table @The item to get the anoint from ----@return string @The name of the anointed node, or nil if there is no anoint +---Gets the names of the anointed nodes on an item. +---@param item? Item @The item to get anoints from +---@return string[] @The names of the anointed nodes function ItemsTabClass:getAnoint(item) local result = { } if item then @@ -2991,10 +3108,9 @@ function ItemsTabClass:getAnoint(item) end return result end - ---Gets how many anoint slots are still missing on an item. ----@param item table @The item to inspect ----@return number @How many additional anoints can still be applied +---@param item? Item @The item to inspect +---@return integer @How many additional anoints can still be applied function ItemsTabClass:getMissingAnointCount(item) if not isAnointable(item) then return 0 @@ -3003,11 +3119,10 @@ function ItemsTabClass:getMissingAnointCount(item) local anointCount = #self:getAnoint(item) return m_max(0, maxAnoints - m_min(anointCount, maxAnoints)) end - ---Returns a copy of the currently displayed item, but anointed with a new node. ---Removes any existing enchantments before anointing. (Anoints are considered enchantments) ----@param node table @The passive tree node to anoint, or nil to just remove existing anoints. ----@return table @The new item +---@param node? Node @The passive tree node to anoint, or nil to just remove existing anoints. +---@return Item @The new item function ItemsTabClass:anointItem(node) self.anointEnchantSlot = self.anointEnchantSlot or 1 local item = new("Item"):Item(self.displayItem:BuildRaw()) @@ -3021,10 +3136,10 @@ function ItemsTabClass:anointItem(node) item:BuildAndParseRaw() return item end - ---Appends tooltip information for anointing a new passive tree node onto the currently editing item ----@param tooltip table @The tooltip to append into ----@param node table @The passive tree node that will be anointed, or nil to remove the current anoint. +---@param tooltip Tooltip @The tooltip to append into +---@param node? Node @The passive tree node that will be anointed, or nil to remove the current anoint. +---@param actionText? string function ItemsTabClass:AppendAnointTooltip(tooltip, node, actionText) if not self.displayItem then return @@ -3064,10 +3179,9 @@ function ItemsTabClass:AppendAnointTooltip(tooltip, node, actionText) tooltip:AddLine(14, "^7"..actionText.." "..node.dn.." changes nothing.") end end - ---Appends tooltip with information about added notable passive node if it would be allocated. ----@param tooltip table @The tooltip to append into ----@param node table @The passive tree node that will be added +---@param tooltip Tooltip @The tooltip to append into +---@param node Node @The passive tree node that will be added function ItemsTabClass:AppendAddedNotableTooltip(tooltip, node) local calcFunc, calcBase = self.build.calcsTab:GetMiscCalculator() local outputNew = calcFunc({ addNodes = { [node] = true } }) @@ -3078,12 +3192,14 @@ function ItemsTabClass:AppendAddedNotableTooltip(tooltip, node) end -- Opens the item anointing popup +---@param enchantSlot? integer function ItemsTabClass:AnointDisplayItem(enchantSlot) self.anointEnchantSlot = enchantSlot or 1 local controls = { } controls.notableDB = new("NotableDBControl"):NotableDBControl({"TOPLEFT",nil,"TOPLEFT"}, {10, 60, 360, 360}, self, self.build.spec.tree.nodes, "ANOINT") + ---@return string local function saveLabel() local node = controls.notableDB.selValue if node then @@ -3095,10 +3211,12 @@ function ItemsTabClass:AnointDisplayItem(enchantSlot) end return "No Anoint" end + ---@return number local function saveLabelWidth() local label = saveLabel() return DrawStringWidth(16, "VAR", label) + 10 end + ---@return number local function saveLabelX() local width = saveLabelWidth() return -(width + 90) / 2 @@ -3142,6 +3260,7 @@ function ItemsTabClass:CorruptDisplayItem() local currentModType = sourceList[1] -- the amount of controls created local maxImplicitNum = 5 + ---@param modType string local function buildImplicitList(modType) if implicitList[modType] then return @@ -3174,6 +3293,9 @@ function ItemsTabClass:CorruptDisplayItem() end end buildImplicitList(currentModType) + ---@param control DropDownControl + ---@param other? DropDownControl + ---@param modType string local function buildScourgeList(control, other, modType) local selfMod = control.selIndex and control.selIndex > 1 and control.list[control.selIndex].mod local otherMod = other and other.selIndex and other.selIndex > 1 and other.list[other.selIndex].mod @@ -3187,6 +3309,7 @@ function ItemsTabClass:CorruptDisplayItem() end control:SelByValue(selfMod, "mod") end + ---@param modType string local function buildCorruptLists(modType) -- avoid letting the user select the same implicit twice local selectedGroups = {} @@ -3216,6 +3339,13 @@ function ItemsTabClass:CorruptDisplayItem() control:SelByValue(entry.val, "mod") end end + ---@param entry table + ---@param modType string + ---@param stat string + ---@param calcFunc function + ---@param slotName string + ---@param useFullDPS? boolean + ---@return number local function getSortValue(entry, modType, stat, calcFunc, slotName, useFullDPS) entry.sortValues = entry.sortValues or { } if entry.sortValues[stat] ~= nil then @@ -3241,6 +3371,11 @@ function ItemsTabClass:CorruptDisplayItem() entry.sortValues[stat] = value return value end + ---@param modType string + ---@param stat? string + ---@param calcFunc? function + ---@param slotName string + ---@param useFullDPS? boolean local function sortModType(modType, stat, calcFunc, slotName, useFullDPS) if not implicitList[modType] then return @@ -3261,6 +3396,7 @@ function ItemsTabClass:CorruptDisplayItem() end) end end + ---@param stat? string local function applySort(stat) if not controls.implicit1 then return @@ -3287,6 +3423,8 @@ function ItemsTabClass:CorruptDisplayItem() control:UpdateSearch() end end + ---@param addingImplicits boolean + ---@return Item local function corruptItem(addingImplicits) local item = new("Item"):Item(self.displayItem:BuildRaw()) item.id = self.displayItem.id @@ -3347,6 +3485,9 @@ function ItemsTabClass:CorruptDisplayItem() for i, mod in ipairs(item.explicitModLines) do local modRange = mod.range or main.defaultItemAffixQuality if itemLib.isModLineScalable(mod.line, modRange, mod.valueScalar) and item:CheckModLineVariant(mod) then + ---@param corruptedRange number + ---@return string label + ---@return integer lineCount local function formatLabel(corruptedRange) local line = itemLib.applyRange(mod.line, modRange, mod.valueScalar or 1, corruptedRange) local lines = main:WrapString("^7" .. line, 16, 430) @@ -3376,6 +3517,8 @@ function ItemsTabClass:CorruptDisplayItem() end explicitOffset = offset end + ---@param implicitNum integer + ---@param canChangeImplicits boolean local function setImplicitControlsShown(implicitNum, canChangeImplicits) for i = 1, maxImplicitNum do local shown = canChangeImplicits and i <= implicitNum @@ -3525,6 +3668,12 @@ function ItemsTabClass:AddCustomModifierToDisplayItem() listMod.sortValues = nil end end + ---@param listMod table + ---@param stat string + ---@param calcFunc function + ---@param slotName string + ---@param useFullDPS? boolean + ---@return number local function getSortValue(listMod, stat, calcFunc, slotName, useFullDPS) listMod.sortValues = listMod.sortValues or { } if listMod.sortValues[stat] ~= nil then @@ -3541,6 +3690,8 @@ function ItemsTabClass:AddCustomModifierToDisplayItem() listMod.sortValues[stat] = value return value end + ---@param stat? string + ---@param selectFirst? boolean local function applySort(stat, selectFirst) if not controls.modSelect or not controls.modSelect:IsShown() then return @@ -3576,6 +3727,8 @@ function ItemsTabClass:AddCustomModifierToDisplayItem() controls.modSelect:SetSel(1, true) end end + ---@param baseCategories table + ---@param modDb table local function buildDropRestricted(baseCategories, modDb) local base = self.displayItem.base local subTypeName = base.subType and base.type .. ": " .. base.subType @@ -3600,6 +3753,9 @@ function ItemsTabClass:AddCustomModifierToDisplayItem() ::nextDrop:: end end + ---@param a table + ---@param b table + ---@return boolean local function sortByPrefixSuffix(a, b) if a.affixType ~= b.affixType then return a.affixType == "Prefix" and b.affixType == "Suffix" @@ -3796,6 +3952,7 @@ function ItemsTabClass:AddCustomModifierToDisplayItem() end t_insert(sourceList, { label = "Custom", sourceId = "CUSTOM" }) buildMods(sourceList[1].sourceId) + ---@return Item local function addModifier() local item = new("Item"):Item(self.displayItem:BuildRaw()) item.id = self.displayItem.id @@ -3871,6 +4028,8 @@ function ItemsTabClass:AddCrucibleModifierToDisplayItem() local controls = { } local modList = {[1] = {"None"}, [2] = {"None"}, [3] = {"None"}, [4] = {"None"}, [5] = {"None"}} local itemModMap, nodeSelections = { }, { } + ---@param mod table + ---@return string local function getLabelFromMod(mod) local label = copyTable(mod) for index, line in ipairs(mod) do @@ -3915,6 +4074,7 @@ function ItemsTabClass:AddCrucibleModifierToDisplayItem() end) end end + ---@return Item local function addModifier() local item = new("Item"):Item(self.displayItem:BuildRaw()) item.id = self.displayItem.id @@ -3979,6 +4139,8 @@ function ItemsTabClass:AddCrucibleModifierToDisplayItem() end +---@param tooltip Tooltip +---@param itemSet ItemSet function ItemsTabClass:AddItemSetTooltip(tooltip, itemSet) for _, slot in ipairs(self.orderedSlots) do if not slot.nodeId then @@ -3990,10 +4152,13 @@ function ItemsTabClass:AddItemSetTooltip(tooltip, itemSet) end end +---@param tooltip Tooltip +---@param item Item function ItemsTabClass:SetTooltipHeaderInfluence(tooltip, item) tooltip.influenceHeader1 = nil tooltip.influenceHeader2 = nil + ---@param name string local function addInfluence(name) if not tooltip.influenceHeader1 then tooltip.influenceHeader1 = name @@ -4061,6 +4226,9 @@ function ItemsTabClass:SetTooltipHeaderInfluence(tooltip, item) end end +---@param text string +---@return string formattedText +---@return integer replacementCount function ItemsTabClass:FormatItemSource(text) return text:gsub("unique{([^}]+)}",colorCodes.UNIQUE.."%1"..colorCodes.SOURCE) :gsub("normal{([^}]+)}",colorCodes.NORMAL.."%1"..colorCodes.SOURCE) @@ -4068,6 +4236,8 @@ function ItemsTabClass:FormatItemSource(text) :gsub("prophecy{([^}]+)}",colorCodes.PROPHECY.."%1"..colorCodes.SOURCE) end +---@param item? Item +---@return boolean local function itemChangesPassiveTree(item) return not not (item and item.type == "Jewel" and item.jewelData and (item.jewelData.conqueredBy or item.jewelRadiusIndex @@ -4097,6 +4267,8 @@ local sharedSpecKeysForJewelComparison = { curSecondaryAscendClassName = true, } +---@param spec PassiveSpec +---@return PassiveSpec local function cloneSpecForJewelComparison(spec) local specCopy = setmetatable({ }, getmetatable(spec)) -- Share only immutable/scalar spec state. Tables that BuildAllDependsAndPaths @@ -4149,10 +4321,10 @@ local function cloneSpecForJewelComparison(spec) return specCopy end - ---@param itemsTab ItemsTab ---@param compareSlot ItemSlotControl ----@param replacementItem Item +---@param replacementItem? Item +---@return PassiveSpec local function buildSpecForJewelComparison(itemsTab, compareSlot, replacementItem) local tempItemId local spec = cloneSpecForJewelComparison(itemsTab.build.spec) @@ -4182,6 +4354,8 @@ local function buildSpecForJewelComparison(itemsTab, compareSlot, replacementIte return spec end +---@param item Item +---@return string function ItemsTabClass:GetSocketDescriptionLine(item) -- Sockets/links local group = 0 @@ -4211,6 +4385,11 @@ function ItemsTabClass:GetSocketDescriptionLine(item) end return line end +---@param tooltip Tooltip +---@param item Item +---@param slot? ItemSlotControl|string +---@param dbMode? boolean +---@param maxWidth? number function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode, maxWidth) local fontSizeSmall = main.showFlavourText and 16 or 14 local fontSizeBig = main.showFlavourText and 18 or 16 @@ -4657,10 +4836,10 @@ function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode, maxWidth) end end end - ---@param tooltip Tooltip ---@param item Item ----@param base any +---@param base ItemBase +---@param slot? ItemSlotControl|string function ItemsTabClass:AddItemStatDifferences(tooltip, item, base, slot) local calcFunc, calcBase = self.build.calcsTab:GetMiscCalculator() if base.flask then @@ -4917,6 +5096,9 @@ function ItemsTabClass:AddItemStatDifferences(tooltip, item, base, slot) end end + ---@param compareSlot ItemSlotControl + ---@return Item? selItem + ---@return table output local function getReplacedItemAndOutput(compareSlot) local selItem = self.items[compareSlot.selItemId] local override = { repSlotName = compareSlot.slotName, repItem = item ~= selItem and item or nil } @@ -4926,6 +5108,9 @@ function ItemsTabClass:AddItemStatDifferences(tooltip, item, base, slot) local output = calcFunc(override) return selItem, output end + ---@param compareSlot ItemSlotControl + ---@param selItem? Item + ---@param output? table local function addCompareForSlot(compareSlot, selItem, output) if not selItem or not output then selItem, output = getReplacedItemAndOutput(compareSlot) @@ -4974,6 +5159,9 @@ function ItemsTabClass:AddItemStatDifferences(tooltip, item, base, slot) -- either the same unique or same base type + ---@param compareItem? Item + ---@param sameUnique? boolean + ---@return integer local function similar(compareItem, sameUnique) -- empty slot if not compareItem then return 0 end @@ -4993,6 +5181,9 @@ function ItemsTabClass:AddItemStatDifferences(tooltip, item, base, slot) -- 2. same base group jewel or unique -- 3. DPS -- 4. EHP + ---@param a table + ---@param b table + ---@return boolean? local function sortFunc(a, b) if a == b then return end @@ -5018,6 +5209,15 @@ function ItemsTabClass:AddItemStatDifferences(tooltip, item, base, slot) end end +---@class ItemsTabUndoState +---@field activeItemSetId integer +---@field items table +---@field itemOrderList integer[] +---@field slotSelItemId table +---@field itemSets table +---@field itemSetOrderList integer[] +-- Captures the state required to undo item tab changes. +---@return ItemsTabUndoState function ItemsTabClass:CreateUndoState() local state = { } state.activeItemSetId = self.activeItemSetId @@ -5035,6 +5235,7 @@ function ItemsTabClass:CreateUndoState() return state end +---@param state ItemsTabUndoState function ItemsTabClass:RestoreUndoState(state) self.items = state.items wipeTable(self.itemOrderList) diff --git a/src/Classes/LabelControl.lua b/src/Classes/LabelControl.lua index f684cfcd32b..a6cc3d478d0 100644 --- a/src/Classes/LabelControl.lua +++ b/src/Classes/LabelControl.lua @@ -4,11 +4,13 @@ -- Simple text label. -- ---@class LabelControl: Control +---@field label Prop local LabelClass = newClass("LabelControl", "Control") ---@param anchor? Anchor ---@param rect? Rect ---@param label Prop +---@return LabelControl function LabelClass:LabelControl(anchor, rect, label) self:Control(anchor, rect) self.label = label diff --git a/src/Classes/ListControl.lua b/src/Classes/ListControl.lua index 55407583503..4f9f87763ca 100644 --- a/src/Classes/ListControl.lua +++ b/src/Classes/ListControl.lua @@ -32,15 +32,48 @@ local m_floor = math.floor ---@class ListControl: Control, ControlHost ---@field list T[] +---@field rowHeight number +---@field scroll "HORIZONTAL"|"VERTICAL"|boolean|nil +---@field scrollH? boolean +---@field isMutable? boolean +---@field forceTooltip? boolean +---@field colList ListColumn[] +---@field tooltip Tooltip +---@field font Font +---@field labelPositionOffset [number, number] +---@field selIndex? integer +---@field selValue? T +---@field selDragging? boolean +---@field selDragActive? boolean +---@field selDragIndex? integer +---@field selCX? number +---@field selCY? number +---@field hoverIndex? integer +---@field hoverValue? T +---@field dragTargetList? ListControl[] +---@field dragTarget? ListControl +---@field dragType? string +---@field dragValue? unknown +---@field otherDragSource? ListControl +---@field otherDragTargeting? boolean + +---@class ListColumn +---@field label? string +---@field width? Prop|fun(list: ListControl, column: ListColumn): number +---@field align? "LEFT"|"RIGHT"|"CENTER_X" +---@field _offset? number +---@field _width? number local ListClass = newClass("ListControl", "Control", "ControlHost") +---@generic T ---@param anchor Anchor? ---@param rect Rect? ---@param rowHeight number ---@param scroll "HORIZONTAL"|"VERTICAL"|boolean|nil ---@param isMutable boolean? ----@param list any[]? ----@param forceTooltip any +---@param list T[]? +---@param forceTooltip? boolean +---@return ListControl function ListClass:ListControl(anchor, rect, rowHeight, scroll, isMutable, list, forceTooltip) self:Control(anchor, rect) self:ControlHost() @@ -85,7 +118,8 @@ function ListClass:ListControl(anchor, rect, rowHeight, scroll, isMutable, list, return self end - +---@param index integer +---@return boolean function ListClass:SelectIndex(index) self.selValue = self.list[index] if not self.selValue then @@ -108,6 +142,10 @@ function ListClass:SelectIndex(index) return true end +---@generic T +---@param column ListColumn +---@param property string +---@return unknown function ListClass:GetColumnProperty(column, property) if type(column[property]) == "function" then return column[property](self, column) @@ -116,6 +154,7 @@ function ListClass:GetColumnProperty(column, property) end end +---@return boolean|Control? function ListClass:IsMouseOver() if not self:IsShown() then return @@ -123,6 +162,13 @@ function ListClass:IsMouseOver() return self:IsMouseInBounds() or self:GetMouseOverControl() end +---@class ListRegion +---@field x number +---@field y number +---@field width number +---@field height number + +---@return ListRegion function ListClass:GetRowRegion() local width, height = self:GetSize() return { @@ -133,6 +179,8 @@ function ListClass:GetRowRegion() } end +---@param viewPort Rect +---@param noTooltip? boolean function ListClass:Draw(viewPort, noTooltip) local x, y = self:GetPos() local width, height = self:GetSize() @@ -337,6 +385,9 @@ function ListClass:Draw(viewPort, noTooltip) end end +---@param key string +---@param doubleClick? boolean +---@return Control? function ListClass:OnKeyDown(key, doubleClick) if not self:IsShown() or not self:IsEnabled() then return @@ -416,7 +467,8 @@ function ListClass:OnKeyDown(key, doubleClick) end return self end - +---@param key string +---@return ListControl? function ListClass:OnKeyUp(key) if not self:IsShown() or not self:IsEnabled() then return @@ -470,6 +522,7 @@ function ListClass:OnKeyUp(key) return self end +---@return integer? function ListClass:GetHoverIndex() local x, y = self:GetPos() local cursorX, cursorY = GetCursorPos() @@ -482,6 +535,8 @@ function ListClass:GetHoverIndex() end end +---@param key? string +---@return T? function ListClass:GetHoverValue(key) local index = self:GetHoverIndex() if index then diff --git a/src/Classes/MinionListControl.lua b/src/Classes/MinionListControl.lua index 2c153fdbaa1..318243758b7 100644 --- a/src/Classes/MinionListControl.lua +++ b/src/Classes/MinionListControl.lua @@ -9,8 +9,20 @@ local t_remove = table.remove local s_format = string.format ---@class MinionListControl: ListControl +---@field data Data +---@field dest? MinionListControl +---@field dragTargetList? ListControl[] +---@field label string +---@field selIndex? integer +---@field selValue? string local MinionListClass = newClass("MinionListControl", "ListControl") +---@param anchor? Anchor +---@param rect? Rect +---@param data Data +---@param list string[] +---@param dest? MinionListControl +---@return MinionListControl function MinionListClass:MinionListControl(anchor, rect, data, list, dest) self:ListControl(anchor, rect, 16, "VERTICAL", not dest, list) self.data = data @@ -42,6 +54,10 @@ function MinionListClass:AddSel() end end +---@param column integer +---@param index integer +---@param minionId string +---@return string? function MinionListClass:GetRowValue(column, index, minionId) local minion = self.data.minions[minionId] if column == 1 then @@ -49,6 +65,9 @@ function MinionListClass:GetRowValue(column, index, minionId) end end +---@param tooltip Tooltip +---@param index integer +---@param minionId string function MinionListClass:AddValueTooltip(tooltip, index, minionId) if tooltip:CheckForUpdate(minionId) then local minion = self.data.minions[minionId] @@ -80,24 +99,39 @@ function MinionListClass:AddValueTooltip(tooltip, index, minionId) end end +---@param index integer +---@param value string +---@return string dragType +---@return string minionId function MinionListClass:GetDragValue(index, value) return "MinionId", value end +---@param type string +---@param value string +---@return boolean function MinionListClass:CanReceiveDrag(type, value) return type == "MinionId" and not isValueInArray(self.list, value) end +---@param type string +---@param value string +---@param source? ListControl function MinionListClass:ReceiveDrag(type, value, source) t_insert(self.list, self.selDragIndex or #self.list + 1, value) end +---@param index integer +---@param minionId string +---@param doubleClick? boolean function MinionListClass:OnSelClick(index, minionId, doubleClick) if doubleClick and self.dest then self:AddSel() end end +---@param index integer +---@param minionId string function MinionListClass:OnSelDelete(index, minionId) if not self.dest then t_remove(self.list, index) diff --git a/src/Classes/MinionSearchListControl.lua b/src/Classes/MinionSearchListControl.lua index 43773259d98..c094bab732e 100644 --- a/src/Classes/MinionSearchListControl.lua +++ b/src/Classes/MinionSearchListControl.lua @@ -9,8 +9,17 @@ local t_remove = table.remove local s_format = string.format ---@class MinionSearchListControl: MinionListControl +---@field isMutable boolean +---@field labelPositionOffset number +---@field unfilteredList string[] local MinionSearchListClass = newClass("MinionSearchListControl", "MinionListControl") +---@param anchor? Anchor +---@param rect? Rect +---@param data Data +---@param list string[] +---@param dest string[] +---@return MinionSearchListControl function MinionSearchListClass:MinionSearchListControl(anchor, rect, data, list, dest) self:MinionListControl(anchor, rect, data, list, dest) self.unfilteredList = copyTable(list) @@ -34,6 +43,10 @@ function MinionSearchListClass:MinionSearchListControl(anchor, rect, data, list, return self end +---@param searchStr string +---@param minionId string +---@param filterMode integer +---@return boolean function MinionSearchListClass:DoesEntryMatchFilters(searchStr, minionId, filterMode) if filterMode == 1 or filterMode == 3 then local err, match = PCall(string.matchOrPattern, self.data.minions[minionId].name:lower(), searchStr) @@ -54,6 +67,8 @@ function MinionSearchListClass:DoesEntryMatchFilters(searchStr, minionId, filter return false end +---@param buf string +---@param filterMode integer function MinionSearchListClass:ListFilterChanged(buf, filterMode) local searchStr = buf:lower():gsub("[%-%.%+%[%]%$%^%%%?%*]", "%%%0") if searchStr:match("%S") then diff --git a/src/Classes/ModDB.lua b/src/Classes/ModDB.lua index ffaf1e5ea59..fc41945f1da 100644 --- a/src/Classes/ModDB.lua +++ b/src/Classes/ModDB.lua @@ -18,14 +18,18 @@ local bor = bit.bor local mod_createMod = modLib.createMod ---@class ModDB: ModStore +---@field mods table local ModDBClass = newClass("ModDB", "ModStore") +---@param parent? ModStore +---@return ModDB function ModDBClass:ModDB(parent) self:ModStore(parent) self.mods = { } return self end +---@param mod Mod function ModDBClass:AddMod(mod) local name = mod.name if not self.mods[name] then @@ -37,7 +41,7 @@ end ---ReplaceModInternal --- Replaces an existing matching mod with a new mod. --- If no matching mod exists, then the function returns false ----@param mod table +---@param mod Mod ---@return boolean @Whether any mod was replaced function ModDBClass:ReplaceModInternal(mod) local name = mod.name @@ -75,7 +79,7 @@ end --- Moves the mod from the old name's bucket to the new name's bucket. --- If no matching mod exists, then the function returns false ---@param oldName string @The name of the existing mod to find ----@param mod table @The new mod to replace it with +---@param mod Mod @The new mod to replace it with ---@return boolean @Whether any mod was converted function ModDBClass:ConvertModInternal(oldName, mod) if not self.mods[oldName] then @@ -109,6 +113,7 @@ function ModDBClass:ConvertModInternal(oldName, mod) return false end +---@param modList Mod[] function ModDBClass:AddList(modList) local mods = self.mods for i, mod in ipairs(modList) do @@ -120,6 +125,7 @@ function ModDBClass:AddList(modList) end end +---@param modDB ModDB|ModList function ModDBClass:AddDB(modDB) local mods = self.mods for modName, modList in pairs(modDB.mods) do @@ -133,6 +139,14 @@ function ModDBClass:AddDB(modDB) end end +---@param context ModStore +---@param modType NumericModTypes|string +---@param cfg? ModCfg +---@param flags integer +---@param keywordFlags integer +---@param source? string +---@param ... string +---@return number function ModDBClass:SumInternal(context, modType, cfg, flags, keywordFlags, source, ...) local result = 0 local globalLimits @@ -161,6 +175,13 @@ function ModDBClass:SumInternal(context, modType, cfg, flags, keywordFlags, sour return result end +---@param context ModStore +---@param cfg? ModCfg +---@param flags integer +---@param keywordFlags integer +---@param source? string +---@param ... string +---@return number function ModDBClass:MoreInternal(context, cfg, flags, keywordFlags, source, ...) local result = 1 local modPrecision = nil @@ -203,6 +224,13 @@ function ModDBClass:MoreInternal(context, cfg, flags, keywordFlags, source, ...) return result end +---@param context ModStore +---@param cfg? ModCfg +---@param flags integer +---@param keywordFlags integer +---@param source? string +---@param ... string +---@return boolean? function ModDBClass:FlagInternal(context, cfg, flags, keywordFlags, source, ...) for i = 1, select('#', ...) do local modList = self.mods[select(i, ...)] @@ -226,6 +254,13 @@ function ModDBClass:FlagInternal(context, cfg, flags, keywordFlags, source, ...) end end +---@param context ModStore +---@param cfg? ModCfg +---@param flags integer +---@param keywordFlags integer +---@param source? string +---@param ... string +---@return unknown function ModDBClass:OverrideInternal(context, cfg, flags, keywordFlags, source, ...) for i = 1, select('#', ...) do local modList = self.mods[select(i, ...)] @@ -250,6 +285,13 @@ function ModDBClass:OverrideInternal(context, cfg, flags, keywordFlags, source, end end +---@param context ModStore +---@param result unknown[] +---@param cfg? ModCfg +---@param flags integer +---@param keywordFlags integer +---@param source? string +---@param ... string function ModDBClass:ListInternal(context, result, cfg, flags, keywordFlags, source, ...) for i = 1, select('#', ...) do local modList = self.mods[select(i, ...)] @@ -275,6 +317,14 @@ function ModDBClass:ListInternal(context, result, cfg, flags, keywordFlags, sour end end +---@param context ModStore +---@param result { value: unknown, mod: Mod }[] +---@param modType? NumericModTypes|string +---@param cfg? ModCfg +---@param flags integer +---@param keywordFlags integer +---@param source? string +---@param ... string function ModDBClass:TabulateInternal(context, result, modType, cfg, flags, keywordFlags, source, ...) local globalLimits for i = 1, select('#', ...) do @@ -307,11 +357,12 @@ end ---HasModInternal --- Checks if a mod exists with the given properties ----@param modType string @The type of the mod, e.g. "BASE" ----@param flags number @The mod flags to match ----@param keywordFlags number @The mod keyword flags to match ----@param source string @The mod source to match ----@return boolean @true if the mod is found, false otherwise. +---@param modType NumericModTypes|string @The type of the mod, e.g. "BASE" +---@param flags integer @The mod flags to match +---@param keywordFlags integer @The mod keyword flags to match +---@param source? string @The mod source to match +---@param ... string +---@return boolean @True if a matching mod is found, false otherwise. function ModDBClass:HasModInternal(modType, flags, keywordFlags, source, ...) for i = 1, select('#', ...) do local modList = self.mods[select(i, ...)] @@ -368,4 +419,4 @@ function ModDBClass:Print() for i, name in ipairs(nameList) do ConPrintf("%s = %d", name, self.multipliers[name]) end -end \ No newline at end of file +end diff --git a/src/Classes/ModList.lua b/src/Classes/ModList.lua index ec686674d77..24052b3a005 100644 --- a/src/Classes/ModList.lua +++ b/src/Classes/ModList.lua @@ -19,11 +19,14 @@ local mod_createMod = modLib.createMod ---@class ModList: ModStore local ModListClass = newClass("ModList", "ModStore") +---@param parent? ModStore +---@return ModList function ModListClass:ModList(parent) self:ModStore(parent) return self end +---@param mod Mod function ModListClass:AddMod(mod) t_insert(self, mod) end @@ -31,7 +34,7 @@ end ---ReplaceModInternal --- Replaces an existing matching mod with a new mod. --- If no matching mod exists, then the function returns false ----@param mod table +---@param mod Mod ---@return boolean @Whether any mod was replaced function ModListClass:ReplaceModInternal(mod) -- Find the index of the existing mod, if it is in the table @@ -53,7 +56,7 @@ end --- Converts an existing mod with oldName to a new mod with a different name. --- If no matching mod exists, then the function returns false ---@param oldName string @The name of the existing mod to find ----@param mod table @The new mod to replace it with +---@param mod Mod @The new mod to replace it with ---@return boolean @Whether any mod was converted function ModListClass:ConvertModInternal(oldName, mod) for i, curMod in ipairs(self) do @@ -70,6 +73,8 @@ function ModListClass:ConvertModInternal(oldName, mod) return false end +---@param mod Mod +---@param skipNonAdditive? boolean function ModListClass:MergeMod(mod, skipNonAdditive) if mod.type == "BASE" or mod.type == "INC" or mod.type == "MORE" then for i = 1, #self do @@ -85,6 +90,7 @@ function ModListClass:MergeMod(mod, skipNonAdditive) end end +---@param modList? Mod[] function ModListClass:AddList(modList) if modList then for i = 1, #modList do @@ -93,11 +99,20 @@ function ModListClass:AddList(modList) end end +---@param ... unknown function ModListClass:MergeNewMod(...) self:MergeMod(mod_createMod(...)) end +---@param context ModStore +---@param modType NumericModTypes|string +---@param cfg? ModCfg +---@param flags integer +---@param keywordFlags integer +---@param source? string +---@param ... string +---@return number function ModListClass:SumInternal(context, modType, cfg, flags, keywordFlags, source, ...) local result = 0 for i = 1, select('#', ...) do @@ -119,6 +134,13 @@ function ModListClass:SumInternal(context, modType, cfg, flags, keywordFlags, so return result end +---@param context ModStore +---@param cfg? ModCfg +---@param flags integer +---@param keywordFlags integer +---@param source? string +---@param ... string +---@return number function ModListClass:MoreInternal(context, cfg, flags, keywordFlags, source, ...) local result = 1 local modPrecision = nil @@ -153,6 +175,13 @@ function ModListClass:MoreInternal(context, cfg, flags, keywordFlags, source, .. return result end +---@param context ModStore +---@param cfg? ModCfg +---@param flags integer +---@param keywordFlags integer +---@param source? string +---@param ... string +---@return boolean? function ModListClass:FlagInternal(context, cfg, flags, keywordFlags, source, ...) for i = 1, select('#', ...) do local modName = select(i, ...) @@ -174,6 +203,13 @@ function ModListClass:FlagInternal(context, cfg, flags, keywordFlags, source, .. end end +---@param context ModStore +---@param cfg? ModCfg +---@param flags integer +---@param keywordFlags integer +---@param source? string +---@param ... string +---@return unknown function ModListClass:OverrideInternal(context, cfg, flags, keywordFlags, source, ...) for i = 1, select('#', ...) do local modName = select(i, ...) @@ -196,6 +232,13 @@ function ModListClass:OverrideInternal(context, cfg, flags, keywordFlags, source end end +---@param context ModStore +---@param result unknown[] +---@param cfg? ModCfg +---@param flags integer +---@param keywordFlags integer +---@param source? string +---@param ... string function ModListClass:ListInternal(context, result, cfg, flags, keywordFlags, source, ...) for i = 1, select('#', ...) do local modName = select(i, ...) @@ -219,6 +262,14 @@ function ModListClass:ListInternal(context, result, cfg, flags, keywordFlags, so end end +---@param context ModStore +---@param result { value: unknown, mod: Mod }[] +---@param modType? NumericModTypes|string +---@param cfg? ModCfg +---@param flags integer +---@param keywordFlags integer +---@param source? string +---@param ... string function ModListClass:TabulateInternal(context, result, modType, cfg, flags, keywordFlags, source, ...) for i = 1, select('#', ...) do local modName = select(i, ...) diff --git a/src/Classes/ModStore.lua b/src/Classes/ModStore.lua index 0dd30e41d66..4d322934d4b 100644 --- a/src/Classes/ModStore.lua +++ b/src/Classes/ModStore.lua @@ -35,8 +35,14 @@ end }) ---@field source string? ---@class ModStore +---@field parent? ModStore +---@field actor? Actor +---@field multipliers table +---@field conditions table local ModStoreClass = newClass("ModStore") +---@param parent? ModStore +---@return ModStore function ModStoreClass:ModStore(parent) self.parent = parent or false self.actor = parent and parent.actor or { } @@ -45,6 +51,9 @@ function ModStoreClass:ModStore(parent) return self end +---@param self ModStore +---@param actorType? "enemy"|"minion"|"player" +---@return Actor? local function getActor(self, actorType) if actorType == "player" then return self.actor.player or (self.actor.parent and self.actor.parent.player) or (self.actor.enemy and self.actor.enemy.player) @@ -53,6 +62,9 @@ local function getActor(self, actorType) end end +---@param mod Mod +---@param scale number +---@param replace? boolean function ModStoreClass:ScaleAddMod(mod, scale, replace) local unscalable = false for _, effects in ipairs(mod) do @@ -90,12 +102,16 @@ function ModStoreClass:ScaleAddMod(mod, scale, replace) end end +---@param modList Mod[] function ModStoreClass:CopyList(modList) for i = 1, #modList do self:AddMod(copyTable(modList[i])) end end +---@param modList Mod[]? +---@param scale number +---@param replace? boolean function ModStoreClass:ScaleAddList(modList, scale, replace) if scale == 1 then self:AddList(modList) @@ -106,6 +122,7 @@ function ModStoreClass:ScaleAddList(modList, scale, replace) end end +---@param ... unknown function ModStoreClass:NewMod(...) self:AddMod(mod_createMod(...)) end @@ -121,7 +138,7 @@ end --- 3 (number): value --- 4 (string): source --- 5+ (optional, varies): additional options ----@param ... any @Parameters to be passed along to the modLib.createMod function +---@param ... unknown @Arguments to pass to modLib.createMod. function ModStoreClass:ReplaceMod(...) local mod = mod_createMod(...) if not self:ReplaceModInternal(mod) then @@ -134,7 +151,7 @@ end --- Finds a mod matching oldName with the same type, flags, keywordFlags, and source as the new mod. --- If no matching mod exists, the new mod is added instead. ---@param oldName string @The name of the existing mod to convert ----@param ... any @Parameters to be passed along to the modLib.createMod function (new name, type, value, source, ...) +---@param ... unknown @Arguments to pass to modLib.createMod (new name, type, value, source, ...). function ModStoreClass:ConvertMod(oldName, ...) local mod = mod_createMod(...) if not self:ConvertModInternal(oldName, mod) then @@ -142,6 +159,13 @@ function ModStoreClass:ConvertMod(oldName, ...) end end +---@overload fun(self: ModStore, modType: "FLAG", cfg?: ModCfg, ...: string): boolean? +---@overload fun(self: ModStore, modType: "LIST", cfg?: ModCfg, ...: string): unknown[] +---@overload fun(self: ModStore, modType: "OVERRIDE", cfg?: ModCfg, ...: string): unknown +---@param modType NumericModTypes|string +---@param cfg? ModCfg +---@param ... string +---@return unknown function ModStoreClass:Combine(modType, cfg, ...) if modType == "MORE" then return self:More(cfg, ...) @@ -158,7 +182,7 @@ function ModStoreClass:Combine(modType, cfg, ...) end end ----@param modType string +---@param modType NumericModTypes|string ---@param cfg? ModCfg ---@param ... string ---@return number @@ -187,6 +211,9 @@ function ModStoreClass:More(cfg, ...) return self:MoreInternal(self, cfg, flags, keywordFlags, source, ...) end +---@param cfg? ModCfg +---@param ... string +---@return boolean? function ModStoreClass:Flag(cfg, ...) local flags, keywordFlags = 0, 0 local source @@ -200,7 +227,7 @@ end ---@param cfg? ModCfg ---@param ... string ----@return any +---@return unknown function ModStoreClass:Override(cfg, ...) local flags, keywordFlags = 0, 0 local source @@ -214,7 +241,7 @@ end ---@param cfg? ModCfg ---@param ... string ----@return any[] +---@return unknown[] function ModStoreClass:List(cfg, ...) local flags, keywordFlags = 0, 0 local source @@ -228,10 +255,10 @@ function ModStoreClass:List(cfg, ...) return result end ----@param modType string +---@param modType NumericModTypes|string ---@param cfg? ModCfg ---@param ... string ----@return table[] +---@return { value: unknown, mod: Mod }[] function ModStoreClass:Tabulate(modType, cfg, ...) local flags, keywordFlags = 0, 0 local source @@ -245,6 +272,9 @@ function ModStoreClass:Tabulate(modType, cfg, ...) return result end +---@param cfg? ModCfg +---@param ... string +---@return number? function ModStoreClass:Max(cfg, ...) local max for _, value in ipairs(self:Tabulate("MAX", cfg, ...)) do @@ -256,6 +286,9 @@ function ModStoreClass:Max(cfg, ...) return max end +---@param cfg? ModCfg +---@param ... string +---@return number? function ModStoreClass:Min(cfg, ...) local min for _, value in ipairs(self:Tabulate("MIN", cfg, ...)) do @@ -271,10 +304,10 @@ end --- Checks if a mod exists with the given properties. --- Useful for determining if the other aggregate functions will find --- anything to aggregate. ----@param modType string @Mod type to match ----@param cfg table @Optional configuration to use - contains flags, keywordFlags, and source to match +---@param modType NumericModTypes|string @Mod type to match. +---@param cfg? ModCfg @Optional configuration to use; contains flags, keywordFlags, and source to match. ---@param ... string @Mod name(s) to check for. ----@return boolean @true if the mod is found, false otherwise. +---@return boolean? @True when a matching mod is found. function ModStoreClass:HasMod(modType, cfg, ...) local flags, keywordFlags = 0, 0 local source @@ -289,7 +322,7 @@ end ---@param var string ---@param cfg? ModCfg ---@param noMod? boolean ----@return boolean +---@return boolean? function ModStoreClass:GetCondition(var, cfg, noMod) return self.conditions[var] or (self.parent and self.parent:GetCondition(var, cfg, true)) or (not noMod and self:Flag(cfg, conditionName[var])) end @@ -308,6 +341,9 @@ end function ModStoreClass:GetStat(stat, cfg) -- Checks if any buff in buffList matches -- Was needed for skills that provide multiple buffs (e.g. Herald of Agony) and can't be accesses with `buffList[1]` + ---@param buffList { name: string }[] + ---@param name string + ---@return boolean local function isNameInBuffList(buffList, name) for _, buff in ipairs(buffList) do if buff.name == name then return true end @@ -358,8 +394,8 @@ end ---@param mod Mod ---@param cfg? ModCfg ----@param globalLimits? table ----@return any +---@param globalLimits? table +---@return unknown function ModStoreClass:EvalMod(mod, cfg, globalLimits) local value = mod.value local GetStat = self.GetStat @@ -763,6 +799,9 @@ function ModStoreClass:EvalMod(mod, cfg, globalLimits) if not cfg or (not tag.slotName and not tag.keyword and not tag.socketColor) then return else + ---@param sockets integer[] + ---@param targetSocket integer + ---@return boolean local function isValidSocket(sockets, targetSocket) for _, val in ipairs(sockets) do if val == targetSocket then diff --git a/src/Classes/NotableDBControl.lua b/src/Classes/NotableDBControl.lua index 8253f4a471f..63cfc8e133d 100644 --- a/src/Classes/NotableDBControl.lua +++ b/src/Classes/NotableDBControl.lua @@ -12,16 +12,34 @@ local m_floor = math.floor local m_huge = math.huge local s_format = string.format ----@param node table +---@param node Node ---@return boolean local function IsAnointableNode(node) return node.recipe and #node.recipe >= 1 end ---@class NotableDBControl : ListControl +---@field itemsTab ItemsTab +---@field db table +---@field dbType string +---@field dragTargetList ListControl[] +---@field sortControl table +---@field sortDropList table[] +---@field sortMode string +---@field sortOrder table[] +---@field sortMaxPower number +---@field sortDetail? PowerStat|table +---@field listBuildFlag boolean +---@field listBuilder? thread +---@field listOutputRevision? integer local NotableDBClass = newClass("NotableDBControl", "ListControl") +---@param anchor? Anchor +---@param rect? Rect ---@param itemsTab ItemsTab +---@param db table +---@param dbType string +---@return NotableDBControl function NotableDBClass:NotableDBControl(anchor, rect, itemsTab, db, dbType) self:ListControl(anchor, rect, 16, "VERTICAL", false) self.itemsTab = itemsTab @@ -49,7 +67,7 @@ function NotableDBClass:NotableDBControl(anchor, rect, itemsTab, db, dbType) return self end ----@param node table @The notable node to check +---@param node Node @The notable node to check ---@return boolean @Whether the notable matches the type and search filters. function NotableDBClass:DoesNotableMatchFilters(node) if not IsAnointableNode(node) then @@ -82,7 +100,7 @@ function NotableDBClass:DoesNotableMatchFilters(node) return true end ----@param sortMode table +---@param sortMode string function NotableDBClass:SetSortMode(sortMode) self.sortMode = sortMode self:BuildSortOrder() @@ -114,6 +132,10 @@ function NotableDBClass:BuildSortOrder() t_insert(self.sortOrder, self.sortControl.NAME) end +---@param selection PowerStat +---@param original number +---@param modified number +---@return number function NotableDBClass:CalculatePowerStat(selection, original, modified) local originalValue = data.powerStatList.GetFromOutput(original, selection) local modifiedValue = data.powerStatList.GetFromOutput(modified, selection) @@ -189,7 +211,7 @@ function NotableDBClass:ListBuilder() self.defaultText = "^7No notables found that match those filters." end ----@param viewPort table +---@param viewPort Rect function NotableDBClass:Draw(viewPort) if self.itemsTab.build.outputRevision ~= self.listOutputRevision then self.listBuildFlag = true @@ -212,10 +234,10 @@ function NotableDBClass:Draw(viewPort) self.ListControl.Draw(self, viewPort) end ----@param column number ----@param index number ----@param node table ----@return string +---@param column integer +---@param index integer +---@param node Node +---@return string? function NotableDBClass:GetRowValue(column, index, node) if column == 1 then if self.sortDetail and self.sortDetail.stat then @@ -234,8 +256,8 @@ function NotableDBClass:GetRowValue(column, index, node) end ---@param tooltip Tooltip ----@param index number ----@param node table +---@param index integer +---@param node Node function NotableDBClass:AddValueTooltip(tooltip, index, node) local dropdownDropped = self.controls.type and self.controls.type.dropped or self.controls.sort.dropped or self.controls.searchMode.dropped if dropdownDropped or (main.popups[1] and main.popups[1].title ~= "Anoint Item") then @@ -274,21 +296,23 @@ function NotableDBClass:AddValueTooltip(tooltip, index, node) end end ----@param index number ----@param node table +---@param index integer +---@param node Node +---@return string +---@return Node function NotableDBClass:GetDragValue(index, node) return "Node", node end ----@param index number ----@param node table ----@param doubleClick boolean +---@param index integer +---@param node Node +---@param doubleClick? boolean function NotableDBClass:OnSelClick(index, node, doubleClick) -- Do nothing end ----@param index number ----@param node table +---@param index integer +---@param node Node function NotableDBClass:OnSelCopy(index, node) Copy(item.dn) end \ No newline at end of file diff --git a/src/Classes/NotesTab.lua b/src/Classes/NotesTab.lua index 8bfc7c7772d..362a288f040 100644 --- a/src/Classes/NotesTab.lua +++ b/src/Classes/NotesTab.lua @@ -6,9 +6,14 @@ local t_insert = table.insert ---@class NotesTab: ControlHost, Control +---@field build Build +---@field modFlag boolean +---@field lastContent? string +---@field showColorCodes boolean local NotesTabClass = newClass("NotesTab", "ControlHost", "Control") ---@param build Build +---@return NotesTab function NotesTabClass:NotesTab(build) self:ControlHost() self:Control() @@ -50,6 +55,7 @@ Below are some common color codes PoB uses: ]] return self end +---@param setting boolean function NotesTabClass:SetShowColorCodes(setting) self.showColorCodes = setting if setting then @@ -61,6 +67,7 @@ function NotesTabClass:SetShowColorCodes(setting) end end +---@param color string function NotesTabClass:SetColor(color) local text = color if self.showColorCodes then text = color:gsub("%^x(%x%x%x%x%x%x)","^_x%1"):gsub("%^(%d)","^_%1") end @@ -72,6 +79,8 @@ function NotesTabClass:SetColor(color) end end +---@param xml table +---@param fileName string function NotesTabClass:Load(xml, fileName) for _, node in ipairs(xml) do if type(node) == "string" then @@ -81,12 +90,15 @@ function NotesTabClass:Load(xml, fileName) self.lastContent = self.controls.edit.buf end +---@param xml table function NotesTabClass:Save(xml) self:SetShowColorCodes(false) t_insert(xml, self.controls.edit.buf) self.lastContent = self.controls.edit.buf end +---@param viewPort Rect +---@param inputEvents InputEvent[] function NotesTabClass:Draw(viewPort, inputEvents) self.x = viewPort.x self.y = viewPort.y diff --git a/src/Classes/PartyTab.lua b/src/Classes/PartyTab.lua index da9af814916..d9c236965fd 100644 --- a/src/Classes/PartyTab.lua +++ b/src/Classes/PartyTab.lua @@ -9,10 +9,32 @@ local s_format = string.format local t_insert = table.insert local m_max = math.max +---@alias PartyBuffType "PartyMemberStats"|"Aura"|"Curse"|"Warcry"|"Link"|"EnemyConditions"|"EnemyMods"|"PlayerMods" + +---@class PartyTabLastContent +---@field PartyMemberStats? string +---@field Aura string +---@field Curse string +---@field Warcry string +---@field Link string +---@field EnemyCond string +---@field EnemyMods string +---@field EnableExportBuffs boolean +---@field showAdvancedTools boolean + ---@class PartyTab: ControlHost, Control +---@field build Build +---@field actor table +---@field enemyModList ModList +---@field buffExports table +---@field enableExportBuffs boolean +---@field lastContent PartyTabLastContent +---@field modFlag boolean +---@field [string] unknown local PartyTabClass = newClass("PartyTab", "ControlHost", "Control") ---@param build Build +---@return PartyTab function PartyTabClass:PartyTab(build) self:ControlHost() self:Control() @@ -513,6 +535,8 @@ function PartyTabClass:PartyTab(build) return self end +---@param xml table +---@param fileName string function PartyTabClass:Load(xml, fileName) for _, node in ipairs(xml) do if node.elem == "ImportedBuffs" then @@ -572,6 +596,7 @@ function PartyTabClass:Load(xml, fileName) self.lastContent.showAdvancedTools = self.controls.ShowAdvanceTools.state end +---@param xml table function PartyTabClass:Save(xml) local child if self.controls.editPartyMemberStats.buf and self.controls.editPartyMemberStats.buf ~= "" then @@ -667,6 +692,8 @@ function PartyTabClass:Save(xml) self.lastContent.showAdvancedTools = self.controls.ShowAdvanceTools.state end +---@param viewPort Rect +---@param inputEvents InputEvent[] function PartyTabClass:Draw(viewPort, inputEvents) self.x = viewPort.x self.y = viewPort.y @@ -715,6 +742,10 @@ function PartyTabClass:Draw(viewPort, inputEvents) or self.lastContent.showAdvancedTools ~= self.controls.ShowAdvanceTools.state) end +---@param list ModDB|ModList|table +---@param buf string +---@param buffType PartyBuffType +---@param label? LabelControl|table function PartyTabClass:ParseBuffs(list, buf, buffType, label) if buffType == "EnemyConditions" then for line in buf:gmatch("([^\n]*)\n?") do @@ -981,6 +1012,7 @@ function PartyTabClass:ParseBuffs(list, buf, buffType, label) end end +---@param buffExports table function PartyTabClass:setBuffExports(buffExports) if not self.enableExportBuffs then return @@ -989,6 +1021,8 @@ function PartyTabClass:setBuffExports(buffExports) self.buffExports = copyTable(buffExports, true) end +---@param buffType PartyBuffType +---@return string function PartyTabClass:exportBuffs(buffType) if not self.enableExportBuffs or not self.buffExports or not self.buffExports[buffType] then return "" diff --git a/src/Classes/PassiveMasteryControl.lua b/src/Classes/PassiveMasteryControl.lua index 99b5570765e..b85d80bcf89 100644 --- a/src/Classes/PassiveMasteryControl.lua +++ b/src/Classes/PassiveMasteryControl.lua @@ -10,6 +10,13 @@ local m_max = math.max local m_floor = math.floor ---@class PassiveMasteryControl: ListControl +---@field list MasterListElem[] +---@field treeTab TreeTab +---@field treeView PassiveTreeView +---@field node Node +---@field saveButton ButtonControl +---@field selIndex? integer +---@field selValue? MasterListElem local PassiveMasteryControlClass = newClass("PassiveMasteryControl", "ListControl") ---@class MasterListElem @@ -22,6 +29,7 @@ local PassiveMasteryControlClass = newClass("PassiveMasteryControl", "ListContro ---@param treeTab TreeTab ---@param node Node ---@param saveButton ButtonControl +---@return PassiveMasteryControl function PassiveMasteryControlClass:PassiveMasteryControl(anchor, rect, list, treeTab, node, saveButton) self.list = list or { } -- automagical width @@ -37,16 +45,24 @@ function PassiveMasteryControlClass:PassiveMasteryControl(anchor, rect, list, tr return self end +---@param viewPort Rect function PassiveMasteryControlClass:Draw(viewPort) self.ListControl.Draw(self, viewPort) end +---@param column integer +---@param index integer +---@param effect MasterListElem +---@return string? function PassiveMasteryControlClass:GetRowValue(column, index, effect) if column == 1 then return effect.label end end +---@param tooltip Tooltip +---@param index integer +---@param effect MasterListElem function PassiveMasteryControlClass:AddValueTooltip(tooltip, index, effect) tooltip:Clear() self.node.sd = self.treeTab.build.spec.tree.masteryEffects[effect.id].sd @@ -55,6 +71,9 @@ function PassiveMasteryControlClass:AddValueTooltip(tooltip, index, effect) self.treeView:AddNodeTooltip(tooltip, self.node, self.treeTab.build) end +---@param index integer +---@param mastery MasterListElem +---@param doubleClick? boolean function PassiveMasteryControlClass:OnSelClick(index, mastery, doubleClick) self.treeTab:SaveMasteryPopup(self.node, self) end \ No newline at end of file diff --git a/src/Classes/PassiveSpec.lua b/src/Classes/PassiveSpec.lua index 3e645124071..cef015a6810 100644 --- a/src/Classes/PassiveSpec.lua +++ b/src/Classes/PassiveSpec.lua @@ -17,13 +17,25 @@ local band = bit.band local bor = bit.bor ---@class PassiveSpec: UndoHandler +---@field build Build +---@field treeVersion string +---@field tree PassiveTree ---@field nodes table ---@field allocNodes table +---@field jewel_data table +---@field allocSubgraphNodes table +---@field allocExtendedNodes table +---@field jewels table +---@field subGraphs table +---@field masterySelections table +---@field hashOverrides table +---@field splitPersonalityPath table local PassiveSpecClass = newClass("PassiveSpec", "UndoHandler") ---@param build Build ----@param treeVersion any ----@param convert any +---@param treeVersion string +---@param convert? boolean +---@return PassiveSpec function PassiveSpecClass:PassiveSpec(build, treeVersion, convert) self:UndoHandler() @@ -36,6 +48,8 @@ function PassiveSpecClass:PassiveSpec(build, treeVersion, convert) return self end +---@param treeVersion string +---@param convert? boolean function PassiveSpecClass:Init(treeVersion, convert) self.treeVersion = treeVersion self.tree = main:LoadTree(treeVersion) @@ -100,6 +114,9 @@ function PassiveSpecClass:Init(treeVersion, convert) self.clusterHashFormatVersion = 2 end +---@param xml table +---@param dbFileName string +---@return boolean? function PassiveSpecClass:Load(xml, dbFileName) self.title = xml.attrib.title -- Specs without this attribute predate the hash-fix migration and are treated as legacy. @@ -194,6 +211,7 @@ function PassiveSpecClass:Load(xml, dbFileName) self:ResetUndo() end +---@param xml table function PassiveSpecClass:Save(xml) local allocNodeIdList = { } for nodeId in pairs(self.allocNodes) do @@ -253,6 +271,14 @@ function PassiveSpecClass:PostLoad() end -- Import passive spec from the provided class IDs and node hash list +---@param className string +---@param classId integer +---@param ascendClassId? integer +---@param secondaryAscendClassId? integer +---@param hashList integer[] +---@param hashOverrides table? +---@param masteryEffects table? +---@param treeVersion string function PassiveSpecClass:ImportFromNodeList(className, classId, ascendClassId, secondaryAscendClassId, hashList, hashOverrides, masteryEffects, treeVersion) if hashOverrides == nil then hashOverrides = {} end if treeVersion and treeVersion ~= self.treeVersion then @@ -311,6 +337,9 @@ function PassiveSpecClass:ImportFromNodeList(className, classId, ascendClassId, self:BuildAllDependsAndPaths() end +---@param nodes integer[] +---@param isCluster? boolean +---@param endian? "little"|"big" function PassiveSpecClass:AllocateDecodedNodes(nodes, isCluster, endian) for i = 1, #nodes - 1, 2 do local id @@ -330,6 +359,8 @@ function PassiveSpecClass:AllocateDecodedNodes(nodes, isCluster, endian) end end +---@param masteryEffects table +---@param endian? "little"|"big" function PassiveSpecClass:AllocateMasteryEffects(masteryEffects, endian) for i = 1, #masteryEffects - 1, 4 do local effectId, id @@ -374,10 +405,16 @@ function PassiveSpecClass:AllocateMasteryEffects(masteryEffects, endian) end -- Decode the given poeplanner passive tree URL +---@param url string +---@param return_tree_version_only? boolean +---@return string|integer[]? function PassiveSpecClass:DecodePoePlannerURL(url, return_tree_version_only) -- poeplanner uses little endian numbers (GGG using BIG). -- If return_tree_version_only is True, then the return value will either be an error message or the tree version. -- both error messages begin with 'Invalid' + ---@param bytes string + ---@param start integer + ---@return integer local function byteToInt(bytes, start) -- get a little endian number from two bytes return bytes:byte(start) + bytes:byte(start + 1) * 256 @@ -477,6 +514,8 @@ function PassiveSpecClass:DecodePoePlannerURL(url, return_tree_version_only) end -- Decode the given GGG passive tree URL +---@param url string +---@return boolean? function PassiveSpecClass:DecodeURL(url) local b = common.base64.decode(url:gsub("^.+/",""):gsub("-","+"):gsub("_","/")) if not b or #b < 6 then @@ -524,6 +563,8 @@ end -- Encodes the current spec into a URL, using the official skill tree's format -- Prepends the URL with an optional prefix +---@param prefix string +---@return string function PassiveSpecClass:EncodeURL(prefix) local a = { 0, 0, 0, 6, self.curClassId, bor(b_lshift(self.curSecondaryAscendClassId or 0, 2), self.curAscendClassId) } @@ -570,6 +611,7 @@ function PassiveSpecClass:EncodeURL(prefix) end -- Change the current class, preserving currently allocated nodes if they connect to the new class's starting node +---@param classId integer function PassiveSpecClass:SelectClass(classId) if self.curClassId then -- Deallocate the current class's starting node @@ -607,6 +649,7 @@ function PassiveSpecClass:ResetAscendClass() end end +---@param ascendClassId integer function PassiveSpecClass:SelectAscendClass(ascendClassId) self:ResetAscendClass() @@ -627,6 +670,7 @@ function PassiveSpecClass:SelectAscendClass(ascendClassId) self:BuildAllDependsAndPaths() end +---@param ascendClassId integer function PassiveSpecClass:SelectSecondaryAscendClass(ascendClassId) -- if Secondary Ascendancy does not exist on this tree version if not self.tree.alternate_ascendancies then @@ -667,6 +711,8 @@ end -- Determines if the given class's start node is connected to the current class's start node -- Attempts to find a path between the nodes which doesn't pass through any ascendancy nodes (i.e. Ascendant) +---@param classId integer +---@return boolean function PassiveSpecClass:IsClassConnected(classId) for _, other in ipairs(self.nodes[self.tree.classes[classId].startNodeId].linked) do -- For each of the nodes to which the given class's start node connects... @@ -691,6 +737,8 @@ function PassiveSpecClass:IsClassConnected(classId) end -- Find and allocate the shortest path to connect to a target class's starting node +---@param classId integer +---@return boolean function PassiveSpecClass:ConnectToClass(classId) local classData = self.tree.classes[classId] if not classData then @@ -701,6 +749,8 @@ function PassiveSpecClass:ConnectToClass(classId) return false end + ---@param node Node + ---@return boolean local function isMainTreeNode(node) return node and not node.isProxy @@ -778,6 +828,8 @@ end -- Allocate the given node, if possible, and all nodes along the path to the node -- An alternate path to the node may be provided, otherwise the default path will be used -- The path must always contain the given node, as will be the case for the default path +---@param node Node +---@param altPath? Node[] function PassiveSpecClass:AllocNode(node, altPath) if not node.path then -- Node cannot be connected to the tree as there is no possible path @@ -817,6 +869,7 @@ function PassiveSpecClass:AllocNode(node, altPath) end end +---@param node Node function PassiveSpecClass:DeallocSingleNode(node) node.alloc = false self.allocNodes[node.id] = nil @@ -827,6 +880,7 @@ function PassiveSpecClass:DeallocSingleNode(node) end -- Deallocate the given node, and all nodes which depend on it (i.e. which are only connected to the tree through this node) +---@param node Node function PassiveSpecClass:DeallocNode(node) local rebuildClusterJewelGraphs = false for _, depNode in ipairs(node.depends) do @@ -843,6 +897,7 @@ function PassiveSpecClass:DeallocNode(node) end -- Count the number of allocated nodes and allocated ascendancy nodes +---@return integer function PassiveSpecClass:CountAllocNodes() local used, ascUsed, secondaryAscUsed, sockets = 0, 0, 0, 0 for _, node in pairs(self.allocNodes) do @@ -867,6 +922,10 @@ end -- Attempt to find a class start node starting from the given node -- Unless noAscent == true it will also look for an ascendancy class start node +---@param node Node +---@param visited? table +---@param noAscend? boolean +---@return Node? function PassiveSpecClass:FindStartFromNode(node, visited, noAscend) -- Mark the current node as visited so we don't go around in circles node.visited = true @@ -898,6 +957,8 @@ function PassiveSpecClass:FindStartFromNode(node, visited, noAscend) end end +---@param itemId integer +---@return Item? function PassiveSpecClass:GetJewel(itemId) if not itemId or itemId == 0 then return @@ -909,6 +970,8 @@ function PassiveSpecClass:GetJewel(itemId) return item end +---@param nodeId integer +---@return Item? function PassiveSpecClass:GetSocketedJewel(nodeId) local itemId = self.jewels[nodeId] if (not itemId or itemId == 0) and self.legacyClusterNodeMapReverse then @@ -922,6 +985,7 @@ end -- Determine this node's distance from the class' start -- Only allocated nodes can be traversed +---@param root Node function PassiveSpecClass:SetNodeDistanceToClassStart(root) root.distanceToClassStart = 0 if not root.alloc or not root.connectedToStart then @@ -965,6 +1029,8 @@ end -- Determine the shortest path from the given node to the class' start -- Only allocated nodes can be traversed +---@param rootId integer +---@return Node[]? function PassiveSpecClass:GetShortestPathToClassStart(rootId) local root = self.nodes[rootId] if not root or not root.alloc then @@ -1026,6 +1092,7 @@ end -- cache the unallocated mastery option data as it doesn't change based on the build -- adds stat descriptions and other node data for unallocated mastery nodes +---@param node Node function PassiveSpecClass:AddMasteryEffectOptionsToNode(node) local treeNode = self.tree.nodes[node.id] local cacheNode = treeNode and treeNode.masteryCache @@ -1058,6 +1125,8 @@ function PassiveSpecClass:AddMasteryEffectOptionsToNode(node) end node.allMasteryOptions = true end +---@param node Node +---@return Node[] function PassiveSpecClass:NodesInIntuitiveLeapLikeRadius(node) local result = { } if self.jewels[node.id] and self.jewels[node.id] > 0 then @@ -1694,6 +1763,9 @@ function PassiveSpecClass:BuildAllDependsAndPaths() self:BuildSplitPersonalityPath() end +---@param old Node +---@param newNode Node +---@return integer? alreadyMatched @Returns 1 when the node already has the requested stat descriptor. function PassiveSpecClass:ReplaceNode(old, newNode) -- Edited nodes can share a name if old.sd == newNode.sd then @@ -1717,8 +1789,8 @@ function PassiveSpecClass:ReplaceNode(old, newNode) old.reminderText = newNode.reminderText or wipeTable(old.reminderText) end ----Reconnects altered timeless jewel to class start, for Pure Talent ----@param node table @ The node to add the Condition:ConnectedTo[Class] flag to, if applicable +-- Reconnects altered timeless jewel to class start, for Pure Talent +---@param node Node @The node to add the Condition:ConnectedTo[Class] flag to, if applicable. function PassiveSpecClass:ReconnectNodeToClassStart(node) for _, linkedNodeId in ipairs(node.linkedId) do for classId, class in pairs(self.tree.classes) do @@ -1731,6 +1803,7 @@ end -- Initializes temporary lookup tables used when loading legacy (v1) cluster hashes. -- Returns true when legacy conversion is active for this graph rebuild. +---@return boolean function PassiveSpecClass:BeginLegacyClusterHashConversion() local needsLegacyClusterHashConversion = (self.clusterHashFormatVersion or 2) < 2 self.legacyClusterNodeMap = needsLegacyClusterHashConversion and { } or nil @@ -1740,6 +1813,8 @@ end -- Legacy conversion updates node IDs while rebuilding cluster subgraphs. -- This helper keeps forward and reverse mappings in sync. +---@param legacyNodeId integer +---@param currentNodeId integer function PassiveSpecClass:RegisterLegacyClusterNodeMap(legacyNodeId, currentNodeId) if not self.legacyClusterNodeMap or not legacyNodeId or not currentNodeId then return @@ -1751,6 +1826,8 @@ function PassiveSpecClass:RegisterLegacyClusterNodeMap(legacyNodeId, currentNode end -- Returns the remapped node ID when a valid legacy -> current cluster mapping exists. +---@param nodeId integer +---@return integer? function PassiveSpecClass:GetMappedClusterNodeId(nodeId) local mappedNodeId = self.legacyClusterNodeMap and self.legacyClusterNodeMap[nodeId] if mappedNodeId and self.nodes[mappedNodeId] then @@ -1880,6 +1957,9 @@ function PassiveSpecClass:BuildClusterJewelGraphs() end -- Finds a specific expansion socket entry within a passive-tree group. +---@param group PassiveTreeGroup +---@param index integer +---@return Node? function PassiveSpecClass:FindClusterSocket(group, index) for _, nodeId in ipairs(group.n) do local node = self.tree.nodes[tonumber(nodeId)] @@ -1891,6 +1971,10 @@ end -- Legacy parser behavior downsized the proxy group while descending into nested clusters. -- Reproducing that traversal lets us recover legacy socket IDs for migration. +---@param proxyGroup PassiveTreeGroup +---@param expansionJewelSize integer +---@param clusterSizeIndex integer +---@return PassiveTreeGroup function PassiveSpecClass:BuildLegacyProxyGroup(proxyGroup, expansionJewelSize, clusterSizeIndex) local legacyGroup = proxyGroup local groupSize = expansionJewelSize @@ -1913,6 +1997,10 @@ end -- Converts cluster orbit indices between different node-count spaces. -- 12<->16 mappings reflect the 3.17 cluster export change; 6<->16 supports legacy nested mapping. +---@param srcOidx integer +---@param srcNodesPerOrbit integer +---@param destNodesPerOrbit integer +---@return integer function PassiveSpecClass:TranslateClusterOrbitIndex(srcOidx, srcNodesPerOrbit, destNodesPerOrbit) if srcNodesPerOrbit == destNodesPerOrbit then return srcOidx @@ -1933,6 +2021,10 @@ function PassiveSpecClass:TranslateClusterOrbitIndex(srcOidx, srcNodesPerOrbit, end -- Applies proxy orbit offsets and converts from cluster template indices into tree orbit-space indices. +---@param indicies integer[] +---@param startOidx integer +---@param clusterTotalIndicies integer +---@param skillsPerOrbit integer function PassiveSpecClass:ApplyClusterOrbitIndexAdjustment(indicies, startOidx, clusterTotalIndicies, skillsPerOrbit) for _, node in pairs(indicies) do local correctedNodeOidxRelativeToClusterIndicies = (node.oidx + startOidx) % clusterTotalIndicies @@ -1941,6 +2033,10 @@ function PassiveSpecClass:ApplyClusterOrbitIndexAdjustment(indicies, startOidx, end -- Builds additional legacy node mappings by matching equivalent nodes in legacy and current orbit spaces. +---@param indicies integer[] +---@param proxyNode Node +---@param clusterTotalIndicies integer +---@param skillsPerOrbit integer function PassiveSpecClass:BuildLegacyClusterOrbitMappings(indicies, proxyNode, clusterTotalIndicies, skillsPerOrbit) if not self.legacyClusterNodeMap then return @@ -1967,6 +2063,12 @@ function PassiveSpecClass:BuildLegacyClusterOrbitMappings(indicies, proxyNode, c end end +---@param jewel Item +---@param parentSocket Node +---@param id integer +---@param upSize? integer +---@param importedNodes? table +---@param importedGroups? table function PassiveSpecClass:BuildSubgraph(jewel, parentSocket, id, upSize, importedNodes, importedGroups) local expansionJewel = parentSocket.expansionJewel local clusterJewel = jewel.clusterJewel @@ -2015,6 +2117,8 @@ function PassiveSpecClass:BuildSubgraph(jewel, parentSocket, id, upSize, importe subGraph.group.y = proxyGroup.y -- end + ---@param node1 Node + ---@param node2 Node local function linkNodes(node1, node2) t_insert(node1.linked, node2) t_insert(node2.linked, node1) @@ -2026,6 +2130,8 @@ function PassiveSpecClass:BuildSubgraph(jewel, parentSocket, id, upSize, importe end end + ---@param proxyId integer + ---@return integer? groupId local function matchGroup(proxyId) for groupId, groupData in pairs(importedGroups) do if groupData.proxy == proxyId then @@ -2034,6 +2140,8 @@ function PassiveSpecClass:BuildSubgraph(jewel, parentSocket, id, upSize, importe end end + ---@param nodeId integer + ---@return boolean local function inExtendedHashes(nodeId) for _, exID in ipairs(self.extended_hashes) do if nodeId == exID then @@ -2043,6 +2151,8 @@ function PassiveSpecClass:BuildSubgraph(jewel, parentSocket, id, upSize, importe return false end + ---@param node Node + ---@return boolean local function addToAllocatedSubgraphNodes(node) -- Don't add to allocSubgraphNodes if node already exists if isValueInArray(self.allocSubgraphNodes, node.id) then @@ -2152,6 +2262,8 @@ function PassiveSpecClass:BuildSubgraph(jewel, parentSocket, id, upSize, importe local indicies = { } + ---@param nodeIndex integer + ---@param jewelIndex integer local function makeJewel(nodeIndex, jewelIndex) -- Look for the socket local socket = self:FindClusterSocket(proxyGroup, jewelIndex) @@ -2358,6 +2470,7 @@ function PassiveSpecClass:BuildSubgraph(jewel, parentSocket, id, upSize, importe --ConPrintTable(subGraph) end +---@return table function PassiveSpecClass:CreateUndoState() local allocNodeIdList = { } for nodeId in pairs(self.allocNodes) do @@ -2382,6 +2495,8 @@ function PassiveSpecClass:CreateUndoState() } end +---@param state table +---@param treeVersion? string function PassiveSpecClass:RestoreUndoState(state, treeVersion) self:ImportFromNodeList(nil, state.classId, state.ascendClassId, state.secondaryAscendClassId, state.hashList, state.hashOverrides, state.masteryEffects, treeVersion or state.treeVersion) self:SetWindowTitleWithBuildClass() @@ -2396,9 +2511,9 @@ function PassiveSpecClass:SetWindowTitleWithBuildClass() end --- Adds a line to or replaces a node given a line to add/replace with ---- @param node table The node to replace/add to ---- @param sd string The line being parsed and added ---- @param replacement? boolean true to replace the node with the new mod, false to simply add it +---@param node Node @The node to replace/add to. +---@param sd string @The line being parsed and added. +---@param replacement? boolean @True to replace the node with the new mod, false to simply add it. function PassiveSpecClass:NodeAdditionOrReplacementFromString(node,sd,replacement) local addition = {} addition.sd = {sd} @@ -2482,6 +2597,10 @@ function PassiveSpecClass:NodeAdditionOrReplacementFromString(node,sd,replacemen node.modList = modList end +---@param keystoneNames table +---@param nodeId integer +---@param radiusIndex integer +---@return boolean function PassiveSpecClass:NodeInKeystoneRadius(keystoneNames, nodeId, radiusIndex) for keystoneName, _ in pairs(keystoneNames) do local keystoneNode = self.tree.keystoneMap[keystoneName] diff --git a/src/Classes/PassiveSpecListControl.lua b/src/Classes/PassiveSpecListControl.lua index 7f760ed3f03..35a4284b80d 100644 --- a/src/Classes/PassiveSpecListControl.lua +++ b/src/Classes/PassiveSpecListControl.lua @@ -8,8 +8,15 @@ local t_remove = table.remove local m_max = math.max ---@class PassiveSpecListControl: ListControl +---@field treeTab TreeTab +---@field selIndex? integer +---@field selValue? PassiveSpec local PassiveSpecListClass = newClass("PassiveSpecListControl", "ListControl") +---@param anchor? Anchor +---@param rect? Rect +---@param treeTab TreeTab +---@return PassiveSpecListControl function PassiveSpecListClass:PassiveSpecListControl(anchor, rect, treeTab) self:ListControl(anchor, rect, 16, "VERTICAL", true, treeTab.specList) self.treeTab = treeTab @@ -47,6 +54,9 @@ function PassiveSpecListClass:PassiveSpecListControl(anchor, rect, treeTab) return self end +---@param spec PassiveSpec +---@param title? string +---@param addOnName? boolean function PassiveSpecListClass:RenameSpec(spec, title, addOnName) local controls = { } controls.label = new("LabelControl"):LabelControl(nil, {0, 20, 0, 16}, "^7Enter name for this passive tree:") @@ -73,6 +83,10 @@ function PassiveSpecListClass:RenameSpec(spec, title, addOnName) main:OpenPopup(370, 100, title, controls, "save", "edit") end +---@param column integer +---@param index integer +---@param spec PassiveSpec +---@return string? function PassiveSpecListClass:GetRowValue(column, index, spec) if column == 1 then local used = spec:CountAllocNodes() @@ -90,12 +104,17 @@ function PassiveSpecListClass:OnOrderChange() self.treeTab.build:SyncLoadouts() end +---@param index integer +---@param spec PassiveSpec +---@param doubleClick? boolean function PassiveSpecListClass:OnSelClick(index, spec, doubleClick) if doubleClick and index ~= self.treeTab.activeSpec then self.treeTab:SetActiveSpec(index) end end +---@param index integer +---@param spec PassiveSpec function PassiveSpecListClass:OnSelDelete(index, spec) if #self.list > 1 then main:OpenConfirmPopup("Delete Tree", "Are you sure you want to delete '"..(spec.title or "Default").."'?", "Delete", function() @@ -114,6 +133,9 @@ function PassiveSpecListClass:OnSelDelete(index, spec) end end +---@param index integer +---@param spec PassiveSpec +---@param key string function PassiveSpecListClass:OnSelKeyDown(index, spec, key) if key == "F2" then self:RenameSpec(spec, "Rename Tree") diff --git a/src/Classes/PassiveTree.lua b/src/Classes/PassiveTree.lua index 2692c70301b..0096cfe301d 100644 --- a/src/Classes/PassiveTree.lua +++ b/src/Classes/PassiveTree.lua @@ -36,6 +36,8 @@ local legacyOrbitRadii = { 0, 82, 162, 335, 493 } -- Retrieve the file at the given URL -- This is currently disabled as it does not work due to issues -- its possible to fix this but its never used due to us performing preprocessing on tree +---@param URL string +---@return string|false local function getFile(URL) local page = "" local easy = common.curl.easy() @@ -57,9 +59,14 @@ end ---@field background any ---@field isProxy boolean? +---@class PassiveTreeSpriteSheet +---@field handle ImageHandle +---@field width number +---@field height number + ---@class PassiveTree ----@field classes any[] A list of classes on the tree ----@field alternate_ascendancies any[]? +---@field classes table[] A list of classes on the tree +---@field alternate_ascendancies table[]? ---@field tree "Default"|"DefaultAltAscendancies" ---@field groups PassiveTreeGroup[] ---@field nodes table<"root"|integer, Node> @@ -68,10 +75,30 @@ end ---@field min_y integer ---@field max_x integer ---@field max_y integer ----@field constants table +---@field constants table ---@field points table +---@field treeVersion string +---@field size number +---@field sockets table +---@field connectors table[] +---@field orbitRadii number[] +---@field skillsPerOrbit integer[] +---@field masteryEffects table +---@field assets table +---@field legion table +---@field tattoo table +---@field classNameMap table +---@field ascendNameMap table +---@field secondaryAscendNameMap? table +---@field internalAscendNameMap table +---@field classNotables table +---@field orbitAnglesByOrbit table +---@field spriteMap table +---@field bloodlineSpritePrefixes? table local PassiveTreeClass = newClass("PassiveTree") +---@param treeVersion string +---@return PassiveTree function PassiveTreeClass:PassiveTree(treeVersion) self.treeVersion = treeVersion local versionNum = treeVersions[treeVersion].num @@ -388,6 +415,8 @@ function PassiveTreeClass:PassiveTree(treeVersion) -- Load legion sprite sheets and build sprite map local legionSprites = require("TreeData.legion.tree-legion") + ---@param data { filename: string } + ---@return PassiveTreeSpriteSheet local function loadLegionSheet(data) local sheet = spriteSheets[data.filename] if not sheet then @@ -792,6 +821,8 @@ function PassiveTreeClass:PassiveTree(treeVersion) return self end +---@param node Node +---@param startIndex? integer function PassiveTreeClass:ProcessStats(node, startIndex) startIndex = startIndex or 1 if startIndex == 1 then @@ -872,6 +903,7 @@ function PassiveTreeClass:ProcessStats(node, startIndex) end -- Common processing code for nodes (used for both real tree nodes and subgraph nodes) +---@param node Node function PassiveTreeClass:ProcessNode(node) -- Assign node artwork assets if node.type == "Mastery" and node.masteryEffects then @@ -906,6 +938,10 @@ function PassiveTreeClass:ProcessNode(node) end -- Checks if a given image is present and downloads it from the given URL if it isn't there +---@param imgName string +---@param url string +---@param data table @Asset metadata table updated with handle, width, and height fields. +---@param ... unknown function PassiveTreeClass:LoadImage(imgName, url, data, ...) local imgFile = io.open("TreeData/"..imgName, "r") if imgFile then @@ -933,6 +969,9 @@ function PassiveTreeClass:LoadImage(imgName, url, data, ...) end -- Generate the quad used to render the line between the two given nodes +---@param node1 Node +---@param node2 Node +---@return table[] function PassiveTreeClass:BuildConnector(node1, node2) local connector = { ascendancyName = node1.ascendancyName, @@ -996,6 +1035,10 @@ function PassiveTreeClass:BuildConnector(node1, node2) return { connector } end +---@param arcAngle number +---@param node1 Node +---@param connector table +---@param isMirroredArc? boolean function PassiveTreeClass:BuildArc(arcAngle, node1, connector, isMirroredArc) connector.type = "Orbit" .. node1.o -- This is an arc texture mapped onto a kite-shaped quad @@ -1038,6 +1081,8 @@ function PassiveTreeClass:BuildArc(arcAngle, node1, connector, isMirroredArc) connector.c[15], connector.c[16] = p, 0 end +---@param nodesInOrbit integer +---@return number[] function PassiveTreeClass:CalcOrbitAngles(nodesInOrbit) local orbitAngles = {} diff --git a/src/Classes/PassiveTreeView.lua b/src/Classes/PassiveTreeView.lua index c7f8976b473..d9e6c887b0d 100644 --- a/src/Classes/PassiveTreeView.lua +++ b/src/Classes/PassiveTreeView.lua @@ -20,14 +20,54 @@ local JEWEL_RADIUS_TINT_COMPARE_ONLY = { 0, 1, 0, 0.7 } local gemTooltip = require("Classes.GemTooltip") +---@param node Node +---@return string? local function isAbyssConquered(node) local conqueror = node and node.conqueredBy and node.conqueredBy.conqueror return conqueror and conqueror.type and conqueror.type:match("^abyss_") end ---@class PassiveTreeView +---@field ring ImageHandle +---@field highlightRing ImageHandle +---@field jewelShadedOuterRing ImageHandle +---@field jewelShadedOuterRingFlipped ImageHandle +---@field jewelShadedInnerRing ImageHandle +---@field jewelShadedInnerRingFlipped ImageHandle +---@field eternal1 ImageHandle +---@field eternal2 ImageHandle +---@field karui1 ImageHandle +---@field karui2 ImageHandle +---@field maraketh1 ImageHandle +---@field maraketh2 ImageHandle +---@field templar1 ImageHandle +---@field templar2 ImageHandle +---@field vaal1 ImageHandle +---@field vaal2 ImageHandle +---@field kalguur1 ImageHandle +---@field kalguur2 ImageHandle +---@field zoom number +---@field zoomLevel number +---@field zoomX number +---@field zoomY number +---@field dragging boolean +---@field dragY number +---@field hoverNode? Node +---@field compareSpec? PassiveSpec +---@field tooltip Tooltip +---@field skillTooltip Tooltip +---@field searchStr string +---@field searchStrSaved string +---@field searchStrCached string +---@field searchParams string[] +---@field searchStrResults table +---@field showHeatMap boolean +---@field showStatDifferences boolean +---@field traceMode boolean +---@field tracePath? Node[] local PassiveTreeViewClass = newClass("PassiveTreeView") +---@return PassiveTreeView function PassiveTreeViewClass:PassiveTreeView() self.ring = NewImageHandle() self.ring:Load("Assets/ring.png", "CLAMP") @@ -84,6 +124,8 @@ function PassiveTreeViewClass:PassiveTreeView() return self end +---@param xml table +---@param fileName string function PassiveTreeViewClass:Load(xml, fileName) if xml.attrib.zoomLevel then self.zoomLevel = tonumber(xml.attrib.zoomLevel) @@ -102,6 +144,7 @@ function PassiveTreeViewClass:Load(xml, fileName) end end +---@param xml table function PassiveTreeViewClass:Save(xml) self.searchStrSaved = self.searchStr xml.attrib = { @@ -115,6 +158,8 @@ end -- Look up the jewel item socketed at a given node ID in a compare spec. -- Uses itemsTab.sockets (the slot controls) which stay in sync with the active item/tree set. +---@param nodeId integer +---@return Item? function PassiveTreeViewClass:GetCompareJewel(nodeId) if not self.compareSpec then return nil end local cBuild = self.compareSpec.build @@ -128,6 +173,9 @@ function PassiveTreeViewClass:GetCompareJewel(nodeId) end -- Returns the overlay asset name for a socketed jewel, or nil if no special overlay applies. +---@param jewel Item +---@param isExpansion? boolean +---@return string? function PassiveTreeViewClass:GetJewelSocketOverlay(jewel, isExpansion) if jewel.baseName == "Crimson Jewel" then return isExpansion and "JewelSocketActiveRedAlt" or "JewelSocketActiveRed" @@ -158,6 +206,9 @@ function PassiveTreeViewClass:GetJewelSocketOverlay(jewel, isExpansion) end end +---@param a Item? +---@param b Item? +---@return boolean local function compareJewelsEqual(a, b) if not a or not b then return a == b @@ -168,10 +219,13 @@ end -- Returns the draw color for a node when compare overlay is active. -- Handles diff coloring for allocated/unallocated, mastery changes, and jewel socket differences. ---@param node Node ----@param compareNode Node +---@param compareNode? Node ---@param spec PassiveSpec ---@param build Build ----@param nodeDefaultColor any +---@param nodeDefaultColor string +---@return string|number +---@return number? +---@return number? function PassiveTreeViewClass:GetCompareNodeColor(node, compareNode, spec, build, nodeDefaultColor) if not compareNode then return nodeDefaultColor @@ -194,6 +248,8 @@ function PassiveTreeViewClass:GetCompareNodeColor(node, compareNode, spec, build end ---@param build Build +---@param viewPort Rect +---@param inputEvents InputEvent[] function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) local spec = build.spec local tree = spec.tree @@ -281,10 +337,19 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) local scale = m_min(viewPort.width, viewPort.height) / tree.size * self.zoom local offsetX = self.zoomX + viewPort.x + viewPort.width/2 local offsetY = self.zoomY + viewPort.y + viewPort.height/2 + ---@param x number + ---@param y number + ---@return number + ---@return number local function treeToScreen(x, y) return x * scale + offsetX, y * scale + offsetY end + + ---@param x number + ---@param y number + ---@return number + ---@return number local function screenToTree(x, y) return (x - offsetX) / scale, (y - offsetY) / scale @@ -575,6 +640,8 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) self:DrawAsset(tree.assets.BackgroundDexInt, scrX, scrY, scale) end + ---@param group PassiveTreeGroup + ---@param isExpansion boolean local function renderGroup(group, isExpansion) local scrX, scrY = treeToScreen(group.x, group.y) if group.ascendancyName then @@ -667,9 +734,16 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) end local connectorColor = { 1, 1, 1 } + ---@param r number + ---@param g number + ---@param b number local function setConnectorColor(r, g, b) connectorColor[1], connectorColor[2], connectorColor[3] = r, g, b end + + ---@param n1 Node + ---@param n2 Node + ---@return string local function getState(n1, n2) -- Determine the connector state local state = "Normal" @@ -682,6 +756,8 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) end return state end + + ---@param connector table local function renderConnector(connector) local node1, node2 = spec.nodes[connector.nodeId1], spec.nodes[connector.nodeId2] local connectorDefaultColor = "^xFFFFFF" @@ -790,6 +866,8 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) if self.searchStrCached ~= self.searchStr then self.searchStrCached = self.searchStr + ---@param search string + ---@return string[] local function prepSearch(search) search = search:lower() --gsub("([%[%]%%])", "%%%1") @@ -1165,6 +1243,10 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) end -- Draw ring overlays for jewel sockets + ---@param jewel Item + ---@param scrX number + ---@param scrY number + ---@param tint number[] local function drawJewelRadius(jewel, scrX, scrY, tint) -- Abyss jewels do not show radius art in game. if isAbyssConquered(jewel.jewelData) then @@ -1257,6 +1339,14 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) end end end + +---@param handle ImageHandle +---@param x number +---@param y number +---@param width number +---@param height number +---@param angle number +---@param ... unknown function PassiveTreeViewClass:DrawImageRotated(handle, x, y, width, height, angle, ...) if main.showAnimations == false then -- Skip rotation and animation @@ -1283,6 +1373,11 @@ function PassiveTreeViewClass:DrawImageRotated(handle, x, y, width, height, angl end -- Draws the given asset at the given position +---@param data table +---@param x number +---@param y number +---@param scale number +---@param isHalf? boolean function PassiveTreeViewClass:DrawAsset(data, x, y, scale, isHalf) if not data then return @@ -1304,6 +1399,8 @@ function PassiveTreeViewClass:DrawAsset(data, x, y, scale, isHalf) end -- Zoom the tree in or out +---@param level number +---@param viewPort Rect function PassiveTreeViewClass:Zoom(level, viewPort) -- Calculate new zoom level and zoom factor self.zoomLevel = m_max(0, m_min(12, self.zoomLevel + level)) @@ -1319,6 +1416,9 @@ function PassiveTreeViewClass:Zoom(level, viewPort) self.zoomY = relY + (self.zoomY - relY) * factor end +---@param x number +---@param y number +---@param viewPort Rect ---@param build Build function PassiveTreeViewClass:Focus(x, y, viewPort, build) self.zoomLevel = 12 @@ -1331,6 +1431,8 @@ function PassiveTreeViewClass:Focus(x, y, viewPort, build) self.zoomY = -y * scale end +---@param node Node +---@return boolean? function PassiveTreeViewClass:DoesNodeMatchSearchParams(node) if node.type == "ClassStart" or (node.type == "Mastery" and not node.masteryEffects) then return @@ -1339,6 +1441,9 @@ function PassiveTreeViewClass:DoesNodeMatchSearchParams(node) local needMatches = copyTable(self.searchParams) local err + ---@param haystack string + ---@param need string[] + ---@return string[] local function search(haystack, need) for i=#need, 1, -1 do if haystack:matchOrPattern(need[i]) then @@ -1472,13 +1577,16 @@ end ---@param tooltip Tooltip ---@param node Node ---@param build Build ----@param returnEarly boolean? Whether the function should stop after writing the mod info, before any allocation-specific info +---@param returnEarly? boolean Whether the function should stop after writing the mod info, before any allocation-specific info function PassiveTreeViewClass:AddNodeTooltip(tooltip, node, build, returnEarly) local fontSizeBig = main.showFlavourText and 18 or 16 self.skillTooltip:Clear() tooltip.center = true tooltip.maxWidth = 800 -- Appends the compare spec's jewel tooltip if it has a jewel in this allocated socket. + ---@param socket Node + ---@param withLabel boolean + ---@return boolean local function addCompareJewelSection(socket, withLabel) local cJewel = self.compareSpec and self:GetCompareJewel(node.id) local cAllocated = self.compareSpec and self.compareSpec.allocNodes and self.compareSpec.allocNodes[node.id] @@ -1540,6 +1648,9 @@ function PassiveTreeViewClass:AddNodeTooltip(tooltip, node, build, returnEarly) end end + ---@param node Node + ---@param i integer + ---@param line string local function addModInfoToTooltip(node, i, line) if node.mods[i] then if launch.devModeAlt and node.mods[i].list then @@ -1769,6 +1880,9 @@ function PassiveTreeViewClass:AddNodeTooltip(tooltip, node, build, returnEarly) end end +---@param tooltip Tooltip +---@param node Node +---@param build Build function PassiveTreeViewClass:AddCompareNodeTooltip(tooltip, node, build) -- Tooltip for compare-only nodes (nodes only in the compared build, e.g. cluster jewel subgraph nodes) local fontSizeBig = main.showFlavourText and 18 or 16 @@ -1829,6 +1943,8 @@ function PassiveTreeViewClass:AddCompareNodeTooltip(tooltip, node, build) tooltip:AddLine(14, colorCodes.DEXTERITY .. "This node is only in the compared build") end +---@param tooltip Tooltip +---@param node Node function PassiveTreeViewClass:AddCompareNodeName(tooltip, node) tooltip:SetRecipe(node.recipe) local tooltipMap = { diff --git a/src/Classes/PathControl.lua b/src/Classes/PathControl.lua index af32ccbbb5d..d95042fc38a 100644 --- a/src/Classes/PathControl.lua +++ b/src/Classes/PathControl.lua @@ -6,9 +6,26 @@ local ipairs = ipairs local t_insert = table.insert ----@class PathControl +---@class PathFolder +---@field label string +---@field path string +---@field button ButtonControl + +---@class PathControl: Control, ControlHost, UndoHandler +---@field basePath string +---@field baseName string +---@field subPath string +---@field folderList PathFolder[] +---@field onChange? fun(subPath: string) +---@field otherDragSource? unknown local PathClass = newClass("PathControl", "Control", "ControlHost", "UndoHandler") +---@param anchor? Anchor +---@param rect? Rect +---@param basePath string +---@param subPath? string +---@param onChange? fun(subPath: string) +---@return PathControl function PathClass:PathControl(anchor, rect, basePath, subPath, onChange) self:Control(anchor, rect) self:ControlHost() @@ -21,6 +38,8 @@ function PathClass:PathControl(anchor, rect, basePath, subPath, onChange) return self end +---@param subPath string +---@param noUndo? boolean function PathClass:SetSubPath(subPath, noUndo) if subPath == self.subPath then return @@ -63,6 +82,7 @@ function PathClass:SetSubPath(subPath, noUndo) end end +---@return boolean|Control? function PathClass:IsMouseOver() if not self:IsShown() then return @@ -70,6 +90,7 @@ function PathClass:IsMouseOver() return self:IsMouseInBounds() or self:GetMouseOverControl() end +---@param viewPort Rect function PathClass:Draw(viewPort) local x, y = self:GetPos() local width, height = self:GetSize() @@ -90,6 +111,9 @@ function PathClass:Draw(viewPort) end end +---@param key string +---@param doubleClick? boolean +---@return Control? function PathClass:OnKeyDown(key, doubleClick) if not self:IsShown() or not self:IsEnabled() then return @@ -100,11 +124,12 @@ function PathClass:OnKeyDown(key, doubleClick) end end +---@return string function PathClass:CreateUndoState() return self.subPath end +---@param state string function PathClass:RestoreUndoState(state) self:SetSubPath(state, true) end - diff --git a/src/Classes/PoBArchivesProvider.lua b/src/Classes/PoBArchivesProvider.lua index d2d694b7746..431a1a6c9ec 100644 --- a/src/Classes/PoBArchivesProvider.lua +++ b/src/Classes/PoBArchivesProvider.lua @@ -10,8 +10,14 @@ local dkjson = require "dkjson" local archivesUrl = 'https://pobarchives.com' ---@class PoBArchivesProvider: ExtBuildListProvider +---@field mode "builds"|"similar" +---@field buildList table[] +---@field contentHeight? number +---@field statusMsg? string local PoBArchivesProviderClass = newClass("PoBArchivesProvider", "ExtBuildListProvider") +---@param mode "builds"|"similar" +---@return PoBArchivesProvider function PoBArchivesProviderClass:PoBArchivesProvider(mode) if mode == "builds" then self:ExtBuildListProvider({"Trending", "Latest"}) @@ -23,6 +29,7 @@ function PoBArchivesProviderClass:PoBArchivesProvider(mode) return self end +---@return string function PoBArchivesProviderClass:GetApiUrl() if self.importCode then return archivesUrl .. '/api/' .. 'recommendations' @@ -31,6 +38,7 @@ function PoBArchivesProviderClass:GetApiUrl() end end +---@return string? function PoBArchivesProviderClass:GetPageUrl() local buildsPath = '/builds' if self.activeList == "Latest" then @@ -47,6 +55,9 @@ function PoBArchivesProviderClass:GetPageUrl() return nil end + +---@param buildCode string +---@param postURL string function PoBArchivesProviderClass:GetRecommendations(buildCode, postURL) local id = LaunchSubScript([[ local code, connectionProtocol, proxyURL = ... @@ -89,6 +100,7 @@ function PoBArchivesProviderClass:GetRecommendations(buildCode, postURL) end +---@param message string function PoBArchivesProviderClass:ParseBuilds(message) local obj = dkjson.decode(message) if not obj or not obj.builds or next(obj.builds) == nil then diff --git a/src/Classes/PoEAPI.lua b/src/Classes/PoEAPI.lua index 7a9031df059..d2ce28eb085 100644 --- a/src/Classes/PoEAPI.lua +++ b/src/Classes/PoEAPI.lua @@ -12,8 +12,20 @@ local scopesOAuth = { local filename = "poe_api_response.json" ---@class PoEAPI +---@field retries integer +---@field authToken? string +---@field refreshToken? string +---@field tokenExpiry? integer +---@field baseUrl string +---@field rateLimiter TradeQueryRateLimiter +---@field tokenHasBeenValidated boolean +---@field ERROR_NO_AUTH string local PoEAPIClass = newClass("PoEAPI") +---@param authToken? string +---@param refreshToken? string +---@param tokenExpiry? integer +---@return PoEAPI function PoEAPIClass:PoEAPI(authToken, refreshToken, tokenExpiry) self.retries = 0 self.authToken = authToken @@ -30,7 +42,7 @@ end -- performs a basic check on the validity of the current login by refreshing the -- token if necessary. if a refresh is attempted and fails, the login details -- will be reset. ---- @param callback fun(valid: boolean, errMsg: string?) +---@param callback fun(valid: boolean, errMsg: string?) function PoEAPIClass:ValidateAuth(callback) if self.authToken and self.refreshToken and self.tokenExpiry then ConPrintf("Validating auth token") @@ -67,8 +79,8 @@ function PoEAPIClass:ValidateAuth(callback) callback(false) end end - ---- @param secret string +---@param secret string +---@return string local function base64_encode(secret) return base64.encode(secret):gsub("+", "-"):gsub("/", "_"):gsub("=$", "") end @@ -88,8 +100,7 @@ function PoEAPIClass:UpdateMain() main.tokenExpiry = self.tokenExpiry main:SaveSettings() end - ---- @param callback fun(errCode: string?) +---@param callback fun(errCode: string?) function PoEAPIClass:FetchAuthToken(callback) math.randomseed(os.time()) local secret = math.random(2 ^ 32 - 1) @@ -152,9 +163,8 @@ function PoEAPIClass:FetchAuthToken(callback) } end end - ---- @param endpoint string ---- @param callback fun(response: table?, errorMsg: string) +---@param endpoint string +---@param callback fun(response: table?, errMsg: string?) function PoEAPIClass:DownloadWithRefresh(endpoint, callback) self:ValidateAuth(function(valid, validationErrMsg) if not valid then @@ -188,11 +198,10 @@ function PoEAPIClass:DownloadWithRefresh(endpoint, callback) end, { header = "Authorization: Bearer " .. self.authToken }) end) end - ---- @alias DownloadCallback fun(body: table?, err: string?, timeout: integer?) ---- @param policy string ---- @param url string ---- @param callback DownloadCallback +---@alias DownloadCallback fun(body: table?, err: string?, timeout: integer?) +---@param policy string +---@param url string +---@param callback DownloadCallback function PoEAPIClass:DownloadWithRateLimit(policy, url, callback) local now = os.time() local timeNext = self.rateLimiter:NextRequestTime(policy, now) diff --git a/src/Classes/PopupDialog.lua b/src/Classes/PopupDialog.lua index 1e54e8c695b..8bc6a175a3f 100644 --- a/src/Classes/PopupDialog.lua +++ b/src/Classes/PopupDialog.lua @@ -6,8 +6,23 @@ local m_floor = math.floor ---@class PopupDialog: ControlHost, Control +---@field title Prop +---@field enterControl? string +---@field escapeControl? string +---@field scrollBarFunc? fun() +---@field resizeFunc? fun() local PopupDialogClass = newClass("PopupDialog", "ControlHost", "Control") +---@param width number +---@param height number +---@param title Prop +---@param controls table +---@param enterControl? string +---@param defaultControl? string +---@param escapeControl? string +---@param scrollBarFunc? fun() +---@param resizeFunc? fun() +---@return PopupDialog function PopupDialogClass:PopupDialog(width, height, title, controls, enterControl, defaultControl, escapeControl, scrollBarFunc, resizeFunc) self:ControlHost() @@ -41,6 +56,7 @@ function PopupDialogClass:PopupDialog(width, height, title, controls, enterContr return self end +---@param viewPort Rect function PopupDialogClass:Draw(viewPort) local x, y = self:GetPos() local width, height = self:GetSize() @@ -69,6 +85,8 @@ function PopupDialogClass:Draw(viewPort) self:DrawControls(viewPort) end +---@param inputEvents InputEvent[] +---@param viewPort Rect function PopupDialogClass:ProcessInput(inputEvents, viewPort) self:ProcessControlsInput(inputEvents, viewPort) for id, event in ipairs(inputEvents) do diff --git a/src/Classes/PowerReportListControl.lua b/src/Classes/PowerReportListControl.lua index 6472b01eeb2..6d7d7bacb58 100644 --- a/src/Classes/PowerReportListControl.lua +++ b/src/Classes/PowerReportListControl.lua @@ -9,8 +9,19 @@ local t_remove = table.remove local t_sort = table.sort ---@class PowerReportListControl: ListControl +---@field nodeSelectCallback fun(node: Node) +---@field originalList Node[] +---@field powerColumn ListColumn +---@field showClusters boolean +---@field showMasteries boolean +---@field allocated boolean +---@field colList ListColumn[] local PowerReportListClass = newClass("PowerReportListControl", "ListControl") +---@param anchor? Anchor +---@param rect? Rect +---@param nodeSelectCallback fun(node: Node) +---@return PowerReportListControl function PowerReportListClass:PowerReportListControl(anchor, rect, nodeSelectCallback) self:ListControl(anchor, rect, 16, "VERTICAL", false) @@ -46,6 +57,8 @@ function PowerReportListClass:PowerReportListControl(anchor, rect, nodeSelectCal return self end +---@param stat? PowerStat +---@param report? Node[] function PowerReportListClass:SetReport(stat, report) self.powerColumn.label = stat and stat.label or "" self.originalList = report or {} @@ -59,6 +72,7 @@ function PowerReportListClass:SetReport(stat, report) self:ReList() end +---@param colIndex integer function PowerReportListClass:ReSort(colIndex) -- Reverse power sort for allocated because it uses negative numbers local compare = self.allocated and @@ -125,12 +139,19 @@ function PowerReportListClass:ReList() end end +---@param index integer +---@param report Node +---@param doubleClick? boolean function PowerReportListClass:OnSelClick(index, report, doubleClick) if self.nodeSelectCallback then self.nodeSelectCallback(report) end end +---@param column integer +---@param index integer +---@param report Node +---@return string function PowerReportListClass:GetRowValue(column, index, report) return column == 1 and report.type or column == 2 and report.name @@ -140,6 +161,9 @@ function PowerReportListClass:GetRowValue(column, index, report) or "" end +---@param tooltip Tooltip +---@param _ integer +---@param node Node function PowerReportListClass:AddValueTooltip(tooltip, _, node) if main.popups[1] then tooltip:Clear() diff --git a/src/Classes/RectangleOutlineControl.lua b/src/Classes/RectangleOutlineControl.lua index d01a58d8147..5b65b8fb44c 100644 --- a/src/Classes/RectangleOutlineControl.lua +++ b/src/Classes/RectangleOutlineControl.lua @@ -4,8 +4,15 @@ -- Simple Outline Only Rectangle control -- ---@class RectangleOutlineControl: Control +---@field stroke number +---@field colors number[] local RectangleOutlineClass = newClass("RectangleOutlineControl", "Control") +---@param anchor? Anchor +---@param rect? Rect +---@param colors? number[] +---@param stroke? number +---@return RectangleOutlineControl function RectangleOutlineClass:RectangleOutlineControl(anchor, rect, colors, stroke) self:Control(anchor, rect) self.stroke = stroke or 1 diff --git a/src/Classes/ResizableEditControl.lua b/src/Classes/ResizableEditControl.lua index 4f5145fc3bb..59689a98ce9 100644 --- a/src/Classes/ResizableEditControl.lua +++ b/src/Classes/ResizableEditControl.lua @@ -7,8 +7,23 @@ local m_max = math.max local m_min = math.min ---@class ResizableEditControl: EditControl +---@field minWidth number +---@field minHeight number +---@field maxWidth number +---@field maxHeight number local ResizableEditClass = newClass("ResizableEditControl", "EditControl") +---@param anchor? Anchor +---@param rect Rect +---@param init? string +---@param prompt? string +---@param filter? fun(text: string): string? +---@param limit? integer +---@param changeFunc? fun(text: string) +---@param lineHeight? number +---@param allowZoom? boolean +---@param clearable? boolean +---@return ResizableEditControl function ResizableEditClass:ResizableEditControl(anchor, rect, init, prompt, filter, limit, changeFunc, lineHeight, allowZoom, clearable) self:EditControl(anchor, rect, init, prompt, filter, limit, changeFunc, lineHeight, allowZoom, clearable) local x, y, width, height, minWidth, minHeight, maxWidth, maxHeight = unpack(rect) @@ -30,10 +45,13 @@ function ResizableEditClass:ResizableEditControl(anchor, rect, init, prompt, fil return self end +---@param viewPort Rect +---@param noTooltip? boolean function ResizableEditClass:Draw(viewPort, noTooltip) self:SetBoundedDrag(self) self.EditControl:Draw(viewPort, noTooltip) end + function ResizableEditClass:SetBoundedDrag() if self.controls.draggerHeight.dragging then local cursorX, cursorY = GetCursorPos() @@ -43,9 +61,12 @@ function ResizableEditClass:SetBoundedDrag() end end +---@param width? number function ResizableEditClass:SetWidth(width) self.width = m_max(m_min(width or 0, self.maxWidth), self.minWidth) end + +---@param height? number function ResizableEditClass:SetHeight(height) self.height = m_max(m_min(height or 0, self.maxHeight), self.minHeight) end \ No newline at end of file diff --git a/src/Classes/ScrollBarControl.lua b/src/Classes/ScrollBarControl.lua index 145fb94bfdf..ab747bd5d1a 100644 --- a/src/Classes/ScrollBarControl.lua +++ b/src/Classes/ScrollBarControl.lua @@ -9,8 +9,32 @@ local m_ceil = math.ceil local m_floor = math.floor ---@class ScrollBarControl: Control +---@field step number +---@field dir "HORIZONTAL"|"VERTICAL" +---@field offset number +---@field conDim number +---@field viewDim number +---@field offsetMax number +---@field knobDim number +---@field knobTravel number +---@field autoHide? boolean +---@field dragging? boolean +---@field dragCX? number +---@field dragCY? number +---@field dragKnobPos? number +---@field holdComp? "UP"|"DOWN" +---@field holdBase? number +---@field holdTime? number +---@field holdPauseTime? number +---@field holdRepeating? boolean local ScrollBarClass = newClass("ScrollBarControl", "Control") +---@param anchor? Anchor +---@param rect? Rect +---@param step? number +---@param dir? "HORIZONTAL"|"VERTICAL" +---@param autoHide? boolean +---@return ScrollBarControl function ScrollBarClass:ScrollBarControl(anchor, rect, step, dir, autoHide) self:Control(anchor, rect) self.step = step or self.width * 2 @@ -25,6 +49,8 @@ function ScrollBarClass:ScrollBarControl(anchor, rect, step, dir, autoHide) return self end +---@param conDim number +---@param viewDim number function ScrollBarClass:SetContentDimension(conDim, viewDim) self.conDim = conDim self.viewDim = viewDim @@ -47,14 +73,18 @@ function ScrollBarClass:SetContentDimension(conDim, viewDim) end end +---@param offset number function ScrollBarClass:SetOffset(offset) self.offset = m_floor(m_max(0, m_min(self.offsetMax or 0, offset))) end +---@param mult number function ScrollBarClass:Scroll(mult) self:SetOffset(self.offset + self.step * mult) end +---@param minDim number +---@param size number function ScrollBarClass:ScrollIntoView(minDim, size) if self.offset > minDim then self:SetOffset(minDim) @@ -63,14 +93,18 @@ function ScrollBarClass:ScrollIntoView(minDim, size) end end +---@param knobPos number function ScrollBarClass:SetOffsetFromKnobPos(knobPos) self:SetOffset(self.offsetMax * (knobPos / self.knobTravel)) end +---@return number function ScrollBarClass:GetKnobPosForOffset() return self.knobTravel * (self.offset / self.offsetMax) end +---@return boolean mouseOver +---@return "UP"|"DOWN"|"SLIDEUP"|"SLIDEDOWN"|"KNOB"? mouseOverComponent function ScrollBarClass:IsMouseOver() if not self:IsShown() then return false @@ -256,6 +290,8 @@ function ScrollBarClass:Draw() end end +---@param key string +---@return ScrollBarControl? function ScrollBarClass:OnKeyDown(key) if not self:IsShown() or not self:IsEnabled() or self:GetProperty("locked") then return @@ -292,6 +328,7 @@ function ScrollBarClass:OnKeyDown(key) return self end +---@param key string function ScrollBarClass:OnKeyUp(key) if not self:IsShown() or not self:IsEnabled() or self:GetProperty("locked") then return @@ -319,9 +356,14 @@ function ScrollBarClass:OnKeyUp(key) end -- Centralize inputs allowed to keep consistent scroll behavior for all scrollBars +---@param key string +---@return boolean function ScrollBarClass:IsScrollDownKey(key) return isValueInTable({"WHEELDOWN", "PAGEDOWN"}, key) end + +---@param key string +---@return boolean function ScrollBarClass:IsScrollUpKey(key) return isValueInTable({"WHEELUP", "PAGEUP"}, key) end diff --git a/src/Classes/SearchHost.lua b/src/Classes/SearchHost.lua index 76163f73e33..e946987b99f 100644 --- a/src/Classes/SearchHost.lua +++ b/src/Classes/SearchHost.lua @@ -4,9 +4,27 @@ -- Search host -- +---@class SearchRange +---@field from integer +---@field to integer + +---@class SearchInfo +---@field ranges SearchRange[] +---@field matches boolean + ---@class SearchHost +---@field searchListAccessor fun(): unknown[]? +---@field valueAccessor? fun(entry: unknown): string +---@field searchTerm string +---@field searchInfos SearchInfo[] +---@field ignoreOrder boolean +---@field matchCount integer local SearchHostClass = newClass("SearchHost") +---@param listAccessor fun(): unknown[]? +---@param valueAccessor? fun(entry: unknown): string +---@param ignoreOrder? boolean +---@return SearchHost function SearchHostClass:SearchHost(listAccessor, valueAccessor, ignoreOrder) self.searchListAccessor = listAccessor self.valueAccessor = valueAccessor @@ -16,6 +34,8 @@ function SearchHostClass:SearchHost(listAccessor, valueAccessor, ignoreOrder) return self end +---@param s string +---@return string[] local function splitWords(s) local words = {} for word in s:gmatch("%S+") do @@ -24,10 +44,14 @@ local function splitWords(s) return words end +---@param c string +---@return string local function letterToCaselessPattern(c) return string.format("[%s%s]", string.lower(c), string.upper(c)) end +---@param words string[] +---@return string[] local function wordsToCaselessPatterns(words) local patterns = {} for idx = 1, #words do @@ -39,6 +63,11 @@ local function wordsToCaselessPatterns(words) return patterns end +---@param searchWords string[] +---@param entry string +---@param valueAccessor? fun(entry: unknown): string +---@param ignoreOrder boolean +---@return SearchInfo local function matchWords(searchWords, entry, valueAccessor, ignoreOrder) local value = valueAccessor and valueAccessor(entry) or entry local searchInfo = { ranges = {}, matches = true } @@ -78,6 +107,11 @@ local function matchWords(searchWords, entry, valueAccessor, ignoreOrder) return searchInfo end +---@param searchTerm string +---@param list string[]? +---@param valueAccessor? fun(entry: unknown): string +---@param ignoreOrder boolean +---@return SearchInfo[] local function matchTerm(searchTerm, list, valueAccessor, ignoreOrder) if not searchTerm or searchTerm == "" or not list then return {} @@ -91,10 +125,13 @@ local function matchTerm(searchTerm, list, valueAccessor, ignoreOrder) return searchInfos end +---@return boolean function SearchHostClass:IsSearchActive() return self.searchTerm and self.searchTerm ~= "" end +---@param char string +---@return SearchHost function SearchHostClass:OnSearchChar(char) if char:match("%s") then -- don't allow space char if search is empty or last character is already a space char @@ -109,6 +146,8 @@ function SearchHostClass:OnSearchChar(char) return self end +---@param key string +---@return SearchHost? function SearchHostClass:OnSearchKeyDown(key) if self:IsSearchActive() and key == "ESCAPE" then self:ResetSearch() @@ -130,6 +169,7 @@ function SearchHostClass:UpdateMatchCount() self.matchCount = matchCount end +---@return integer function SearchHostClass:GetMatchCount() return self.matchCount end @@ -147,6 +187,7 @@ function SearchHostClass:ResetSearch() self.searchInfos = {} end +---@return string function SearchHostClass:GetSearchTermPretty() local color = self:IsSearchActive() and self.matchCount > 0 and "^xFFFFFF" or "^xFF0000" return color .. self.searchTerm diff --git a/src/Classes/SectionControl.lua b/src/Classes/SectionControl.lua index e0acb6fd213..c2f8f5852d3 100644 --- a/src/Classes/SectionControl.lua +++ b/src/Classes/SectionControl.lua @@ -5,14 +5,18 @@ -- ---@class SectionControl: Control +---@field label Prop local SectionClass = newClass("SectionControl", "Control") +---@param anchor? Anchor +---@param rect? Rect +---@param label Prop +---@return SectionControl function SectionClass:SectionControl(anchor, rect, label) self:Control(anchor, rect) self.label = label return self end - function SectionClass:Draw() local x, y = self:GetPos() local width, height = self:GetSize() diff --git a/src/Classes/SharedItemListControl.lua b/src/Classes/SharedItemListControl.lua index cbbb32728f4..2fab74b30fa 100644 --- a/src/Classes/SharedItemListControl.lua +++ b/src/Classes/SharedItemListControl.lua @@ -8,12 +8,20 @@ local t_insert = table.insert local t_remove = table.remove ---@class SharedItemListControl: ListControl +---@field itemsTab ItemsTab +---@field defaultText string +---@field dragTargetList ListControl[] +---@field label string +---@field selDragging? boolean +---@field selIndex? integer +---@field selValue? Item local SharedItemListClass = newClass("SharedItemListControl", "ListControl") ---@param anchor Anchor? ---@param rect Rect? ---@param itemsTab ItemsTab ---@param forceTooltip boolean? +---@return SharedItemListControl function SharedItemListClass:SharedItemListControl(anchor, rect, itemsTab, forceTooltip) self:ListControl(anchor, rect, 16, "VERTICAL", true, main.sharedItemList, forceTooltip) self.itemsTab = itemsTab @@ -29,12 +37,19 @@ function SharedItemListClass:SharedItemListControl(anchor, rect, itemsTab, force return self end +---@param column integer +---@param index integer +---@param item Item +---@return string? function SharedItemListClass:GetRowValue(column, index, item) if column == 1 then return colorCodes[item.rarity] .. item.name end end +---@param tooltip Tooltip +---@param index integer +---@param item Item function SharedItemListClass:AddValueTooltip(tooltip, index, item) if main.popups[1] then tooltip:Clear() @@ -45,10 +60,17 @@ function SharedItemListClass:AddValueTooltip(tooltip, index, item) end end +---@param index integer +---@param item Item +---@return string dragType +---@return Item dragValue function SharedItemListClass:GetDragValue(index, item) return "Item", item end +---@param type string +---@param value Item +---@param source? ListControl function SharedItemListClass:ReceiveDrag(type, value, source) if type == "Item" then local rawItem = { raw = value:BuildRaw() } @@ -60,6 +82,9 @@ function SharedItemListClass:ReceiveDrag(type, value, source) end end +---@param index integer +---@param item Item +---@param doubleClick? boolean function SharedItemListClass:OnSelClick(index, item, doubleClick) if doubleClick then self.itemsTab:CreateDisplayItemFromRaw(item.raw, true) @@ -67,10 +92,14 @@ function SharedItemListClass:OnSelClick(index, item, doubleClick) end end +---@param index integer +---@param item Item function SharedItemListClass:OnSelCopy(index, item) Copy(item:BuildRaw():gsub("\n","\r\n")) end +---@param index integer +---@param item Item function SharedItemListClass:OnSelDelete(index, item) main:OpenConfirmPopup("Delete Item", "Are you sure you want to remove '"..item.name.."' from the shared item list?", "Delete", function() t_remove(self.list, index) diff --git a/src/Classes/SharedItemSetListControl.lua b/src/Classes/SharedItemSetListControl.lua index ba604874c77..5dd14c7dabe 100644 --- a/src/Classes/SharedItemSetListControl.lua +++ b/src/Classes/SharedItemSetListControl.lua @@ -8,9 +8,21 @@ local t_remove = table.remove local m_max = math.max local s_format = string.format +---@class SharedItemSet +---@field title? string +---@field slots table + ---@class SharedItemSetListControl: ListControl +---@field itemsTab ItemsTab +---@field defaultText string +---@field selIndex? integer +---@field selValue? SharedItemSet local SharedItemSetListClass = newClass("SharedItemSetListControl", "ListControl") +---@param anchor? Anchor +---@param rect? Rect +---@param itemsTab ItemsTab +---@return SharedItemSetListControl function SharedItemSetListClass:SharedItemSetListControl(anchor, rect, itemsTab) self:ListControl(anchor, rect, 16, "VERTICAL", true, main.sharedItemSetList) self.itemsTab = itemsTab @@ -30,6 +42,7 @@ function SharedItemSetListClass:SharedItemSetListControl(anchor, rect, itemsTab) return self end +---@param sharedItemSet SharedItemSet function SharedItemSetListClass:RenameSet(sharedItemSet) local controls = { } controls.label = new("LabelControl"):LabelControl(nil, {0, 20, 0, 16}, "^7Enter name for this item set:") @@ -48,12 +61,19 @@ function SharedItemSetListClass:RenameSet(sharedItemSet) main:OpenPopup(370, 100, sharedItemSet.title and "Rename" or "Set Name", controls, "save", "edit") end +---@param column integer +---@param index integer +---@param sharedItemSet SharedItemSet +---@return string? function SharedItemSetListClass:GetRowValue(column, index, sharedItemSet) if column == 1 then return sharedItemSet.title or "Default" end end +---@param tooltip Tooltip +---@param index integer +---@param sharedItemSet SharedItemSet function SharedItemSetListClass:AddValueTooltip(tooltip, index, sharedItemSet) tooltip:Clear() for _, slot in ipairs(self.itemsTab.orderedSlots) do @@ -67,14 +87,24 @@ function SharedItemSetListClass:AddValueTooltip(tooltip, index, sharedItemSet) end end +---@param index integer +---@param value SharedItemSet +---@return string dragType +---@return SharedItemSet dragValue function SharedItemSetListClass:GetDragValue(index, value) return "SharedItemList", value end +---@param type string +---@param value SharedItemSet +---@return boolean function SharedItemSetListClass:CanReceiveDrag(type, value) return type == "ItemList" end +---@param type string +---@param value ItemSet +---@param source? ListControl function SharedItemSetListClass:ReceiveDrag(type, value, source) if type == "ItemList" then local sharedItemList = { title = value.title, slots = { } } @@ -98,6 +128,8 @@ function SharedItemSetListClass:ReceiveDrag(type, value, source) end end +---@param index integer +---@param sharedItemSet SharedItemSet function SharedItemSetListClass:OnSelDelete(index, sharedItemSet) main:OpenConfirmPopup("Delete Item Set", "Are you sure you want to delete '"..(sharedItemSet.title or "Default").."' from the shared item set list?", "Delete", function() t_remove(self.list, index) @@ -106,6 +138,9 @@ function SharedItemSetListClass:OnSelDelete(index, sharedItemSet) end) end +---@param index integer +---@param sharedItemSet SharedItemSet +---@param key string function SharedItemSetListClass:OnSelKeyDown(index, sharedItemSet, key) if key == "F2" then self:RenameSet(sharedItemSet) diff --git a/src/Classes/SkillListControl.lua b/src/Classes/SkillListControl.lua index f5b86e4084a..45431d4e86d 100644 --- a/src/Classes/SkillListControl.lua +++ b/src/Classes/SkillListControl.lua @@ -27,11 +27,16 @@ local slot_map = { } ---@class SkillListControl: ListControl +---@field skillsTab SkillsTab +---@field label string +---@field selIndex? integer +---@field selValue? table local SkillListClass = newClass("SkillListControl", "ListControl") ---@param anchor Anchor? ---@param rect Rect? ---@param skillsTab SkillsTab +---@return SkillListControl function SkillListClass:SkillListControl(anchor, rect, skillsTab) self:ListControl(anchor, rect, 16, "VERTICAL", true, skillsTab.socketGroupList) self.skillsTab = skillsTab @@ -76,6 +81,10 @@ function SkillListClass:SkillListControl(anchor, rect, skillsTab) return self end +---@param column integer +---@param index integer +---@param socketGroup table +---@return string? function SkillListClass:GetRowValue(column, index, socketGroup) if column == 1 then local label = socketGroup.displayLabel or "?" @@ -117,6 +126,9 @@ function SkillListClass:GetRowValue(column, index, socketGroup) end end +---@param tooltip Tooltip +---@param index integer +---@param socketGroup table function SkillListClass:AddValueTooltip(tooltip, index, socketGroup) if not socketGroup.displaySkillList then tooltip:Clear() @@ -127,6 +139,8 @@ function SkillListClass:AddValueTooltip(tooltip, index, socketGroup) end end +---@param selIndex integer +---@param selDragIndex integer function SkillListClass:OnOrderChange(selIndex, selDragIndex) local skillsTabIndex = self.skillsTab.build.mainSocketGroup if skillsTabIndex == selIndex then @@ -148,16 +162,22 @@ function SkillListClass:OnOrderChange(selIndex, selDragIndex) self.skillsTab.build.buildFlag = true end +---@param index integer +---@param socketGroup table function SkillListClass:OnSelect(index, socketGroup) self.skillsTab:SetDisplayGroup(socketGroup) end +---@param index integer +---@param socketGroup table function SkillListClass:OnSelCopy(index, socketGroup) if not socketGroup.source then self.skillsTab:CopySocketGroup(socketGroup) end end +---@param index integer +---@param socketGroup table function SkillListClass:OnSelDelete(index, socketGroup) local function updateActiveSocketGroupIndex() local skillsTabIndex = self.skillsTab.build.mainSocketGroup @@ -196,6 +216,7 @@ function SkillListClass:OnSelDelete(index, socketGroup) end end +---@param key string function SkillListClass:OnHoverKeyUp(key) local item = self.ListControl:GetHoverValue() if item then @@ -234,10 +255,15 @@ function SkillListClass:OnHoverKeyUp(key) end +---@param viewPort Rect function SkillListClass:Draw(viewPort) self.ListControl.Draw(self, viewPort) end +---@param column integer +---@param index integer +---@param socketGroup table +---@return ImageHandle? function SkillListClass:GetRowIcon(column, index, socketGroup) if column == 1 then local slot = socketGroup.slot diff --git a/src/Classes/SkillSetListControl.lua b/src/Classes/SkillSetListControl.lua index fb815b71b90..12945cf3f0c 100644 --- a/src/Classes/SkillSetListControl.lua +++ b/src/Classes/SkillSetListControl.lua @@ -9,11 +9,15 @@ local m_max = math.max local s_format = string.format ---@class SkillSetListControl: ListControl +---@field skillsTab SkillsTab +---@field selIndex? integer +---@field selValue? integer local SkillSetListClass = newClass("SkillSetListControl", "ListControl") ---@param anchor Anchor? ---@param rect Rect? ---@param skillsTab SkillsTab +---@return SkillSetListControl function SkillSetListClass:SkillSetListControl(anchor, rect, skillsTab) self:ListControl(anchor, rect, 16, "VERTICAL", true, skillsTab.skillSetOrderList) self.skillsTab = skillsTab @@ -57,6 +61,8 @@ function SkillSetListClass:SkillSetListControl(anchor, rect, skillsTab) return self end +---@param skillSet table +---@param addOnName? boolean function SkillSetListClass:RenameSet(skillSet, addOnName) local controls = { } controls.label = new("LabelControl"):LabelControl(nil, {0, 20, 0, 16}, "^7Enter name for this skill set:") @@ -85,6 +91,10 @@ function SkillSetListClass:RenameSet(skillSet, addOnName) main:OpenPopup(370, 100, skillSet.title and "Rename" or "Set Name", controls, "save", "edit", "cancel") end +---@param column integer +---@param index integer +---@param skillSetId integer +---@return string? function SkillSetListClass:GetRowValue(column, index, skillSetId) local skillSet = self.skillsTab.skillSets[skillSetId] if column == 1 then @@ -96,6 +106,9 @@ function SkillSetListClass:OnOrderChange() self.skillsTab.modFlag = true end +---@param index integer +---@param skillSetId integer +---@param doubleClick? boolean function SkillSetListClass:OnSelClick(index, skillSetId, doubleClick) if doubleClick and skillSetId ~= self.skillsTab.activeSkillSetId then self.skillsTab:SetActiveSkillSet(skillSetId) @@ -103,6 +116,8 @@ function SkillSetListClass:OnSelClick(index, skillSetId, doubleClick) end end +---@param index integer +---@param skillSetId integer function SkillSetListClass:OnSelDelete(index, skillSetId) local skillSet = self.skillsTab.skillSets[skillSetId] if #self.list > 1 then @@ -120,6 +135,9 @@ function SkillSetListClass:OnSelDelete(index, skillSetId) end end +---@param index integer +---@param skillSetId integer +---@param key string function SkillSetListClass:OnSelKeyDown(index, skillSetId, key) if key == "F2" then self:RenameSet(self.skillsTab.skillSets[skillSetId]) diff --git a/src/Classes/SkillsTab.lua b/src/Classes/SkillsTab.lua index f1326ddc980..997aeb2e382 100644 --- a/src/Classes/SkillsTab.lua +++ b/src/Classes/SkillsTab.lua @@ -75,10 +75,39 @@ local sortGemTypeList = { { label = "Effective Hit Pool", type = "TotalEHP" }, } +---@class SkillSet +---@field id integer +---@field title? string +---@field socketGroupList table[] + +---@class SkillsTabUndoState +---@field activeSkillSetId integer +---@field skillSets table +---@field skillSetOrderList integer[] +---@field activeSocketGroup integer? +---@field activeSocketGroup2 integer? + ---@class SkillsTab: UndoHandler, ControlHost, Control +---@field build Build +---@field socketGroupList table[] +---@field skillSets table +---@field skillSetOrderList integer[] +---@field activeSkillSetId integer +---@field displayGroup? table +---@field gemSlots table +---@field imbuedSupportBySlot table +---@field sortGemsByDPS boolean +---@field sortGemsByDPSField string +---@field showSupportGemTypes string +---@field showLegacyGems boolean +---@field defaultGemLevel string +---@field defaultGemQuality number +---@field modFlag boolean +---@field [string] unknown local SkillsTabClass = newClass("SkillsTab", "UndoHandler", "ControlHost", "Control") ---@param build Build +---@return SkillsTab function SkillsTabClass:SkillsTab(build) self:UndoHandler() self:ControlHost() @@ -215,6 +244,8 @@ function SkillsTabClass:SkillsTab(build) self.build.buildFlag = true end) + ---@return Item? item + ---@return table? groupSlot local function getSelectedItem() local item local groupSlot = self.controls.groupSlot:GetSelValue() @@ -243,6 +274,9 @@ function SkillsTabClass:SkillsTab(build) local item = getSelectedItem() return not not item end + ---@param item Item + ---@return integer maxSockets + ---@return integer abyssalSocketCount local function getSocketCounts(item) local abyssalSocketCount = 0 for _, socket in ipairs(item.sockets) do @@ -348,7 +382,9 @@ function SkillsTabClass:SkillsTab(build) end end end, true, true) - local function isImbuedEnabled() -- socketedIn must be set and the displayGroup must have an imbued, otherwise disable the imbued dropdown + -- socketedIn must be set and the displayGroup must have an imbued, otherwise disable the imbued dropdown + ---@return boolean + local function isImbuedEnabled() return (self.displayGroup and self.displayGroup.slot and ((self.imbuedSupportBySlot[self.displayGroup.slot] and self.displayGroup.imbuedSupport) or not self.imbuedSupportBySlot[self.displayGroup.slot])) end self.controls.imbuedSupport.enabled = function() @@ -438,6 +474,8 @@ will automatically apply to the skill.]] end +---@param node table +---@param skillSetId integer function SkillsTabClass:LoadSkill(node, skillSetId) if node.elem ~= "Skill" then return @@ -520,6 +558,8 @@ function SkillsTabClass:LoadSkill(node, skillSetId) t_insert(self.skillSets[skillSetId].socketGroupList, socketGroup) end +---@param xml table +---@param fileName string function SkillsTabClass:Load(xml, fileName) self.activeSkillSetId = 0 self.skillSets = { } @@ -570,6 +610,7 @@ function SkillsTabClass:Load(xml, fileName) self:ResetUndo() end +---@param xml table function SkillsTabClass:Save(xml) xml.attrib = { activeSkillSet = tostring(self.activeSkillSetId), @@ -628,6 +669,8 @@ function SkillsTabClass:Save(xml) end end +---@param viewPort Rect +---@param inputEvents InputEvent[] function SkillsTabClass:Draw(viewPort, inputEvents) self.x = viewPort.x self.y = viewPort.y @@ -691,6 +734,7 @@ function SkillsTabClass:Draw(viewPort, inputEvents) self:DrawControls(viewPort) end +---@param socketGroup table function SkillsTabClass:CopySocketGroup(socketGroup) local skillText = "" if socketGroup.label and socketGroup.label:match("%S") then @@ -705,6 +749,7 @@ function SkillsTabClass:CopySocketGroup(socketGroup) Copy(skillText) end +---@param testInput? string function SkillsTabClass:PasteSocketGroup(testInput) local skillText = sanitiseText(Paste() or testInput) if skillText then @@ -740,6 +785,7 @@ function SkillsTabClass:PasteSocketGroup(testInput) end -- Create the controls for editing the gem at a given index +---@param index integer function SkillsTabClass:CreateGemSlot(index) local slot = { } self.gemSlots[index] = slot @@ -1100,6 +1146,9 @@ function SkillsTabClass:UpdateGemSlots() end -- Find the skill gem matching the given specification +---@param nameSpec string +---@return string? errMsg +---@return table? gemData function SkillsTabClass:FindSkillGem(nameSpec) -- Search for gem name using increasingly broad search patterns local patternList = { @@ -1126,6 +1175,9 @@ function SkillsTabClass:FindSkillGem(nameSpec) return "Unrecognised gem name '" .. nameSpec .. "'" end +---@param gemData table +---@param imbued? boolean +---@return integer function SkillsTabClass:ProcessGemLevel(gemData, imbued) local grantedEffect = gemData.grantedEffect local naturalMaxLevel = gemData.naturalMaxLevel @@ -1272,6 +1324,7 @@ function SkillsTabClass:UpdateSocketGroups() end end -- Set the skill to be displayed/edited +---@param socketGroup? table function SkillsTabClass:SetDisplayGroup(socketGroup) self.displayGroup = socketGroup if socketGroup then @@ -1307,6 +1360,8 @@ function SkillsTabClass:SetDisplayGroup(socketGroup) end end +---@param tooltip Tooltip +---@param socketGroup table function SkillsTabClass:AddSocketGroupTooltip(tooltip, socketGroup) if socketGroup.explodeSources then for _, source in ipairs(socketGroup.explodeSources) do @@ -1394,6 +1449,7 @@ function SkillsTabClass:AddSocketGroupTooltip(tooltip, socketGroup) end end +---@return SkillsTabUndoState function SkillsTabClass:CreateUndoState() local state = { } state.activeSkillSetId = self.activeSkillSetId @@ -1418,6 +1474,7 @@ function SkillsTabClass:CreateUndoState() return state end +---@param state SkillsTabUndoState function SkillsTabClass:RestoreUndoState(state) local displayId = isValueInArray(self.socketGroupList, self.displayGroup) wipeTable(self.skillSets) @@ -1449,6 +1506,8 @@ function SkillsTabClass:OpenSkillSetManagePopup() end -- Creates a new skill set +---@param skillSetId? integer +---@return SkillSet function SkillsTabClass:NewSkillSet(skillSetId) local skillSet = { id = skillSetId, socketGroupList = {} } if not skillSetId then @@ -1475,6 +1534,7 @@ function SkillsTabClass:RebuildImbuedSupportBySlot() end -- Changes the active skill set +---@param skillSetId? integer function SkillsTabClass:SetActiveSkillSet(skillSetId) -- Initialize skill sets if needed if not self.skillSetOrderList[1] then diff --git a/src/Classes/SliderControl.lua b/src/Classes/SliderControl.lua index 89dba3e1fc0..f92018549de 100644 --- a/src/Classes/SliderControl.lua +++ b/src/Classes/SliderControl.lua @@ -8,8 +8,21 @@ local m_max = math.max local m_ceil = math.ceil ---@class SliderControl: Control, TooltipHost +---@field knobSize number +---@field val number +---@field changeFunc? fun(value: number) +---@field scrollWheelSpeedTbl table<"SHIFT"|"CTRL"|"DEFAULT", number> +---@field divCount? integer +---@field dragging? boolean +---@field dragCX? number +---@field dragKnobX? number local SliderClass = newClass("SliderControl", "Control", "TooltipHost") +---@param anchor? Anchor +---@param rect? Rect +---@param changeFunc? fun(value: number) +---@param scrollWheelSpeedTbl? table<"SHIFT"|"CTRL"|"DEFAULT", number> +---@return SliderControl function SliderClass:SliderControl(anchor, rect, changeFunc, scrollWheelSpeedTbl) self:Control(anchor, rect) self:TooltipHost() @@ -20,6 +33,8 @@ function SliderClass:SliderControl(anchor, rect, changeFunc, scrollWheelSpeedTbl return self end +---@return boolean mouseOver +---@return "KNOB"|"SLIDE"|nil component function SliderClass:IsMouseOver() if not self:IsShown() then return false @@ -41,11 +56,13 @@ function SliderClass:IsMouseOver() return mOver, mOverComp end +---@return number function SliderClass:GetKnobTravel() local width, height = self:GetSize() return width - self.knobSize - 2 end +---@param newVal number function SliderClass:SetVal(newVal) newVal = m_max(0, m_min(1, newVal)) if newVal ~= self.val then @@ -56,6 +73,9 @@ function SliderClass:SetVal(newVal) end end +---@param val? number +---@return integer divisionIndex +---@return number divisionValue function SliderClass:GetDivVal(val) val = val or self.val if self.divCount and self.divCount > 1 then @@ -66,15 +86,18 @@ function SliderClass:GetDivVal(val) end end +---@param knobX number function SliderClass:SetValFromKnobX(knobX) self:SetVal(knobX / self:GetKnobTravel()) end +---@return number function SliderClass:GetKnobXForVal() local knobTravel = self:GetKnobTravel() return knobTravel * self.val end +---@param viewPort Rect function SliderClass:Draw(viewPort) local x, y = self:GetPos() local width, height = self:GetSize() @@ -132,6 +155,8 @@ function SliderClass:Draw(viewPort) end end +---@param key string +---@return SliderControl? function SliderClass:OnKeyDown(key) if not self:IsShown() or not self:IsEnabled() then return @@ -155,6 +180,7 @@ function SliderClass:OnKeyDown(key) return self end +---@param key string function SliderClass:OnKeyUp(key) if not self:IsShown() or not self:IsEnabled() then return diff --git a/src/Classes/TextListControl.lua b/src/Classes/TextListControl.lua index 50e58a2d168..7b448e23d82 100644 --- a/src/Classes/TextListControl.lua +++ b/src/Classes/TextListControl.lua @@ -4,8 +4,18 @@ -- Simple list control for displaying a block of text -- ---@class TextListControl: Control, ControlHost +---@field columns table[] +---@field list string[] +---@field sectionHeights number[] +---@field hoveredLine? integer local TextListClass = newClass("TextListControl", "Control", "ControlHost") +---@param anchor? Anchor +---@param rect? Rect +---@param columns table[] +---@param list string[] +---@param sectionHeights? number[] +---@return TextListControl function TextListClass:TextListControl(anchor, rect, columns, list, sectionHeights) self:Control(anchor, rect) self:ControlHost() @@ -20,6 +30,7 @@ function TextListClass:TextListControl(anchor, rect, columns, list, sectionHeigh return self end +---@return boolean|Control? function TextListClass:IsMouseOver() if not self:IsShown() then return @@ -27,6 +38,7 @@ function TextListClass:IsMouseOver() return self:IsMouseInBounds() or self:GetMouseOverControl() end +---@param viewPort Rect function TextListClass:Draw(viewPort) local x, y = self:GetPos() local width, height = self:GetSize() @@ -80,6 +92,9 @@ function TextListClass:Draw(viewPort) SetViewport() end +---@param key string +---@param doubleClick? boolean +---@return TextListControl? function TextListClass:OnKeyDown(key, doubleClick) if not self:IsShown() or not self:IsEnabled() then return @@ -93,6 +108,8 @@ function TextListClass:OnKeyDown(key, doubleClick) end end +---@param key string +---@return TextListControl? function TextListClass:OnKeyUp(key) if not self:IsShown() or not self:IsEnabled() then return diff --git a/src/Classes/TimelessJewelListControl.lua b/src/Classes/TimelessJewelListControl.lua index 8b82e3b2d95..95a7cbe3443 100644 --- a/src/Classes/TimelessJewelListControl.lua +++ b/src/Classes/TimelessJewelListControl.lua @@ -10,9 +10,18 @@ local m_max = math.max local t_concat = table.concat ---@class TimelessJewelListControl: ListControl +---@field build Build +---@field list table +---@field noTooltip? boolean +---@field selIndex? integer +---@field highlightIndex? integer +---@field sharedList table local TimelessJewelListControlClass = newClass("TimelessJewelListControl", "ListControl") +---@param anchor? Anchor +---@param rect? Rect ---@param build Build +---@return TimelessJewelListControl function TimelessJewelListControlClass:TimelessJewelListControl(anchor, rect, build) self.build = build self.sharedList = self.build.timelessData.sharedResults or { } @@ -22,11 +31,16 @@ function TimelessJewelListControlClass:TimelessJewelListControl(anchor, rect, bu return self end +---@param viewPort Rect +---@param noTooltip? boolean function TimelessJewelListControlClass:Draw(viewPort, noTooltip) self.noTooltip = noTooltip self.ListControl.Draw(self, viewPort) end +---@param index integer +---@param value table +---@return boolean function TimelessJewelListControlClass:SetHighlightColor(index, value) if not self.highlightIndex or not self.selIndex then return false @@ -46,12 +60,15 @@ function TimelessJewelListControlClass:SetHighlightColor(index, value) return false end +---@param index integer function TimelessJewelListControlClass:ScrollToIndex(index) if self.scroll then self.controls.scrollBarV:SetOffset((index - 1) * self.rowHeight) end end +---@param index integer +---@return boolean function TimelessJewelListControlClass:OverrideSelectIndex(index) if IsKeyDown("SHIFT") and self.selIndex then self.highlightIndex = index @@ -63,12 +80,17 @@ function TimelessJewelListControlClass:OverrideSelectIndex(index) return false end +---@param column integer +---@param index integer +---@param data table +---@return string? function TimelessJewelListControlClass:GetRowValue(column, index, data) if column == 1 then return data.label end end +---@param data table ---@return Item item function TimelessJewelListControlClass:GetJewelItem(data) local socketInfo = data.socketLabel or (self.sharedList.socket and self.sharedList.socket.keystone) or "Unknown" @@ -286,8 +308,8 @@ Historic end ---@param tooltip Tooltip ----@param index any ----@param data any +---@param index integer +---@param data table function TimelessJewelListControlClass:AddValueTooltip(tooltip, index, data) local socketId = data.socketId or self.sharedList.socket.id local socket = socketId and socketId ~= -1 and self.build.itemsTab:GetSocketAndJewelForNodeID(socketId) @@ -332,6 +354,9 @@ function TimelessJewelListControlClass:AddValueTooltip(tooltip, index, data) end end +---@param index integer +---@param data table +---@param doubleClick? boolean function TimelessJewelListControlClass:OnSelClick(index, data, doubleClick) if doubleClick and self.list[index].label:match("B2B2B2") == nil then local item = self:GetJewelItem(data) diff --git a/src/Classes/TimelessJewelSocketControl.lua b/src/Classes/TimelessJewelSocketControl.lua index 5562d604e07..94e063ee9e5 100644 --- a/src/Classes/TimelessJewelSocketControl.lua +++ b/src/Classes/TimelessJewelSocketControl.lua @@ -6,15 +6,21 @@ local m_min = math.min +---@class TimelessJewelSocket +---@field id integer + ---@class TimelessJewelSocketControl: DropDownControl +---@field build Build +---@field socketViewer PassiveTreeView local TimelessJewelSocketClass = newClass("TimelessJewelSocketControl", "DropDownControl") ---@param anchor Anchor? ---@param rect Rect? ----@param list any[] ----@param selFunc any +---@param list TimelessJewelSocket[] +---@param selFunc fun(index: integer, data: TimelessJewelSocket, doubleClick?: boolean) ---@param build Build ----@param socketViewer any +---@param socketViewer PassiveTreeView +---@return TimelessJewelSocketControl function TimelessJewelSocketClass:TimelessJewelSocketControl(anchor, rect, list, selFunc, build, socketViewer) self:DropDownControl(anchor, rect, list, selFunc) self.build = build @@ -22,6 +28,8 @@ function TimelessJewelSocketClass:TimelessJewelSocketControl(anchor, rect, list, return self end +---@param viewPort Rect +---@param noTooltip? boolean function TimelessJewelSocketClass:Draw(viewPort, noTooltip) local x, y = self:GetPos() local width, height = self:GetSize() diff --git a/src/Classes/Tooltip.lua b/src/Classes/Tooltip.lua index 0a806c0cedc..23f85bfe143 100644 --- a/src/Classes/Tooltip.lua +++ b/src/Classes/Tooltip.lua @@ -40,9 +40,39 @@ for _, recipeName in pairs(recipeNames) do recipeImages[recipeName]:Load("TreeData/" .. recipeName .. ".png", "CLAMP") end +---@class TooltipLine +---@field size number +---@field text string|boolean +---@field font Font +---@field modLine? ModLine +---@field background? number[] + ---@class Tooltip +---@field lines TooltipLine[] +---@field blocks table[] +---@field childTooltips? Tooltip[] +---@field updateParams? unknown[] +---@field maxWidth? number +---@field recipe? string[] +---@field tooltipHeader string|boolean +---@field titleYOffset number +---@field center boolean +---@field color string|number[] +---@field separatorImage? ImageHandle +---@field separatorImagePath? string +---@field headerLeft? ImageHandle +---@field headerLeftPath? string +---@field headerMiddle? ImageHandle +---@field headerMiddlePath? string +---@field headerRight? ImageHandle +---@field headerRightPath? string +---@field influenceHeader1? string +---@field influenceHeader2? string +---@field foilType? string +---@field _bgHandles? table local TooltipClass = newClass("Tooltip") +---@return Tooltip function TooltipClass:Tooltip() self.lines = { } self.blocks = { } @@ -51,6 +81,7 @@ function TooltipClass:Tooltip() return self end +---@param clearUpdateParams? boolean function TooltipClass:Clear(clearUpdateParams) wipeTable(self.lines) wipeTable(self.blocks) @@ -67,7 +98,8 @@ function TooltipClass:Clear(clearUpdateParams) self.color = { 0.5, 0.3, 0 } t_insert(self.blocks, { height = 0 }) end - +---@param ... unknown +---@return boolean? function TooltipClass:CheckForUpdate(...) local doUpdate = false if not self.updateParams then @@ -88,6 +120,11 @@ function TooltipClass:CheckForUpdate(...) end end +---@param size number +---@param text string|boolean +---@param font? Font +---@param modLine? ModLine +---@param background? number[] function TooltipClass:AddLine(size, text, font, modLine, background) if text then local fontToUse @@ -113,10 +150,12 @@ function TooltipClass:AddLine(size, text, font, modLine, background) end end +---@param recipe string[] function TooltipClass:SetRecipe(recipe) self.recipe = recipe end +---@param size? number function TooltipClass:AddSeparator(size) size = size or 10 @@ -159,7 +198,8 @@ function TooltipClass:AddSeparator(size) }) end - +---@return number width +---@return number height function TooltipClass:GetSize() local ttW, ttH = 0, 0 for i, data in ipairs(self.lines) do @@ -191,6 +231,9 @@ function TooltipClass:GetSize() return ttW + H_PAD, ttH + V_PAD end +---@param viewPort Rect +---@return number width +---@return number height function TooltipClass:GetDynamicSize(viewPort) local staticttW, staticttH = self:GetSize() local columns, ttH, _, extraColumnWidth = self:CalculateColumns(0, 0, staticttH, staticttW, viewPort) @@ -201,17 +244,16 @@ function TooltipClass:GetDynamicSize(viewPort) return ttW + H_PAD, ttH + V_PAD end - --- Calculates the column breaks, layout heights, and individual rendering instructions for tooltip lines. --- By default, items exceeding window height will wrap to a new column. ---@param ttY number Base y-coordinate for the tooltip content ---@param ttX number Base x-coordinate for the tooltip content ---@param ttH number The total estimated height of the tooltip content, used to determine column breakpoints ---@param ttW number The pixel width of the primary (first) tooltip column ----@param viewPort table A table `{x, y, width, height}` containing active screen boundaries +---@param viewPort Rect Active screen boundaries ---@return number columns The total number of layout columns generated ---@return number maxColumnHeight The maximum pixel height reached across all formatted columns ----@return table drawStack An array of sequential rendering instructions (texts, images, separators, and their coordinates) +---@return table[] drawStack An array of sequential rendering instructions (texts, images, separators, and their coordinates) ---@return number extraColumnWidth The required dynamic pixel width calculated for any additional columns beyond the first function TooltipClass:CalculateColumns(ttY, ttX, ttH, ttW, viewPort) local y = ttY + 2 * BORDER_WIDTH @@ -373,9 +415,11 @@ end --- Draws tooltip to screen ---@param x number x-coordinate to draw the tooltip at ---@param y number y-coordinate to draw the tooltip at ----@param w number|nil optional width of the UI element being hovered over. Tooltip will position itself outside this box (if possible) ----@param h number|nil optional height of the UI element being hovered over. Needs to be provided alongside `w` ----@param viewPort table A table `{x, y, width, height}` contains active screen boundaries +---@param w? number optional width of the UI element being hovered over. Tooltip will position itself outside this box (if possible) +---@param h? number optional height of the UI element being hovered over. Needs to be provided alongside `w` +---@param viewPort Rect Active screen boundaries +---@return number? width +---@return number? height function TooltipClass:Draw(x, y, w, h, viewPort) if #self.lines == 0 then return diff --git a/src/Classes/TooltipHost.lua b/src/Classes/TooltipHost.lua index 42a9c3e0e90..8958b88f4c0 100644 --- a/src/Classes/TooltipHost.lua +++ b/src/Classes/TooltipHost.lua @@ -4,14 +4,26 @@ -- Tooltip host -- ---@class TooltipHost +---@field tooltip Tooltip +---@field tooltipText? Prop +---@field tooltipFunc? fun(tooltip: Tooltip, ...: unknown) +---@field Object Control local TooltipHostClass = newClass("TooltipHost") +---@param tooltipText? Prop +---@return TooltipHost function TooltipHostClass:TooltipHost(tooltipText) self.tooltip = new("Tooltip"):Tooltip() self.tooltipText = tooltipText return self end +---@param x number +---@param y number +---@param width number +---@param height number +---@param viewPort Rect +---@param ... unknown function TooltipHostClass:DrawTooltip(x, y, width, height, viewPort, ...) if self.tooltipFunc then self.tooltipFunc(self.tooltip, ...) diff --git a/src/Classes/TradeHelpers.lua b/src/Classes/TradeHelpers.lua index 444834a7974..05c72df77c5 100644 --- a/src/Classes/TradeHelpers.lua +++ b/src/Classes/TradeHelpers.lua @@ -9,6 +9,7 @@ local m_floor = math.floor -- so it and its precalculated patterns are built lazily local numberPattern = "%%d%+%%.%?%%d*" local statDescData +---@return table local function getStatDescData() -- this is not perfect. some currently known issues include: -- death's oath chaos damage line: formatted as a # to # value on trade site which shows 3 to 450. nonsensical @@ -64,10 +65,12 @@ local function getStatDescData() return statDescData end +---@class TradeHelpers local M = {} -- Helper: get rarity color code for an item ---- @param item table +---@param item Item? +---@return string function M.getRarityColor(item) if not item then return "^7" end if item.rarity and colorCodes[item.rarity] then @@ -78,7 +81,9 @@ function M.getRarityColor(item) end -- Helper: normalize a mod line by replacing numbers with "#" for template matching ---- @param line string +---@param line ModLine +---@return string template +---@return integer replacements function M.modLineTemplate(line) -- Replace decimal numbers first (e.g. "1.5"), then integers return line:gsub("%-?[%d]+%.?[%d]*", "#") @@ -86,8 +91,9 @@ end -- Helper: extract the first number from a mod line for value comparison, or in the case of # to # -- mods, the midpoint of that range ---- @param line string ---- @param onlyFromTo? boolean whether we should only check for # to # matches +---@param line ModLine +---@param onlyFromTo? boolean @Whether to only check for # to # matches. +---@return number? value function M.modLineValue(line, onlyFromTo) local low, high = line:match("(%-?%d+%.?%d*) to (%-?%d+%.?%d*)") if low and high then @@ -98,15 +104,21 @@ function M.modLineValue(line, onlyFromTo) return tonumber(line:match("%-?[%d]+%.?[%d]*")) end ----@return table? tradeStats +---@return table[] tradeStats function M.getTradeStats() return require("Data.TradeSiteStats") end local _optionTradeStatMap ----@param tradeStats table table of data from https://www.pathofexile.com/api/trade2/data/stats ----@return table optionTradeStatMap table containing helper data for matching trade option filters +---@alias TradeOptionValue number|string + +---@class TradeHelpersOptionTradeStatMap +---@field exact table +---@field patterns table + +---@param tradeStats table[] @Table of data from https://www.pathofexile.com/api/trade2/data/stats. +---@return TradeHelpersOptionTradeStatMap optionTradeStatMap @Helper data for matching trade option filters. local function getOptionTradeStatMap(tradeStats) if _optionTradeStatMap then return _optionTradeStatMap end local optionTradeStatMap = { @@ -151,7 +163,9 @@ M.sourceTypeToCategory = { } -- inverses a mod. e.g. more x -> less x ---- @param modLine string +---@param modLine ModLine +---@return ModLine modLine +---@return string? inverseKey function M.swapInverse(modLine) local priorStr = modLine local inverseKey @@ -178,8 +192,10 @@ function M.swapInverse(modLine) end +---@param modLine ModLine +---@param modType string ---@return string? tradeId ----@return number? value Only returned when applicable (primarily timeless jewels) +---@return TradeOptionValue? value @Only returned when applicable (primarily timeless jewels). function M.findTradeIdOption(modLine, modType) -- match stringify() behaviour and ignore casing modLine = modLine:gsub("\n", " "):lower() @@ -225,22 +241,26 @@ end -- form is only used when the stat has a known value, which is recorded in the -- form's limits. this is for example common with % chance, where 100% chance -- omits the chance text +---@param statForm table +---@param canonical_stat? integer ---@return number? value local function impliedValue(statForm, canonical_stat) local limit = statForm.limit and statForm.limit[canonical_stat or 1] return limit and tonumber(limit[1]) end +---@param resultIds string[] +---@param tradeHash string local function insertUniqueHash(resultIds, tradeHash) if not isValueInArray(resultIds, tradeHash) then table.insert(resultIds, tradeHash) end end -- Helper: find the trade stat ID for a mod line ----@param modLine string ----@return table[] results Can include more than one result if the results are ambiguous ----@return number? value Might be nil if the line has no sensible number value ----@return boolean shouldNegate whether the mod needs to be negated when given to the trade site +---@param modLine ModLine +---@return string[] resultIds @Can include more than one result if the results are ambiguous. +---@return number? value @Might be nil if the line has no sensible number value. +---@return boolean? shouldNegate @Whether the mod needs to be negated when given to the trade site. function M.findTradeHash(modLine) modLine = modLine:lower() local resultIds = {} @@ -346,8 +366,10 @@ end -- Map slot name + item type to (trade API category string, itemCategoryTags key). -- queryStr: e.g. "armour.shield", "weapon.onemace" -- categoryLabel: e.g. "Shield", "1HMace", "1HWeapon" (nil for flask / generic jewel / unsupported) ---- @param slotName string ---- @param item table +---@param slotName string +---@param item Item? +---@return string? queryStr +---@return string? categoryLabel function M.getTradeCategory(slotName, item) if not slotName then return nil, nil end local itemType = item and (item.type or (item.base and item.base.type)) @@ -388,7 +410,9 @@ end -- Helper: get a display-friendly category name from slot name ---- @param item table +---@param slotName string +---@param item Item? +---@return string function M.getTradeCategoryLabel(slotName, item) if not item or not item.base then return "Item" end local baseType = item.base.type or item.type @@ -397,7 +421,8 @@ end -- Helper: build a mod comparison map from an item. -- Returns a table keyed by template string → { line = original text, value = first number } ---- @param item table +---@param item Item? +---@return table function M.buildModMap(item) local modMap = {} if not item then return modMap end @@ -416,8 +441,9 @@ function M.buildModMap(item) end -- Helper: get diff label string for an item slot comparison ---- @param pItem table ---- @param cItem table +---@param pItem Item? +---@param cItem Item? +---@return string function M.getSlotDiffLabel(pItem, cItem) if not pItem and not cItem then return "^8(both empty)" @@ -437,6 +463,22 @@ end -- btnStartX is the left edge where the first button (Buy) should appear. -- copyBtnW, copyBtnH, buyBtnW are button dimensions (passed from LAYOUT by caller). -- Returns copyHovered, equipHovered, buyHovered booleans. +---@param cursorX number +---@param cursorY number +---@param btnStartX number +---@param btnY number +---@param slotMissing boolean +---@param copyBtnW number +---@param copyBtnH number +---@param buyBtnW number +---@param equipBtnW number +---@return boolean copyHovered +---@return boolean equipHovered +---@return boolean buyHovered +---@return number equipBtnX +---@return number equipBtnY +---@return number equipBtnW +---@return number equipBtnH function M.drawCopyButtons(cursorX, cursorY, btnStartX, btnY, slotMissing, copyBtnW, copyBtnH, buyBtnW, equipBtnW) local btnW = copyBtnW local btnH = copyBtnH @@ -446,6 +488,10 @@ function M.drawCopyButtons(cursorX, cursorY, btnStartX, btnY, slotMissing, copyB local btn1X = btn3X + buyW + 4 local btn2X = btn1X + btnW + 4 + ---@param x number + ---@param w number + ---@param hover boolean + ---@param label string local function drawBtn(x, w, hover, label) local pressed = hover and IsKeyDown("LEFTBUTTON") -- Outer border @@ -496,6 +542,10 @@ function M.drawCopyButtons(cursorX, cursorY, btnStartX, btnY, slotMissing, copyB end -- Helper: fit a colored item name within maxW pixels, truncating with "..." if needed. +---@param colorCode string +---@param name string +---@param maxW number +---@return string local function fitItemName(colorCode, name, maxW) local display = colorCode .. name if DrawStringWidth(16, "VAR", display) <= maxW then @@ -520,6 +570,40 @@ local ITEM_BOX_W = 310 M.ITEM_BOX_W = ITEM_BOX_W local ITEM_BOX_H = 20 +---@param drawY number +---@param slotLabel string +---@param pItem Item? +---@param cItem Item? +---@param colWidth number +---@param cursorX number +---@param cursorY number +---@param maxLabelW number +---@param primaryItemsTab ItemsTab +---@param compareItemsTab ItemsTab +---@param pWarn? string +---@param cWarn? string +---@param slotMissing boolean +---@param copyBtnW number +---@param copyBtnH number +---@param buyBtnW number +---@param equipBtnW number +---@param xOffset? number +---@param shouldUnderlineLabel? boolean +---@return boolean pHover +---@return boolean cHover +---@return boolean copyHovered +---@return boolean equipHovered +---@return boolean buyHovered +---@return number equipBtnX +---@return number equipBtnY +---@return number equipBtnW +---@return number equipBtnH +---@return Item? hoverItem +---@return ItemsTab? hoverItemsTab +---@return number hoverX +---@return number hoverY +---@return number hoverW +---@return number hoverH function M.drawCompactSlotRow(drawY, slotLabel, pItem, cItem, colWidth, cursorX, cursorY, maxLabelW, primaryItemsTab, compareItemsTab, pWarn, cWarn, slotMissing, copyBtnW, copyBtnH, buyBtnW, equipBtnW, xOffset, shouldUnderlineLabel) @@ -609,6 +693,14 @@ end -- Helper: create a numeric EditControl without +/- spinner buttons, and -- with a preset changeFunc intended for mod values +---@param anchor? Anchor +---@param rect? Rect +---@param init number +---@param prompt? string +---@param limit? integer +---@param integer? boolean +---@param changeFunc? fun(value: number) +---@return EditControl function M.newPlainNumericEdit(anchor, rect, init, prompt, limit, integer, changeFunc) local format = integer and "%D" or "^%d." local ctrl = new("EditControl"):EditControl(anchor, rect, init, prompt, format, limit, changeFunc) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index 66ad1fc1ff9..ec3d187fff6 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -19,10 +19,30 @@ local s_format = string.format local baseSlots = { "Weapon 1", "Weapon 2", "Weapon 1 Swap", "Weapon 2 Swap", "Helmet", "Body Armour", "Gloves", "Boots", "Amulet", "Ring 1", "Ring 2", "Ring 3", "Belt", "Flask 1", "Flask 2", "Flask 3", "Flask 4", "Flask 5" } +---@class WeightedPowerStat: PowerStat +---@field weightMult number + ---@class TradeQuery +---@field itemsTab ItemsTab +---@field tradeQueryGenerator? TradeQueryGenerator +---@field tradeQueryRequests TradeQueryRequests +---@field slotTables TradeQuerySlotTable[] +---@field controls table +---@field totalPrice table +---@field resultTbl table +---@field sortedResultTbl table +---@field itemIndexTbl table +---@field onlyWeightedBaseOutput table> +---@field lastComparedWeightList table> +---@field statSortSelectionList WeightedPowerStat[] +---@field itemSortSelectionList string[] +---@field pbCurrencyConversion table>> +---@field allLeagues table +---@field realmIds table +---@field lastQueries table local TradeQueryClass = newClass("TradeQuery") - ---@param itemsTab ItemsTab +---@return TradeQuery function TradeQueryClass:TradeQuery(itemsTab) self.itemsTab = itemsTab self.itemsTab.leagueDropList = { } @@ -41,7 +61,7 @@ function TradeQueryClass:TradeQuery(itemsTab) self.slotTables = { } self.pbItemSortSelectionIndex = 1 -- for each realm and league, a table of values of each currency in div - --- @type table>> + ---@type table>> self.pbCurrencyConversion = {} self.lastCurrencyFileTime = { } self.pbFileTimestampDiff = { } @@ -56,7 +76,7 @@ function TradeQueryClass:TradeQuery(itemsTab) ["Xbox"] = "xbox", ["Sony"] = "sony" } - --- @type integer? + ---@type integer? self.backoffFinish = nil -- last query for each row self.lastQueries = {} @@ -105,10 +125,9 @@ function TradeQueryClass:PullLeagueList() end end) end - ---- @param currencyId string ---- @param amount integer ---- @return number? +---@param currencyId string +---@param amount number +---@return number? function TradeQueryClass:ConvertCurrencyToDivs(currencyId, amount) local map = self.pbCurrencyConversion[self.pbRealm] and self.pbCurrencyConversion[self.pbRealm][self.pbLeague] if map and map[currencyId] then @@ -254,7 +273,7 @@ function TradeQueryClass:PullCXData() end) end) end - +---@param list WeightedPowerStat[] local function initStatSortSelectionList(list) t_insert(list, { label = "Full DPS", @@ -269,6 +288,8 @@ local function initStatSortSelectionList(list) end -- we do not want to overwrite previous list if the new list is the default, e.g. hitting reset multiple times in a row +---@param list WeightedPowerStat[]? +---@return boolean local function isSameAsDefaultList(list) return list and #list == 2 and list[1].stat == "FullDPS" and list[1].weightMult == 1.0 @@ -657,7 +678,7 @@ Highest Weight - Displays the order retrieved from trade]] self.controls.scrollBar:SetContentDimension(self.pane_height-100, self.effective_rows_height) self.controls.sectionAnchor.y = -self.controls.scrollBar.offset end - + ---@param backoff integer local function onRateLimit(backoff) self.backoffFinish = get_time() + backoff self.countDown = coroutine.create(function() @@ -688,6 +709,7 @@ Highest Weight - Displays the order retrieved from trade]] end -- Popup to set stat weight multipliers for sorting +---@param previousSelectionList? WeightedPowerStat[] function TradeQueryClass:SetStatWeights(previousSelectionList) previousSelectionList = previousSelectionList or {} local controls = { } @@ -794,6 +816,8 @@ function TradeQueryClass:SetStatWeights(previousSelectionList) end -- Method to set the notice message in upper right of PoB Trader pane +---@param notice_control Control +---@param msg string function TradeQueryClass:SetNotice(notice_control, msg) if msg:find("No Matching Results") then msg = colorCodes.WARNING .. msg @@ -804,6 +828,8 @@ function TradeQueryClass:SetNotice(notice_control, msg) end -- Method to reduce the full output to only the values that were 'weighted' +---@param output Output +---@return table function TradeQueryClass:ReduceOutput(output) local smallOutput = {} for _, statTable in ipairs(self.statSortSelectionList) do @@ -818,6 +844,11 @@ function TradeQueryClass:ReduceOutput(output) end -- Method to evaluate a result by getting it's output and weight +---@param row_idx integer +---@param result_index integer +---@param calcFunc fun(item: Item): Output +---@param baseOutput Output +---@return table? function TradeQueryClass:GetResultEvaluation(row_idx, result_index, calcFunc, baseOutput) local result = self.resultTbl[row_idx][result_index] if not calcFunc then -- Always evaluate when calcFunc is given @@ -880,6 +911,7 @@ function TradeQueryClass:GetResultEvaluation(row_idx, result_index, calcFunc, ba end -- Method to update controls after a search is completed +---@param row_idx integer function TradeQueryClass:UpdateDropdownList(row_idx) local dropdownLabels = {} @@ -896,6 +928,7 @@ function TradeQueryClass:UpdateDropdownList(row_idx) self.controls["resultDropdown".. row_idx].selIndex = 1 self.controls["resultDropdown".. row_idx]:SetList(dropdownLabels) end +---@param rowIdx integer function TradeQueryClass:ResetResultRow(rowIdx) self.itemIndexTbl[rowIdx] = nil self.sortedResultTbl[rowIdx] = nil @@ -904,6 +937,7 @@ function TradeQueryClass:ResetResultRow(rowIdx) self:UpdateDropdownList(rowIdx) self.controls.fullPrice.label = "^7Total Price: " .. self:GetTotalPriceString() end +---@param row_idx integer function TradeQueryClass:UpdateControlsWithItems(row_idx) local sortMode = self.itemSortSelectionList[self.pbItemSortSelectionIndex] local sortedItems, errMsg = self:SortFetchResults(row_idx, sortMode) @@ -935,6 +969,8 @@ function TradeQueryClass:UpdateControlsWithItems(row_idx) end -- Method to set the current result return in the pane based of an index +---@param row_idx integer +---@param index integer function TradeQueryClass:SetFetchResultReturn(row_idx, index) if self.resultTbl[row_idx] and self.resultTbl[row_idx][index] then self.totalPrice[row_idx] = { @@ -946,8 +982,14 @@ function TradeQueryClass:SetFetchResultReturn(row_idx, index) end -- Method to sort the fetched results +---@param row_idx integer +---@param mode string +---@return { outputAttr: number, index: integer }[]? +---@return string? function TradeQueryClass:SortFetchResults(row_idx, mode) local calcFunc, baseOutput + ---@param result_index integer + ---@return number local function getResultWeight(result_index) if not calcFunc then calcFunc, baseOutput = self.itemsTab.build.calcsTab:GetMiscCalculator() @@ -958,9 +1000,9 @@ function TradeQueryClass:SortFetchResults(row_idx, mode) end return sum end - --- @return table? + ---@return table? local function getPriceTable() - --- @type table + ---@type table local divPrices = {} for idx, item in ipairs(self.resultTbl[row_idx]) do if item.currency and item.amount then @@ -1023,8 +1065,9 @@ end -- ensure we only take in items that parse properly to avoid crash issues and fit in the -- provided slotName ----@param itemEntries table ----@param slotName string +---@param itemEntries table[] +---@param slotName? string +---@return table[] function TradeQueryClass:FilterToSafeItems(itemEntries, slotName) local itemsSafe = {} for _, entry in ipairs(itemEntries) do @@ -1036,6 +1079,10 @@ function TradeQueryClass:FilterToSafeItems(itemEntries, slotName) return itemsSafe end -- Method to generate pane elements for each item slot +---@param row_idx integer +---@param top_pane_alignment_ref Anchor +---@param row_vertical_padding number +---@param row_height number function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, row_vertical_padding, row_height) local controls = self.controls local slotTbl = self.slotTables[row_idx] @@ -1045,6 +1092,7 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro slotTbl.slotName and (self.itemsTab.slots[slotTbl.slotName] or -- fullName for Abyssal Sockets slotTbl.fullName and self.itemsTab.slots[slotTbl.fullName]) + ---@return ItemSlotControl? local function getSelectedSlot() local selectedNodeId = slotTbl.nodeId or slotTbl.selectedJewelNodeId return selectedNodeId and self.itemsTab.sockets[selectedNodeId] or activeSlot @@ -1213,6 +1261,8 @@ you can add them, copy the link here, and press "Price Item" to evaluate the ite self:SetFetchResultReturn(row_idx, self.itemIndexTbl[row_idx]) end) self:UpdateDropdownList(row_idx) + ---@param tooltip Tooltip + ---@param result_index integer local function addMegalomaniacCompareToTooltipIfApplicable(tooltip, result_index) if slotTbl.slotName ~= "Megalomaniac" then return @@ -1328,6 +1378,7 @@ you can add them, copy the link here, and press "Price Item" to evaluate the ite end -- Method to update the Total Price string sum of all items +---@return string function TradeQueryClass:GetTotalPriceString() local text = "" -- sum up prices diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index 826cb34a086..dec0a17e49c 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -56,7 +56,9 @@ local tradeCategoryNames = { ["Flask"] = { "Flask: Utility" }, } local basesForType - +---@param mod TradeQueryGeneratorMod +---@param category string +---@return boolean local function canModSpawnForItemCategory(mod, category) -- lazy load type list as it's only required when generating QueryMods.lua if not basesForType then @@ -105,6 +107,7 @@ local function canModSpawnForItemCategory(mod, category) end return false end +---@param modType string ---@return table[]? category list of entries for the mod type local function getStatEntries(modType) local tradeStats = tradeHelpers.getTradeStats() @@ -145,10 +148,55 @@ local function logToFile(...) ConPrintf(...) end +---@class TradeQueryGeneratorMod +---@field type string +---@field group? string +---@field statOrder integer[] +---@field types? table +---@field weightKey? string[] +---@field modTags? string[] +---@field [integer] string + +---@class TradeQuerySelectorOption +---@field label string +---@field tradeId? string +---@field value? number + +---@class TradeQueryGeneratorOptions +---@field account? string +---@field blockedMods? TradeQuerySelectorOption[] +---@field includeAllWEMods? boolean +---@field includeCorrupted? boolean +---@field includeEldritch? string +---@field includeMirrored? boolean +---@field includeScourge? boolean +---@field includeTalisman? boolean +---@field jewelType? "Base"|"Abyss" +---@field links? integer +---@field maxLevel? integer +---@field maxPrice? number +---@field maxPriceType? string +---@field requiredMods? TradeQuerySelectorOption[] +---@field sockets? integer +---@field special? { itemName: string } +---@field statWeights WeightedPowerStat[] + ---@class TradeQueryGenerator +---@field queryTab TradeQuery +---@field itemsTab ItemsTab +---@field calcContext table +---@field modData table +---@field modWeights table[] +---@field alreadyWeightedMods table +---@field lastMaxPrice? number +---@field lastMaxPriceTypeIndex? integer +---@field lastMaxLevel? integer +---@field requesterCallback? fun(context: table, query: string?, errMsg: string?) +---@field requesterContext? table +---@field [string] unknown local TradeQueryGeneratorClass = newClass("TradeQueryGenerator") - ---@param queryTab TradeQuery +---@return TradeQueryGenerator function TradeQueryGeneratorClass:TradeQueryGenerator(queryTab) self:InitMods() self.queryTab = queryTab @@ -160,10 +208,13 @@ function TradeQueryGeneratorClass:TradeQueryGenerator(queryTab) self.lastMaxLevel = nil return self end - +---@param baseOutput Output +---@param newOutput Output +---@param statWeights WeightedPowerStat[] +---@return number function TradeQueryGeneratorClass.WeightedRatioOutputs(baseOutput, newOutput, statWeights) local meanStatDiff = 0 - + ---@return number local function ratioModSums(...) local baseModSum = 0 local newModSum = 0 @@ -197,7 +248,11 @@ function TradeQueryGeneratorClass.WeightedRatioOutputs(baseOutput, newOutput, st end return meanStatDiff end - +---@param modId string|integer +---@param mod TradeQueryGeneratorMod +---@param tradeQueryStatsParsed table +---@param itemCategoriesMask? table +---@param itemCategoriesOverride? table function TradeQueryGeneratorClass:ProcessMod(modId, mod, tradeQueryStatsParsed, itemCategoriesMask, itemCategoriesOverride) if type(modId) == "string" and modId:find("HellscapeDownside") ~= nil then -- skip scourge downsides, they often don't follow standard parsing rules, and should basically never be beneficial anyways goto continue @@ -358,7 +413,10 @@ function TradeQueryGeneratorClass:ProcessMod(modId, mod, tradeQueryStatsParsed, end ::continue:: end - +---@param mods table +---@param tradeQueryStatsParsed table +---@param itemCategoriesMask? table +---@param itemCategoriesOverride? table function TradeQueryGeneratorClass:GenerateModData(mods, tradeQueryStatsParsed, itemCategoriesMask, itemCategoriesOverride) for modId, mod in pairsSortByKey(mods) do self:ProcessMod(modId, mod, tradeQueryStatsParsed, itemCategoriesMask, itemCategoriesOverride) @@ -453,6 +511,8 @@ function TradeQueryGeneratorClass:InitMods() self:GenerateModData(data.itemMods.Flask, tradeQueryStatsParsed, { ["Flask"] = true }) -- translate base type name to trade category name for e.g. essences and drop-restricted mods + ---@param mask table + ---@param cat string local function getTradeCategoryNamesForType(mask, cat) for tradeName, typeNames in pairs(tradeCategoryNames) do if tradeName == cat then @@ -516,6 +576,9 @@ function TradeQueryGeneratorClass:InitMods() { ["AnyJewel"] = "AnyJewel" }) -- implicit mods + ---@param baseEntry ItemBaseEntry + ---@param modId string + ---@param modType "Implicit"|"Enchant" local function processBaseMod(baseEntry, modId, modType) local mod = copyTable(data.itemMods.ItemExclusive[modId] or error("mod id doesn't exist " .. modId)) mod.type = modType @@ -559,7 +622,7 @@ relevant for generating search weights. See TradeSiteStats.lua for a list of all trade site stats.]] utils.saveTableToFile(queryModFilePath, self.modData, qmDescription) end - +---@param modsToTest table function TradeQueryGeneratorClass:GenerateModWeights(modsToTest) local start = GetTime() for _, entry in pairs(modsToTest) do @@ -609,7 +672,7 @@ function TradeQueryGeneratorClass:GenerateModWeights(modsToTest) ::continue:: end end - +---@param nodesToTest table function TradeQueryGeneratorClass:GeneratePassiveNodeWeights(nodesToTest) local start = GetTime() for _, entry in pairs(nodesToTest) do @@ -675,7 +738,8 @@ local currencyTable = { { name = "Regal Orb", id = "regal" }, { name = "Vaal Orb", id = "vaal" } } - +---@param slot ItemSlotControl? +---@param options TradeQueryGeneratorOptions function TradeQueryGeneratorClass:StartQuery(slot, options) if self.lastMaxPrice then options.maxPrice = self.lastMaxPrice @@ -857,6 +921,8 @@ function TradeQueryGeneratorClass:ExecuteQuery() local eaterMods = self.modData["Eater"] local exarchMods = self.modData["Exarch"] if omitConditional then + ---@param mods table + ---@return table local function filterMods(mods) local filtered = {} for name, mod in pairs(mods) do @@ -878,6 +944,8 @@ function TradeQueryGeneratorClass:ExecuteQuery() end function TradeQueryGeneratorClass:addMoreWEMods() + ---@param tbl { tradeModId: string }[] + ---@return string[] local function getTableOfTradeModIds(tbl) local tmpTable={} for _,val in ipairs(tbl) do @@ -1164,7 +1232,10 @@ function TradeQueryGeneratorClass:FinishQuery() -- Close blocker popup main:ClosePopup() end - +---@param slot ItemSlotControl? +---@param context table +---@param statWeights WeightedPowerStat[] +---@param callback fun(context: table, query: string?, errMsg: string?) function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callback) self.requesterCallback = callback self.requesterContext = context @@ -1182,6 +1253,8 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb local isEldritchModSlot = slot and eldritchModSlots[slot.slotName] == true local lastItemAnchor + ---@param anchor Control + ---@param height? number local function updateLastAnchor(anchor, height) lastItemAnchor = anchor popupHeight = popupHeight + (height or 23) @@ -1465,6 +1538,8 @@ Remove: %s will be removed from the search results.]], term, term, term) { (popupWidth - totalWidth) / 2, lastItemH + lastItemY, 0, 0 }) updateLastAnchor(controls.modSelectorHeaderAnchor) -- get mod selector list + ---@param firstLabel string + ---@return TradeQuerySelectorOption[] local function getModList(firstLabel) local _, itemCategory = tradeHelpers.getTradeCategory(slot.slotName, slot and self.itemsTab.items[slot.selItemId]) -- add radius/base as they have different mods @@ -1516,6 +1591,10 @@ Remove: %s will be removed from the search results.]], term, term, term) -- stats fit in the weighted sum, and this means a static popup size is ok local maxSelectors = 5 -- set mod selector dropdown labels, adjust width, and possibly change the mod list + ---@param controls table + ---@param modList? TradeQuerySelectorOption[] + ---@param prefix string + ---@param selectedList TradeQuerySelectorOption[] local function setModSelectors(controls, modList, prefix, selectedList) -- reset selections if modList then @@ -1543,7 +1622,9 @@ Remove: %s will be removed from the search results.]], term, term, term) setModSelectors(controls, getModList("^7+ Add Required Stat"), "modSelector", selectedMods) setModSelectors(controls, getModList("^7+ Add Blocked Stat"), "modNotSelector", notMods) end - + ---@param selectedList TradeQuerySelectorOption[] + ---@param prefix string + ---@param i integer local function createDropdownRow(selectedList, prefix, i) -- dropdown which lists all mods that fit local dropdown = new("DropDownControl"):DropDownControl({ "TOPLEFT", lastItemAnchor, "BOTTOMLEFT", true }, diff --git a/src/Classes/TradeQueryRateLimiter.lua b/src/Classes/TradeQueryRateLimiter.lua index f98c59f669f..03c9c8aaaba 100644 --- a/src/Classes/TradeQueryRateLimiter.lua +++ b/src/Classes/TradeQueryRateLimiter.lua @@ -5,9 +5,37 @@ -- https://www.pathofexile.com/forum/view-thread/2079853 -- +---@class TradeRateLimitBucket +---@field request integer +---@field timeout integer +---@field decremented? boolean + +---@class TradeRateRule +---@field limits table +---@field state table + +---@class TradeRatePolicy +---@field retryAfter? integer +---@field [string] TradeRateRule + +---@class TradeRateRequestHistory +---@field timestamps integer[] +---@field maxWindow? integer +---@field lastCheck? integer + ---@class TradeQueryRateLimiter +---@field policies table +---@field policyNames table +---@field requestHistory table +---@field pendingRequests table +---@field requestId integer +---@field retryAfter table +---@field delayCache table +---@field lastUpdate table +---@field limitMargin number local TradeQueryRateLimiterClass = newClass("TradeQueryRateLimiter") +---@return TradeQueryRateLimiter function TradeQueryRateLimiterClass:TradeQueryRateLimiter() -- policies_sample = { -- -- label: policy @@ -61,10 +89,14 @@ function TradeQueryRateLimiterClass:TradeQueryRateLimiter() return self end +---@param key string +---@return string function TradeQueryRateLimiterClass:GetPolicyName(key) return self.policyNames[key] end +---@param headerString string +---@return table function TradeQueryRateLimiterClass:ParseHeader(headerString) local headers = {} for k, v in headerString:gmatch("([%a%d%-]+): ([%g ]+)") do @@ -74,7 +106,10 @@ function TradeQueryRateLimiterClass:ParseHeader(headerString) return headers end -function TradeQueryRateLimiterClass:ParsePolicy(headerString, policy) +---@param headerString string +---@param policy string +---@return table +function TradeQueryRateLimiterClass:ParsePolicy(headerString, policy) local policies = {} local headers = self:ParseHeader(headerString) local policyName = headers["x-rate-limit-policy"] or policy @@ -112,6 +147,8 @@ function TradeQueryRateLimiterClass:ParsePolicy(headerString, policy) return policies end +---@param headerString string +---@param policy string function TradeQueryRateLimiterClass:UpdateFromHeader(headerString, policy) local newPolicies = self:ParsePolicy(headerString, policy) if not newPolicies then @@ -152,6 +189,9 @@ function TradeQueryRateLimiterClass:UpdateFromHeader(headerString, policy) end end +---@param policy string +---@param time? integer +---@return integer function TradeQueryRateLimiterClass:NextRequestTime(policy, time) local now = time or os.time() local nextTime = now @@ -201,6 +241,10 @@ function TradeQueryRateLimiterClass:NextRequestTime(policy, time) return nextTime end +---@param policy string +---@param timestamp? integer +---@param time? integer +---@return integer requestId function TradeQueryRateLimiterClass:InsertRequest(policy, timestamp, time) local now = time or os.time() timestamp = timestamp or now @@ -229,6 +273,8 @@ function TradeQueryRateLimiterClass:InsertRequest(policy, timestamp, time) return requestId end +---@param policy string +---@param requestId integer function TradeQueryRateLimiterClass:FinishRequest(policy, requestId) if self.pendingRequests[policy] then for index, value in ipairs(self.pendingRequests[policy]) do @@ -239,6 +285,8 @@ function TradeQueryRateLimiterClass:FinishRequest(policy, requestId) end end +---@param policy string +---@param time? integer function TradeQueryRateLimiterClass:AgeOutRequests(policy, time) local now = time or os.time() local requestHistory = self.requestHistory[policy] @@ -273,6 +321,9 @@ function TradeQueryRateLimiterClass:AgeOutRequests(policy, time) end -- Reduce limits visible to pob so the user can safely interact with the trade site +---@param margin number +---@param policies table +---@return table function TradeQueryRateLimiterClass:ReduceLimits(margin, policies) for _, policy in pairs(policies) do for _, rule in pairs(policy) do diff --git a/src/Classes/TradeQueryRequests.lua b/src/Classes/TradeQueryRequests.lua index 709d0c51bbf..430b843a835 100644 --- a/src/Classes/TradeQueryRequests.lua +++ b/src/Classes/TradeQueryRequests.lua @@ -18,9 +18,44 @@ local tradeInfluenceApiKeys = { tangle = "tangled", } +---@class TradeQueryRequest +---@field url string +---@field body? string +---@field callback fun(response: string, errMsg: string?, ...: unknown) +---@field callbackParams? unknown[] +---@field retryTime? integer +---@field attempts? integer + +---@class TradeQuerySearchError +---@field code? integer|string +---@field message? string + +---@class TradeQuerySearchResponse +---@field id string +---@field result string[] +---@field total integer +---@field error? TradeQuerySearchError|string|table + +---@class TradeQueryItem +---@field amount number +---@field currency string +---@field priceType string +---@field item_string string +---@field whisper string +---@field trader string +---@field weight string +---@field id string + ---@class TradeQueryRequests +---@field rateLimiter TradeQueryRateLimiter +---@field requestQueue table<"search"|"fetch", TradeQueryRequest[]> +---@field hostName string +---@field hostNamePattern string +---@field maxFetchPerSearch integer local TradeQueryRequestsClass = newClass("TradeQueryRequests") +---@param rateLimiter? TradeQueryRateLimiter +---@return TradeQueryRequests function TradeQueryRequestsClass:TradeQueryRequests(rateLimiter) self.maxFetchPerSearch = 10 self.rateLimiter = rateLimiter or new("TradeQueryRateLimiter"):TradeQueryRateLimiter() @@ -34,7 +69,7 @@ function TradeQueryRequestsClass:TradeQueryRequests(rateLimiter) end ---Main routine for processing request queue ---- @param onRateLimit fun(integer)? +---@param onRateLimit? fun(backoff: integer) function TradeQueryRequestsClass:ProcessQueue(onRateLimit) for key, queue in pairs(self.requestQueue) do if #queue > 0 then @@ -92,10 +127,11 @@ function TradeQueryRequestsClass:ProcessQueue(onRateLimit) end ---Performs search and fetches results +---@param realm? string ---@param league string ---@param query string ----@param callback fun(items:table, errMsg:string) ----@param params table @ params = { callbackQueryId = fun(queryId:string) } +---@param callback fun(items: TradeQueryItem[]?, errMsg: string?) +---@param params? { callbackQueryId?: fun(queryId: string) } function TradeQueryRequestsClass:SearchWithQuery(realm, league, query, callback, params) params = params or {} --ConPrintf("Query json: %s", query) @@ -112,10 +148,11 @@ end ---Performs search and fetches results, adjusting the query weight and repeating ---the search to fetch more items when the search cap (10k items) is reached +---@param realm? string ---@param league string ---@param query string ----@param callback fun(items:table, errMsg:string) ----@param params table @ params = { callbackQueryId = fun(queryId:string) } +---@param callback fun(items: TradeQueryItem[]?, errMsg: string?) +---@param params? { callbackQueryId?: fun(queryId: string) } function TradeQueryRequestsClass:SearchWithQueryWeightAdjusted(realm, league, query, callback, params) params = params or {} local previousSearchId = nil @@ -125,6 +162,9 @@ function TradeQueryRequestsClass:SearchWithQueryWeightAdjusted(realm, league, qu -- Each repeat is a leap of 10k items, normally we shouldn't need more than 1-2 steps anyways local maxRecursion = 5 local currentRecursion = 0 +---@param response? TradeQuerySearchResponse +---@param errMsg? string +---@return unknown? callbackResult local function performSearchCallback(response, errMsg) currentRecursion = currentRecursion + 1 if params.callbackQueryId and response and response.id then @@ -213,11 +253,11 @@ function TradeQueryRequestsClass:SearchWithQueryWeightAdjusted(realm, league, qu end ---Perform search and run callback function on returned item hashes. ----Item info has to be fetched separately ----@param realm string +---Item info has to be fetched separately +---@param realm? string ---@param league string ---@param query string ----@param callback fun(response:table, errMsg:string) +---@param callback fun(response: TradeQuerySearchResponse?, errMsg: string?) function TradeQueryRequestsClass:PerformSearch(realm, league, query, callback) table.insert(self.requestQueue["search"], { url = self:buildUrl(self.hostName .. "api/trade/search", realm, league), @@ -258,7 +298,7 @@ end ---Fetch item details for itemHashes ---@param itemHashes string[] ---@param queryId string ----@param callback fun(items:table, errMsg:string) +---@param callback fun(items: TradeQueryItem[]?, errMsg: string?) function TradeQueryRequestsClass:FetchResults(itemHashes, queryId, callback) local quantity_found = math.min(#itemHashes, self.maxFetchPerSearch) local max_block_size = 10 @@ -284,7 +324,7 @@ end ---Fetch details for paginated items ---@param url string ----@param callback fun(items: table, errMsg:string) +---@param callback fun(items: TradeQueryItem[]?, errMsg: string?) function TradeQueryRequestsClass:FetchResultBlock(url, callback) table.insert(self.requestQueue["fetch"], { url = url, @@ -348,6 +388,8 @@ function TradeQueryRequestsClass:FetchResultBlock(url, callback) end end +---@param modLine { flags?: table, description: string } +---@return string local function processLine(modLine) local s = "" for flagName, flag in pairs(modLine.flags or {}) do @@ -400,7 +442,9 @@ function TradeQueryRequestsClass:FetchResultBlock(url, callback) }) end ----@param callback fun(items:table, errMsg:string, query: string?) +---@param url string +---@param callback fun(items: TradeQueryItem[]?, errMsg: string?, query: string?): unknown? +---@return unknown? callbackResult function TradeQueryRequestsClass:SearchWithURL(url, callback) local subpath = url:match(self.hostNamePattern .. "trade/search/(.+)$") local paths = {} @@ -442,9 +486,10 @@ function TradeQueryRequestsClass:SearchWithURL(url, callback) end ---Fetch query data needed to perform the search ----@param queryId string +---@param realm? string ---@param league string ----@param callback fun(query:string, errMsg:string) +---@param queryId string +---@param callback fun(query: string?, errMsg: string?) function TradeQueryRequestsClass:FetchSearchQuery(realm, league, queryId, callback) local url = self:buildUrl(self.hostName .. "api/trade/search", realm, league, queryId) table.insert(self.requestQueue["search"], { @@ -464,7 +509,7 @@ end --- Fetches the list of all available leagues using trade league API ---@param realm string ----@param callback fun(query:table, errMsg:string) +---@param callback fun(leagues: string[], errMsg: string?) function TradeQueryRequestsClass:FetchLeagues(realm, callback) local header = "Authorization: Bearer " .. (main.api.authToken or "") launch:DownloadPage( @@ -496,9 +541,10 @@ end --- Build search and trade URLs with proper encoding ---@param root string ----@param realm string +---@param realm? string ---@param league string ----@param queryId string +---@param queryId? string +---@return string function TradeQueryRequestsClass:buildUrl(root, realm, league, queryId) local result = root if realm and realm ~='pc' then diff --git a/src/Classes/TradeStatWeightMultiplierListControl.lua b/src/Classes/TradeStatWeightMultiplierListControl.lua index 3be20855b17..bb643f47be6 100644 --- a/src/Classes/TradeStatWeightMultiplierListControl.lua +++ b/src/Classes/TradeStatWeightMultiplierListControl.lua @@ -4,9 +4,28 @@ -- Specialized UI element for listing and modifying Trade Stat Weight Multipliers. -- +---@class TradeStatWeightMultiplier +---@field label string +---@field stat { label: string, weightMult: number } + +---@class TradeStatWeightMultiplierIndexController +---@field index? integer +---@field SliderLabel LabelControl +---@field Slider SliderControl +---@field SliderValue LabelControl + ---@class TradeStatWeightMultiplierListControl: ListControl +---@field list TradeStatWeightMultiplier[] +---@field indexController TradeStatWeightMultiplierIndexController +---@field selIndex? integer +---@field noTooltip? boolean local TradeStatWeightMultiplierListControlClass = newClass("TradeStatWeightMultiplierListControl", "ListControl") +---@param anchor? Anchor +---@param rect? Rect +---@param list TradeStatWeightMultiplier[] +---@param indexController TradeStatWeightMultiplierIndexController +---@return TradeStatWeightMultiplierListControl function TradeStatWeightMultiplierListControlClass:TradeStatWeightMultiplierListControl(anchor, rect, list, indexController) self.list = list self.indexController = indexController @@ -15,17 +34,26 @@ function TradeStatWeightMultiplierListControlClass:TradeStatWeightMultiplierList return self end +---@param viewPort Rect +---@param noTooltip? boolean function TradeStatWeightMultiplierListControlClass:Draw(viewPort, noTooltip) self.noTooltip = noTooltip self.ListControl.Draw(self, viewPort) end +---@param column integer +---@param index integer +---@param data TradeStatWeightMultiplier +---@return string? function TradeStatWeightMultiplierListControlClass:GetRowValue(column, index, data) if column == 1 then return data.label end end +---@param tooltip Tooltip +---@param index integer +---@param data TradeStatWeightMultiplier function TradeStatWeightMultiplierListControlClass:AddValueTooltip(tooltip, index, data) tooltip:Clear() if not self.noTooltip then @@ -33,6 +61,9 @@ function TradeStatWeightMultiplierListControlClass:AddValueTooltip(tooltip, inde end end +---@param index integer +---@param data TradeStatWeightMultiplier +---@param doubleClick? boolean function TradeStatWeightMultiplierListControlClass:OnSelClick(index, data, doubleClick) if self.indexController.index ~= index then self.indexController.index = index diff --git a/src/Classes/TreeTab.lua b/src/Classes/TreeTab.lua index 842b3b4e09a..dd9185d675a 100644 --- a/src/Classes/TreeTab.lua +++ b/src/Classes/TreeTab.lua @@ -19,10 +19,77 @@ local s_gsub = string.gsub local s_byte = string.byte local dkjson = require "dkjson" +---@class TreeVersionOption +---@field label string +---@field value string + +---@class TattooModGroup +---@field label string +---@field descriptions string[] +---@field id string + +---@class PowerReportEntry +---@field name string +---@field power number +---@field powerStr string +---@field pathPower number +---@field pathPowerStr string +---@field allocated boolean +---@field id integer +---@field x number +---@field y number +---@field type string +---@field sd string[] +---@field pathDist integer|string + +---@class TimelessJewelSocketOption +---@field label string +---@field keystone string +---@field id integer + +---@class TimelessDesiredNode +---@field nodeWeight number +---@field nodeWeight2 number +---@field displayName string +---@field desiredIdx integer + +---@class TimelessSeedNodeResult +---@field targetNodeNames string[] +---@field totalWeight number +---@field [integer] integer + +---@class TimelessSocketResult +---@field resultNodes table?> +---@field seedWeights table +---@field desiredNodes table +---@field socketInfo TimelessJewelSocketOption + ---@class TreeTab: ControlHost +---@field build Build +---@field modFlag boolean +---@field viewer PassiveTreeView +---@field isComparing boolean +---@field isCustomMaxDepth boolean +---@field specList PassiveSpec[] +---@field activeSpec integer +---@field activeCompareSpec integer +---@field compareSpec PassiveSpec +---@field anchorControls Control +---@field treeVersions TreeVersionOption[] +---@field tradeLeaguesList table +---@field defaultTattoo table +---@field powerStatList PowerStat[] +---@field powerBuilderToastId? number +---@field lastProgressToastUpdate number +---@field jumpToNode boolean +---@field jumpToX number +---@field jumpToY number +---@field showLegacyTattoo boolean +---@field allocatedNodesInRadiusCount integer +---@field [string] unknown local TreeTabClass = newClass("TreeTab", "ControlHost") - ---@param build Build +---@return TreeTab function TreeTabClass:TreeTab(build) self:ControlHost() @@ -327,12 +394,15 @@ function TreeTabClass:TreeTab(build) self.controls.specConvertText.shown = function() return self.showConvert end + ---@return string local function getLatestTreeVersion() return latestTreeVersion .. (self.specList[self.activeSpec].treeVersion:match("^" .. latestTreeVersion .. "(.*)") or "") end + ---@return string local function buildConvertButtonLabel() return colorCodes.POSITIVE.."Convert to "..treeVersions[getLatestTreeVersion()].display end + ---@return string local function buildConvertAllButtonLabel() return colorCodes.POSITIVE.."Convert all trees to "..treeVersions[getLatestTreeVersion()].display end @@ -347,7 +417,7 @@ function TreeTabClass:TreeTab(build) self.jumpToY = 0 return self end - +---@param node Node function TreeTabClass:RemoveTattooFromNode(node) self.build.spec.tree.nodes[node.id].isTattoo = false self.build.spec.hashOverrides[node.id] = nil @@ -355,7 +425,8 @@ function TreeTabClass:RemoveTattooFromNode(node) node.allMasteryOptions = false self.build.spec:BuildAllDependsAndPaths() end - +---@param viewPort Rect +---@param inputEvents InputEvent[] function TreeTabClass:Draw(viewPort, inputEvents) self.anchorControls.x = viewPort.x + 4 self.anchorControls.y = viewPort.y + viewPort.height - 24 @@ -488,7 +559,7 @@ function TreeTabClass:Draw(viewPort, inputEvents) self:DrawControls(viewPort) end - +---@return string[] function TreeTabClass:GetSpecList() local newSpecList = { } for _, spec in ipairs(self.specList) do @@ -496,7 +567,9 @@ function TreeTabClass:GetSpecList() end return newSpecList end - +---@param xml table +---@param dbFileName string +---@return boolean? function TreeTabClass:Load(xml, dbFileName) self.specList = { } if xml.elem == "Spec" then @@ -532,7 +605,7 @@ function TreeTabClass:PostLoad() end self.build.itemsTab:PopulateSlots() end - +---@param xml table function TreeTabClass:Save(xml) xml.attrib = { activeSpec = tostring(self.activeSpec) @@ -545,7 +618,7 @@ function TreeTabClass:Save(xml) t_insert(xml, child) end end - +---@param specId integer function TreeTabClass:SetActiveSpec(specId) local prevSpec = self.build.spec self.activeSpec = m_min(specId, #self.specList) @@ -583,14 +656,17 @@ function TreeTabClass:SetActiveSpec(specId) end self.build:SyncLoadouts() end - +---@param specId integer function TreeTabClass:SetCompareSpec(specId) self.activeCompareSpec = m_min(specId, #self.specList) local curSpec = self.specList[self.activeCompareSpec] self.compareSpec = curSpec end - +---@param version string +---@param remove boolean +---@param success boolean +---@param ignoreTreeSubType? boolean function TreeTabClass:ConvertToVersion(version, remove, success, ignoreTreeSubType) local treeSubTypeCapture = self.build.spec.treeVersion:match("(_%l+_?%l*)") if not ignoreTreeSubType and treeSubTypeCapture and not version:match(treeSubTypeCapture) then @@ -616,7 +692,7 @@ function TreeTabClass:ConvertToVersion(version, remove, success, ignoreTreeSubTy main:OpenMessagePopup("Tree Converted", "The tree has been converted to "..treeVersions[version].display..".\nNote that some or all of the passives may have been de-allocated due to changes in the tree.\n\nYou can switch back to the old tree using the tree selector at the bottom left.") end end - +---@param version string function TreeTabClass:ConvertAllToVersion(version) local currActiveSpec = self.activeSpec local specVersionList = { } @@ -651,7 +727,8 @@ function TreeTabClass:OpenSpecManagePopup() end), }) end - +---@param version string +---@param ignoreTreeSubType? boolean function TreeTabClass:OpenVersionConvertPopup(version, ignoreTreeSubType) local controls = { } controls.warningLabel = new("LabelControl"):LabelControl(nil, {0, 20, 0, 16}, "^7Warning: some or all of the passives may be de-allocated due to changes in the tree.\n\n" .. @@ -670,7 +747,7 @@ function TreeTabClass:OpenVersionConvertPopup(version, ignoreTreeSubType) end) main:OpenPopup(570, 140, "Convert to Version "..treeVersions[version].display, controls, "convert", "edit") end - +---@param version string function TreeTabClass:OpenVersionConvertAllPopup(version) local controls = { } controls.warningLabel = new("LabelControl"):LabelControl(nil, {0, 20, 0, 16}, "^7Warning: some or all of the passives may be de-allocated due to changes in the tree.\n\n" .. @@ -688,6 +765,7 @@ end function TreeTabClass:OpenImportPopup() local versionLookup = "tree/([0-9]+)%.([0-9]+)%.([0-9]+)/" local controls = { } + ---@param treeLink string local function decodePoePlannerTreeLink(treeLink) -- treeVersion is not known at this point. We need to decode the URL to get it. local tmpSpec = new("PassiveSpec"):PassiveSpec(self.build, latestTreeVersion) @@ -710,7 +788,8 @@ function TreeTabClass:OpenImportPopup() self.build.buildFlag = true main:ClosePopup() end - + ---@param treeLink string + ---@param newTreeVersion string local function decodeTreeLink(treeLink, newTreeVersion) -- newTreeVersion is passed in as an output of validateTreeVersion(). It will always be a valid tree version text string -- 20230908. We always create a new Spec() @@ -730,6 +809,10 @@ function TreeTabClass:OpenImportPopup() main:ClosePopup() end end + ---@param alternateType? string + ---@param major? string + ---@param minor? string + ---@return string local function validateTreeVersion(alternateType, major, minor) -- Take the Major and Minor version numbers and confirm it is a valid tree version. The point release is also passed in but it is not used -- Return: the passed in tree version as text or latestTreeVersion @@ -843,12 +926,13 @@ function TreeTabClass:OpenExportPopup() end) popup = main:OpenPopup(380, 100, "Export Tree", controls, "done", "edit") end - +---@param selectedNode Node function TreeTabClass:ModifyNodePopup(selectedNode) local controls = { } local modGroups = { } local treeNodes = self.build.spec.tree.nodes local nodeName = treeNodes[selectedNode.id].dn + ---@param selectedNode Node local function buildMods(selectedNode) wipeTable(modGroups) local numLinkedNodes = selectedNode.linkedId and #selectedNode.linkedId or 0 @@ -882,6 +966,7 @@ function TreeTabClass:ModifyNodePopup(selectedNode) end table.sort(modGroups, function(a, b) return a.label < b.label end) end + ---@param selectedNode Node local function addModifier(selectedNode) local newTattooNode = self.build.spec.tree.tattoo.nodes[modGroups[controls.modSelect.selIndex].id] newTattooNode.id = selectedNode.id @@ -892,7 +977,7 @@ function TreeTabClass:ModifyNodePopup(selectedNode) end self.build.spec:BuildAllDependsAndPaths() end - + ---@param modGroup TattooModGroup local function constructUI(modGroup) local totalHeight = 43 local maxWidth = 375 @@ -951,7 +1036,8 @@ function TreeTabClass:ModifyNodePopup(selectedNode) controls.close = new("ButtonControl"):ButtonControl(nil, {90, 75, 80, 20}, "Cancel", function() main:ClosePopup() end) - + ---@param tooltip? Tooltip + ---@return integer|string local function getTattooCount(tooltip) if tooltip then tooltip:Clear() @@ -997,7 +1083,8 @@ function TreeTabClass:ModifyNodePopup(selectedNode) end) controls.showLegacyTattoo.state = self.showLegacyTattoo end - +---@param node Node +---@param listControl PassiveMasteryControl function TreeTabClass:SaveMasteryPopup(node, listControl) if listControl.selValue == nil then return @@ -1016,7 +1103,8 @@ function TreeTabClass:SaveMasteryPopup(node, listControl) self.build.buildFlag = true main:ClosePopup() end - +---@param node Node +---@param viewPort Rect function TreeTabClass:OpenMasteryPopup(node, viewPort) local controls = { } local effects = { } @@ -1043,7 +1131,7 @@ function TreeTabClass:OpenMasteryPopup(node, viewPort) main:OpenPopup(controls.effect.width + 12, controls.effect.height + 60, node.name, controls, nil, nil, "close") end end - +---@param powerStat PowerStat function TreeTabClass:SetPowerCalc(powerStat) self.viewer.showHeatMap = true self.build.buildFlag = true @@ -1057,7 +1145,8 @@ function TreeTabClass:SetPowerCalc(powerStat) self.powerBuilderToastId = nil end end - +---@param currentStat PowerStat? +---@return PowerReportEntry[] function TreeTabClass:BuildPowerReportList(currentStat) local report = {} @@ -1087,6 +1176,8 @@ function TreeTabClass:BuildPowerReportList(currentStat) } end local powerMultiplier = (displayStat.pc or displayStat.mod) and 100 or 1 + ---@param power number + ---@return string local function formatPower(power) local powerStr = formatNumSep(s_format("%"..displayStat.fmt, power)) if (power > 0 and not displayStat.lowerIsBetter) or (power < 0 and displayStat.lowerIsBetter) then @@ -1096,12 +1187,22 @@ function TreeTabClass:BuildPowerReportList(currentStat) end return powerStr end + ---@param node Node + ---@param isAlloc boolean + ---@return integer local function getNodePathDist(node, isAlloc) if isAlloc then return #(node.depends or { }) == 0 and 1 or #node.depends end return node.power.distance or #(node.path or {}) == 0 and 1 or #node.path end + ---@param node Node + ---@param name string + ---@param nodePower number + ---@param pathPower number + ---@param pathDist integer|string + ---@param isAlloc boolean + ---@param pathPowerStr? string local function addReportEntry(node, name, nodePower, pathPower, pathDist, isAlloc, pathPowerStr) t_insert(report, { name = name, @@ -1410,7 +1511,7 @@ function TreeTabClass:FindTimelessJewel() modData[#modData + 1] = smallModData[i] end end - + ---@return table local function getNodeWeights() local nodeWeights = { [1] = controls.nodeSliderValue.label:sub(3):lower(), @@ -1427,6 +1528,8 @@ function TreeTabClass:FindTimelessJewel() local searchListTbl = { } local searchListFallbackTbl = { } + ---@param mode integer + ---@param fallback boolean local function parseSearchList(mode, fallback) if mode == 0 then if fallback then @@ -1502,6 +1605,8 @@ function TreeTabClass:FindTimelessJewel() end parseSearchList(0, false) -- initial load: [timelessData.searchList => searchListTbl] parseSearchList(0, true) -- initial load: [timelessData.searchListFallback => searchListFallbackTbl] + ---@param text string + ---@param fallback boolean local function updateSearchList(text, fallback) if fallback then timelessData.searchListFallback = text @@ -1534,7 +1639,7 @@ function TreeTabClass:FindTimelessJewel() local protectedNodesCount = 0 local setAllocatedNodes self.allocatedNodesInRadiusCount = 0 - + ---@param nodes? { label: string, node: Node }[] local function buildNodeOptionCheckboxes(nodes) local i = 1 protectedNodes = {} @@ -1847,7 +1952,7 @@ function TreeTabClass:FindTimelessJewel() return controls.nodeSlider3.tooltip.realDraw(self, x, y, width, height, viewPort) end controls.nodeSlider3:SetVal(0) - + ---@param sliderData table local function updateSliders(sliderData) if sliderData[2] == "required" then controls.nodeSlider.val = 1 @@ -1875,6 +1980,10 @@ function TreeTabClass:FindTimelessJewel() end buildMods() + ---@param legionPassive Node + ---@return integer statCount + ---@return string primaryLabel + ---@return string secondaryLabel local function getLegionStatLabels(legionPassive) local statCount = timelessData.jewelType.id >= 7 and #legionPassive.sortedStats or #legionPassive.sd if statCount > #legionPassive.sd then @@ -1948,7 +2057,9 @@ function TreeTabClass:FindTimelessJewel() end end end - + ---@param nodes table[] + ---@param powerStat PowerStat + ---@return { id: string, weight1?: number, weight2?: number, weight3?: number }[] local function generateFallbackWeights(nodes, powerStat) local calcFunc, calcBase = self.build.calcsTab:GetMiscCalculator(self.build) local newList = { } @@ -1996,6 +2107,8 @@ function TreeTabClass:FindTimelessJewel() end return statToFix -- if it doesn't need to be changed end + ---@param legionPassive Node + ---@return { modList: Mod[]?, divisor: integer }[] local function buildStatModLists(legionPassive) -- Give each stat its own mod list even when several stats share one display line. local modLists = { } @@ -2439,6 +2552,9 @@ function TreeTabClass:FindTimelessJewel() { -labelSpacing, 0, 0, labelHeight }, "^7Search Maximum Amount:") -- Helper function to search a single socket + ---@param socketId integer + ---@param socketInfo TimelessJewelSocketOption + ---@return TimelessSocketResult? local function searchSingleSocket(socketId, socketInfo) if not treeData.nodes[socketId] or not treeData.nodes[socketId].isJewelSocket then return nil @@ -2727,7 +2843,8 @@ function TreeTabClass:FindTimelessJewel() socketInfo = socketInfo } end - + ---@param input number + ---@return string local function formatSearchValue(input) local matchPattern1 = " 0" local replacePattern1 = " " @@ -2743,7 +2860,11 @@ function TreeTabClass:FindTimelessJewel() :gsub(matchPattern3, replacePattern3) :gsub(matchPattern4, replacePattern4) end - + ---@param resultNodes table?> + ---@param seedWeights table + ---@param desiredNodes table + ---@param socketInfo TimelessJewelSocketOption? + ---@return table[] local function formatResults(resultNodes, seedWeights, desiredNodes, socketInfo) local results = { } for seedMatch, seedData in pairs(resultNodes) do diff --git a/src/Classes/UndoHandler.lua b/src/Classes/UndoHandler.lua index 4e6674808fc..4bfeb85a398 100644 --- a/src/Classes/UndoHandler.lua +++ b/src/Classes/UndoHandler.lua @@ -10,10 +10,14 @@ local t_insert = table.insert local t_remove = table.remove ---@class UndoHandler ----@field CreateUndoState fun() Must be manually defined. Creates a state that can be restored ----@field RestoreUndoState fun(state: any) Must be manually defined. Restores a state created by calling CreateUndoState() +---@field CreateUndoState fun(): unknown Must be manually defined. Creates a state that can be restored +---@field RestoreUndoState fun(state: unknown) Must be manually defined. Restores a state created by calling CreateUndoState() +---@field undo unknown[] +---@field redo unknown[] +---@field modFlag? boolean local UndoHandlerClass = newClass("UndoHandler") +---@return UndoHandler function UndoHandlerClass:UndoHandler() self.undo = { } self.redo = { }