From 7671be9c79b4dbfdc487b264df95c6edc67b80fa Mon Sep 17 00:00:00 2001 From: Theodore Di Pietro Date: Thu, 24 Sep 2026 10:10:36 +0000 Subject: [PATCH 1/5] Complete class type annotations --- src/Classes/BuildListControl.lua | 63 ++- src/Classes/ButtonControl.lua | 24 + src/Classes/CalcBreakdownControl.lua | 55 ++- src/Classes/CalcSectionControl.lua | 58 ++- src/Classes/CalcsTab.lua | 43 ++ src/Classes/CheckBoxControl.lua | 20 + src/Classes/CompareBuySimilar.lua | 51 ++- src/Classes/CompareCalcsHelpers.lua | 49 +- src/Classes/CompareEntry.lua | 69 +++ src/Classes/ComparePowerReportListControl.lua | 20 + src/Classes/CompareTab.lua | 423 ++++++++++++++++-- src/Classes/ConfigSetListControl.lua | 21 + src/Classes/ConfigTab.lua | 88 +++- src/Classes/Control.lua | 38 +- src/Classes/ControlHost.lua | 14 + src/Classes/DraggerControl.lua | 34 ++ src/Classes/DropDownControl.lua | 56 ++- src/Classes/EditControl.lua | 53 +++ src/Classes/ExtBuildListControl.lua | 53 +++ src/Classes/ExtBuildListProvider.lua | 29 ++ src/Classes/FolderListControl.lua | 28 +- src/Classes/GemSelectControl.lua | 41 +- src/Classes/GemTooltip.lua | 18 +- src/Classes/ImportTab.lua | 68 ++- src/Classes/Item.lua | 109 ++++- src/Classes/ItemDBControl.lua | 37 ++ src/Classes/ItemListControl.lua | 42 +- src/Classes/ItemSetListControl.lua | 39 ++ src/Classes/ItemSlotControl.lua | 21 +- src/Classes/ItemsTab.lua | 249 ++++++++++- src/Classes/LabelControl.lua | 2 + src/Classes/ListControl.lua | 63 ++- src/Classes/MinionListControl.lua | 34 ++ src/Classes/MinionSearchListControl.lua | 15 + src/Classes/ModDB.lua | 67 ++- src/Classes/ModList.lua | 55 ++- src/Classes/ModStore.lua | 65 ++- src/Classes/NotableDBControl.lua | 58 ++- src/Classes/NotesTab.lua | 12 + src/Classes/PartyTab.lua | 34 ++ src/Classes/PassiveMasteryControl.lua | 19 + src/Classes/PassiveSpec.lua | 133 +++++- src/Classes/PassiveSpecListControl.lua | 22 + src/Classes/PassiveTree.lua | 51 ++- src/Classes/PassiveTreeView.lua | 122 ++++- src/Classes/PathControl.lua | 29 +- src/Classes/PoBArchivesProvider.lua | 12 + src/Classes/PoEAPI.lua | 35 +- src/Classes/PopupDialog.lua | 18 + src/Classes/PowerReportListControl.lua | 24 + src/Classes/RectangleOutlineControl.lua | 7 + src/Classes/ResizableEditControl.lua | 21 + src/Classes/ScrollBarControl.lua | 42 ++ src/Classes/SearchHost.lua | 41 ++ src/Classes/SectionControl.lua | 6 +- src/Classes/SharedItemListControl.lua | 29 ++ src/Classes/SharedItemSetListControl.lua | 35 ++ src/Classes/SkillListControl.lua | 26 ++ src/Classes/SkillSetListControl.lua | 18 + src/Classes/SkillsTab.lua | 62 ++- src/Classes/SliderControl.lua | 26 ++ src/Classes/TextListControl.lua | 17 + src/Classes/TimelessJewelListControl.lua | 29 +- src/Classes/TimelessJewelSocketControl.lua | 14 +- src/Classes/Tooltip.lua | 60 ++- src/Classes/TooltipHost.lua | 12 + src/Classes/TradeHelpers.lua | 130 +++++- src/Classes/TradeQuery.lua | 77 +++- src/Classes/TradeQueryGenerator.lua | 103 ++++- src/Classes/TradeQueryRateLimiter.lua | 53 ++- src/Classes/TradeQueryRequests.lua | 78 +++- .../TradeStatWeightMultiplierListControl.lua | 31 ++ src/Classes/TreeTab.lua | 173 +++++-- src/Classes/UndoHandler.lua | 8 +- 74 files changed, 3604 insertions(+), 277 deletions(-) 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 = { } From 04da116208a12309eec384d887dc0e81ccde8236 Mon Sep 17 00:00:00 2001 From: Theodore Di Pietro Date: Thu, 1 Oct 2026 10:55:58 +0000 Subject: [PATCH 2/5] Fix static analysis diagnostics --- src/Classes/BuildListControl.lua | 8 +- src/Classes/ButtonControl.lua | 2 +- src/Classes/CalcBreakdownControl.lua | 18 ++- src/Classes/CalcSectionControl.lua | 6 +- src/Classes/CalcsTab.lua | 12 +- src/Classes/CheckBoxControl.lua | 4 +- src/Classes/CompareCalcsHelpers.lua | 29 ++-- src/Classes/CompareEntry.lua | 26 ++- src/Classes/ComparePowerReportListControl.lua | 18 ++- src/Classes/CompareTab.lua | 50 ++++-- src/Classes/ConfigSetListControl.lua | 2 +- src/Classes/ConfigTab.lua | 38 +++-- src/Classes/Control.lua | 14 +- src/Classes/ControlHost.lua | 6 +- src/Classes/DraggerControl.lua | 16 +- src/Classes/DropDownControl.lua | 26 ++- src/Classes/EditControl.lua | 24 ++- src/Classes/ExtBuildListControl.lua | 2 +- src/Classes/FolderListControl.lua | 2 +- src/Classes/GemSelectControl.lua | 2 +- src/Classes/ImportTab.lua | 24 ++- src/Classes/Item.lua | 151 ++++++++++++++---- src/Classes/ItemDBControl.lua | 4 +- src/Classes/ItemListControl.lua | 8 +- src/Classes/ItemSetListControl.lua | 4 +- src/Classes/ItemSlotControl.lua | 6 +- src/Classes/ItemsTab.lua | 100 +++++++++--- src/Classes/LabelControl.lua | 4 +- src/Classes/ListControl.lua | 14 +- src/Classes/MinionListControl.lua | 6 +- src/Classes/ModDB.lua | 15 +- src/Classes/ModList.lua | 22 ++- src/Classes/ModStore.lua | 14 +- src/Classes/NotableDBControl.lua | 10 +- src/Classes/NotesTab.lua | 2 +- src/Classes/PartyTab.lua | 5 +- src/Classes/PassiveMasteryControl.lua | 4 +- src/Classes/PassiveSpec.lua | 60 ++++--- src/Classes/PassiveSpecListControl.lua | 2 +- src/Classes/PassiveTree.lua | 99 +++++++++++- src/Classes/PassiveTreeView.lua | 20 ++- src/Classes/PathControl.lua | 4 +- src/Classes/PoBArchivesProvider.lua | 2 + src/Classes/PopupDialog.lua | 4 +- src/Classes/PowerReportListControl.lua | 2 +- src/Classes/ResizableEditControl.lua | 2 +- src/Classes/ScrollBarControl.lua | 1 + src/Classes/SharedItemListControl.lua | 6 +- src/Classes/SharedItemSetListControl.lua | 4 +- src/Classes/SkillListControl.lua | 4 +- src/Classes/SkillSetListControl.lua | 2 +- src/Classes/SkillsTab.lua | 2 +- src/Classes/SliderControl.lua | 2 +- src/Classes/TextListControl.lua | 2 +- src/Classes/TimelessJewelListControl.lua | 4 +- src/Classes/TimelessJewelSocketControl.lua | 6 +- src/Classes/Tooltip.lua | 16 +- src/Classes/TooltipHost.lua | 2 +- src/Classes/TradeHelpers.lua | 13 +- src/Classes/TradeQuery.lua | 13 +- src/Classes/TradeQueryGenerator.lua | 17 +- src/Classes/TradeQueryRateLimiter.lua | 8 + src/Classes/TradeQueryRequests.lua | 1 + .../TradeStatWeightMultiplierListControl.lua | 4 +- src/Classes/TreeTab.lua | 59 +++++-- src/GameVersions.lua | 1 + src/Modules/AddImplicitPopup.lua | 2 +- src/Modules/CalcBase.lua | 1 + src/Modules/CalcSections.lua | 5 + src/Modules/CalcSetup.lua | 1 + src/Modules/CalcTools.lua | 4 +- src/Modules/Common.lua | 7 +- src/Modules/Data.lua | 2 + src/Modules/Main.lua | 10 ++ src/_SimpleGraphic.def.lua | 3 +- 75 files changed, 790 insertions(+), 305 deletions(-) diff --git a/src/Classes/BuildListControl.lua b/src/Classes/BuildListControl.lua index ae4411a5516..c40e68ff049 100644 --- a/src/Classes/BuildListControl.lua +++ b/src/Classes/BuildListControl.lua @@ -21,7 +21,7 @@ local buildListHelpers = require("Modules.BuildListHelpers") ---@field subPath string ---@field list BuildListEntry[] ----@class BuildListControl: ListControl +---@class BuildListControl: ListControl ---@field listMode BuildListMode local BuildListClass = newClass("BuildListControl", "ListControl") @@ -54,7 +54,7 @@ function BuildListClass:BuildListControl(anchor, rect, listMode) end ---@param type string ---@param build BuildListEntry - ---@param source ListControl + ---@param source ListControl function self.controls.path:ReceiveDrag(type, build, source) if type == "Build" then for index, folder in ipairs(self.folderList) do @@ -271,7 +271,7 @@ end ---@param type string ---@param build BuildListEntry ----@param source? ListControl +---@param source? ListControl function BuildListClass:ReceiveDrag(type, build, source) if type == "Build" then if self.hoverValue and self.hoverValue.folderName then @@ -295,7 +295,7 @@ end ---@param index integer ---@param build BuildListEntry ----@param source? ListControl +---@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 .. "/") diff --git a/src/Classes/ButtonControl.lua b/src/Classes/ButtonControl.lua index 04f642dedd4..e83260e56b6 100644 --- a/src/Classes/ButtonControl.lua +++ b/src/Classes/ButtonControl.lua @@ -55,7 +55,7 @@ function ButtonClass:IsMouseOver() return self:IsMouseInBounds() end ----@param viewPort Rect +---@param viewPort Viewport ---@param noTooltip? boolean ---@return Control? function ButtonClass:Draw(viewPort, noTooltip) diff --git a/src/Classes/CalcBreakdownControl.lua b/src/Classes/CalcBreakdownControl.lua index 09ca478c3dc..556d5b7af7c 100644 --- a/src/Classes/CalcBreakdownControl.lua +++ b/src/Classes/CalcBreakdownControl.lua @@ -15,7 +15,7 @@ local band = bit.band ---@class CalcBreakdownControl: Control, ControlHost ---@field calcsTab CalcsTab ----@field sourceData? CalcSectionEntry[] +---@field sourceData? CalcSectionColumn ---@field pinned boolean ---@field forceActor? "player"|"minion" ---@field tooltip Tooltip @@ -74,7 +74,7 @@ function CalcBreakdownClass:GetActor() end return actor, env end ----@param displayData? CalcSectionEntry[] +---@param displayData? CalcSectionColumn ---@param pinned boolean ---@param forceActor? "player"|"minion" function CalcBreakdownClass:SetBreakdownData(displayData, pinned, forceActor) @@ -164,8 +164,10 @@ function CalcBreakdownClass:SetBreakdownData(displayData, pinned, forceActor) end -- Add sections based on the breakdown data generated by the Calcs module +-- Precondition: caller has already verified sectionData.breakdown is set ---@param sectionData CalcSectionEntry function CalcBreakdownClass:AddBreakdownSection(sectionData) + ---@cast sectionData.breakdown string local actor = self:GetActor() local breakdown local ns, name = sectionData.breakdown:match("^(%a+)%.(%a+)$") @@ -333,6 +335,7 @@ function CalcBreakdownClass:AddModSection(sectionData, modList) if modList then rowList = copyTable(modList) else + ---@cast sectionData.modType string if type(sectionData.modName) == "table" then rowList = modStore:Tabulate(sectionData.modType, cfg, unpack(sectionData.modName)) else @@ -397,6 +400,7 @@ function CalcBreakdownClass:AddModSection(sectionData, modList) cfg.source = sourceType for _, modType in ipairs(typeList) do if type(sectionData.modName) == "table" then + ---@cast sectionData.modName string[] -- Multiple stats, show each separately for _, modName in ipairs(sectionData.modName) do local total = modStore:Combine(modType, cfg, modName) @@ -559,7 +563,11 @@ end ---@param varList? string[] ---@return string function CalcBreakdownClass:FormatVarNameOrList(var, varList) - return var and self:FormatModName(var) or self:FormatModName(table.concat(varList, " / ")) + if var then + return self:FormatModName(var) + end + ---@cast varList string[] + return self:FormatModName(table.concat(varList, " / ")) end ---@param mod Mod @@ -602,7 +610,7 @@ function CalcBreakdownClass:FormatModValue(value, modType) end end ----@param viewPort Rect +---@param viewPort Viewport ---@param x number ---@param y number ---@param section table @@ -730,7 +738,7 @@ function CalcBreakdownClass:DrawRadiusVisual(x, y, width, height, radius) DrawImage(self.uiOverlay, x, y, width, height) end ----@param viewPort Rect +---@param viewPort Viewport function CalcBreakdownClass:Draw(viewPort) local sourceData = self.sourceData local scrollBar = self.controls.scrollBar diff --git a/src/Classes/CalcSectionControl.lua b/src/Classes/CalcSectionControl.lua index 4f51ad08d77..a8d37454bdb 100644 --- a/src/Classes/CalcSectionControl.lua +++ b/src/Classes/CalcSectionControl.lua @@ -204,7 +204,7 @@ function CalcSectionClass:UpdatePos() end end ----@param viewPort Rect +---@param viewPort Viewport ---@param noTooltip? boolean function CalcSectionClass:Draw(viewPort, noTooltip) local x, y = self:GetPos() @@ -400,7 +400,7 @@ function CalcSectionClass:HandleOverlayRelease(key) end end ----@param viewPort Rect +---@param viewPort Viewport ---@param inputEvents InputEvent[] function CalcSectionClass:DrawOverlay(viewPort, inputEvents) local cursorX, cursorY = GetCursorPos() @@ -475,7 +475,7 @@ end ---@param startLineY number ---@param drawWidth number ---@param actor Actor ----@param viewPort Rect +---@param viewPort Viewport ---@param isOverlay boolean ---@param noTooltip? boolean function CalcSectionClass:DrawContent(drawX, startLineY, drawWidth, actor, viewPort, isOverlay, noTooltip) diff --git a/src/Classes/CalcsTab.lua b/src/Classes/CalcsTab.lua index d56d7ee283b..058c10f5e30 100644 --- a/src/Classes/CalcsTab.lua +++ b/src/Classes/CalcsTab.lua @@ -19,7 +19,7 @@ local buffModeDropList = { ---@class CalcsTab: UndoHandler, ControlHost, Control ---@field build Build ---@field modFlag boolean ----@field displayData? table +---@field displayData? CalcSectionColumn ---@field powerStat PowerStat? ---@field nodePowerMaxDepth integer? Maximum distance for power report local CalcsTabClass = newClass("CalcsTab", "UndoHandler", "ControlHost", "Control") @@ -231,7 +231,7 @@ function CalcsTabClass:Save(xml) end end ----@param viewPort Rect +---@param viewPort Viewport ---@param inputEvents InputEvent[] function CalcsTabClass:Draw(viewPort, inputEvents) self.x = viewPort.x @@ -392,7 +392,7 @@ function CalcsTabClass:ClearDisplayStat() self.controls.breakdown:SetBreakdownData() end ----@param displayData table +---@param displayData CalcSectionColumn ---@param pin? boolean function CalcsTabClass:SetDisplayStat(displayData, pin) if not displayData or (not pin and self.displayPinned) then @@ -408,8 +408,8 @@ function CalcsTabClass:SetDisplayStat(displayData, pin) end ---@param obj table ----@param actor Actor ----@param player Actor +---@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) @@ -564,7 +564,7 @@ function CalcsTabClass:PowerBuilder() ---@param distance number ---@param node Node ---@param output Output - ---@param buildPathNodes table + ---@param buildPathNodes fun(): 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) diff --git a/src/Classes/CheckBoxControl.lua b/src/Classes/CheckBoxControl.lua index 91e3dd936a5..f5dba35f010 100644 --- a/src/Classes/CheckBoxControl.lua +++ b/src/Classes/CheckBoxControl.lua @@ -13,7 +13,7 @@ ---@field borderFunc? fun(): number, number, number local CheckBoxClass = newClass("CheckBoxControl", "Control", "TooltipHost") ----@param anchor Anchor +---@param anchor? Anchor ---@param rect Rect ---@param label? Prop ---@param changeFunc? fun(state: boolean) @@ -52,7 +52,7 @@ function CheckBoxClass:IsMouseOver() return cursorX >= x and cursorY >= y and cursorX < x + width and cursorY < y + height end ----@param viewPort Rect +---@param viewPort Viewport ---@param noTooltip? boolean function CheckBoxClass:Draw(viewPort, noTooltip) local x, y = self:GetPos() diff --git a/src/Classes/CompareCalcsHelpers.lua b/src/Classes/CompareCalcsHelpers.lua index 9048788798c..cdad0ad5db3 100644 --- a/src/Classes/CompareCalcsHelpers.lua +++ b/src/Classes/CompareCalcsHelpers.lua @@ -52,7 +52,7 @@ end -- Resolve a modifier's source to a human-readable name ---@param mod Mod ----@param build Build +---@param build Build|CompareEntry ---@return string function M.ResolveSourceName(mod, build) if not mod.source then return "" end @@ -88,7 +88,7 @@ function M.ResolveSourceName(mod, build) end -- Get the modDB and config for a sectionData entry and actor ----@param sectionData CalcSectionData +---@param sectionData CalcSectionData|CalcSectionEntry ---@param actor Actor ---@return ModStore? ---@return ModCfg? @@ -112,18 +112,22 @@ function M.GetModStoreAndCfg(sectionData, actor) end -- Tabulate modifiers for a sectionData entry and actor ----@param sectionData CalcSectionData +---@param sectionData CalcSectionData|CalcSectionEntry ---@param actor Actor ---@return CompareCalcModRow[] function M.TabulateMods(sectionData, actor) local modStore, cfg = M.GetModStoreAndCfg(sectionData, actor) if not modStore then return {} end + local modType = sectionData.modType + if not modType then return {} end + local modName = sectionData.modName + if not modName then return {} end local rowList - if type(sectionData.modName) == "table" then - rowList = modStore:Tabulate(sectionData.modType, cfg, unpack(sectionData.modName)) + if type(modName) == "table" then + rowList = modStore:Tabulate(modType, cfg, unpack(modName)) else - rowList = modStore:Tabulate(sectionData.modType, cfg, sectionData.modName) + rowList = modStore:Tabulate(modType, cfg, modName) end return rowList or {} end @@ -143,8 +147,8 @@ end -- Format a single modifier row as a tooltip line ---@param row CompareCalcModRow ----@param sectionData CalcSectionData ----@param build Build +---@param sectionData CalcSectionData|CalcSectionEntry +---@param build Build|CompareEntry ---@return string displayValue ---@return string sourceType ---@return string sourceName @@ -168,8 +172,8 @@ function M.FormatModRow(row, sectionData, build) end -- Get breakdown text lines for a build's actor ----@param sectionData CalcSectionData ----@param build Build +---@param sectionData CalcSectionData|CalcSectionEntry +---@param build Build|CompareEntry ---@return string[]? function M.GetBreakdownLines(sectionData, build) if not sectionData.breakdown then return nil end @@ -206,7 +210,7 @@ end ---@param rowY number ---@param rowW number ---@param rowH number ----@param vp Rect +---@param vp Viewport ---@param compareEntry CompareEntry function M.DrawCalcsTooltip(tooltip, primaryBuild, primaryLabel, colData, rowLabel, rowX, rowY, rowW, rowH, vp, compareEntry) if tooltip:CheckForUpdate(colData, rowLabel) then @@ -368,6 +372,7 @@ end ---@return string sourceName local function resolveModSource(mod, build) local sourceType = mod.source and mod.source:match("[^:]+") or "?" + ---@cast mod.source string local sourceName = "" if sourceType == "Item" then local itemId = mod.source:match("Item:(%d+):.+") @@ -407,7 +412,7 @@ end ---@param cellY number ---@param cellW number ---@param cellH number ----@param vp Rect +---@param vp Viewport 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 fcecd748da3..53649765397 100644 --- a/src/Classes/CompareEntry.lua +++ b/src/Classes/CompareEntry.lua @@ -52,7 +52,9 @@ function CompareEntryClass:CompareEntry(xmlText, label) -- Default build properties self.viewMode = "TREE" - self.characterLevel = m_min(m_max(main.defaultCharLevel or 1, 1), 100) + local defaultCharacterLevel = m_min(m_max(main.defaultCharLevel or 1, 1), 100) + ---@cast defaultCharacterLevel integer + self.characterLevel = defaultCharacterLevel self.targetVersion = liveTargetVersion self.bandit = "None" self.pantheonMajorGod = "None" @@ -142,11 +144,14 @@ function CompareEntryClass:LoadFromXML(xmlText) local partyActor = { Aura = {}, Curse = {}, Warcry = {}, Link = {}, modDB = new("ModDB"):ModDB(), output = {} } partyActor.modDB.actor = partyActor self.partyTab = { enemyModList = new("ModList"):ModList(), actor = partyActor } - self.configTab = new("ConfigTab"):ConfigTab(self) - self.itemsTab = new("ItemsTab"):ItemsTab(self) - self.treeTab = new("TreeTab"):TreeTab(self) - self.skillsTab = new("SkillsTab"):SkillsTab(self) - self.calcsTab = new("CalcsTab"):CalcsTab(self) + local selfAsBuild = self + ---@diagnostic disable-next-line: cast-type-mismatch + ---@cast selfAsBuild Build + self.configTab = new("ConfigTab"):ConfigTab(selfAsBuild) + self.itemsTab = new("ItemsTab"):ItemsTab(selfAsBuild) + self.treeTab = new("TreeTab"):TreeTab(selfAsBuild) + self.skillsTab = new("SkillsTab"):SkillsTab(selfAsBuild) + self.calcsTab = new("CalcsTab"):CalcsTab(selfAsBuild) -- Set up savers table self.savers = { @@ -225,12 +230,16 @@ function CompareEntryClass:LoadBuildSection(xml) if xml.attrib.viewMode then self.viewMode = xml.attrib.viewMode end - self.characterLevel = tonumber(xml.attrib.level) or 1 + local levelAttrib = tonumber(xml.attrib.level) or 1 + ---@cast levelAttrib integer + self.characterLevel = levelAttrib self.characterLevelAutoMode = xml.attrib.characterLevelAutoMode == "true" for _, diff in pairs({ "bandit", "pantheonMajorGod", "pantheonMinorGod" }) do self[diff] = xml.attrib[diff] or "None" end - self.mainSocketGroup = tonumber(xml.attrib.mainSkillIndex) or tonumber(xml.attrib.mainSocketGroup) or 1 + local mainSkillIndexAttrib = tonumber(xml.attrib.mainSkillIndex) or tonumber(xml.attrib.mainSocketGroup) or 1 + ---@cast mainSkillIndexAttrib integer + self.mainSocketGroup = mainSkillIndexAttrib wipeTable(self.spectreList) for _, child in ipairs(xml) do if child.elem == "Spectre" then @@ -588,6 +597,7 @@ do end if self.calcsTab.mainEnv.modDB:Flag(nil, "OmniscienceRequirements") then local omniSatisfy = self.calcsTab.mainEnv.modDB:Sum("INC", nil, "OmniAttributeRequirements") + ---@type number local highestAttribute = 0 for i, stat in ipairs({str, dex, int}) do if((stat or 0) > highestAttribute) then diff --git a/src/Classes/ComparePowerReportListControl.lua b/src/Classes/ComparePowerReportListControl.lua index 8a1ada07789..ec1bff6cab6 100644 --- a/src/Classes/ComparePowerReportListControl.lua +++ b/src/Classes/ComparePowerReportListControl.lua @@ -7,7 +7,21 @@ local t_insert = table.insert local t_sort = table.sort ----@class ComparePowerReportListControl: ListControl +---@class ComparePowerReportEntry +---@field category string +---@field categoryColor? string +---@field name string +---@field nameColor? string +---@field impact number +---@field impactStr? string +---@field combinedImpactStr? string +---@field pathDist? number +---@field perPoint number +---@field perPointStr? string +---@field nodeId? integer +---@field itemObj? Item + +---@class ComparePowerReportListControl: ListControl ---@field reportData table ---@field impactColumn? ListColumn
---@field lastTooltipIndex? integer @@ -63,7 +77,7 @@ function ComparePowerReportListClass:SetProgress(progress) end end ----@param viewPort Rect +---@param viewPort Viewport ---@param noTooltip? boolean function ComparePowerReportListClass:Draw(viewPort, noTooltip) if self.hoverIndex ~= self.lastTooltipIndex then diff --git a/src/Classes/CompareTab.lua b/src/Classes/CompareTab.lua index 6f0ad9b9fef..eeb348cab3a 100644 --- a/src/Classes/CompareTab.lua +++ b/src/Classes/CompareTab.lua @@ -1218,7 +1218,7 @@ end ---@param orderListField string ---@param setsField string ---@param activeIdField string ----@param control DropDownControl +---@param control DropDownControl function CompareTabClass:PopulateSetDropdown(tab, orderListField, setsField, activeIdField, control) local list = {} local orderList = tab[orderListField] @@ -1819,7 +1819,7 @@ end -- ============================================================ -- DRAW - Main render method -- ============================================================ ----@param viewPort Rect +---@param viewPort Viewport ---@param inputEvents InputEvent[] function CompareTabClass:Draw(viewPort, inputEvents) main:DrawBackground(viewPort) @@ -1886,6 +1886,7 @@ function CompareTabClass:Draw(viewPort, inputEvents) if compareEntry and mode ~= "TREE" and mode ~= "CALCS" then local topReserve = 0 local bottomReserve = 0 + ---@type number local contentHeight = 0 if mode == "SUMMARY" then topReserve = LAYOUT.summaryHeaderHeight @@ -1903,6 +1904,7 @@ function CompareTabClass:Draw(viewPort, inputEvents) contentHeight = self.skillsTotalContentHeight or 0 end local viewHeight = m_max(contentVP.height - topReserve - bottomReserve, 0) + ---@cast viewHeight number viewScrollBar.x = contentVP.x + contentVP.width - 18 viewScrollBar.y = contentVP.y + topReserve viewScrollBar.height = viewHeight @@ -2043,7 +2045,7 @@ end -- DRAW HELPERS -- ============================================================ ----@param viewPort Rect +---@param viewPort Viewport ---@param controls Control[] function CompareTabClass:DrawControlList(viewPort, controls) local noTooltip = function(control) @@ -2505,7 +2507,7 @@ function CompareTabClass:RefreshCalcsSkillControls(compareEntry) end -- Layout calcs skill detail controls into a two-column header area ----@param vp Rect +---@param vp Viewport ---@param compareEntry CompareEntry ---@return number headerHeight function CompareTabClass:LayoutCalcsSkillControls(vp, compareEntry) @@ -2631,6 +2633,7 @@ function CompareTabClass:HandleScrollInput(contentVP, inputEvents) self.scrollY = self.controls.viewScrollBar.offset inputEvents[id] = nil elseif event.key == "WHEELDOWN" and self.compareViewMode ~= "TREE" and not mouseOverViewScrollBar then + ---@type number local maxScroll = 0 local viewportH = contentVP.height if self.compareViewMode == "CONFIG" and self.configTotalContentHeight then @@ -2850,6 +2853,7 @@ local function formatImpact(impact) local str = color .. sign .. numStr -- Compute percentage change + ---@type number local percent = 0 if baseStatValue ~= 0 then percent = (impact / math.abs(baseStatValue)) * 100 @@ -3374,7 +3378,7 @@ end -- ============================================================ -- SUMMARY VIEW -- ============================================================ ----@param vp Rect +---@param vp Viewport ---@param compareEntry CompareEntry function CompareTabClass:DrawSummary(vp, compareEntry) local primaryCalcs = self.primaryBuild.calcsTab @@ -3412,6 +3416,7 @@ function CompareTabClass:DrawSummary(vp, compareEntry) local minCol3R = col2R + compareNameW + 16 local maxCol3R = vp.width - 200 local col3R = m_min(m_max(LAYOUT.summaryCol3Right, minCol3R), maxCol3R) + ---@cast col3R number local col4 = col3R + 20 SetViewport(vp.x, vp.y, vp.width, headerReserve) @@ -3611,10 +3616,12 @@ function CompareTabClass:DrawStatList(drawY, displayStats, primaryOutput, compar elseif statData.label and statData.condFunc then -- Label-only stat (e.g. "Chaos Resistance: Immune") local labelColor = statData.color or "^7" - if statData.condFunc(primaryOutput) or statData.condFunc(compareOutput) then + ---@type fun(output: Output): boolean + local labelCondFunc = statData.condFunc + if labelCondFunc(primaryOutput) or labelCondFunc(compareOutput) then local valStr = statData.val or "" - local primaryShown = statData.condFunc(primaryOutput) - local compareShown = statData.condFunc(compareOutput) + local primaryShown = labelCondFunc(primaryOutput) + local compareShown = labelCondFunc(compareOutput) DrawString(col1, drawY, "LEFT", lineHeight, "VAR", labelColor .. statData.label) DrawString(col2R, drawY, "RIGHT_X", lineHeight, "VAR", "^7" .. (primaryShown and valStr or "-")) DrawString(col3R, drawY, "RIGHT_X", lineHeight, "VAR", colorCodes.WARNING .. (compareShown and valStr or "-")) @@ -3628,7 +3635,7 @@ end -- ============================================================ -- TREE VIEW (overlay + side-by-side) -- ============================================================ ----@param vp Rect +---@param vp Viewport ---@param inputEvents InputEvent[] ---@param compareEntry CompareEntry function CompareTabClass:DrawTree(vp, inputEvents, compareEntry) @@ -3708,7 +3715,10 @@ function CompareTabClass:DrawTree(vp, inputEvents, compareEntry) return x - rightAbsX, y - rightAbsY end local rightTreeVP = { x = 0, y = 0, width = halfWidth, height = treeHeight } - compareEntry.treeTab.viewer:Draw(compareEntry, rightTreeVP, leftHasInput and {} or inputEvents) + local compareEntryAsBuild = compareEntry + ---@diagnostic disable-next-line: cast-type-mismatch + ---@cast compareEntryAsBuild Build + compareEntry.treeTab.viewer:Draw(compareEntryAsBuild, rightTreeVP, leftHasInput and {} or inputEvents) SetViewport() end @@ -3938,7 +3948,7 @@ function CompareTabClass:AddAbyssSockets(comparison, destTable, requireBothSides end end ----@param vp Rect +---@param vp Viewport ---@param compareEntry CompareEntry ---@param inputEvents InputEvent[] function CompareTabClass:DrawItems(vp, compareEntry, inputEvents) @@ -3973,6 +3983,7 @@ function CompareTabClass:DrawItems(vp, compareEntry, inputEvents) local primaryContentW if self.itemsExpandedMode then -- Expanded mode: measure the widest rendered line of every primary item card + ---@type number local widest = 0 local pItems = self.primaryBuild.itemsTab and self.primaryBuild.itemsTab.items local pSlots = self.primaryBuild.itemsTab and self.primaryBuild.itemsTab.slots @@ -4049,7 +4060,9 @@ local function measureDiff(pItem, cItem) cursorX = cursorX - vp.x cursorY = cursorY - (vp.y + checkboxOffset) local hoverItem = nil + ---@type number, number local hoverX, hoverY = 0, 0 + ---@type number, number local hoverW, hoverH = 0, 0 local hoverItemsTab = nil @@ -4062,7 +4075,9 @@ local function measureDiff(pItem, cItem) -- Track Equip button hover for stat comparison tooltip local hoverEquipItem = nil local hoverEquipSlotName = nil + ---@type number, number local hoverEquipBtnX, hoverEquipBtnY = 0, 0 + ---@type number, number local hoverEquipBtnW, hoverEquipBtnH = 0, 0 -- Helper: process copy/buy button hover state and click events for a slot. @@ -4316,7 +4331,7 @@ end -- ============================================================ -- SKILLS VIEW -- ============================================================ ----@param vp Rect +---@param vp Viewport ---@param compareEntry CompareEntry function CompareTabClass:DrawSkills(vp, compareEntry) local lineHeight = 18 @@ -4676,7 +4691,9 @@ local function drawGroupHeader(skillsTab, group, idx, x, y) DrawString(textX, y, "LEFT", 18, "VAR", "^7" .. getGroupLabel(group, idx)) end + ---@type table local displayListsByPair = {} + ---@type number local maxPrimaryW = 0 for idx, pair in ipairs(renderPairs) do local pSet = pair.pIdx and pSets[pair.pIdx] or {} @@ -4823,7 +4840,7 @@ end ---@param rowY number ---@param rowW number ---@param rowH number ----@param vp Rect +---@param vp Viewport ---@param compareEntry CompareEntry function CompareTabClass:DrawCalcsTooltip(colData, rowLabel, rowX, rowY, rowW, rowH, vp, compareEntry) local primaryLabel = self:GetShortBuildName(self.primaryBuild.buildName) @@ -4838,7 +4855,7 @@ end -- ============================================================ -- Draw the skill detail header area with labels for controls and text info lines ----@param vp Rect +---@param vp Viewport ---@param compareEntry CompareEntry ---@param headerHeight number ---@param primaryEnv table @@ -5033,7 +5050,7 @@ local function subSectionExtraMatches(subSecData, primaryActor, compareActor) return primaryExtraOk and compareExtraOk and tostring(primaryExtraText or "") == tostring(compareExtraText or "") end ----@param vp Rect +---@param vp Viewport ---@param compareEntry CompareEntry function CompareTabClass:DrawCalcs(vp, compareEntry) -- Use calcsEnv for both values and tooltips (has breakdown data + respects Calcs skill selection) @@ -5059,6 +5076,7 @@ function CompareTabClass:DrawCalcs(vp, compareEntry) -- Card dimensions -- Layout: [2px border | 130px label | 2px gap | 2px sep | valW | 2px sep | valW | 2px border] local cardWidth = m_min(LAYOUT.calcsMaxCardWidth, gridWidth - 16) + ---@cast cardWidth number local labelWidth = LAYOUT.calcsLabelWidth local sepW = LAYOUT.calcsSepW local valColWidth = m_floor((cardWidth - 140) / 2) @@ -5287,7 +5305,7 @@ end -- ============================================================ -- CONFIG VIEW -- ============================================================ ----@param vp Rect +---@param vp Viewport ---@param compareEntry CompareEntry ---@param headerOnly boolean? function CompareTabClass:DrawConfig(vp, compareEntry, headerOnly) diff --git a/src/Classes/ConfigSetListControl.lua b/src/Classes/ConfigSetListControl.lua index 3113cdd44ab..45cf0c5a57c 100644 --- a/src/Classes/ConfigSetListControl.lua +++ b/src/Classes/ConfigSetListControl.lua @@ -7,7 +7,7 @@ local t_insert = table.insert local t_remove = table.remove local m_max = math.max ----@class ConfigSetListControl: ListControl +---@class ConfigSetListControl: ListControl ---@field configTab ConfigTab ---@field selIndex? integer ---@field selValue? integer diff --git a/src/Classes/ConfigTab.lua b/src/Classes/ConfigTab.lua index 40bd825ea87..ca83bd1c5cd 100644 --- a/src/Classes/ConfigTab.lua +++ b/src/Classes/ConfigTab.lua @@ -94,7 +94,7 @@ function CustomModBlockClass:CustomModBlockControl(anchor, rect, configTab, bloc self.controls.textEdit.inactiveText = function(val) local inactiveText = "" - for line in val:gmatch("([^\n]*)\n?") do + for line in (val or ""):gmatch("([^\n]*)\n?") do local strippedLine = StripEscapes(line):match("^%s*(.-)%s*$") local mods, extra = modLib.parseMod(strippedLine) inactiveText = inactiveText .. ((mods and not extra) and colorCodes.MAGIC or colorCodes.UNSUPPORTED) .. (IsKeyDown("ALT") and strippedLine or line) .. "\n" @@ -133,7 +133,7 @@ function CustomModBlockClass:OnKeyDown(key, doubleClick) end end ----@param viewPort Rect +---@param viewPort Viewport function CustomModBlockClass:Draw(viewPort) if not self:IsShown() then return @@ -145,7 +145,7 @@ end ---@alias ConfigValue string|number|boolean ---@alias ConfigConditionalOption string|integer ----@class ConfigSet +---@class (partial) ConfigSet ---@field id integer ---@field title? string ---@field input table @@ -757,9 +757,17 @@ function ConfigTabClass:ConfigTab(build) end local innerShown = control.shown + ---@param prop Prop + ---@return boolean + local function resolveInnerShown(prop) + if type(prop) == "boolean" then + return prop + end + return prop() + end if not varData.doNotHighlight then control.borderFunc = function() - local shown = type(innerShown) == "boolean" and innerShown or innerShown() + local shown = resolveInnerShown(innerShown) local cur = self.configSets[self.activeConfigSetId].input[varData.var] local def = self:GetDefaultState(varData.var, type(cur)) if cur ~= nil and cur ~= def then @@ -777,14 +785,14 @@ function ConfigTabClass:ConfigTab(build) if not searchMatch(varData) then return false end - local shown = type(innerShown) == "boolean" and innerShown or innerShown() + local shown = resolveInnerShown(innerShown) local cur = self.configSets[self.activeConfigSetId].input[varData.var] local def = self:GetDefaultState(varData.var, type(cur)) return not shown and cur ~= nil and cur ~= def or shown end local innerLabel = labelControl.label labelControl.label = function() - local shown = type(innerShown) == "boolean" and innerShown or innerShown() + local shown = resolveInnerShown(innerShown) local cur = self.configSets[self.activeConfigSetId].input[varData.var] local def = self:GetDefaultState(varData.var, type(cur)) if not shown and cur ~= nil and cur ~= def then @@ -807,7 +815,7 @@ function ConfigTabClass:ConfigTab(build) end end - local shown = type(innerShown) == "boolean" and innerShown or innerShown() + local shown = resolveInnerShown(innerShown) local inputs = self.configSets[self.activeConfigSetId].input local cur = inputs[varData.var] local def = self:GetDefaultState(varData.var, type(cur)) @@ -868,7 +876,7 @@ function ConfigTabClass:ConfigTab(build) if isCollapsed(ownSection) then return false end - return type(eligibleShown) == "boolean" and eligibleShown or eligibleShown() + return resolveInnerShown(eligibleShown) end t_insert(self.controls, control) @@ -1096,7 +1104,7 @@ function ConfigTabClass:UpdateControls() self:UpdateCustomModsControls() end ----@param viewPort Rect +---@param viewPort Viewport ---@param inputEvents InputEvent[] function ConfigTabClass:Draw(viewPort, inputEvents) self.x = viewPort.x @@ -1195,10 +1203,14 @@ end function ConfigTabClass:UpdateLevel() local input = self.configSets[self.activeConfigSetId].input local placeholder = self.configSets[self.activeConfigSetId].placeholder - if input.enemyLevel and input.enemyLevel > 0 then - self.enemyLevel = m_min(data.misc.MaxEnemyLevel, input.enemyLevel) - elseif placeholder.enemyLevel and placeholder.enemyLevel > 0 then - self.enemyLevel = m_min(data.misc.MaxEnemyLevel, placeholder.enemyLevel) + local inputEnemyLevel = input.enemyLevel + local placeholderEnemyLevel = placeholder.enemyLevel + if inputEnemyLevel and inputEnemyLevel > 0 then + ---@cast inputEnemyLevel integer + self.enemyLevel = m_min(data.misc.MaxEnemyLevel, inputEnemyLevel) + elseif placeholderEnemyLevel and placeholderEnemyLevel > 0 then + ---@cast placeholderEnemyLevel integer + self.enemyLevel = m_min(data.misc.MaxEnemyLevel, placeholderEnemyLevel) else self.enemyLevel = m_min(data.misc.MaxEnemyLevel, self.build.characterLevel) end diff --git a/src/Classes/Control.lua b/src/Classes/Control.lua index 5655ef6f619..0301eb18677 100644 --- a/src/Classes/Control.lua +++ b/src/Classes/Control.lua @@ -42,6 +42,7 @@ local rect = { ---@field tabOrder? Control[] ---@field OnFocusGained? fun() ---@field OnFocusLost? fun() +---@field OnKeyDown? fun(...: any): any ---@field shown Prop ---@field x Prop? ---@field y Prop? @@ -51,9 +52,15 @@ local rect = { ---@field collapseX number? An additional offset which is applied when this control uses a collapsed anchor. local ControlClass = newClass("Control") ----@alias Anchor [AnchorPoint, Control|ControlHost, AnchorPoint, boolean|nil] +---@alias Anchor [AnchorPoint, (Control|ControlHost)?, AnchorPoint, boolean|nil] ---@alias Rect [Prop?, Prop?, Prop?, Prop?] +---@class Viewport A resolved, absolute drawing region, as passed to :Draw() methods (distinct from the positional Rect tuple used for anchoring). +---@field x number +---@field y number +---@field width number +---@field height number + ---@class AnchorState ---@field point? AnchorPoint ---@field other? Control|ControlHost @@ -76,8 +83,7 @@ function ControlClass:Control(anchor, rect) return self end ----@generic T ----@alias Prop (fun(self: self): T) | T +---@alias Prop (fun(...: any): T) | T ---@param name string ---@return unknown value @@ -119,7 +125,9 @@ function ControlClass:GetPos() local y = self:GetProperty("y") if self.anchor.other then local otherX, otherY = self.anchor.other:GetPos() + ---@type number, number local otherW, otherH = 0, 0 + ---@type number, number local width, height = 0, 0 local otherPos = anchorPos[self.anchor.otherPoint] assert(otherPos, "invalid anchor position '"..tostring(self.anchor.otherPoint).."'") diff --git a/src/Classes/ControlHost.lua b/src/Classes/ControlHost.lua index ca35325c8d1..263ad4856db 100644 --- a/src/Classes/ControlHost.lua +++ b/src/Classes/ControlHost.lua @@ -57,7 +57,7 @@ end ---@field doubleClick? boolean ---@param inputEvents InputEvent[] ----@param viewPort Rect +---@param viewPort Viewport function ControlHostClass:ProcessControlsInput(inputEvents, viewPort) local processedImbuedControl for id, event in ipairs(inputEvents) do @@ -118,8 +118,8 @@ function ControlHostClass:ProcessControlsInput(inputEvents, viewPort) end end ----@param viewPort Rect ----@param selControl? Control +---@param viewPort Viewport +---@param selControl? Control|false 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 caccbf5e85d..1f3cdabfd46 100644 --- a/src/Classes/DraggerControl.lua +++ b/src/Classes/DraggerControl.lua @@ -3,15 +3,15 @@ -- Class: Dragger Button Control -- Dragger button control. -- ----@class CursorPosition +---@class DraggerPosition ---@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 onKeyDown? fun(position: DraggerPosition) +---@field onKeyUp? fun(offset: DraggerPosition) +---@field onRightClick? fun(position: DraggerPosition) ---@field onHover? fun(): Control? ---@field forceTooltip? boolean ---@field image? ImageHandle @@ -24,9 +24,9 @@ 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 onKeyDown? fun(position: DraggerPosition) +---@param onKeyUp? fun(offset: DraggerPosition) +---@param onRightClick? fun(position: DraggerPosition) ---@param onHover? fun(): Control? ---@param forceTooltip? boolean ---@return DraggerControl @@ -62,7 +62,7 @@ function DraggerClass:IsMouseOver() return self:IsMouseInBounds() end ----@param viewPort Rect +---@param viewPort Viewport ---@param noTooltip? boolean ---@return Control? function DraggerClass:Draw(viewPort, noTooltip) diff --git a/src/Classes/DropDownControl.lua b/src/Classes/DropDownControl.lua index ea601f783f0..f983d6f88ed 100644 --- a/src/Classes/DropDownControl.lua +++ b/src/Classes/DropDownControl.lua @@ -22,12 +22,13 @@ local m_floor = math.floor ---@field hoverSel? integer ---@field hoverSelDrop? integer ---@field tag? string +---@overload fun(mix: table): DropDownControl local DropDownClass = newClass("DropDownControl", "Control", "ControlHost", "TooltipHost", "SearchHost") ---@generic T ---@param anchor? Anchor ---@param rect? Rect ----@param list T[] +---@param list? T[] ---@param selFunc? fun(index: integer, data: T, doubleClick?: boolean) ---@param tooltipText? Prop ---@param ignoreSearchOrder? boolean @@ -51,7 +52,7 @@ function DropDownClass:DropDownControl(anchor, rect, list, selFunc, tooltipText, return StripEscapes(listVal.label) end end - return StripEscapes(listVal) + return StripEscapes(tostring(listVal)) end, ignoreSearchOrder ) @@ -145,7 +146,9 @@ end ---@param height number function DropDownClass:DrawSearchHighlights(label, searchInfo, x, y, width, height) if searchInfo and searchInfo.matches then + ---@type number local startX = 0 + ---@type number local endX = 0 local last = 0 SetDrawColor(1, 1, 0, 0.2) @@ -165,7 +168,7 @@ function DropDownClass:DrawSearchHighlights(label, searchInfo, x, y, width, heig end end ----@param value T +---@param value unknown ---@param key? string function DropDownClass:SelByValue(value, key) for index, listVal in ipairs(self.list) do @@ -186,17 +189,22 @@ end ---@param key string ---@return T? function DropDownClass:GetSelValueByKey(key) + ---@cast self.selIndex integer return self.list[self.selIndex][key] end ---@return T? function DropDownClass:GetSelValue() + ---@cast self.selIndex integer return self.list[self.selIndex] end ---@param newSel integer? ---@param noCallSelFunc? boolean function DropDownClass:SetSel(newSel, noCallSelFunc) + if not newSel then + return + end newSel = m_max(1, m_min(self:GetDropCount(), newSel)) newSel = self:DropIndexToListIndex(newSel) if newSel and newSel ~= self.selIndex then @@ -210,6 +218,7 @@ end function DropDownClass:ScrollSelIntoView() local width, height = self:GetSize() local scrollBar = self.controls.scrollBar + ---@cast self.selIndex integer scrollBar:SetContentDimension((height - 4) * self:GetDropCount(), self.dropHeight) scrollBar:ScrollIntoView((self:ListIndexToDropIndex(self.selIndex, 1) - 2) * (height - 4), 3 * (height - 4)) end @@ -229,6 +238,7 @@ function DropDownClass:IsMouseOver() local mOver if self.dropped then + ---@cast self.droppedWidth number width = m_max(width, self.droppedWidth) if self.dropUp then mOver = cursorX >= x and cursorY >= y - dropExtra and cursorX < x + width and cursorY < y + height @@ -249,7 +259,7 @@ function DropDownClass:IsMouseOver() return mOver, mOverComp end ----@param viewPort Rect +---@param viewPort Viewport ---@param noTooltip? boolean function DropDownClass:Draw(viewPort, noTooltip) local x, y = self:GetPos() @@ -388,6 +398,7 @@ function DropDownClass:Draw(viewPort, noTooltip) if self.dropped then SetDrawLayer(nil, 5) self:DrawControls(viewPort) + ---@cast self.droppedWidth number width = self.droppedWidth -- draw tooltip for hovered item @@ -436,7 +447,7 @@ function DropDownClass:Draw(viewPort, noTooltip) else label = listVal end - DrawString(0, y, "LEFT", lineHeight, "VAR", label) + DrawString(0, y, "LEFT", lineHeight, "VAR", label or "") if detail ~= nil then local detail = listVal.detail local dx = DrawStringWidth(lineHeight, "VAR", detail) @@ -454,7 +465,7 @@ function DropDownClass:Draw(viewPort, noTooltip) end DrawString(width - dx - 4 - 22, y, "LEFT", lineHeight, "VAR", detail) end - self:DrawSearchHighlights(label, searchInfo, 0, y, width - 4, lineHeight) + self:DrawSearchHighlights(label or "", searchInfo, 0, y, width - 4, lineHeight) end end SetDrawColor(1, 1, 1) @@ -588,6 +599,7 @@ end ---@param enable boolean function DropDownClass:CheckDroppedWidth(enable) self.enableDroppedWidth = enable + ---@cast self.maxDroppedWidth number if self.enableDroppedWidth and self.list then local scrollWidth = 0 if self.dropped and self.controls.scrollBar.enabled then @@ -602,7 +614,7 @@ function DropDownClass:CheckDroppedWidth(enable) line = line.label or "" end -- +10 to stop clipping - dWidth = m_max(dWidth, DrawStringWidth(lineHeight, "VAR", line) + 10) + dWidth = m_max(dWidth, DrawStringWidth(lineHeight, "VAR", line or "") + 10) end -- no greater than self.maxDroppedWidth self.droppedWidth = m_min(dWidth + scrollWidth, self.maxDroppedWidth) diff --git a/src/Classes/EditControl.lua b/src/Classes/EditControl.lua index 5024ea4e0a8..f51db1f05c3 100644 --- a/src/Classes/EditControl.lua +++ b/src/Classes/EditControl.lua @@ -174,7 +174,10 @@ end ---@return string function EditClass:GetSelText() + ---@cast self.sel integer + ---@type integer local left = m_min(self.caret, self.sel) + ---@type integer local right = m_max(self.caret, self.sel) local newBuf = self.buf:sub(left, right - 1) return newBuf @@ -186,7 +189,10 @@ function EditClass:ReplaceSel(text) if text:match(self.filterPattern) then return end + ---@cast self.sel integer + ---@type integer local left = m_min(self.caret, self.sel) + ---@type integer local right = m_max(self.caret, self.sel) local newBuf = self.buf:sub(1, left - 1) .. text .. self.buf:sub(right) if self.limit and #newBuf > self.limit then @@ -226,7 +232,7 @@ function EditClass:Insert(text) self:AddUndoState() end ----@param zoom number +---@param zoom "+"|"-"|"0" function EditClass:ZoomText(zoom) if not self.allowZoom or not self.lineHeight then return @@ -274,8 +280,9 @@ function EditClass:ScrollCaretIntoView() end end ----@param offset integer +---@param offset number function EditClass:MoveCaretVertically(offset) + ---@cast self.lineHeight number local pre = self.buf:sub(1, self.caret - 1) local caretX = DrawStringWidth(self.lineHeight, self.font, lastLine(pre)) local caretY = newlineCount(pre) * self.lineHeight @@ -285,7 +292,7 @@ function EditClass:MoveCaretVertically(offset) self.blinkStart = GetTime() end ----@param viewPort Rect +---@param viewPort Viewport ---@param noTooltip? boolean function EditClass:Draw(viewPort, noTooltip) local x, y = self:GetPos() @@ -315,7 +322,9 @@ function EditClass:Draw(viewPort, noTooltip) SetDrawColor(0, 0, 0) end DrawImage(nil, x + 1, y + 1, width - 2, height - 2) + ---@type number local textX = x + 2 + ---@type number local textY = y + 2 local textHeight = self.lineHeight or (height - 4) if self.prompt then @@ -370,13 +379,20 @@ function EditClass:Draw(viewPort, noTooltip) end textX = -self.controls.scrollBarH.offset textY = -self.controls.scrollBarV.offset + ---@cast textX number + ---@cast textY number if self.lineHeight then local left = m_min(self.caret, self.sel or self.caret) local right = m_max(self.caret, self.sel or self.caret) - local caretX + local caretX, caretY SetDrawColor(self.textCol) for s, line, e in (self.buf.."\n"):gmatch("()([^\n]*)\n()") do + ---@diagnostic disable-next-line: cast-type-mismatch + ---@cast s integer + ---@diagnostic disable-next-line: cast-type-mismatch + ---@cast e integer textX = -self.controls.scrollBarH.offset + ---@cast textX number if left >= e or right <= s then DrawString(textX, textY, "LEFT", textHeight, self.font, line) end diff --git a/src/Classes/ExtBuildListControl.lua b/src/Classes/ExtBuildListControl.lua index 1e97fdc8555..d3918311c81 100644 --- a/src/Classes/ExtBuildListControl.lua +++ b/src/Classes/ExtBuildListControl.lua @@ -288,7 +288,7 @@ function ExtBuildListControlClass:DrawString(left, top, align, height, font, tex end end ----@param viewPort Rect +---@param viewPort Viewport ---@param noTooltip? boolean function ExtBuildListControlClass:Draw(viewPort, noTooltip) if self.activeListProvider == nil then diff --git a/src/Classes/FolderListControl.lua b/src/Classes/FolderListControl.lua index 8b87f4a8917..4ee34ea5458 100644 --- a/src/Classes/FolderListControl.lua +++ b/src/Classes/FolderListControl.lua @@ -11,7 +11,7 @@ local t_insert = table.insert ---@field fullFileName string ---@field modified integer ----@class FolderListControl: ListControl +---@class FolderListControl: ListControl ---@field subPath string ---@field sortMode "NAME"|"EDITED" ---@field onChangeCallback? fun(newSubPath: string) diff --git a/src/Classes/GemSelectControl.lua b/src/Classes/GemSelectControl.lua index b423483308c..aaa8ef46854 100644 --- a/src/Classes/GemSelectControl.lua +++ b/src/Classes/GemSelectControl.lua @@ -526,7 +526,7 @@ function GemSelectClass:IsMouseOver() return mOver, mOverComp end ----@param viewPort Rect +---@param viewPort Viewport ---@param noTooltip? boolean function GemSelectClass:Draw(viewPort, noTooltip) self.sortPercentage = self.sortPercentage or "" diff --git a/src/Classes/ImportTab.lua b/src/Classes/ImportTab.lua index f674a8a73fc..0823e781f27 100644 --- a/src/Classes/ImportTab.lua +++ b/src/Classes/ImportTab.lua @@ -268,11 +268,15 @@ local function addOAuthControls(self) self.controls.charImportTree = new("ButtonControl"):ButtonControl({ "LEFT", self.controls.charImportHeader, "RIGHT" }, { labelSpacing, 0, 170, 20 }, "Passive Tree and Jewels", function() local realm = self.controls.accountRealm:GetSelValue() + ---@cast realm RealmInfo local league = self.controls.charSelectLeague:GetSelValue() + ---@cast league string local selectedName = self.controls.charSelect:GetSelValue().label + ---@cast selectedName string saveDetails(realm.id, league, selectedName) local deleteJewels = self.controls.charImportTreeClearJewels.state + ---@cast deleteJewels boolean ---@param data table? ---@param errMsg string? local function importHandler(data, errMsg) @@ -307,14 +311,18 @@ local function addOAuthControls(self) self.controls.charImportItems = new("ButtonControl"):ButtonControl({ "TOPLEFT", self.controls.charImportTree, "BOTTOMLEFT" }, { 0, rowSpacing, 110, 20 }, "Items and Skills", function() local realm = self.controls.accountRealm:GetSelValue() + ---@cast realm RealmInfo local league = self.controls.charSelectLeague:GetSelValue() + ---@cast league string local selectedName = self.controls.charSelect:GetSelValue().label + ---@cast selectedName string saveDetails(realm.id, league, selectedName) self.oauthLoading = true main.api:DownloadCharacter(realm.realmCode, selectedName, function(data, errMsg) local clearItems = self.controls.charImportItemsClearItems.state + ---@cast clearItems boolean local clearSkills = self.controls.charImportItemsClearSkills.state local ignoreWeaponSwap = self.controls.charImportItemsIgnoreWeaponSwap.state if data and data.character then @@ -385,6 +393,7 @@ local function addAccountNameControls(self) self.controls.siteAccountNameGo = new("ButtonControl"):ButtonControl({ "LEFT", self.controls.siteAccountName, "RIGHT" }, { 8, 0, 60, 20 }, "Start", function() local realm = self.controls.siteAccountRealm:GetSelValue() + ---@cast realm RealmInfo self:DownloadSiteCharacterList(realm) end) self.controls.siteAccountNameGo.enabled = function() @@ -439,6 +448,7 @@ local function addAccountNameControls(self) self.controls.siteCharSelectLeague = new("DropDownControl"):DropDownControl({ "LEFT", self.controls.siteCharSelectLeagueLabel, "RIGHT" }, { 4, 0, 150, 18 }, nil, function(index, value) local realm = self.controls.siteAccountRealm:GetSelValue() + ---@cast realm RealmInfo self:BuildCharacterList(realm.realmCode, value.league, self.lastCharList, self.controls.siteCharSelect) end) self.controls.siteCharSelect = new("DropDownControl"):DropDownControl({ "TOPLEFT", self.controls.siteCharSelectHeader, "BOTTOMLEFT" }, @@ -451,6 +461,7 @@ local function addAccountNameControls(self) self.controls.siteCharImportTree = new("ButtonControl"):ButtonControl({ "LEFT", self.controls.siteCharImportHeader, "RIGHT" }, { 8, 0, 170, 20 }, "Passive Tree and Jewels", function() local realm = self.controls.siteAccountRealm:GetSelValue() + ---@cast realm RealmInfo if self.build.spec:CountAllocNodes() > 0 then main:OpenConfirmPopup("Character Import", "Importing the passive tree will overwrite your current tree.", "Import", function() @@ -469,6 +480,7 @@ local function addAccountNameControls(self) self.controls.siteCharImportItems = new("ButtonControl"):ButtonControl({ "LEFT", self.controls.siteCharImportTree, "LEFT" }, { 0, 36, 110, 20 }, "Items and Skills", function() local realm = self.controls.siteAccountRealm:GetSelValue() + ---@cast realm RealmInfo self:DownloadItems(realm) self:SetPredefinedBuildName() end) @@ -564,6 +576,7 @@ function ImportTabClass:ImportTab(build) self.controls.exportFrom:SelByValue(self.exportWebsiteSelected or main.lastExportWebsite or "Pastebin", "id") self.controls.generateCodeByLink = new("ButtonControl"):ButtonControl({ "LEFT", self.controls.exportFrom, "RIGHT" }, { 8, 0, 100, 20 }, "Share", function() local exportWebsite = exportWebsitesList[self.controls.exportFrom.selIndex] + if not exportWebsite then return end local subScriptId = buildSites.UploadBuild(self.controls.generateCodeOut.buf, exportWebsite) if subScriptId then self.controls.generateCodeOut:SetText("") @@ -801,7 +814,7 @@ 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 viewPort Viewport ---@param inputEvents InputEvent[] function ImportTabClass:Draw(viewPort, inputEvents) self.x = viewPort.x @@ -849,6 +862,7 @@ function ImportTabClass:DownloadPassiveTree(realm) self.charImportMode = "IMPORTING" self.charImportStatus = "Retrieving character passive tree..." local accountName = self.controls.siteAccountName.buf + ---@cast accountName string local charSelect = self.controls.siteCharSelect local charListData = charSelect.list[charSelect.selIndex].char launch:DownloadPage( @@ -879,6 +893,7 @@ function ImportTabClass:DownloadPassiveTree(realm) charData.passives = responseLua charData.jewels = responseLua.items local deleteJewels = self.controls.siteCharImportTreeClearJewels.state + ---@cast deleteJewels boolean self:ImportPassiveTreeAndJewels(charData, deleteJewels) end) end @@ -888,6 +903,7 @@ function ImportTabClass:DownloadItems(realm) self.charImportMode = "IMPORTING" self.charImportStatus = "Retrieving character items..." local accountName = self.controls.siteAccountName.buf + ---@cast accountName string local charSelect = self.controls.siteCharSelect local charListData = charSelect.list[charSelect.selIndex].char launch:DownloadPage( @@ -914,6 +930,7 @@ function ImportTabClass:DownloadItems(realm) charData.equipment = responseLua.items charData.guardian = responseLua.guardian local clearItems = self.controls.siteCharImportItemsClearItems.state + ---@cast clearItems boolean local clearSkills = self.controls.siteCharImportItemsClearSkills.state local ignoreWeaponSwap = self.controls.siteCharImportItemsIgnoreWeaponSwap.state self:ImportItemsAndSkills(charData, clearItems, clearSkills, ignoreWeaponSwap) @@ -942,6 +959,7 @@ function ImportTabClass:DownloadSiteCharacterList(realm) self.charImportMode = "DOWNLOADCHARLIST" self.charImportStatus = "Retrieving character list..." + ---@type string local accountName -- Handle spaces in the account name if realm.realmCode == "pc" then @@ -1059,7 +1077,7 @@ function ImportTabClass:DownloadSiteCharacterList(realm) end --- @param realm string ---- @param league string +--- @param league? string --- @param characters table? --- @param control table function ImportTabClass:BuildCharacterList(realm, league, characters, control) @@ -1267,7 +1285,7 @@ function ImportTabClass:ImportPassiveTreeAndJewels(charData, deleteJewels) end self.build.configTab.varControls["resistancePenalty"]:SetSel(resistancePenaltyIndex) - ---@param dropdown DropDownControl + ---@param dropdown DropDownControl ---@param val string local function setSelByVal(dropdown, val) for i, v in ipairs(dropdown.list) do diff --git a/src/Classes/Item.lua b/src/Classes/Item.lua index 2fb75b4e768..467c5a7f7b6 100644 --- a/src/Classes/Item.lua +++ b/src/Classes/Item.lua @@ -28,9 +28,9 @@ local catalystTags = { { "critical" }, } ----@param catalystId string ----@param mod Mod ----@param quality number +---@param catalystId? integer +---@param mod ModLine +---@param quality? number ---@return number local function getCatalystScalar(catalystId, mod, quality) if mod.unscalable then @@ -65,8 +65,8 @@ local function getCatalystScalar(catalystId, mod, quality) return 1 end ----@param line ModLine ----@return ModLine +---@param line string +---@return string local function normaliseModLine(line) return line:gsub("%d+%.?%d*", "#") :gsub("%(%-?#%-#%)", "#"):lower() @@ -95,6 +95,71 @@ end local influenceInfo = itemLib.influenceInfo.all +---@class ItemDataMod An item modifier definition sourced from static data (crucible/veiled/necropolis/master mod tables); distinct from the runtime calc-engine `Mod` class. +---@field [integer] string The mod's text lines +---@field type string +---@field affix? string +---@field statOrder integer[] +---@field level integer +---@field group string +---@field weightKey string[] +---@field weightVal number[] +---@field modTags string[] +---@field types? table +---@field nodeLocation? integer[] +---@field nodeType? string +---@field tier? integer +---@field tradeHashes? table + +---@class Conqueror +---@field type string +---@field id string|integer + +---@class ConqueredBy +---@field id integer|string +---@field conqueror Conqueror +---@field modification? table + +---@class ClusterJewelSkill +---@field name string +---@field icon string +---@field tag string +---@field stats string[] +---@field enchant string[] + +---@class ClusterJewelInfo Static data describing a cluster jewel's structure, stored on the `Item` as `item.clusterJewel`. +---@field size string +---@field sizeIndex integer +---@field minNodes integer +---@field maxNodes integer +---@field smallIndicies integer[] +---@field notableIndicies integer[] +---@field socketIndicies integer[] +---@field totalIndicies integer +---@field skills table + +---@class JewelData A timeless/cluster jewel's computed data, stored on the `Item` as `item.jewelData`. +---@field conqueredBy? ConqueredBy +---@field funcList? table +---@field impossibleEscapeKeystone? unknown +---@field impossibleEscapeKeystones? table +---@field intuitiveLeapKeystoneOnly? boolean +---@field intuitiveLeapLike? boolean +---@field limitDisabled? boolean +---@field radiusIndex? integer +---@field clusterJewelValid? boolean +---@field clusterJewelSkill? string +---@field clusterJewelKeystone? unknown +---@field clusterJewelNotables string[] +---@field clusterJewelAddedMods string[] +---@field clusterJewelIncEffect? number +---@field clusterJewelNodeCount? integer +---@field clusterJewelNothingnessCount? integer +---@field clusterJewelSmallsAreNothingness? boolean +---@field clusterJewelSocketCount? integer +---@field clusterJewelSocketCountOverride? integer +---@field jewelIncEffectFromClassStart? number + ---@class Item ---@field raw string ---@field rawLines string[] @@ -102,7 +167,7 @@ local influenceInfo = itemLib.influenceInfo.all ---@field namePrefix string ---@field nameSuffix string ---@field rarity string ----@field base? ItemBaseEntry +---@field base? ItemBase ---@field baseName string ---@field itemLevel integer ---@field quality integer @@ -123,14 +188,21 @@ local influenceInfo = itemLib.influenceInfo.all ---@field crucibleModLines ModLine[] ---@field buffModLines ModLine[] ---@field modMagnitudeMods table ----@field variantList? table ----@field versionList? table +---@field affixes? table +---@field jewelData? JewelData +---@field clusterJewel? ClusterJewelInfo +---@field clusterJewelSkill? string +---@field variantList? string[] +---@field versionList? string[] ---@field baseModList ModList ---@field modList ModList ---@field slotModList table ---@field variantGroupSelections table ---@field sockets table[] ---@field isUnique boolean +---@field affixLimit number +---@field abyssalSocketCount number +---@field selectableSocketCount number local ItemClass = newClass("Item") ---@param raw? string @@ -466,7 +538,7 @@ function ItemClass:NormaliseVariantSelections() end end ----@class ModLine A modifier line on an item. An in-game mod can translate to multiple ModLines. +---@class (partial) ModLine A modifier line on an item. An in-game mod can translate to multiple ModLines. ---@field modList Mod[] ---@field line string The actual text for the line. This might describe a range of values, in which case applyRange() can be used with this and the range value to get a ranged line. ---@field range number? @@ -564,6 +636,7 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) -- line with a range, or advanced copy lines self.advancedCopy = false self.modMagnitudeMods = {} + ---@type number local implicitLines = 0 self.variantList = nil self.versionList = nil @@ -646,6 +719,7 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) if not fullModName then fullModName = line:match("^{ (.-) }$") end + ---@cast fullModName string local modName = fullModName:match("^.*Modifier \"(.*)\"") if modName and modName ~= "" and self.affixes then self.pendingAffixList = { } @@ -723,6 +797,7 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) specName, specVal = line:match("^(Requires %a+) (.+)$") end if specName then + ---@cast specVal string if specName == "Unique ID" then self.uniqueID = specVal elseif specName == "Item Level" then @@ -732,7 +807,8 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) elseif specName == "Requires Class" then self.classRestriction = specVal elseif specName:match("Quality %([%a%s]+ Modifiers%)") then - self.catalystQuality = specToNumber(specVal:match("(%d+)%%")) + local qualityMatch = specVal:match("(%d+)%%") + self.catalystQuality = qualityMatch and specToNumber(qualityMatch) for i=1, #catalystDescriptorList do if specName:match("Quality %(([%a%s]+) Modifiers%)") == catalystDescriptorList[i] then self.catalyst = i @@ -768,12 +844,14 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) if not self.versionList then self.versionList = { } end + ---@cast self.versionList string[] t_insert(self.versionList, specVal) self.usesVariantGroups = true elseif specName == "Variant" then if not self.variantList then self.variantList = { } end + ---@cast self.variantList string[] -- This has to be kept for backwards compatibility local ver, name = specVal:match("{([%w_]+)}(.+)") if ver then @@ -931,7 +1009,7 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) end if not specName or foundExplicit or foundImplicit then ---@type ModLine - local modLine = { modTags = {} } + local modLine = { line = "", modList = {}, modTags = {} } line = line:gsub("{(%a*):?([^}]*)}", function(k,val) if k == "variant" then @@ -955,6 +1033,7 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) end self.usesVariantGroups = true elseif k == "tags" then + modLine.modTags = modLine.modTags or { } for tag in val:gmatch("[%a_]+") do t_insert(modLine.modTags, tag) end @@ -1141,6 +1220,7 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) foundImplicit = true gameModeStage = "IMPLICIT" end + ---@type number local catalystScalar = 1 if line:match(" %- Unscalable Value$") then line = line:gsub(" %- Unscalable Value$", "") @@ -1177,7 +1257,9 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) self.pendingAffixList = { exactAffix or fallbackAffix or self.pendingAffixList[1] } end -- Use rolling Delta/Range in case one range is 1-3 and another is 1-100 so we get the finest precision possible + ---@type number local bestPrecisionDelta = -1 + ---@type number local bestPrecisionRange = -1 local rollRanges = { } local affixMod = self.affixes[self.pendingAffixList[1].modId] @@ -1204,7 +1286,9 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) }) self.pendingAffixList = {} else + ---@type number local bestPrecisionDelta = -1 + ---@type number local bestPrecisionRange = -1 local firstRollRange local hasIndependentRolls @@ -1247,13 +1331,13 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) end local rangedLine = itemLib.applyRange(line, 1, catalystScalar, modLine.corruptedRange) local modList, extra = modLib.parseMod(rangedLine) - local nextModGroup = self.rawLines[l + 1] - and self.rawLines[l + 1]:match("{modGroup:([^}]+)}") + local rawNextLine = self.rawLines[l + 1] + local nextModGroup = rawNextLine and rawNextLine:match("{modGroup:([^}]+)}") -- Only combine generated lines that came from the same modifier. - if (not modList or extra) and self.rawLines[l + 1] + if (not modList or extra) and rawNextLine and modLine.modGroup == nextModGroup then -- Try to combine it with the next line - local nextLine = self.rawLines[l+1]:gsub("%b{}", ""):gsub(" ?%(%l+%)","") + local nextLine = rawNextLine:gsub("%b{}", ""):gsub(" ?%(%l+%)","") local combLine = line.." "..nextLine rangedLine = itemLib.applyRange(combLine, 1, catalystScalar, modLine.corruptedRange) modList, extra = modLib.parseMod(rangedLine, true) @@ -1464,6 +1548,7 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) -- apply mod magnitude boost to matching mods if #self.modMagnitudeMods > 0 then for _, modMagnitudeMod in ipairs(self.modMagnitudeMods) do + ---@type ModLine[][] local modLists if modMagnitudeMod.modType then modLists = { self[modMagnitudeMod.modType .. "ModLines"] } @@ -1565,7 +1650,7 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) if not list[i] then list[i] = { modId = "None" } elseif list[i].modId ~= "None" and not self.affixes[list[i].modId] then - for modId, mod in pairs(self.affixes) do + for modId, mod in pairs(self.affixes or {}) do if list[i].modId == mod.affix then list[i].modId = modId break @@ -1615,7 +1700,7 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) for origModId, foulModId in pairs(self.mutatedLines) do ---@param modId string ---@param newModId string - ---@param mutated table + ---@param mutated? boolean ---@return boolean local function checkMod(modId, newModId, mutated) local originalMod = mutated and data.itemMods.Foulborn[modId] or data.itemMods.ItemExclusive[modId] @@ -1623,8 +1708,8 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) ConPrintf("mod not found while testing mutated mods %s, %s", modId, mutated) return end - ---@param lines ModLine[] - ---@return table + ---@param lines string[] + ---@return ModLine[]|false local function findMatchingLines(lines) local matchingLines = {} local matchedLines = {} @@ -1736,6 +1821,9 @@ function ItemClass:MutateMod(modId, newModId, mutatedValue) i = i + 1 end end + if not insertIdx then + return + end table.insert(self.explicitModLines, insertIdx, { line = table.concat(newMod, "\n"), modTags = newMod.modTags, @@ -1757,7 +1845,7 @@ function ItemClass:NormaliseQuality() end end ----@param mod Mod +---@param mod ItemDataMod ---@param includeTags? table ---@param excludeTags? table ---@param baseTags? table @@ -1779,10 +1867,10 @@ function ItemClass:GetModSpawnWeight(mod, includeTags, excludeTags, baseTags) return false end - ---@param modAffix string + ---@param modAffix? string ---@return boolean local function HasMavenInfluence(modAffix) - return modAffix:match("Elevated") + return modAffix and modAffix:match("Elevated") or false end if self.restrictTag then for _, key in ipairs(mod.modTags) do @@ -1827,7 +1915,7 @@ function ItemClass:GetModSpawnWeight(mod, includeTags, excludeTags, baseTags) return weight end ----@param mod Mod +---@param mod ItemDataMod ---@return number function ItemClass:GetNecropolisModSpawnWeight(mod) local weight = 0 @@ -1842,7 +1930,7 @@ function ItemClass:GetNecropolisModSpawnWeight(mod) return weight end ----@param mod Mod +---@param mod ItemDataMod ---@return boolean function ItemClass:CheckIfModIsDelve(mod) return mod.affix == "Subterranean" or mod.affix == "of the Underground" @@ -1928,7 +2016,7 @@ function ItemClass:BuildRaw() local function prependToAllLines(prefix) line = prefix .. line:gsub("\n", "\n" .. prefix) end - ---@param idList string[] + ---@param idList table ---@return string local function makeIdSpec(idList) local ids = { } @@ -2280,6 +2368,8 @@ end ---@param type "FLAG"|"MORE"|"BASE"|"INC" ---@param flags integer ---@return boolean|number +---@overload fun(modList: ModList, name: string, type: "FLAG", flags: integer): boolean +---@overload fun(modList: ModList, name: string, type: "MORE"|"BASE"|"INC", flags: integer): number local function calcLocal(modList, name, type, flags) local result if type == "FLAG" then @@ -2314,7 +2404,7 @@ end ---@param slotNum integer ---@return ModList function ItemClass:BuildModListForSlotNum(baseList, slotNum) - local slotName = self:GetPrimarySlot() + local slotName = self:GetPrimarySlot() or "" if slotNum ~= 1 then slotName = slotName:gsub("1", tostring(slotNum)) end @@ -2366,9 +2456,12 @@ function ItemClass:BuildModListForSlotNum(baseList, slotNum) modList:NewMod("Multiplier:UnlinkedSocketIn"..slotName, "BASE", unlinkedSockets, "Unlinked Item Sockets") end local craftedQuality = calcLocal(modList,"Quality","BASE",0) or 0 + ---@cast craftedQuality number if craftedQuality ~= self.craftedQuality then if self.craftedQuality then - self.quality = (self.quality or 0) - self.craftedQuality + craftedQuality + local newQuality = (self.quality or 0) - self.craftedQuality + craftedQuality + ---@cast newQuality integer + self.quality = newQuality end self.craftedQuality = craftedQuality end @@ -2432,6 +2525,7 @@ function ItemClass:BuildModListForSlotNum(baseList, slotNum) t_insert(mod, { type = "Condition", var = (slotNum == 1) and "MainHandAttack" or "OffHandAttack" }) end end + ---@type number weaponData.TotalDPS = 0 for _, dmgType in ipairs(dmgTypeList) do weaponData.TotalDPS = weaponData.TotalDPS + (weaponData[dmgType.."DPS"] or 0) @@ -2559,6 +2653,7 @@ function ItemClass:BuildModListForSlotNum(baseList, slotNum) elseif self.type == "Jewel" then if self.name:find("Grand Spectrum") then local spectrumMod = modLib.createMod("Multiplier:GrandSpectrum", "BASE", 1, self.name) + ---@cast spectrumMod Mod modList:AddMod(spectrumMod) modList:NewMod("MinionModifier", "LIST", { mod = spectrumMod }, self.name) end @@ -2801,7 +2896,7 @@ function ItemClass:BuildModList() end end ----@param mod Mod +---@param mod ItemDataMod ---@param includeTags? table ---@return boolean function ItemClass:CanHaveMod(mod, includeTags) diff --git a/src/Classes/ItemDBControl.lua b/src/Classes/ItemDBControl.lua index 3f5dfc0fca4..da723e57343 100644 --- a/src/Classes/ItemDBControl.lua +++ b/src/Classes/ItemDBControl.lua @@ -10,7 +10,7 @@ local m_max = math.max local m_floor = math.floor ----@class ItemDBControl: ListControl +---@class ItemDBControl: ListControl ---@field itemsTab ItemsTab ---@field db ItemDBData ---@field dbType "RARE"|"UNIQUE" @@ -318,7 +318,7 @@ function ItemDBClass:ListBuilder() self.defaultText = "^7No items found that match those filters." end ----@param viewPort Rect +---@param viewPort Viewport function ItemDBClass:Draw(viewPort) if self.itemsTab.build.outputRevision ~= self.listOutputRevision then self.listBuildFlag = true diff --git a/src/Classes/ItemListControl.lua b/src/Classes/ItemListControl.lua index 1b9637d78ce..8c9e1beedd6 100644 --- a/src/Classes/ItemListControl.lua +++ b/src/Classes/ItemListControl.lua @@ -7,10 +7,10 @@ local pairs = pairs local ipairs = ipairs local t_insert = table.insert ----@class ItemListControl: ListControl +---@class ItemListControl: ListControl ---@field itemsTab ItemsTab ---@field defaultText string ----@field dragTargetList ListControl[] +---@field dragTargetList ListControl[] ---@field isMutable boolean ---@field loadoutListKey? string ---@field lastOutputRevision? integer @@ -201,7 +201,7 @@ function ItemListClass:UpdateList() self.selValue = self.selIndex and self.list[self.selIndex] or nil end ----@param viewPort Rect +---@param viewPort Viewport function ItemListClass:Draw(viewPort) local loadoutListChanged = self:UpdateLoadoutList() local outputRevision = self.itemsTab.build and self.itemsTab.build.outputRevision @@ -308,7 +308,7 @@ end ---@param type string ---@param value Item ----@param source? ListControl +---@param source? ListControl function ItemListClass:ReceiveDrag(type, value, source) if type == "Item" then local newItem = new("Item"):Item(value.raw) diff --git a/src/Classes/ItemSetListControl.lua b/src/Classes/ItemSetListControl.lua index edbda643e17..adee1a57c7b 100644 --- a/src/Classes/ItemSetListControl.lua +++ b/src/Classes/ItemSetListControl.lua @@ -13,7 +13,7 @@ local s_format = string.format ---@field title? string ---@field slots? table ----@class ItemSetListControl: ListControl +---@class ItemSetListControl: ListControl ---@field itemsTab ItemsTab ---@field selIndex? integer ---@field selValue? integer @@ -124,7 +124,7 @@ end ---@param type string ---@param value SharedItemSet ----@param source? ListControl +---@param source? ListControl function ItemSetListClass:ReceiveDrag(type, value, source) if type == "SharedItemList" then local itemSet = self.itemsTab:NewItemSet() diff --git a/src/Classes/ItemSlotControl.lua b/src/Classes/ItemSlotControl.lua index a71639a03e6..357adb59562 100644 --- a/src/Classes/ItemSlotControl.lua +++ b/src/Classes/ItemSlotControl.lua @@ -8,7 +8,7 @@ local t_insert = table.insert local m_min = math.min local itemSlotHelper = require("Modules.ItemSlotHelper") ----@class ItemSlotControl: DropDownControl +---@class ItemSlotControl: DropDownControl ---@field itemsTab ItemsTab ---@field slotName string ---@field slotNum integer @@ -140,7 +140,7 @@ end ---@param type string ---@param value Item ----@param source? ListControl +---@param source? ListControl function ItemSlotClass:ReceiveDrag(type, value, source) if value.id and self.itemsTab.items[value.id] then self:SetSelItemId(value.id) @@ -156,7 +156,7 @@ function ItemSlotClass:ReceiveDrag(type, value, source) self.itemsTab.build.buildFlag = true end ----@param viewPort Rect +---@param viewPort Viewport function ItemSlotClass:Draw(viewPort) local x, y = self:GetPos() local width, height = self:GetSize() diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index 91ed7b87705..f6ae8e9c13a 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -43,6 +43,7 @@ local forbiddenJewelCounterpart = { local influenceInfo = itemLib.influenceInfo.all +---@type string[] local catalystQualityFormat = { "^x7F7F7FQuality (Attack Modifiers): "..colorCodes.MAGIC.."+%d%% (augmented)", "^x7F7F7FQuality (Speed Modifiers): "..colorCodes.MAGIC.."+%d%% (augmented)", @@ -133,7 +134,9 @@ function ItemsTabClass:ItemsTab(build) -- Set selector self.controls.setSelect = new("DropDownControl"):DropDownControl({"TOPLEFT",self,"TOPLEFT"}, {96, 8, 216, 20}, nil, function(index, value) - self:SetActiveItemSet(self.itemSetOrderList[index]) + local itemSetId = self.itemSetOrderList[index] + if not itemSetId then return end + self:SetActiveItemSet(itemSetId) self:AddUndoState() end) self.controls.setSelect.enableDroppedWidth = true @@ -378,6 +381,7 @@ holding Shift will put it in the second.]]) self.controls.displayItemBuySimilar = new("ButtonControl"):ButtonControl({ "LEFT", self.controls.removeDisplayItem, "RIGHT", true }, { 8, 0, 100, 20 }, "Buy similar", function() + ---@cast self.displayItem Item local itemSlot = self:GetComparisonSlotNameForItem(self.displayItem) buySimilar.openPopup(self.displayItem, itemSlot, self.build) end) @@ -523,6 +527,7 @@ holding Shift will put it in the second.]]) end self.controls.displayItemAddSocket = new("ButtonControl"):ButtonControl({"TOPLEFT",self.controls.displayItemSectionSockets,"TOPLEFT"}, {function() return (#self.displayItem.sockets - self.displayItem.abyssalSocketCount) * 64 - 12 end, 0, 20, 20}, "+", function() local insertIndex = #self.displayItem.sockets - self.displayItem.abyssalSocketCount + 1 + ---@cast insertIndex integer t_insert(self.displayItem.sockets, insertIndex, { color = self.displayItem.defaultSocketColor, group = self.displayItem.sockets[insertIndex - 1].group + 1 @@ -594,6 +599,7 @@ holding Shift will put it in the second.]]) return self.displayItem and self.displayItem.corruptible end self.controls.displayItemAddImplicit = new("ButtonControl"):ButtonControl({"TOPLEFT",self.controls.displayItemCorrupt,"TOPRIGHT",true}, {8, 0, 120, 20}, "Add Implicit...", function() + ---@cast self.displayItem Item addImplicit.AddImplicitToDisplayItem(self, self.displayItem) end) self.controls.displayItemAddImplicit.shown = function() @@ -639,6 +645,7 @@ holding Shift will put it in the second.]]) local influenceTipText = table.concat(main:WrapString("Selecting an influence here will also allow the modifier dropdowns to contain influenced mods.", 16, 140), "\n") self.controls.displayItemInfluence = new("DropDownControl"):DropDownControl({"TOPLEFT",self.controls.displayItemSectionInfluence,"TOPRIGHT"}, {0, 0, 100, 20}, influenceDisplayList, function(index, value) local otherIndex = self.controls.displayItemInfluence2.selIndex + ---@cast otherIndex integer setDisplayItemInfluence({ index - 1, otherIndex - 1 }) end) self.controls.displayItemInfluence.tooltipText = influenceTipText @@ -647,6 +654,7 @@ holding Shift will put it in the second.]]) end self.controls.displayItemInfluence2 = new("DropDownControl"):DropDownControl({"TOPLEFT",self.controls.displayItemInfluence,"TOPRIGHT",true}, {8, 0, 100, 20}, influenceDisplayList, function(index, value) local otherIndex = self.controls.displayItemInfluence.selIndex + ---@cast otherIndex integer setDisplayItemInfluence({ index - 1, otherIndex - 1 }) end) self.controls.displayItemInfluence2.shown = function() @@ -781,22 +789,23 @@ holding Shift will put it in the second.]]) end for i = 1, maxModCount do local prev = self.controls["displayItemAffix"..(i-1)] or self.controls.displayItemSectionAffix + ---@type DropDownControl, SliderControl local drop, slider -- flips range if it will form discontinuous values ---@param range number ---@param index integer - ---@param drop DropDownControl + ---@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 -- assumes all pairs are ordered the same - ---@param modA table - ---@param modB table + ---@param modA ItemDataMod + ---@param modB ItemDataMod ---@return boolean local function flipRange(modA, modB) -- gets first valid range from a mod - ---@param mod table + ---@param mod ItemDataMod ---@return number? min ---@return number? max local function getMinMax(mod) @@ -825,11 +834,15 @@ holding Shift will put it in the second.]]) end if priorMod then - if flipRange(priorMod, self.displayItem.affixes[drop.list[drop.selIndex].modList[index]]) then + local currentMod = self.displayItem.affixes[drop.list[drop.selIndex].modList[index]] + ---@cast currentMod table + if flipRange(priorMod, currentMod) then range = 1 - range end elseif nextMod then - if flipRange(self.displayItem.affixes[drop.list[drop.selIndex].modList[index]], nextMod) then + local currentMod = self.displayItem.affixes[drop.list[drop.selIndex].modList[index]] + ---@cast currentMod table + if flipRange(currentMod, nextMod) then range = 1 - range end end @@ -861,6 +874,7 @@ holding Shift will put it in the second.]]) elseif tooltip:CheckForUpdate(modList) then if value.modId or #modList == 1 then local mod = self.displayItem.affixes[value.modId or modList[1]] + ---@cast mod ItemDataMod tooltip:AddLine(16, "^7Affix: "..mod.affix) for _, line in ipairs(mod) do tooltip:AddLine(14, "^7"..line) @@ -875,6 +889,8 @@ holding Shift will put it in the second.]]) tooltip:AddLine(16, "^7"..#modList.." Tiers") local minMod = self.displayItem.affixes[modList[1]] local maxMod = self.displayItem.affixes[modList[#modList]] + ---@cast minMod ItemDataMod + ---@cast maxMod ItemDataMod for l, line in ipairs(minMod) do local minLine = line:gsub("%((%d[%d%.]*)%-(%d[%d%.]*)%)", "%1") local maxLine = maxMod[l]:gsub("%((%d[%d%.]*)%-(%d[%d%.]*)%)", "%2") @@ -900,6 +916,7 @@ holding Shift will put it in the second.]]) end end local mod = self.displayItem.affixes[value.modId or modList[1]] + ---@cast mod ItemDataMod local notableName = mod[1] and mod[1]:match("1 Added Passive Skill is (.*)") local node = notableName and self.build.spec.tree.clusterNodeMap[notableName] if node then @@ -955,6 +972,7 @@ holding Shift will put it in the second.]]) end end else + ---@type ItemDataMod local mod = { } if value.modId or #modList == 1 then mod = self.displayItem.affixes[value.modId or modList[1]] @@ -991,6 +1009,7 @@ holding Shift will put it in the second.]]) range = verifyRange(range, index, drop) local modId = modList[index] local mod = self.displayItem.affixes[modId] + ---@cast mod ItemDataMod for _, line in ipairs(mod) do tooltip:AddLine(16, itemLib.applyRange(line, range)) end @@ -1320,7 +1339,9 @@ function ItemsTabClass:Load(xml, dbFileName) end end elseif node.elem == "ItemSet" then - local itemSet = self:NewItemSet(tonumber(node.attrib.id)) + ---@type integer? + local itemSetId = tonumber(node.attrib.id) + local itemSet = self:NewItemSet(itemSetId) itemSet.title = node.attrib.title itemSet.useSecondWeaponSet = node.attrib.useSecondWeaponSet == "true" for _, child in ipairs(node) do @@ -1361,7 +1382,9 @@ function ItemsTabClass:Load(xml, dbFileName) self.activeItemSet.useSecondWeaponSet = xml.attrib.useSecondWeaponSet == "true" self.itemSetOrderList[1] = 1 end - self:SetActiveItemSet(tonumber(xml.attrib.activeItemSet) or 1) + ---@type integer + local activeItemSetId = tonumber(xml.attrib.activeItemSet) or 1 + self:SetActiveItemSet(activeItemSetId) if xml.attrib.showStatDifferences then self.showStatDifferences = xml.attrib.showStatDifferences == "true" end @@ -1459,7 +1482,7 @@ function ItemsTabClass:Save(xml) end end ----@param viewPort Rect +---@param viewPort Viewport ---@param inputEvents InputEvent[] function ItemsTabClass:Draw(viewPort, inputEvents) self.x = viewPort.x @@ -1670,6 +1693,7 @@ end function ItemsTabClass:EquipItemInSet(item, itemSetId) local itemSet = self.itemSets[itemSetId] local slotName = item:GetPrimarySlot() + if not slotName then return end if self.slots[slotName].weaponSet == 1 and itemSet.useSecondWeaponSet then -- Redirect to second weapon set slotName = slotName .. " Swap" @@ -1864,6 +1888,7 @@ end ---@param noAutoEquip? boolean function ItemsTabClass:AddDisplayItem(noAutoEquip) local item = self.displayItem + ---@cast item Item local oldItem = item and item.id and self.items[item.id] -- Add it to the list and clear the current display item self:AddItem(item, noAutoEquip) @@ -2029,8 +2054,9 @@ end ---@param index integer ---@param value? string|table ---@param legacyField string ----@param control DropDownControl +---@param control DropDownControl function ItemsTabClass:SelectDisplayItemVariant(index, value, legacyField, control) + ---@cast self.displayItem Item if self.displayItem.usesVariantGroups then if not value or not value.variantId then return @@ -2190,6 +2216,7 @@ end function ItemsTabClass:UpdateDisplayItemTooltip() self.displayItemTooltip:Clear() + ---@cast self.displayItem Item self:AddItemTooltip(self.displayItemTooltip, self.displayItem) self.displayItemTooltip.center = true end @@ -2271,11 +2298,14 @@ end -- Update affix selection controls function ItemsTabClass:UpdateAffixControls() local item = self.displayItem + ---@cast item Item + ---@type integer local prefixLimit = item.prefixes.limit or (item.affixLimit / 2) local ignoreModType = item.rareLikeUnique and item.rareLikeUnique.ignoreModType local powerCache = {} for i = 1, item.affixLimit do if i <= prefixLimit then + ---@type "Prefix"|"Suffix"|nil local modType = "Prefix" if ignoreModType then modType = nil @@ -2290,7 +2320,7 @@ function ItemsTabClass:UpdateAffixControls() self:UpdateCustomControls() end ----@param control DropDownControl +---@param control DropDownControl ---@param item Item ---@param affixType? "Prefix"|"Suffix" ---@param outputTable "prefixes"|"suffixes" @@ -2326,7 +2356,7 @@ function ItemsTabClass:UpdateAffixControl(control, item, affixType, outputTable, local selAffix = item[outputTable][outputIndex] and item[outputTable][outputIndex].modId local affixList = { } local retainedAffixes = { } - for modId, mod in pairs(item.affixes) do + for modId, mod in pairs(item.affixes or {}) do if (not affixType or (mod.type == affixType)) and not excludeGroups[mod.group] and not item:CheckIfModIsDelve(mod) then if item:CanHaveMod(mod, extraTags) then t_insert(affixList, modId) @@ -2365,6 +2395,7 @@ function ItemsTabClass:UpdateAffixControl(control, item, affixType, outputTable, -- in the list for _, modId in ipairs(affixList) do local mod = item.affixes[modId] + ---@cast mod ItemDataMod if not lastSeries or not tableDeepEquals(lastSeries.statOrder, mod.statOrder) then local modString = table.concat(mod, "/") lastSeries = { @@ -2415,6 +2446,7 @@ function ItemsTabClass:UpdateAffixControl(control, item, affixType, outputTable, return controlPowerCache[modId] end local mod = testSubject.affixes[modId] + ---@cast mod ItemDataMod local modCount = #mod -- magnitude scaling happens during item parsing, which means we @@ -2499,6 +2531,7 @@ function ItemsTabClass:UpdateAffixControl(control, item, affixType, outputTable, -- Imported legacy rolls can sit outside the current 0-1 affix range. -- Keep that value on the affix, but show the nearest slider endpoint. local affixRange = item[outputTable][outputIndex].range + ---@type number local range = m_min(1, m_max(0, type(affixRange) == "table" and affixRange[1] or affixRange or 0.5)) -- Avoid exact integer boundary that slider:GetDivVal's ceil would assign to the previous segment if range == 0 and index > 1 then @@ -2590,7 +2623,7 @@ local function checkLineForAllocates(line, nodes) end ---@param tooltip Tooltip ----@param mod table +---@param mod ItemDataMod ---@param replaceImplicits? boolean function ItemsTabClass:AddModComparisonTooltip(tooltip, mod, replaceImplicits) local slotName = self.displayItem:GetPrimarySlot() @@ -2824,7 +2857,7 @@ function ItemsTabClass:EditDisplayItemText(alsoAddItem) controls.rarity = new("DropDownControl"):DropDownControl(nil, {-190, 10, 100, 18}, rarityDropList) controls.edit = new("EditControl"):EditControl(nil, {0, 40, 480, 420}, "", nil, "^%C\t\n", nil, nil, 14) if self.displayItem then - controls.edit:SetText(self.displayItem:BuildRaw():gsub("Rarity: %w+\n","")) + controls.edit:SetText((self.displayItem:BuildRaw():gsub("Rarity: %w+\n",""))) controls.rarity:SelByValue(self.displayItem.rarity, "rarity") else controls.rarity.selIndex = 3 @@ -2872,6 +2905,7 @@ function ItemsTabClass:EnchantDisplayItem(enchantSlot) local controls = { } local enchantments = self.displayItem.enchantments + ---@cast enchantments table local haveSkills = true for _, source in ipairs(self.build.data.enchantmentSource) do if self.displayItem.enchantments[source.name] then @@ -3005,6 +3039,7 @@ function ItemsTabClass:EnchantDisplayItem(enchantSlot) local selected = not selectFirst and enchantmentList[controls.enchantment.selIndex] or nil if stat then local slotName = self.displayItem:GetPrimarySlot() + if not slotName then return end local calcFunc = self.build.calcsTab:GetMiscCalculator() local useFullDPS = stat == "FullDPS" for _, entry in ipairs(enchantmentList) do @@ -3061,7 +3096,9 @@ function ItemsTabClass:EnchantDisplayItem(enchantSlot) controls.enchantmentSourceLabel = new("LabelControl"):LabelControl({"TOPRIGHT",nil,"TOPLEFT"}, {95, 45, 0, 16}, "^7Source:") controls.enchantmentSource = new("DropDownControl"):DropDownControl({"TOPLEFT",nil,"TOPLEFT"}, {100, 45, 180, 18}, enchantmentSourceList, function(index, value) buildEnchantmentList() - controls.enchantment:SetSel(m_min(controls.enchantment.selIndex, #enchantmentList)) + if controls.enchantment.selIndex then + controls.enchantment:SetSel(m_min(controls.enchantment.selIndex, #enchantmentList)) + end if controls.sort then applySort(controls.sort.list[controls.sort.selIndex].stat, true) end @@ -3244,6 +3281,7 @@ function ItemsTabClass:CorruptDisplayItem() local corruptedRanges = {} local sourceList = { "Corrupted", "Scourge" } local sortList, sortStats = buildModSortList() + ---@type integer local itemMaxCorruptImplicits = self.displayItem.corruptImplicitCount or 2 for i, modLine in ipairs(self.displayItem.implicitModLines) do for _, mod in ipairs(modLine.modList or {}) do @@ -3273,7 +3311,7 @@ function ItemsTabClass:CorruptDisplayItem() end end else - for modId, mod in pairs(self.displayItem.affixes) do + for modId, mod in pairs(self.displayItem.affixes or {}) do if mod.type == modType and self.displayItem:GetModSpawnWeight(mod) > 0 then t_insert(implicitList[modType], { mod = mod }) end @@ -3293,8 +3331,8 @@ function ItemsTabClass:CorruptDisplayItem() end end buildImplicitList(currentModType) - ---@param control DropDownControl - ---@param other? DropDownControl + ---@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 @@ -3314,7 +3352,7 @@ function ItemsTabClass:CorruptDisplayItem() -- avoid letting the user select the same implicit twice local selectedGroups = {} for i = 1, maxImplicitNum do - ---@type DropDownControl + ---@type DropDownControl local control = controls[string.format("implicit%d", i)] local selected = control:GetSelValue() if selected and selected.mod then @@ -3322,7 +3360,7 @@ function ItemsTabClass:CorruptDisplayItem() end end for i = 1, maxImplicitNum do - ---@type DropDownControl + ---@type DropDownControl local control = controls[string.format("implicit%d", i)] wipeTable(control.list) t_insert(control.list, { label = "None" }) @@ -3334,7 +3372,7 @@ function ItemsTabClass:CorruptDisplayItem() end end for _, entry in pairs(selectedGroups) do - ---@type DropDownControl + ---@type DropDownControl local control = controls[string.format("implicit%d", entry.idx)] control:SelByValue(entry.val, "mod") end @@ -3381,6 +3419,7 @@ function ItemsTabClass:CorruptDisplayItem() return end if stat then + ---@cast calcFunc function for _, entry in ipairs(implicitList[modType]) do entry.sortValue = getSortValue(entry, modType, stat, calcFunc, slotName, useFullDPS) end @@ -3402,6 +3441,7 @@ function ItemsTabClass:CorruptDisplayItem() return end local slotName = self.displayItem:GetPrimarySlot() + if not slotName then return end local calcFunc = stat and self.build.calcsTab:GetMiscCalculator() or nil local useFullDPS = stat == "FullDPS" if currentModType ~= "ScourgeUpside" then @@ -3529,6 +3569,7 @@ function ItemsTabClass:CorruptDisplayItem() end controls.implicits = new("ButtonControl"):ButtonControl({ "TOPLEFT", nil, "TOPLEFT" }, { 5, 5, 80, 20 }, "Implicits", function() + ---@type integer local implicitNum = currentModType ~= "ScourgeUpside" and itemMaxCorruptImplicits or 4 local canChangeImplicits = currentModType ~= "Corrupted" or not self.displayItem.implicitsCannotBeChanged setImplicitControlsShown(implicitNum, canChangeImplicits) @@ -3699,6 +3740,7 @@ function ItemsTabClass:AddCustomModifierToDisplayItem() local selected = not selectFirst and modList[controls.modSelect.selIndex] or nil if stat then local slotName = self.displayItem:GetPrimarySlot() + if not slotName then return end local calcFunc = self.build.calcsTab:GetMiscCalculator() local useFullDPS = stat == "FullDPS" for _, listMod in ipairs(modList) do @@ -3815,7 +3857,7 @@ function ItemsTabClass:AddCustomModifierToDisplayItem() end end) elseif sourceId == "PREFIX" or sourceId == "SUFFIX" then - for _, mod in pairs(self.displayItem.affixes) do + for _, mod in pairs(self.displayItem.affixes or {}) do if sourceId:lower() == (mod.type and mod.type:lower()) and self.displayItem:GetModSpawnWeight(mod) > 0 then t_insert(modList, { label = mod.affix .. " ^8[" .. table.concat(mod, "/") .. "]", @@ -3840,6 +3882,7 @@ function ItemsTabClass:AddCustomModifierToDisplayItem() end) elseif sourceId == "VEILED" then for i, mod in pairs(self.build.data.veiledMods) do + ---@cast mod ItemDataMod if self.displayItem:GetModSpawnWeight(mod) > 0 then t_insert(modList, { label = table.concat(mod, "/") .. " (" .. mod.type .. ")", @@ -3864,7 +3907,7 @@ function ItemsTabClass:AddCustomModifierToDisplayItem() buildDropRestricted(delveDropOnlyCategories, data.itemMods.Delve) table.sort(modList, sortByPrefixSuffix) elseif sourceId == "FOSSIL" then - for i, mod in pairs(self.displayItem.affixes) do + for i, mod in pairs(self.displayItem.affixes or {}) do if self.displayItem:CheckIfModIsDelve(mod) and self.displayItem:GetModSpawnWeight(mod) > 0 then t_insert(modList, { label = table.concat(mod, "/") .. " (" .. mod.type .. ")", @@ -3878,6 +3921,7 @@ function ItemsTabClass:AddCustomModifierToDisplayItem() table.sort(modList, sortByPrefixSuffix) elseif sourceId == "NECROPOLIS" then for i, mod in pairs(self.build.data.necropolisMods) do + ---@cast mod ItemDataMod if self.displayItem:GetNecropolisModSpawnWeight(mod) > 0 then t_insert(modList, { label = table.concat(mod, "/") .. " (" .. mod.type .. ")", @@ -4028,7 +4072,7 @@ function ItemsTabClass:AddCrucibleModifierToDisplayItem() local controls = { } local modList = {[1] = {"None"}, [2] = {"None"}, [3] = {"None"}, [4] = {"None"}, [5] = {"None"}} local itemModMap, nodeSelections = { }, { } - ---@param mod table + ---@param mod ItemDataMod ---@return string local function getLabelFromMod(mod) local label = copyTable(mod) @@ -4039,6 +4083,7 @@ function ItemsTabClass:AddCrucibleModifierToDisplayItem() end local function buildCrucibleMods() for i, mod in pairs(self.build.data.crucible) do + ---@cast mod ItemDataMod if self.displayItem:CanHaveMod(mod) then -- item mod must match the whole mod, whether that's one line or two if itemModMap[checkLineForAllocates(mod[1], self.build.spec.nodes)] and ((mod[2] and itemModMap[checkLineForAllocates(mod[2], self.build.spec.nodes)]) or not mod[2]) then @@ -4850,7 +4895,9 @@ function ItemsTabClass:AddItemStatDifferences(tooltip, item, base, slot) local output = self.build.calcsTab.mainOutput local durInc = modDB:Sum("INC", nil, "FlaskDuration") local effectInc = modDB:Sum("INC", { actor = "player" }, "FlaskEffect") + ---@type number local lifeDur = 0 + ---@type number local manaDur = 0 if item.rarity == "MAGIC" and not item.base.flask.life and not item.base.flask.mana then @@ -4976,6 +5023,7 @@ function ItemsTabClass:AddItemStatDifferences(tooltip, item, base, slot) if not item.base.flask.mana and not item.base.flask.life then chargesGenerated = chargesGenerated + modDB:Sum("BASE", nil, "UtilityFlaskChargesGenerated") end + ---@type number local chargesGeneratedOnWardBreak = 0 if item.baseName == "Iron Flask" then chargesGeneratedOnWardBreak = chargesGeneratedOnWardBreak + modDB:Sum("BASE", nil, "IronFlaskChargesGeneratedOnWardBreak") @@ -5024,10 +5072,12 @@ function ItemsTabClass:AddItemStatDifferences(tooltip, item, base, slot) local per3Duration = flaskDuration - (flaskDuration % 3) local per5Duration = flaskDuration - (flaskDuration % 5) local minimumChargesGenerated = per3Duration * chargesGenerated + per5Duration * chargesGeneratedPerFlask + ---@type number local percentageMin = m_min(minimumChargesGenerated / flaskChargesUsed * 100, 100) if percentageMin < 100 and chanceToNotConsumeCharges < 100 then local averageChargesGenerated = (chargesGenerated + chargesGeneratedPerFlask) * flaskDuration local averageChargesUsed = flaskChargesUsed * (100 - chanceToNotConsumeCharges) / 100 + ---@type number local percentageAvg = m_min(averageChargesGenerated / averageChargesUsed * 100, 100) t_insert(stats, s_format("^8Flask uptime: ^7%d%%^8 average, ^7%d%%^8 minimum", percentageAvg, percentageMin)) else diff --git a/src/Classes/LabelControl.lua b/src/Classes/LabelControl.lua index a6cc3d478d0..852b4ea46f1 100644 --- a/src/Classes/LabelControl.lua +++ b/src/Classes/LabelControl.lua @@ -4,12 +4,12 @@ -- Simple text label. -- ---@class LabelControl: Control ----@field label Prop +---@field label? Prop local LabelClass = newClass("LabelControl", "Control") ---@param anchor? Anchor ---@param rect? Rect ----@param label Prop +---@param label? Prop ---@return LabelControl function LabelClass:LabelControl(anchor, rect, label) self:Control(anchor, rect) diff --git a/src/Classes/ListControl.lua b/src/Classes/ListControl.lua index 4f9f87763ca..e556e19b4ed 100644 --- a/src/Classes/ListControl.lua +++ b/src/Classes/ListControl.lua @@ -57,12 +57,13 @@ local m_floor = math.floor ---@field otherDragSource? ListControl ---@field otherDragTargeting? boolean ----@class ListColumn +---@class (partial) 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 _ListColumn = { } local ListClass = newClass("ListControl", "Control", "ControlHost") ---@generic T @@ -179,7 +180,7 @@ function ListClass:GetRowRegion() } end ----@param viewPort Rect +---@param viewPort Viewport ---@param noTooltip? boolean function ListClass:Draw(viewPort, noTooltip) local x, y = self:GetPos() @@ -187,6 +188,7 @@ function ListClass:Draw(viewPort, noTooltip) local rowHeight = self.rowHeight local list = self.list + ---@type number local colOffset = 0 for index, column in ipairs(self.colList) do column._offset = colOffset @@ -267,16 +269,19 @@ function ListClass:Draw(viewPort, noTooltip) local clipWidth = DrawStringWidth(textHeight, colFont, "...") colOffset = column._offset - scrollOffsetH local colWidth = column._width + ---@cast colWidth number local relX = cursorX - (x + 2) local relY = cursorY - (y + 2) for index = minIndex, maxIndex do local lineY = rowHeight * (index - 1) - scrollOffsetV + (self.colLabels and 18 or 0) local value = list[index] - local text = self:GetRowValue(colIndex, index, value) + local text = self:GetRowValue(colIndex, index, value) or "" + ---@cast text string local icon = nil if self.GetRowIcon then icon = self:GetRowIcon(colIndex, index, value) end + ---@cast icon ImageHandle? local textWidth = DrawStringWidth(textHeight, colFont, text) if textWidth > colWidth - 2 then local clipIndex = DrawStringCursorIndex(textHeight, colFont, text, colWidth - clipWidth - 2, 0) @@ -378,6 +383,9 @@ function ListClass:Draw(viewPort, noTooltip) self.hoverIndex = ttIndex self.hoverValue = ttValue if ttIndex and self.AddValueTooltip and (not noTooltip or self.forceTooltip) then + ---@cast ttX number + ---@cast ttY number + ---@cast ttWidth number SetDrawLayer(nil, 100) self:AddValueTooltip(self.tooltip, ttIndex, ttValue) self.tooltip:Draw(ttX, ttY, ttWidth, rowHeight, viewPort) diff --git a/src/Classes/MinionListControl.lua b/src/Classes/MinionListControl.lua index 318243758b7..91e9f23da27 100644 --- a/src/Classes/MinionListControl.lua +++ b/src/Classes/MinionListControl.lua @@ -8,10 +8,10 @@ local t_insert = table.insert local t_remove = table.remove local s_format = string.format ----@class MinionListControl: ListControl +---@class MinionListControl: ListControl ---@field data Data ---@field dest? MinionListControl ----@field dragTargetList? ListControl[] +---@field dragTargetList? ListControl[] ---@field label string ---@field selIndex? integer ---@field selValue? string @@ -116,7 +116,7 @@ end ---@param type string ---@param value string ----@param source? ListControl +---@param source? ListControl function MinionListClass:ReceiveDrag(type, value, source) t_insert(self.list, self.selDragIndex or #self.list + 1, value) end diff --git a/src/Classes/ModDB.lua b/src/Classes/ModDB.lua index fc41945f1da..9299b8a1a52 100644 --- a/src/Classes/ModDB.lua +++ b/src/Classes/ModDB.lua @@ -148,6 +148,7 @@ end ---@param ... string ---@return number function ModDBClass:SumInternal(context, modType, cfg, flags, keywordFlags, source, ...) + ---@type number local result = 0 local globalLimits for i = 1, select('#', ...) do @@ -183,16 +184,18 @@ end ---@param ... string ---@return number function ModDBClass:MoreInternal(context, cfg, flags, keywordFlags, source, ...) + ---@type number local result = 1 local modPrecision = nil local globalLimits for i = 1, select('#', ...) do local modList = self.mods[select(i, ...)] + ---@type number local modResult = 1 --The more multipliers for each mod are computed to the nearest percent then applied. if modList then for i = 1, #modList do local mod = modList[i] - if mod.type == "MORE" and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or mod.source:match("[^:]+") == source) then + if mod.type == "MORE" and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or (mod.source and mod.source:match("[^:]+") == source)) then local value if mod[1] then if not globalLimits then @@ -237,7 +240,7 @@ function ModDBClass:FlagInternal(context, cfg, flags, keywordFlags, source, ...) if modList then for i = 1, #modList do local mod = modList[i] - if mod.type == "FLAG" and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or mod.source:match("[^:]+") == source) then + if mod.type == "FLAG" and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or (mod.source and mod.source:match("[^:]+") == source)) then if mod[1] then if context:EvalMod(mod, cfg) then return true @@ -267,7 +270,7 @@ function ModDBClass:OverrideInternal(context, cfg, flags, keywordFlags, source, if modList then for i = 1, #modList do local mod = modList[i] - if mod.type == "OVERRIDE" and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or mod.source:match("[^:]+") == source) then + if mod.type == "OVERRIDE" and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or (mod.source and mod.source:match("[^:]+") == source)) then if mod[1] then local value = context:EvalMod(mod, cfg) if value then @@ -298,7 +301,7 @@ function ModDBClass:ListInternal(context, result, cfg, flags, keywordFlags, sour if modList then for i = 1, #modList do local mod = modList[i] - if mod.type == "LIST" and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or mod.source:match("[^:]+") == source) then + if mod.type == "LIST" and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or (mod.source and mod.source:match("[^:]+") == source)) then local value if mod[1] then local value = context:EvalMod(mod, cfg) or nullValue @@ -333,7 +336,7 @@ function ModDBClass:TabulateInternal(context, result, modType, cfg, flags, keywo if modList then for i = 1, #modList do local mod = modList[i] - if (mod.type == modType or not modType) and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or mod.source:match("[^:]+") == source) then + if (mod.type == modType or not modType) and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or (mod.source and mod.source:match("[^:]+") == source)) then local value if mod[1] then if not globalLimits then @@ -369,7 +372,7 @@ function ModDBClass:HasModInternal(modType, flags, keywordFlags, source, ...) if modList then for i = 1, #modList do local mod = modList[i] - if mod.type == modType and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or mod.source:match("[^:]+") == source) then + if mod.type == modType and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or (mod.source and mod.source:match("[^:]+") == source)) then return true end end diff --git a/src/Classes/ModList.lua b/src/Classes/ModList.lua index 24052b3a005..80f72bd7b27 100644 --- a/src/Classes/ModList.lua +++ b/src/Classes/ModList.lua @@ -17,6 +17,7 @@ local bor = bit.bor local mod_createMod = modLib.createMod ---@class ModList: ModStore +---@field [integer] Mod local ModListClass = newClass("ModList", "ModStore") ---@param parent? ModStore @@ -114,12 +115,14 @@ end ---@param ... string ---@return number function ModListClass:SumInternal(context, modType, cfg, flags, keywordFlags, source, ...) + ---@type number local result = 0 for i = 1, select('#', ...) do local modName = select(i, ...) for i = 1, #self do + ---@type Mod local mod = self[i] - if mod.name == modName and mod.type == modType and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or mod.source:match("[^:]+") == source) then + if mod.name == modName and mod.type == modType and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or (mod.source and mod.source:match("[^:]+") == source)) then if mod[1] then result = result + (context:EvalMod(mod, cfg) or 0) else @@ -142,14 +145,17 @@ end ---@param ... string ---@return number function ModListClass:MoreInternal(context, cfg, flags, keywordFlags, source, ...) + ---@type number local result = 1 local modPrecision = nil for i = 1, select('#', ...) do + ---@type number local modResult = 1 --The more multipliers for each mod are computed to the nearest percent then applied. local modName = select(i, ...) for i = 1, #self do + ---@type Mod local mod = self[i] - if mod.name == modName and mod.type == "MORE" and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or mod.source:match("[^:]+") == source) then + if mod.name == modName and mod.type == "MORE" and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or (mod.source and mod.source:match("[^:]+") == source)) then if mod[1] then modResult = modResult * (1 + (context:EvalMod(mod, cfg) or 0) / 100) else @@ -186,8 +192,9 @@ function ModListClass:FlagInternal(context, cfg, flags, keywordFlags, source, .. for i = 1, select('#', ...) do local modName = select(i, ...) for i = 1, #self do + ---@type Mod local mod = self[i] - if mod.name == modName and mod.type == "FLAG" and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or mod.source:match("[^:]+") == source) then + if mod.name == modName and mod.type == "FLAG" and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or (mod.source and mod.source:match("[^:]+") == source)) then if mod[1] then if context:EvalMod(mod, cfg) then return true @@ -214,8 +221,9 @@ function ModListClass:OverrideInternal(context, cfg, flags, keywordFlags, source for i = 1, select('#', ...) do local modName = select(i, ...) for i = 1, #self do + ---@type Mod local mod = self[i] - if mod.name == modName and mod.type == "OVERRIDE" and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or mod.source:match("[^:]+") == source) then + if mod.name == modName and mod.type == "OVERRIDE" and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or (mod.source and mod.source:match("[^:]+") == source)) then if mod[1] then local value = context:EvalMod(mod, cfg) if value then @@ -243,8 +251,9 @@ function ModListClass:ListInternal(context, result, cfg, flags, keywordFlags, so for i = 1, select('#', ...) do local modName = select(i, ...) for i = 1, #self do + ---@type Mod local mod = self[i] - if mod.name == modName and mod.type == "LIST" and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or mod.source:match("[^:]+") == source) then + if mod.name == modName and mod.type == "LIST" and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or (mod.source and mod.source:match("[^:]+") == source)) then local value if mod[1] then local value = context:EvalMod(mod, cfg) or nullValue @@ -274,8 +283,9 @@ function ModListClass:TabulateInternal(context, result, modType, cfg, flags, key for i = 1, select('#', ...) do local modName = select(i, ...) for i = 1, #self do + ---@type Mod local mod = self[i] - if mod.name == modName and (mod.type == modType or not modType) and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or mod.source:match("[^:]+") == source) then + if mod.name == modName and (mod.type == modType or not modType) and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or (mod.source and mod.source:match("[^:]+") == source)) then local value if mod[1] then value = context:EvalMod(mod, cfg) diff --git a/src/Classes/ModStore.lua b/src/Classes/ModStore.lua index 4d322934d4b..73f4d9d98a0 100644 --- a/src/Classes/ModStore.lua +++ b/src/Classes/ModStore.lua @@ -342,7 +342,7 @@ 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 + ---@param name? string ---@return boolean local function isNameInBuffList(buffList, name) for _, buff in ipairs(buffList) do @@ -355,7 +355,7 @@ function ModStoreClass:GetStat(stat, cfg) -- Check if mana is 0 (i.e. from Blood Magic) to avoid division by 0. local totalMana = self.actor.output["Mana"] if totalMana == 0 then return 0 else - for _, activeSkill in ipairs(self.actor.activeSkillList) do + for _, activeSkill in ipairs(self.actor.activeSkillList or {}) do if (activeSkill.skillTypes[SkillType.HasReservation] and not activeSkill.skillFlags.disable and activeSkill.buffList and activeSkill.buffList[1] and cfg and (isNameInBuffList(activeSkill.buffList, cfg.skillName) or isNameInBuffList(activeSkill.buffList, cfg.summonSkillName)) ) then local manaBase = activeSkill.skillData["ManaReservedBase"] or 0 reservedPercentMana = m_floor(manaBase / totalMana * 100) @@ -369,7 +369,7 @@ function ModStoreClass:GetStat(stat, cfg) local reservedPercentLife = 0 local totalLife = self.actor.output["Life"] if totalLife == 0 then return 0 else - for _, activeSkill in ipairs(self.actor.activeSkillList) do + for _, activeSkill in ipairs(self.actor.activeSkillList or {}) do if (activeSkill.skillTypes[SkillType.HasReservation] and not activeSkill.skillFlags.disable and activeSkill.buffList and activeSkill.buffList[1] and cfg and (isNameInBuffList(activeSkill.buffList, cfg.skillName) or isNameInBuffList(activeSkill.buffList, cfg.summonSkillName)) ) then local lifeBase = activeSkill.skillData["LifeReservedBase"] or 0 reservedPercentLife = m_floor(lifeBase / totalLife * 100) @@ -425,6 +425,7 @@ function ModStoreClass:EvalMod(mod, cfg, globalLimits) return end end + ---@type number local base = 0 if tag.varList then for _, var in pairs(tag.varList) do @@ -436,6 +437,7 @@ function ModStoreClass:EvalMod(mod, cfg, globalLimits) if tag.divVar then tag.div = GetMultiplier(self, tag.divVar, cfg) end + ---@type number local mult = m_floor(base / (tag.div or 1) + 0.0001) if tag.noFloor then mult = base / (tag.div or 1) @@ -456,6 +458,7 @@ function ModStoreClass:EvalMod(mod, cfg, globalLimits) mult = 1 / mult end if type(value) == "table" then + ---@cast value table value = copyTable(value) if value.mod then value.mod.value = value.mod.value * mult + (tag.base or 0) @@ -475,6 +478,7 @@ function ModStoreClass:EvalMod(mod, cfg, globalLimits) end end else + ---@cast value number value = value * mult + (tag.base or 0) if limitTotal then value = m_min(value, limitTotal) @@ -502,6 +506,7 @@ function ModStoreClass:EvalMod(mod, cfg, globalLimits) return end end + ---@type number local mult = 0 if tag.varList then for _, var in pairs(tag.varList) do @@ -534,6 +539,7 @@ function ModStoreClass:EvalMod(mod, cfg, globalLimits) if tag.divVar then tag.div = GetMultiplier(self, tag.divVar, cfg) end + ---@type number local mult = m_floor(base / (tag.div or 1) + 0.0001) local limitTotal if tag.limit or tag.limitVar then @@ -729,7 +735,7 @@ function ModStoreClass:EvalMod(mod, cfg, globalLimits) local itemSlot = tag.itemSlot:lower():gsub("(%l)(%w*)", function(a,b) return string.upper(a)..b end):gsub('^%s*(.-)%s*$', '%1') local items = {} if tag.allSlots then - items = self.actor.itemList + items = self.actor.itemList or {} elseif self.actor.itemList then if tag.bothSlots then local itemSlot1 = self.actor.itemList[itemSlot .. " 1"] diff --git a/src/Classes/NotableDBControl.lua b/src/Classes/NotableDBControl.lua index 63cfc8e133d..40440c6b363 100644 --- a/src/Classes/NotableDBControl.lua +++ b/src/Classes/NotableDBControl.lua @@ -18,11 +18,11 @@ local function IsAnointableNode(node) return node.recipe and #node.recipe >= 1 end ----@class NotableDBControl : ListControl +---@class NotableDBControl : ListControl ---@field itemsTab ItemsTab ----@field db table +---@field db table<"root"|integer, Node> ---@field dbType string ----@field dragTargetList ListControl[] +---@field dragTargetList ListControl[] ---@field sortControl table ---@field sortDropList table[] ---@field sortMode string @@ -37,7 +37,7 @@ local NotableDBClass = newClass("NotableDBControl", "ListControl") ---@param anchor? Anchor ---@param rect? Rect ---@param itemsTab ItemsTab ----@param db table +---@param db table<"root"|integer, Node> ---@param dbType string ---@return NotableDBControl function NotableDBClass:NotableDBControl(anchor, rect, itemsTab, db, dbType) @@ -211,7 +211,7 @@ function NotableDBClass:ListBuilder() self.defaultText = "^7No notables found that match those filters." end ----@param viewPort Rect +---@param viewPort Viewport function NotableDBClass:Draw(viewPort) if self.itemsTab.build.outputRevision ~= self.listOutputRevision then self.listBuildFlag = true diff --git a/src/Classes/NotesTab.lua b/src/Classes/NotesTab.lua index 362a288f040..04f07f8d2a9 100644 --- a/src/Classes/NotesTab.lua +++ b/src/Classes/NotesTab.lua @@ -97,7 +97,7 @@ function NotesTabClass:Save(xml) self.lastContent = self.controls.edit.buf end ----@param viewPort Rect +---@param viewPort Viewport ---@param inputEvents InputEvent[] function NotesTabClass:Draw(viewPort, inputEvents) self.x = viewPort.x diff --git a/src/Classes/PartyTab.lua b/src/Classes/PartyTab.lua index d9c236965fd..1aea86e9a2c 100644 --- a/src/Classes/PartyTab.lua +++ b/src/Classes/PartyTab.lua @@ -692,7 +692,7 @@ function PartyTabClass:Save(xml) self.lastContent.showAdvancedTools = self.controls.ShowAdvanceTools.state end ----@param viewPort Rect +---@param viewPort Viewport ---@param inputEvents InputEvent[] function PartyTabClass:Draw(viewPort, inputEvents) self.x = viewPort.x @@ -803,6 +803,7 @@ function PartyTabClass:ParseBuffs(list, buf, buffType, label) label[k1][k2] = tonumber(v) elseif line:match("|") then local k, tags, v = line:match("([%w ]-%w+)|(.+)=(.+)") + ---@cast tags string v = tonumber(v) for tag in tags:gmatch("([^|]*)|?") do if tag == "percent" then @@ -895,6 +896,7 @@ function PartyTabClass:ParseBuffs(list, buf, buffType, label) end end if currentName ~= "SKIP" then + ---@cast mod.source string if mod.source:match("Item") then local oldItem oldItem, mod.source = mod.source:match("Item:(%d+):(.+)") @@ -912,6 +914,7 @@ function PartyTabClass:ParseBuffs(list, buf, buffType, label) end end if buffType == "Link" then + ---@cast mod.name string mod.name = mod.name:gsub("Parent", "PartyMember") for _, modTag in ipairs(mod) do if modTag.actor and modTag.actor == "parent" then diff --git a/src/Classes/PassiveMasteryControl.lua b/src/Classes/PassiveMasteryControl.lua index b85d80bcf89..afd2b4a1e81 100644 --- a/src/Classes/PassiveMasteryControl.lua +++ b/src/Classes/PassiveMasteryControl.lua @@ -9,7 +9,7 @@ local m_min = math.min local m_max = math.max local m_floor = math.floor ----@class PassiveMasteryControl: ListControl +---@class PassiveMasteryControl: ListControl ---@field list MasterListElem[] ---@field treeTab TreeTab ---@field treeView PassiveTreeView @@ -45,7 +45,7 @@ function PassiveMasteryControlClass:PassiveMasteryControl(anchor, rect, list, tr return self end ----@param viewPort Rect +---@param viewPort Viewport function PassiveMasteryControlClass:Draw(viewPort) self.ListControl.Draw(self, viewPort) end diff --git a/src/Classes/PassiveSpec.lua b/src/Classes/PassiveSpec.lua index cef015a6810..8ad1c3b8387 100644 --- a/src/Classes/PassiveSpec.lua +++ b/src/Classes/PassiveSpec.lua @@ -23,9 +23,9 @@ local bor = bit.bor ---@field nodes table ---@field allocNodes table ---@field jewel_data table ----@field allocSubgraphNodes table ----@field allocExtendedNodes table ----@field jewels table +---@field allocSubgraphNodes integer[] +---@field allocExtendedNodes integer[] +---@field jewels table Maps socket/node id -> equipped jewel's item id ---@field subGraphs table ---@field masterySelections table ---@field hashOverrides table @@ -66,7 +66,7 @@ function PassiveSpecClass:Init(treeVersion, convert) for _, treeNode in pairs(self.tree.nodes) do -- Exclude proxy or groupless nodes, as well as expansion sockets if treeNode.group and not treeNode.isProxy and not treeNode.group.isProxy and (not treeNode.expansionJewel or not treeNode.expansionJewel.parent) then - ---@class Node + ---@class (partial) Node self.nodes[treeNode.id] = setmetatable({ linked = { }, power = { } @@ -120,7 +120,9 @@ end function PassiveSpecClass:Load(xml, dbFileName) self.title = xml.attrib.title -- Specs without this attribute predate the hash-fix migration and are treated as legacy. - self.clusterHashFormatVersion = tonumber(xml.attrib.clusterHashFormatVersion) or (xml.attrib.nodes and 1 or 2) + local clusterHashFormatVersion = tonumber(xml.attrib.clusterHashFormatVersion) or (xml.attrib.nodes and 1 or 2) + ---@cast clusterHashFormatVersion integer + self.clusterHashFormatVersion = clusterHashFormatVersion local url for _, node in pairs(xml) do if type(node) == "table" then @@ -271,14 +273,14 @@ function PassiveSpecClass:PostLoad() end -- Import passive spec from the provided class IDs and node hash list ----@param className string +---@param className? string ---@param classId integer ---@param ascendClassId? integer ---@param secondaryAscendClassId? integer ---@param hashList integer[] ----@param hashOverrides table? +---@param hashOverrides table? ---@param masteryEffects table? ----@param treeVersion string +---@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 @@ -299,7 +301,7 @@ function PassiveSpecClass:ImportFromNodeList(className, classId, ascendClassId, self.hashOverrides = hashOverrides -- move above setting allocNodes so we can compare mastery with selection wipeTable(self.masterySelections) - for mastery, effect in pairs(masteryEffects) do + for mastery, effect in pairs(masteryEffects or {}) do -- ignore ggg codes from profile import if (tonumber(effect) < 65536) then self.masterySelections[mastery] = effect @@ -337,7 +339,7 @@ function PassiveSpecClass:ImportFromNodeList(className, classId, ascendClassId, self:BuildAllDependsAndPaths() end ----@param nodes integer[] +---@param nodes string ---@param isCluster? boolean ---@param endian? "little"|"big" function PassiveSpecClass:AllocateDecodedNodes(nodes, isCluster, endian) @@ -359,7 +361,7 @@ function PassiveSpecClass:AllocateDecodedNodes(nodes, isCluster, endian) end end ----@param masteryEffects table +---@param masteryEffects string ---@param endian? "little"|"big" function PassiveSpecClass:AllocateMasteryEffects(masteryEffects, endian) for i = 1, #masteryEffects - 1, 4 do @@ -408,6 +410,7 @@ end ---@param url string ---@param return_tree_version_only? boolean ---@return string|integer[]? +---@overload fun(self, url: string, return_tree_version_only: true): string? 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. @@ -670,7 +673,7 @@ function PassiveSpecClass:SelectAscendClass(ascendClassId) self:BuildAllDependsAndPaths() end ----@param ascendClassId integer +---@param ascendClassId? integer function PassiveSpecClass:SelectSecondaryAscendClass(ascendClassId) -- if Secondary Ascendancy does not exist on this tree version if not self.tree.alternate_ascendancies then @@ -759,8 +762,11 @@ function PassiveSpecClass:ConnectToClass(classId) and node.type ~= "AscendClassStart" end + ---@type table local visited = {} + ---@type table local prev = {} + ---@type Node[] local queue = { targetStartNode } visited[targetStartNode] = true local head = 1 @@ -788,6 +794,7 @@ function PassiveSpecClass:ConnectToClass(classId) return false end + ---@type Node[] local pathBack = {} local current = foundNode while current do @@ -806,6 +813,7 @@ function PassiveSpecClass:ConnectToClass(classId) for idx = 2, #pathBack - 1 do altPath[idx] = pathBack[idx] local node = pathBack[idx] + ---@cast node Node if not node.alloc then self:AllocNode(node, altPath) end @@ -923,7 +931,7 @@ 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 visited Node[] ---@param noAscend? boolean ---@return Node? function PassiveSpecClass:FindStartFromNode(node, visited, noAscend) @@ -1097,7 +1105,8 @@ function PassiveSpecClass:AddMasteryEffectOptionsToNode(node) local treeNode = self.tree.nodes[node.id] local cacheNode = treeNode and treeNode.masteryCache if not cacheNode then - cacheNode = { id = node.id, sd = {} } + ---@diagnostic disable-next-line: missing-fields + cacheNode = { id = node.id, sd = {} } --[[@as Node]] -- minimal stand-in; only sd/mods/modList/modKey are used by ProcessStats if node.masteryEffects ~= nil and #node.masteryEffects > 0 then for _, effect in ipairs(node.masteryEffects) do effect = self.tree.masteryEffects[effect.effect] @@ -1118,6 +1127,7 @@ function PassiveSpecClass:AddMasteryEffectOptionsToNode(node) for k, v in pairs(cacheNode) do if k == "modList" then node.modList = new("ModList"):ModList() + ---@cast v Mod[] node.modList:AddList(v) else node[k] = v @@ -1173,6 +1183,7 @@ function PassiveSpecClass:BuildAllDependsAndPaths() abyss_special = 11, } -- This table will keep track of which nodes have been visited during each path-finding attempt + ---@type Node[] local visited = { } local attributes = { "Dexterity", "Intelligence", "Strength" } -- Read Abyss changes before resetting the nodes. Zorath needs the currently @@ -1295,7 +1306,7 @@ function PassiveSpecClass:BuildAllDependsAndPaths() local legionAdditions = self.tree.legion.additions local jewelType = timelessJewelTypeByConqueror[conqueredBy.conqueror.type] or 5 - local seed = conqueredBy.id + local seed = tonumber(conqueredBy.id) or 0 if jewelType == 5 then seed = seed / 20 end @@ -1320,6 +1331,7 @@ function PassiveSpecClass:BuildAllDependsAndPaths() end if jewelType >= 7 and node.type ~= "Mastery" then + ---@cast conqueredBy.modification table for _, component in ipairs(conqueredBy.modification) do local changedNode, replacesNode = data.resolveAbyssJewelComponent(component, self.tree.legion) if changedNode then @@ -1417,7 +1429,7 @@ function PassiveSpecClass:BuildAllDependsAndPaths() for _, addStat in ipairs(addition.sd) do self:NodeAdditionOrReplacementFromString(node, " \n" .. addStat) end - elseif next(jewelData) then + elseif next(jewelData --[[@as table]]) then -- unreachable (jewelData is numeric here; kept as defensive dead code) ConPrintf("Unhandled OP: " .. jewelData + 1) end end @@ -2043,6 +2055,7 @@ function PassiveSpecClass:BuildLegacyClusterOrbitMappings(indicies, proxyNode, c end local legacySkillsPerOrbit = self.tree.skillsPerOrbit[proxyNode.o + 1] + ---@cast legacySkillsPerOrbit integer local legacyProxyNodeOidxRelativeToClusterIndicies = self:TranslateClusterOrbitIndex(proxyNode.oidx, legacySkillsPerOrbit, clusterTotalIndicies) local legacyNodeIdsByOidx = { } local currentNodeIdsByOidx = { } @@ -2065,14 +2078,17 @@ end ---@param jewel Item ---@param parentSocket Node ----@param id integer +---@param id? integer ---@param upSize? integer ----@param importedNodes? table ----@param importedGroups? table +---@param importedNodes table +---@param importedGroups table function PassiveSpecClass:BuildSubgraph(jewel, parentSocket, id, upSize, importedNodes, importedGroups) local expansionJewel = parentSocket.expansionJewel local clusterJewel = jewel.clusterJewel local jewelData = jewel.jewelData + ---@cast jewelData JewelData + ---@cast clusterJewel ClusterJewelInfo + ---@cast expansionJewel { parent?: integer, size: integer, index: integer, proxy: string|integer } local subGraph = { nodes = { }, @@ -2166,7 +2182,8 @@ function PassiveSpecClass:BuildSubgraph(jewel, parentSocket, id, upSize, importe or (node.oidx == data.orbitIndex and not data.isMastery) if matches then for _, extendedId in ipairs(importedGroups[proxyGroup].nodes) do - if id == extendedId and inExtendedHashes(tonumber(id)) then + local numId = tonumber(id) + if id == extendedId and numId and inExtendedHashes(numId) then return true end end @@ -2197,6 +2214,7 @@ function PassiveSpecClass:BuildSubgraph(jewel, parentSocket, id, upSize, importe linked = { }, power = { }, } + ---@cast node Node t_insert(subGraph.nodes, node) -- Process and add it @@ -2260,6 +2278,7 @@ function PassiveSpecClass:BuildSubgraph(jewel, parentSocket, id, upSize, importe }) end + ---@type table local indicies = { } ---@param nodeIndex integer @@ -2417,6 +2436,7 @@ function PassiveSpecClass:BuildSubgraph(jewel, parentSocket, id, upSize, importe -- Convert from cluster-template index space into the tree's orbit index space. local skillsPerOrbit = self.tree.skillsPerOrbit[clusterJewel.sizeIndex+2] + ---@cast skillsPerOrbit integer local startOidx = data.clusterJewels.orbitOffsets[proxyNode.id][clusterJewel.sizeIndex] self:ApplyClusterOrbitIndexAdjustment(indicies, startOidx, clusterJewel.totalIndicies, skillsPerOrbit) self:BuildLegacyClusterOrbitMappings(indicies, proxyNode, clusterJewel.totalIndicies, skillsPerOrbit) diff --git a/src/Classes/PassiveSpecListControl.lua b/src/Classes/PassiveSpecListControl.lua index 35a4284b80d..434f3e6014a 100644 --- a/src/Classes/PassiveSpecListControl.lua +++ b/src/Classes/PassiveSpecListControl.lua @@ -7,7 +7,7 @@ local t_insert = table.insert local t_remove = table.remove local m_max = math.max ----@class PassiveSpecListControl: ListControl +---@class PassiveSpecListControl: ListControl ---@field treeTab TreeTab ---@field selIndex? integer ---@field selValue? PassiveSpec diff --git a/src/Classes/PassiveTree.lua b/src/Classes/PassiveTree.lua index 0096cfe301d..eb2adadd3e1 100644 --- a/src/Classes/PassiveTree.lua +++ b/src/Classes/PassiveTree.lua @@ -64,6 +64,99 @@ end ---@field width number ---@field height number +---@class MasteryEffect +---@field effect integer +---@field stats string[] +---@field reminderText? string[] +---@field sd? string[] + +---@class (partial) Node A passive tree node. Declared across PassiveTree.lua/PassiveSpec.lua with `(partial)`, so declarations merge. +---@field id integer +---@field dn string Display name +---@field sd string[] Stat description lines +---@field type "ClassStart"|"AscendClassStart"|"Mastery"|"Socket"|"Keystone"|"Notable"|"Normal" +---@field g? integer Raw group id (pre-migration; use `group` afterwards) +---@field o integer Orbit index +---@field oidx integer Index within the orbit +---@field group? PassiveTreeGroup +---@field x? number +---@field y? number +---@field angle? number +---@field alloc? boolean +---@field linked Node[] +---@field linkedId integer[] +---@field power table +---@field mods? table +---@field modList? ModList +---@field modKey? string +---@field depends Node[] +---@field path? Node[] +---@field pathDist? number +---@field distanceToClassStart? number +---@field visited? boolean +---@field connectedToStart? boolean +---@field name string +---@field ascendancyName? string +---@field classStartIndex? integer +---@field masteryCache? Node +---@field isAscendancyStart? boolean +---@field isMastery? boolean +---@field isMultipleChoiceOption? boolean +---@field isJewelSocket? boolean +---@field isProxy? boolean +---@field isTattoo? boolean +---@field isKeystone? boolean +---@field isNotable? boolean +---@field isSocket? boolean +---@field isBlighted? boolean +---@field m? boolean +---@field masteryEffects? MasteryEffect[] +---@field allMasteryOptions? boolean +---@field overrideType? string +---@field recipe? string[] +---@field icon? string +---@field activeIcon? string +---@field inactiveIcon? string +---@field startArt? string +---@field overlay? table +---@field rsq? number +---@field size? number +---@field sprites? table +---@field masterySprites? table +---@field effectSprites? table +---@field activeEffectImage? string +---@field bloodlineOverlayPrefix? string +---@field expansionJewel? { parent?: integer, size: integer, index: integer, proxy: string|integer } +---@field conqueredBy? ConqueredBy +---@field attrib? table +---@field elem? unknown +---@field extra? unknown +---@field charmSocket? boolean +---@field flavourText? string[] +---@field keystoneMod? unknown +---@field ks? unknown +---@field skill? integer +---@field spc? table +---@field orbit? integer +---@field orbitIndex? integer +---@field passivePointsGranted? integer +---@field grantedPassivePoints? integer +---@field stats? string[] +---@field expansionSkill? boolean +---@field targetType? string +---@field targetValue? unknown +---@field modKey2? string +---@field nodesInRadius? table +---@field weight2? number +---@field sortedStats? table +---@field modList2? table +---@field __index? table +---@field unknown? unknown +---@field out? unknown +---@field MinimumConnected? unknown +---@field divisor? number +---@field intuitiveLeapLikesAffecting Node[] + ---@class PassiveTree ---@field classes table[] A list of classes on the tree ---@field alternate_ascendancies table[]? @@ -83,7 +176,7 @@ end ---@field connectors table[] ---@field orbitRadii number[] ---@field skillsPerOrbit integer[] ----@field masteryEffects table +---@field masteryEffects table ---@field assets table ---@field legion table ---@field tattoo table @@ -548,7 +641,6 @@ function PassiveTreeClass:PassiveTree(treeVersion) self.masteryEffects = { } local nodeMap = { } for _, n in pairs(self.nodes) do - ---@class Node local node = n -- Migration... if versionNum < 3.10 then @@ -587,7 +679,8 @@ function PassiveTreeClass:PassiveTree(treeVersion) if node.masteryEffects then for _, effect in pairs(node.masteryEffects) do if not self.masteryEffects[effect.effect] then - self.masteryEffects[effect.effect] = { id = effect.effect, sd = effect.stats } + ---@diagnostic disable-next-line: missing-fields + self.masteryEffects[effect.effect] = { id = effect.effect, sd = effect.stats } --[[@as Node]] -- minimal stand-in; only sd/mods/modList/modKey are used by ProcessStats self:ProcessStats(self.masteryEffects[effect.effect]) else -- Copy multiline stats from an earlier ProcessStats call diff --git a/src/Classes/PassiveTreeView.lua b/src/Classes/PassiveTreeView.lua index d9e6c887b0d..25afe213aa5 100644 --- a/src/Classes/PassiveTreeView.lua +++ b/src/Classes/PassiveTreeView.lua @@ -20,7 +20,7 @@ local JEWEL_RADIUS_TINT_COMPARE_ONLY = { 0, 1, 0, 0.7 } local gemTooltip = require("Classes.GemTooltip") ----@param node Node +---@param node Node|JewelData ---@return string? local function isAbyssConquered(node) local conqueror = node and node.conqueredBy and node.conqueredBy.conqueror @@ -248,7 +248,7 @@ function PassiveTreeViewClass:GetCompareNodeColor(node, compareNode, spec, build end ---@param build Build ----@param viewPort Rect +---@param viewPort Viewport ---@param inputEvents InputEvent[] function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) local spec = build.spec @@ -400,6 +400,7 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) -- If hovering over a node, find the path to it (if unallocated) or the list of dependent nodes (if allocated) local hoverPath, hoverDep if self.traceMode then + ---@cast self.tracePath Node[] -- Path tracing mode is enabled if hoverNode then if not hoverNode.path then @@ -524,6 +525,7 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) end if targetBaseClassId then + ---@cast targetAscendClassId integer local used = spec:CountAllocNodes() local clickedAscendNodeId = hoverNode and hoverNode.id local function allocateClickedAscendancy() @@ -733,6 +735,7 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) end end + ---@type number[] local connectorColor = { 1, 1, 1 } ---@param r number ---@param g number @@ -798,7 +801,7 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) -- Fade out lines in ascendancy classes other than the current one setConnectorColor(0.75, 0.75, 0.75) end - SetDrawColor(unpack(connectorColor)) + SetDrawColor(connectorColor[1], connectorColor[2], connectorColor[3]) local assetName = connector.type .. state -- The game uses Abyss connector art only when both connected nodes are conquered. if isAbyssConquered(node1) and isAbyssConquered(node2) then @@ -1021,6 +1024,7 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) end end else + ---@diagnostic disable-next-line: param-type-mismatch SetDrawColor(self:GetCompareNodeColor(node, compareNode, spec, build, nodeDefaultColor)) end elseif launch.devModeAlt then @@ -1033,6 +1037,7 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) SetDrawColor(0, 0, 0) end else + ---@diagnostic disable-next-line: param-type-mismatch SetDrawColor(self:GetCompareNodeColor(node, compareNode, spec, build, nodeDefaultColor)) end @@ -1146,8 +1151,10 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) -- main tooltip is anchored from top left to the node local nodeX = m_floor(scrX + size) + ---@type number local ttX = m_floor(scrX + size) local nodeY = m_floor(scrY - size) + ---@type number local ttY = m_floor(scrY - size) @@ -1255,6 +1262,7 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) local radData = build.data.jewelRadius[jewel.jewelRadiusIndex] local outerSize = radData.outer * scale local innerSize = radData.inner * scale * 1.06 + ---@cast tint { [1]: number, [2]: number, [3]: number, [4]: number? } SetDrawColor(tint[1], tint[2], tint[3], tint[4]) if jewel.title:match("Impossible Escape") then -- Impossible Escape ring shows on the allocated Keystone @@ -1333,6 +1341,7 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) drawJewelRadius(jewel, scrX, scrY, tint) end if cHasRadius and not sameJewel then + ---@cast cJewel Item drawJewelRadius(cJewel, scrX, scrY, JEWEL_RADIUS_TINT_COMPARE_ONLY) end end @@ -1400,7 +1409,7 @@ end -- Zoom the tree in or out ---@param level number ----@param viewPort Rect +---@param viewPort Viewport function PassiveTreeViewClass:Zoom(level, viewPort) -- Calculate new zoom level and zoom factor self.zoomLevel = m_max(0, m_min(12, self.zoomLevel + level)) @@ -1418,7 +1427,7 @@ end ---@param x number ---@param y number ----@param viewPort Rect +---@param viewPort Viewport ---@param build Build function PassiveTreeViewClass:Focus(x, y, viewPort, build) self.zoomLevel = 12 @@ -1703,6 +1712,7 @@ function PassiveTreeViewClass:AddNodeTooltip(tooltip, node, build, returnEarly) end end end + ---@cast mNode Node local isRunegraft = mNode.overrideType == "AlternateMastery" if mNode.sd[1] and mNode.allMasteryOptions and not isRunegraft then diff --git a/src/Classes/PathControl.lua b/src/Classes/PathControl.lua index d95042fc38a..519e4d78dd5 100644 --- a/src/Classes/PathControl.lua +++ b/src/Classes/PathControl.lua @@ -9,7 +9,7 @@ local t_insert = table.insert ---@class PathFolder ---@field label string ---@field path string ----@field button ButtonControl +---@field button? ButtonControl ---@class PathControl: Control, ControlHost, UndoHandler ---@field basePath string @@ -90,7 +90,7 @@ function PathClass:IsMouseOver() return self:IsMouseInBounds() or self:GetMouseOverControl() end ----@param viewPort Rect +---@param viewPort Viewport function PathClass:Draw(viewPort) local x, y = self:GetPos() local width, height = self:GetSize() diff --git a/src/Classes/PoBArchivesProvider.lua b/src/Classes/PoBArchivesProvider.lua index 431a1a6c9ec..b6e58da275b 100644 --- a/src/Classes/PoBArchivesProvider.lua +++ b/src/Classes/PoBArchivesProvider.lua @@ -34,6 +34,7 @@ function PoBArchivesProviderClass:GetApiUrl() if self.importCode then return archivesUrl .. '/api/' .. 'recommendations' else + ---@cast self.activeList string return archivesUrl .. '/api/builds?q=' .. string.lower(self.activeList) end end @@ -145,6 +146,7 @@ function PoBArchivesProviderClass:GetBuilds() if self.mode == 'similar' then + ---@cast self.importCode string self:GetRecommendations(self.importCode,self:GetApiUrl()) return else diff --git a/src/Classes/PopupDialog.lua b/src/Classes/PopupDialog.lua index 8bc6a175a3f..c2c13577ec2 100644 --- a/src/Classes/PopupDialog.lua +++ b/src/Classes/PopupDialog.lua @@ -56,7 +56,7 @@ function PopupDialogClass:PopupDialog(width, height, title, controls, enterContr return self end ----@param viewPort Rect +---@param viewPort Viewport function PopupDialogClass:Draw(viewPort) local x, y = self:GetPos() local width, height = self:GetSize() @@ -86,7 +86,7 @@ function PopupDialogClass:Draw(viewPort) end ---@param inputEvents InputEvent[] ----@param viewPort Rect +---@param viewPort Viewport 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 6d7d7bacb58..7d9143f84cc 100644 --- a/src/Classes/PowerReportListControl.lua +++ b/src/Classes/PowerReportListControl.lua @@ -8,7 +8,7 @@ local t_insert = table.insert local t_remove = table.remove local t_sort = table.sort ----@class PowerReportListControl: ListControl +---@class PowerReportListControl: ListControl ---@field nodeSelectCallback fun(node: Node) ---@field originalList Node[] ---@field powerColumn ListColumn diff --git a/src/Classes/ResizableEditControl.lua b/src/Classes/ResizableEditControl.lua index 59689a98ce9..316dfbf25b7 100644 --- a/src/Classes/ResizableEditControl.lua +++ b/src/Classes/ResizableEditControl.lua @@ -45,7 +45,7 @@ function ResizableEditClass:ResizableEditControl(anchor, rect, init, prompt, fil return self end ----@param viewPort Rect +---@param viewPort Viewport ---@param noTooltip? boolean function ResizableEditClass:Draw(viewPort, noTooltip) self:SetBoundedDrag(self) diff --git a/src/Classes/ScrollBarControl.lua b/src/Classes/ScrollBarControl.lua index ab747bd5d1a..7c5faadcb44 100644 --- a/src/Classes/ScrollBarControl.lua +++ b/src/Classes/ScrollBarControl.lua @@ -27,6 +27,7 @@ local m_floor = math.floor ---@field holdTime? number ---@field holdPauseTime? number ---@field holdRepeating? boolean +---@overload fun(mix: table): ScrollBarControl local ScrollBarClass = newClass("ScrollBarControl", "Control") ---@param anchor? Anchor diff --git a/src/Classes/SharedItemListControl.lua b/src/Classes/SharedItemListControl.lua index 2fab74b30fa..9513a341df3 100644 --- a/src/Classes/SharedItemListControl.lua +++ b/src/Classes/SharedItemListControl.lua @@ -7,10 +7,10 @@ local pairs = pairs local t_insert = table.insert local t_remove = table.remove ----@class SharedItemListControl: ListControl +---@class SharedItemListControl: ListControl ---@field itemsTab ItemsTab ---@field defaultText string ----@field dragTargetList ListControl[] +---@field dragTargetList ListControl[] ---@field label string ---@field selDragging? boolean ---@field selIndex? integer @@ -70,7 +70,7 @@ end ---@param type string ---@param value Item ----@param source? ListControl +---@param source? ListControl function SharedItemListClass:ReceiveDrag(type, value, source) if type == "Item" then local rawItem = { raw = value:BuildRaw() } diff --git a/src/Classes/SharedItemSetListControl.lua b/src/Classes/SharedItemSetListControl.lua index 5dd14c7dabe..e3dc31bfabb 100644 --- a/src/Classes/SharedItemSetListControl.lua +++ b/src/Classes/SharedItemSetListControl.lua @@ -12,7 +12,7 @@ local s_format = string.format ---@field title? string ---@field slots table ----@class SharedItemSetListControl: ListControl +---@class SharedItemSetListControl: ListControl ---@field itemsTab ItemsTab ---@field defaultText string ---@field selIndex? integer @@ -104,7 +104,7 @@ end ---@param type string ---@param value ItemSet ----@param source? ListControl +---@param source? ListControl function SharedItemSetListClass:ReceiveDrag(type, value, source) if type == "ItemList" then local sharedItemList = { title = value.title, slots = { } } diff --git a/src/Classes/SkillListControl.lua b/src/Classes/SkillListControl.lua index 45431d4e86d..b505a5b812e 100644 --- a/src/Classes/SkillListControl.lua +++ b/src/Classes/SkillListControl.lua @@ -26,7 +26,7 @@ local slot_map = { ["Belt"] = { icon = NewImageHandle(), path = "Assets/icon_belt.png" }, } ----@class SkillListControl: ListControl +---@class SkillListControl: ListControl
---@field skillsTab SkillsTab ---@field label string ---@field selIndex? integer @@ -255,7 +255,7 @@ function SkillListClass:OnHoverKeyUp(key) end ----@param viewPort Rect +---@param viewPort Viewport function SkillListClass:Draw(viewPort) self.ListControl.Draw(self, viewPort) end diff --git a/src/Classes/SkillSetListControl.lua b/src/Classes/SkillSetListControl.lua index 12945cf3f0c..d4d015243d8 100644 --- a/src/Classes/SkillSetListControl.lua +++ b/src/Classes/SkillSetListControl.lua @@ -8,7 +8,7 @@ local t_remove = table.remove local m_max = math.max local s_format = string.format ----@class SkillSetListControl: ListControl +---@class SkillSetListControl: ListControl ---@field skillsTab SkillsTab ---@field selIndex? integer ---@field selValue? integer diff --git a/src/Classes/SkillsTab.lua b/src/Classes/SkillsTab.lua index 997aeb2e382..48061570ae8 100644 --- a/src/Classes/SkillsTab.lua +++ b/src/Classes/SkillsTab.lua @@ -669,7 +669,7 @@ function SkillsTabClass:Save(xml) end end ----@param viewPort Rect +---@param viewPort Viewport ---@param inputEvents InputEvent[] function SkillsTabClass:Draw(viewPort, inputEvents) self.x = viewPort.x diff --git a/src/Classes/SliderControl.lua b/src/Classes/SliderControl.lua index f92018549de..c28712d2100 100644 --- a/src/Classes/SliderControl.lua +++ b/src/Classes/SliderControl.lua @@ -97,7 +97,7 @@ function SliderClass:GetKnobXForVal() return knobTravel * self.val end ----@param viewPort Rect +---@param viewPort Viewport function SliderClass:Draw(viewPort) local x, y = self:GetPos() local width, height = self:GetSize() diff --git a/src/Classes/TextListControl.lua b/src/Classes/TextListControl.lua index 7b448e23d82..4db53098413 100644 --- a/src/Classes/TextListControl.lua +++ b/src/Classes/TextListControl.lua @@ -38,7 +38,7 @@ function TextListClass:IsMouseOver() return self:IsMouseInBounds() or self:GetMouseOverControl() end ----@param viewPort Rect +---@param viewPort Viewport function TextListClass:Draw(viewPort) local x, y = self:GetPos() local width, height = self:GetSize() diff --git a/src/Classes/TimelessJewelListControl.lua b/src/Classes/TimelessJewelListControl.lua index 95a7cbe3443..c0755169649 100644 --- a/src/Classes/TimelessJewelListControl.lua +++ b/src/Classes/TimelessJewelListControl.lua @@ -9,7 +9,7 @@ local m_min = math.min local m_max = math.max local t_concat = table.concat ----@class TimelessJewelListControl: ListControl +---@class TimelessJewelListControl: ListControl
---@field build Build ---@field list table ---@field noTooltip? boolean @@ -31,7 +31,7 @@ function TimelessJewelListControlClass:TimelessJewelListControl(anchor, rect, bu return self end ----@param viewPort Rect +---@param viewPort Viewport ---@param noTooltip? boolean function TimelessJewelListControlClass:Draw(viewPort, noTooltip) self.noTooltip = noTooltip diff --git a/src/Classes/TimelessJewelSocketControl.lua b/src/Classes/TimelessJewelSocketControl.lua index 94e063ee9e5..ff3a6b0cd97 100644 --- a/src/Classes/TimelessJewelSocketControl.lua +++ b/src/Classes/TimelessJewelSocketControl.lua @@ -8,8 +8,10 @@ local m_min = math.min ---@class TimelessJewelSocket ---@field id integer +---@field label string +---@field keystone string ----@class TimelessJewelSocketControl: DropDownControl +---@class TimelessJewelSocketControl: DropDownControl ---@field build Build ---@field socketViewer PassiveTreeView local TimelessJewelSocketClass = newClass("TimelessJewelSocketControl", "DropDownControl") @@ -28,7 +30,7 @@ function TimelessJewelSocketClass:TimelessJewelSocketControl(anchor, rect, list, return self end ----@param viewPort Rect +---@param viewPort Viewport ---@param noTooltip? boolean function TimelessJewelSocketClass:Draw(viewPort, noTooltip) local x, y = self:GetPos() diff --git a/src/Classes/Tooltip.lua b/src/Classes/Tooltip.lua index 23f85bfe143..c52700f08c4 100644 --- a/src/Classes/Tooltip.lua +++ b/src/Classes/Tooltip.lua @@ -42,10 +42,11 @@ end ---@class TooltipLine ---@field size number ----@field text string|boolean +---@field text? string ---@field font Font ---@field modLine? ModLine ----@field background? number[] +---@field background? number[]|string|ImageHandle +---@field bounds? { x: number, y: number, width: number, height: number } ---@class Tooltip ---@field lines TooltipLine[] @@ -121,10 +122,10 @@ function TooltipClass:CheckForUpdate(...) end ---@param size number ----@param text string|boolean +---@param text string|boolean|nil ---@param font? Font ---@param modLine? ModLine ----@param background? number[] +---@param background? number[]|string|ImageHandle function TooltipClass:AddLine(size, text, font, modLine, background) if text then local fontToUse @@ -201,6 +202,7 @@ end ---@return number width ---@return number height function TooltipClass:GetSize() + ---@type number, number local ttW, ttH = 0, 0 for i, data in ipairs(self.lines) do if data.text or (self.lines[i - 1] and self.lines[i + 1] and self.lines[i + 1].text) then @@ -231,7 +233,7 @@ function TooltipClass:GetSize() return ttW + H_PAD, ttH + V_PAD end ----@param viewPort Rect +---@param viewPort Viewport ---@return number width ---@return number height function TooltipClass:GetDynamicSize(viewPort) @@ -250,7 +252,7 @@ end ---@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 Rect Active screen boundaries +---@param viewPort Viewport 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) @@ -417,7 +419,7 @@ end ---@param y number y-coordinate to draw the tooltip at ---@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 +---@param viewPort Viewport Active screen boundaries ---@return number? width ---@return number? height function TooltipClass:Draw(x, y, w, h, viewPort) diff --git a/src/Classes/TooltipHost.lua b/src/Classes/TooltipHost.lua index 8958b88f4c0..af238903047 100644 --- a/src/Classes/TooltipHost.lua +++ b/src/Classes/TooltipHost.lua @@ -22,7 +22,7 @@ end ---@param y number ---@param width number ---@param height number ----@param viewPort Rect +---@param viewPort Viewport ---@param ... unknown function TooltipHostClass:DrawTooltip(x, y, width, height, viewPort, ...) if self.tooltipFunc then diff --git a/src/Classes/TradeHelpers.lua b/src/Classes/TradeHelpers.lua index 05c72df77c5..8cde0c9bbae 100644 --- a/src/Classes/TradeHelpers.lua +++ b/src/Classes/TradeHelpers.lua @@ -81,7 +81,7 @@ function M.getRarityColor(item) end -- Helper: normalize a mod line by replacing numbers with "#" for template matching ----@param line ModLine +---@param line string ---@return string template ---@return integer replacements function M.modLineTemplate(line) @@ -91,7 +91,7 @@ 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 ModLine +---@param line string ---@param onlyFromTo? boolean @Whether to only check for # to # matches. ---@return number? value function M.modLineValue(line, onlyFromTo) @@ -163,8 +163,8 @@ M.sourceTypeToCategory = { } -- inverses a mod. e.g. more x -> less x ----@param modLine ModLine ----@return ModLine modLine +---@param modLine string +---@return string modLine ---@return string? inverseKey function M.swapInverse(modLine) local priorStr = modLine @@ -192,7 +192,7 @@ function M.swapInverse(modLine) end ----@param modLine ModLine +---@param modLine string ---@param modType string ---@return string? tradeId ---@return TradeOptionValue? value @Only returned when applicable (primarily timeless jewels). @@ -257,7 +257,7 @@ local function insertUniqueHash(resultIds, tradeHash) end end -- Helper: find the trade stat ID for a mod line ----@param modLine ModLine +---@param modLine string ---@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. @@ -676,6 +676,7 @@ function M.drawCompactSlotRow(drawY, slotLabel, pItem, cItem, -- Determine hovered item and tooltip anchor position local hoverItem = nil local hoverItemsTab = nil + ---@type number, number, number, number local hoverBoxX, hoverBoxY, hoverBoxW, hoverBoxH = 0, 0, 0, 0 if pHover then hoverItem = pItem diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index ec3d187fff6..740f113f86c 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -846,9 +846,9 @@ 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 calcFunc fun(adjustments?: table): Output ---@param baseOutput Output ----@return table? +---@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 @@ -1065,9 +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 itemEntries TradeQueryItem[] ---@param slotName? string ----@return table[] +---@return TradeQueryItem[] function TradeQueryClass:FilterToSafeItems(itemEntries, slotName) local itemsSafe = {} for _, entry in ipairs(itemEntries) do @@ -1108,6 +1108,7 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro self:SetNotice(context.controls.pbNotice, "") end if main.api.authToken == nil then + ---@cast self.pbLeague string local url = self.tradeQueryRequests:buildUrl(self.hostName .. "trade/search", self.pbRealm, self.pbLeague) url = url .. "?q=" .. urlEncode(query) controls["uri"..context.row_idx]:SetText(url, true) @@ -1115,6 +1116,7 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro end context.controls["priceButton"..context.row_idx].label = "Searching..." self.lastQueries[row_idx] = query + ---@cast self.pbLeague string self.tradeQueryRequests:SearchWithQueryWeightAdjusted(self.pbRealm, self.pbLeague, query, function(items, errMsg) if errMsg then @@ -1152,6 +1154,7 @@ function TradeQueryClass:PriceItemRowDisplay(row_idx, top_pane_alignment_ref, ro end, { callbackQueryId = function(queryId) + ---@cast self.pbLeague string local url = self.tradeQueryRequests:buildUrl(self.hostName .. "trade/search", self.pbRealm, self.pbLeague, queryId) controls["uri"..context.row_idx]:SetText(url, true) end @@ -1171,6 +1174,7 @@ you can add them, copy the link here, and press "Price Item" to evaluate the ite local buttonWidth, _ = button:GetSize() local nodeId = slotTbl.nodeId if not nodeId then return end + ---@cast nodeId integer local boxSize = 250 -- anchor bottom to top of button local viewerY = y - boxSize - 4 @@ -1298,6 +1302,7 @@ you can add them, copy the link here, and press "Price Item" to evaluate the ite controls["importButton"..row_idx] = new("ButtonControl"):ButtonControl({ "TOPLEFT", controls["resultDropdown"..row_idx], "TOPRIGHT"}, {8, 0, 100, row_height}, "Import Item", function() self.itemsTab:CreateDisplayItemFromRaw(self.resultTbl[row_idx][self.itemIndexTbl[row_idx]].item_string) local item = self.itemsTab.displayItem + ---@cast item Item -- pass "true" to not auto equip it as we will have our own logic self.itemsTab:AddDisplayItem(true) -- Autoequip it diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index dec0a17e49c..bc0ac3ffb28 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -148,14 +148,7 @@ 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 +---@alias TradeQueryGeneratorMod ItemDataMod ---@class TradeQuerySelectorOption ---@field label string @@ -208,11 +201,12 @@ function TradeQueryGeneratorClass:TradeQueryGenerator(queryTab) self.lastMaxLevel = nil return self end ----@param baseOutput Output ----@param newOutput Output +---@param baseOutput Output|table +---@param newOutput Output|table ---@param statWeights WeightedPowerStat[] ---@return number function TradeQueryGeneratorClass.WeightedRatioOutputs(baseOutput, newOutput, statWeights) + ---@type number local meanStatDiff = 0 ---@return number local function ratioModSums(...) @@ -305,6 +299,7 @@ function TradeQueryGeneratorClass:ProcessMod(modId, mod, tradeQueryStatsParsed, if self.modData[modType][uniqueIndex] == nil then local tradeMod = nil -- Try to match to a local mod fallback to global if no match + ---@cast mod.group string if mod.group:match("Local") then local matchLocalStr = (modLine .. " (Local)"):gsub("[#()0-9%-%+%.]","") for _, entry in pairs(getStatEntries(modType) or {}) do @@ -348,6 +343,7 @@ function TradeQueryGeneratorClass:ProcessMod(modId, mod, tradeQueryStatsParsed, -- tokenize the numerical variables for this mod and store the sign if there is one local tokens = { } + ---@type number, number local poundPos, tokenizeOffset = 0, 0 while true do poundPos = self.modData[modType][uniqueIndex].tradeMod.text:find("#", poundPos + 1) @@ -1242,6 +1238,7 @@ function TradeQueryGeneratorClass:RequestQuery(slot, context, statWeights, callb local controls = { } local options = { } + ---@type number local popupHeight = 110 local popupWidth = 480 diff --git a/src/Classes/TradeQueryRateLimiter.lua b/src/Classes/TradeQueryRateLimiter.lua index 03c9c8aaaba..4e3df74ce8e 100644 --- a/src/Classes/TradeQueryRateLimiter.lua +++ b/src/Classes/TradeQueryRateLimiter.lua @@ -169,6 +169,7 @@ function TradeQueryRateLimiterClass:UpdateFromHeader(headerString, policy) self.policies[policyKey] = policyValue else for rule, ruleValue in pairs(policyValue) do + ---@cast ruleValue TradeRateRule for window, state in pairs(ruleValue.state) do local oldState = self.policies[policyKey][rule]["state"][window] if state.request > oldState.request then @@ -181,6 +182,7 @@ function TradeQueryRateLimiterClass:UpdateFromHeader(headerString, policy) -- calculate maxWindow sizes for requestHistory tables local maxWindow = 0 for _, rule in pairs(policyValue) do + ---@cast rule TradeRateRule for window, _ in pairs(rule.limits) do maxWindow = math.max(maxWindow, window) end @@ -211,6 +213,7 @@ function TradeQueryRateLimiterClass:NextRequestTime(policy, time) end self:AgeOutRequests(policy) for _, rule in pairs(self.policies[policy]) do + ---@cast rule TradeRateRule for window, _ in pairs(rule.limits) do if rule.state[window].timeout > 0 then --an extra second is added to the time calculations here and below in order to avoid problems caused by the low resolution of os.time() @@ -233,6 +236,7 @@ function TradeQueryRateLimiterClass:NextRequestTime(policy, time) else -- the expiration time of oldest timestamp in the window local nextAvailableTime = self.requestHistory[policy].timestamps[oldestRequestIdx] + window + 1 + ---@cast nextAvailableTime integer nextTime = math.max(nextTime, nextAvailableTime) end end @@ -261,6 +265,7 @@ function TradeQueryRateLimiterClass:InsertRequest(policy, timestamp, time) table.insert(self.requestHistory[policy].timestamps, insertIndex, timestamp) if self.policies[policy] then for _, rule in pairs(self.policies[policy]) do + ---@cast rule TradeRateRule for _, window in pairs(rule.state) do window.request = window.request + 1 end @@ -297,6 +302,7 @@ function TradeQueryRateLimiterClass:AgeOutRequests(policy, time) for i = #requestHistory.timestamps, 1 , -1 do local timestamp = requestHistory.timestamps[i] for _, rule in pairs(self.policies[policy]) do + ---@cast rule TradeRateRule for window, windowValue in pairs(rule.state) do if timestamp >= (requestHistory.lastCheck - window) and timestamp < (now - window) then -- timestamp that used to be in the window on last check @@ -313,6 +319,7 @@ function TradeQueryRateLimiterClass:AgeOutRequests(policy, time) end -- Reset flags after processing for _, rule in pairs(self.policies[policy]) do + ---@cast rule TradeRateRule for window, windowValue in pairs(rule.state) do windowValue.decremented = nil end @@ -327,6 +334,7 @@ end function TradeQueryRateLimiterClass:ReduceLimits(margin, policies) for _, policy in pairs(policies) do for _, rule in pairs(policy) do + ---@cast rule TradeRateRule for _, window in pairs(rule.limits) do window.request = math.max(window.request - margin, 1) end diff --git a/src/Classes/TradeQueryRequests.lua b/src/Classes/TradeQueryRequests.lua index 430b843a835..c53dfcc407d 100644 --- a/src/Classes/TradeQueryRequests.lua +++ b/src/Classes/TradeQueryRequests.lua @@ -447,6 +447,7 @@ end ---@return unknown? callbackResult function TradeQueryRequestsClass:SearchWithURL(url, callback) local subpath = url:match(self.hostNamePattern .. "trade/search/(.+)$") + ---@cast subpath string local paths = {} for path in subpath:gmatch("[^/]+") do table.insert(paths, path) diff --git a/src/Classes/TradeStatWeightMultiplierListControl.lua b/src/Classes/TradeStatWeightMultiplierListControl.lua index bb643f47be6..610a3a620bb 100644 --- a/src/Classes/TradeStatWeightMultiplierListControl.lua +++ b/src/Classes/TradeStatWeightMultiplierListControl.lua @@ -14,7 +14,7 @@ ---@field Slider SliderControl ---@field SliderValue LabelControl ----@class TradeStatWeightMultiplierListControl: ListControl +---@class TradeStatWeightMultiplierListControl: ListControl ---@field list TradeStatWeightMultiplier[] ---@field indexController TradeStatWeightMultiplierIndexController ---@field selIndex? integer @@ -34,7 +34,7 @@ function TradeStatWeightMultiplierListControlClass:TradeStatWeightMultiplierList return self end ----@param viewPort Rect +---@param viewPort Viewport ---@param noTooltip? boolean function TradeStatWeightMultiplierListControlClass:Draw(viewPort, noTooltip) self.noTooltip = noTooltip diff --git a/src/Classes/TreeTab.lua b/src/Classes/TreeTab.lua index dd9185d675a..120b98f1b6a 100644 --- a/src/Classes/TreeTab.lua +++ b/src/Classes/TreeTab.lua @@ -325,6 +325,8 @@ function TreeTabClass:TreeTab(build) end) self.controls.treeHeatMap.tooltipText = function() local offCol, defCol = main.nodePowerTheme:match("(%a+)/(%a+)") + ---@cast offCol string + ---@cast defCol string return "When enabled, an estimate of the offensive and defensive strength of\neach unallocated passive is calculated and displayed visually.\nOffensive power shows as "..offCol:lower()..", defensive power as "..defCol:lower().."." end @@ -425,7 +427,7 @@ function TreeTabClass:RemoveTattooFromNode(node) node.allMasteryOptions = false self.build.spec:BuildAllDependsAndPaths() end ----@param viewPort Rect +---@param viewPort Viewport ---@param inputEvents InputEvent[] function TreeTabClass:Draw(viewPort, inputEvents) self.anchorControls.x = viewPort.x + 4 @@ -596,7 +598,9 @@ function TreeTabClass:Load(xml, dbFileName) if not self.specList[1] then self.specList[1] = new("PassiveSpec"):PassiveSpec(self.build, latestTreeVersion) end - self:SetActiveSpec(tonumber(xml.attrib.activeSpec) or 1) + ---@type integer + local activeSpec = tonumber(xml.attrib.activeSpec) or 1 + self:SetActiveSpec(activeSpec) end function TreeTabClass:PostLoad() @@ -770,6 +774,10 @@ function TreeTabClass:OpenImportPopup() -- treeVersion is not known at this point. We need to decode the URL to get it. local tmpSpec = new("PassiveSpec"):PassiveSpec(self.build, latestTreeVersion) local newTreeVersion_or_errMsg = tmpSpec:DecodePoePlannerURL(treeLink, true) + if not newTreeVersion_or_errMsg then + controls.msg.label = "^1Invalid tree link." + return + end -- Check for an error message if string.find(newTreeVersion_or_errMsg, "Invalid") then controls.msg.label = "^1"..newTreeVersion_or_errMsg @@ -900,6 +908,7 @@ end function TreeTabClass:OpenExportPopup() local treeLink = self.build.spec:EncodeURL(treeVersions[self.build.spec.treeVersion].url) + ---@type PopupDialog local popup local controls = { } controls.label = new("LabelControl"):LabelControl(nil, {0, 20, 0, 16}, "Passive tree link:") @@ -1096,7 +1105,8 @@ function TreeTabClass:SaveMasteryPopup(node, listControl) self.build.spec.tree:ProcessStats(node) self.build.spec.masterySelections[node.id] = effect.id if not node.alloc then - self.build.spec:AllocNode(node, self.viewer.tracePath and node == self.viewer.tracePath[#self.viewer.tracePath] and self.viewer.tracePath) + local altPath = (self.viewer.tracePath and node == self.viewer.tracePath[#self.viewer.tracePath] and self.viewer.tracePath) or nil + self.build.spec:AllocNode(node, altPath) end self.build.spec:AddUndoState() self.modFlag = true @@ -1104,7 +1114,7 @@ function TreeTabClass:SaveMasteryPopup(node, listControl) main:ClosePopup() end ---@param node Node ----@param viewPort Rect +---@param viewPort Viewport function TreeTabClass:OpenMasteryPopup(node, viewPort) local controls = { } local effects = { } @@ -1112,7 +1122,7 @@ function TreeTabClass:OpenMasteryPopup(node, viewPort) local cachedAllMasteryOption = node.allMasteryOptions wipeTable(effects) - for _, effect in pairs(node.masteryEffects) do + for _, effect in pairs(node.masteryEffects or {}) do local assignedNodeId = isValueInTable(self.build.spec.masterySelections, effect.effect) if not assignedNodeId or assignedNodeId == node.id then t_insert(effects, {label = t_concat(effect.stats, " / "), id = effect.effect}) @@ -1131,7 +1141,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 +---@param powerStat? PowerStat function TreeTabClass:SetPowerCalc(powerStat) self.viewer.showHeatMap = true self.build.buildFlag = true @@ -1222,7 +1232,7 @@ function TreeTabClass:BuildPowerReportList(currentStat) -- search all nodes, ignoring ascendancies, sockets, etc. for nodeId, node in pairs(self.build.spec.nodes) do - local isAlloc = node.alloc or self.build.calcsTab.mainEnv.grantedPassives[nodeId] + local isAlloc = node.alloc or self.build.calcsTab.mainEnv.grantedPassives[nodeId] or false if (node.type == "Normal" or node.type == "Keystone" or node.type == "Notable") and not node.ascendancyName then local pathDist = getNodePathDist(node, isAlloc) local nodePower = (node.power.singleStat or 0) * powerMultiplier @@ -1403,6 +1413,7 @@ function TreeTabClass:FindTimelessJewel() end t_sort(abyssAscendancyOptions, function(a, b) return a.label < b.label end) t_insert(abyssAscendancyOptions, 1, { label = "Any" }) + ---@type { label: string, keystone: string, id: integer }[] local jewelSockets = { } t_insert(jewelSockets, { label = "All Sockets", @@ -1513,14 +1524,18 @@ function TreeTabClass:FindTimelessJewel() end ---@return table local function getNodeWeights() + ---@cast controls.nodeSliderValue.label string + ---@cast controls.nodeSlider2Value.label string + ---@cast controls.nodeSlider3Value.label string local nodeWeights = { [1] = controls.nodeSliderValue.label:sub(3):lower(), [2] = controls.nodeSlider2Value.label:sub(3):lower(), [3] = controls.nodeSlider3Value.label:sub(3):lower() } for i, nodeWeight in ipairs(nodeWeights) do - if tonumber(nodeWeight) ~= nil then - nodeWeights[i] = round(tonumber(nodeWeight), 3) + local numWeight = tonumber(nodeWeight) + if numWeight ~= nil then + nodeWeights[i] = round(numWeight, 3) end end return nodeWeights @@ -1871,7 +1886,7 @@ function TreeTabClass:FindTimelessJewel() local nodeSliderStatLabel = "None" controls.nodeSlider = new("SliderControl"):SliderControl({"TOPLEFT", controls.socketAllocate, "BOTTOMLEFT"}, {0, rowSpacing, 200, rowHeight}, function(value) controls.nodeSliderValue.label = s_format("^7%.3f", value * 10) - parseSearchList(1, controls.searchListFallback and controls.searchListFallback.shown or false) + parseSearchList(1, controls.searchListFallback and controls.searchListFallback:IsShown() or false) end, scrollWheelSpeedTbl) controls.nodeSliderLabel = new("LabelControl"):LabelControl({"RIGHT", controls.nodeSlider, "LEFT"}, {-labelSpacing, 0, 0, labelHeight}, "^7Primary Node Weight:") controls.nodeSlider.tooltipFunc = function(tooltip, mode, index, value) @@ -1900,7 +1915,7 @@ function TreeTabClass:FindTimelessJewel() local nodeSlider2StatLabel = "None" controls.nodeSlider2 = new("SliderControl"):SliderControl({"TOPLEFT", controls.nodeSlider, "BOTTOMLEFT"}, {0, rowSpacing, 200, rowHeight}, function(value) controls.nodeSlider2Value.label = s_format("^7%.3f", value * 10) - parseSearchList(1, controls.searchListFallback and controls.searchListFallback.shown or false) + parseSearchList(1, controls.searchListFallback and controls.searchListFallback:IsShown() or false) end, scrollWheelSpeedTbl) controls.nodeSlider2Label = new("LabelControl"):LabelControl({"RIGHT", controls.nodeSlider2, "LEFT"}, {-labelSpacing, 0, 0, labelHeight}, "^7Secondary Node Weight:") controls.nodeSlider2.tooltipFunc = function(tooltip, mode, index, value) @@ -1932,7 +1947,7 @@ function TreeTabClass:FindTimelessJewel() else controls.nodeSlider3Value.label = s_format("^7%.f", value * 500) end - parseSearchList(1, controls.searchListFallback and controls.searchListFallback.shown or false) + parseSearchList(1, controls.searchListFallback and controls.searchListFallback:IsShown() or false) end, scrollWheelSpeedTbl2) controls.nodeSlider3Label = new("LabelControl"):LabelControl({"RIGHT", controls.nodeSlider3, "LEFT"}, {-labelSpacing, 0, 0, labelHeight}, "^7Minimum Node Weight:") controls.nodeSlider3.tooltipFunc = function(tooltip, mode, index, value) @@ -1985,6 +2000,7 @@ function TreeTabClass:FindTimelessJewel() ---@return string primaryLabel ---@return string secondaryLabel local function getLegionStatLabels(legionPassive) + ---@cast legionPassive.sortedStats string[] local statCount = timelessData.jewelType.id >= 7 and #legionPassive.sortedStats or #legionPassive.sd if statCount > #legionPassive.sd then return statCount, "Minimum value: " .. legionPassive.sd[1], "Maximum value: " .. legionPassive.sd[1] @@ -2112,6 +2128,8 @@ function TreeTabClass:FindTimelessJewel() local function buildStatModLists(legionPassive) -- Give each stat its own mod list even when several stats share one display line. local modLists = { } + ---@cast legionPassive.sortedStats string[] + ---@cast legionPassive.stats table for statIndex, statKey in ipairs(legionPassive.sortedStats) do local statValues = { } for key in pairs(legionPassive.stats) do @@ -2333,10 +2351,12 @@ function TreeTabClass:FindTimelessJewel() controls.msg = new("LabelControl"):LabelControl(nil, { -280, 5, 0, 20 }, "") controls.searchTradeButton = new("ButtonControl"):ButtonControl({ "BOTTOMRIGHT", controls.searchResults, "TOPRIGHT" }, { 0, -rowSpacing, 170, buttonHeight }, "Open Trade URL", function() local seedTrades = {} + ---@type integer, integer local startRow, endRow if controls.searchResults.highlightIndex and not controls.searchMore.state then - startRow = m_min(controls.searchResults.selIndex, controls.searchResults.highlightIndex) - endRow = m_max(controls.searchResults.selIndex, controls.searchResults.highlightIndex) + local selIndex = controls.searchResults.selIndex or 1 + startRow = m_min(selIndex, controls.searchResults.highlightIndex) + endRow = m_max(selIndex, controls.searchResults.highlightIndex) else startRow = controls.searchResults.selIndex or 1 local maxFilters = controls.searchMore.state and 180 or 10 @@ -2367,6 +2387,7 @@ function TreeTabClass:FindTimelessJewel() local result = timelessData.searchResults[i] local conquerorKeystoneTradeIds = data.timelessJewelTradeIDs[timelessData.jewelType.id].keystone + ---@type unknown[] local conquerorTradeIds if timelessData.jewelType.id >= 7 then conquerorTradeIds = { conquerorKeystoneTradeIds[1] } @@ -2431,12 +2452,14 @@ function TreeTabClass:FindTimelessJewel() end -- if the league was not selected via dropdown, then default to the first league in the dropdown or "" if the leagues could not be read - local selectedRealm = controls.realmSelection:GetSelValue():lower() + local selectedRealm = (controls.realmSelection:GetSelValue() or "PC"):lower() local realmPath = selectedRealm == "pc" and "" or (selectedRealm .. "/") + ---@type string + local encodedSearch = dkjson.encode(search) local url = "https://www.pathofexile.com/trade/search/" .. realmPath .. (controls.searchTradeLeagueSelect:GetSelValue()) .. - "/?q=" .. (s_gsub(dkjson.encode(search), "[^a-zA-Z0-9]", function(a) + "/?q=" .. (s_gsub(encodedSearch, "[^a-zA-Z0-9]", function(a) return s_format("%%%02X", s_byte(a)) end)) OpenURL(url) @@ -2467,7 +2490,7 @@ function TreeTabClass:FindTimelessJewel() controls.realmSelection = new("DropDownControl"):DropDownControl({ "BOTTOMLEFT", controls.searchTradeLeagueSelect, "TOPLEFT" }, { 0, -rowSpacing, 50, buttonHeight }, self.realmList, nil) local function updateLeagues() - local currentRealmId = controls.realmSelection:GetSelValue():lower() + local currentRealmId = (controls.realmSelection:GetSelValue() or "PC"):lower() if self.tradeLeaguesList[currentRealmId] == nil then self.tradeLeaguesList[currentRealmId] = {} end local leagueList = self.tradeLeaguesList[currentRealmId] if leagueList and #leagueList > 0 then @@ -2570,6 +2593,7 @@ function TreeTabClass:FindTimelessJewel() local unAllocatedNodesDistance = { } local targetNodes = { } local targetSmallNodes = { ["attributeSmalls"] = 0, ["otherSmalls"] = 0 } + ---@type table local desiredNodes = { } local minimumWeights = { } local resultNodes = { } @@ -2900,6 +2924,7 @@ function TreeTabClass:FindTimelessJewel() end end local sortedNodeArray = { } + ---@cast result table for legionId, desiredNode in pairs(desiredNodes) do if seedData[legionId] then if desiredNode.desiredIdx == 8 then diff --git a/src/GameVersions.lua b/src/GameVersions.lua index 0cba059025b..cb8b3555c95 100644 --- a/src/GameVersions.lua +++ b/src/GameVersions.lua @@ -14,6 +14,7 @@ treeVersionList = { "2_6", "3_6", "3_7", "3_8", "3_9", "3_10", "3_11", "3_12", " --- Always points to the latest skill tree version. latestTreeVersion = treeVersionList[#treeVersionList] ---Tree version where multiple skill trees per build were introduced to PoBC. +---@type string defaultTreeVersion = treeVersionList[2] ---Display, comparison and export data for all supported skill tree versions. ---@type table diff --git a/src/Modules/AddImplicitPopup.lua b/src/Modules/AddImplicitPopup.lua index b324c47cddb..33e919f84b7 100644 --- a/src/Modules/AddImplicitPopup.lua +++ b/src/Modules/AddImplicitPopup.lua @@ -47,7 +47,7 @@ function M.AddImplicitToDisplayItem(itemsTab, displayItem) wipeTable(modGroups) local groupIndexes = {} if sourceId == "EXARCH" or sourceId == "EATER" then - for i, mod in pairs(displayItem.affixes) do + for i, mod in pairs(displayItem.affixes or {}) do if displayItem:GetModSpawnWeight(mod) > 0 and sourceId:lower() == mod.type:lower() then local modLabel = table.concat(mod, "/") local group = mod.group:gsub("PinnaclePresence", ""):gsub("UniquePresence", "") diff --git a/src/Modules/CalcBase.lua b/src/Modules/CalcBase.lua index 391aeda567a..150dde9f188 100644 --- a/src/Modules/CalcBase.lua +++ b/src/Modules/CalcBase.lua @@ -87,6 +87,7 @@ return calcs ---@field modDB ModDB ---@field enemy Actor? ---@field breakdown? Breakdown? +---@field activeSkillList? ActiveSkill[] ---@class ActiveSkill ---@field skillModList ModList diff --git a/src/Modules/CalcSections.lua b/src/Modules/CalcSections.lua index e6b2284aa57..f2429119bfb 100644 --- a/src/Modules/CalcSections.lua +++ b/src/Modules/CalcSections.lua @@ -69,6 +69,11 @@ local rageCost = { ---@class CalcSectionColumn : CalcSectionEntry One column cell: a format and descriptions for the popup ---@field [integer] CalcSectionEntry ---@field format? string Value format string (e.g. "{0:output:Life}") or literal header text +---@field x? number Screen x position, set when this column is the source of a shown breakdown popup +---@field y? number Screen y position, set when this column is the source of a shown breakdown popup +---@field width? number Column width, set when this column is the source of a shown breakdown popup +---@field xOffset? number Horizontal offset relative to the section, used to compute x +---@field yOffset? number Vertical offset relative to the section, used to compute y ---@class CalcSectionRow One row of a subsection ---@field [integer] CalcSectionColumn Column cells diff --git a/src/Modules/CalcSetup.lua b/src/Modules/CalcSetup.lua index 70301168360..57d84764c8a 100644 --- a/src/Modules/CalcSetup.lua +++ b/src/Modules/CalcSetup.lua @@ -490,6 +490,7 @@ function calcs.initEnv(build, mode, override, specEnv) if not env then ---@class Env ---@field minion Actor? + ---@field grantedPassives table env = { } env.build = build env.data = build.data diff --git a/src/Modules/CalcTools.lua b/src/Modules/CalcTools.lua index aafc74e523e..658361f25d6 100644 --- a/src/Modules/CalcTools.lua +++ b/src/Modules/CalcTools.lua @@ -273,9 +273,9 @@ function calcLib.getConvertedModTags(mod, multiplier, minionMods) end --- Get the gameId from the gemName which will be the same as the base gem for transfigured gems ---- @param gemName string +--- @param gemName string? --- @param dropVaal boolean ---- @return string +--- @return string? function calcLib.getGameIdFromGemName(gemName, dropVaal) if type(gemName) ~= "string" then return diff --git a/src/Modules/Common.lua b/src/Modules/Common.lua index b71053f5f24..5f1e1c1ef47 100644 --- a/src/Modules/Common.lua +++ b/src/Modules/Common.lua @@ -301,8 +301,9 @@ function convertUTF8to16(text, offset) end --- Clean item text by removing or replacing unsupported or redundant characters or sequences ----@param text string ----@return string +---@param text? string +---@return string? +---@overload fun(text: string): string function sanitiseText(text) if not text then return nil end -- Something something unicode support something grumble @@ -998,6 +999,8 @@ end -- returns a list of fields, not including the separator. -- Will return the first field as blank if the first character of the string is the separator -- Separator defaults to colon +---@param sep? string +---@return string[] fields function string:split(sep) -- Initially from http://lua-users.org/wiki/SplitJoin -- function will ignore duplicate separators diff --git a/src/Modules/Data.lua b/src/Modules/Data.lua index 26afb5f4dfb..3c44b0476c2 100644 --- a/src/Modules/Data.lua +++ b/src/Modules/Data.lua @@ -924,6 +924,7 @@ data.timelessJewelTypes = { [10] = "Abyss Amanamu", [11] = "Abyss Zorath", } +---@type table data.timelessJewelSeedMin = { [1] = 100, [2] = 10000, @@ -937,6 +938,7 @@ data.timelessJewelSeedMin = { [10] = 100, [11] = 100, } +---@type table data.timelessJewelSeedMax = { [1] = 8000, [2] = 18000, diff --git a/src/Modules/Main.lua b/src/Modules/Main.lua index 294fb64c24b..af31c66c8db 100644 --- a/src/Modules/Main.lua +++ b/src/Modules/Main.lua @@ -1657,6 +1657,16 @@ function main:CopyFolder(srcName, dstName) end end +---@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 main:OpenPopup(width, height, title, controls, enterControl, defaultControl, escapeControl, scrollBarFunc, resizeFunc) local popup = new("PopupDialog"):PopupDialog(width, height, title, controls, enterControl, defaultControl, escapeControl, scrollBarFunc, resizeFunc) t_insert(self.popups, 1, popup) diff --git a/src/_SimpleGraphic.def.lua b/src/_SimpleGraphic.def.lua index 6e159e4134e..91857d304db 100644 --- a/src/_SimpleGraphic.def.lua +++ b/src/_SimpleGraphic.def.lua @@ -461,9 +461,8 @@ function PLoadModule(modName, ...) end end ----@generic T ---@generic R ----@param func fun(...: T): R +---@param func fun(...: any): R ---@param ... any ---@return any? err ---@return R? retVal From e3ba3060a5e27ce5e7291f4a9041e2a0f4065e5a Mon Sep 17 00:00:00 2001 From: Theodore Di Pietro Date: Thu, 1 Oct 2026 14:53:26 +0000 Subject: [PATCH 3/5] Improve mod config and item DB type annotations --- src/Classes/BuildListControl.lua | 1 + src/Classes/CompareBuySimilar.lua | 10 ++- src/Classes/CompareEntry.lua | 1 + src/Classes/CompareTab.lua | 31 ++++++- src/Classes/Control.lua | 27 ++++++ src/Classes/DropDownControl.lua | 4 + src/Classes/EditControl.lua | 6 ++ src/Classes/GemSelectControl.lua | 6 +- src/Classes/ImportTab.lua | 15 ++++ src/Classes/Item.lua | 1 + src/Classes/ItemDBControl.lua | 18 +++- src/Classes/ItemSetListControl.lua | 1 + src/Classes/ItemSlotControl.lua | 2 + src/Classes/ItemsTab.lua | 19 +++++ src/Classes/ListControl.lua | 7 ++ src/Classes/ModStore.lua | 54 +++++++++++- src/Classes/NotesTab.lua | 4 + src/Classes/PartyTab.lua | 12 +++ src/Classes/PassiveTree.lua | 1 + src/Classes/ScrollBarControl.lua | 2 + src/Classes/SkillsTab.lua | 11 +++ src/Classes/Tooltip.lua | 2 + src/Data/Global.lua | 17 +++- src/Modules/Build.lua | 1 + src/Modules/BuildList.lua | 10 +++ src/Modules/CalcBase.lua | 127 ++++++++++++++++++++++++++++- src/Modules/CalcSections.lua | 1 + src/Modules/CalcSetup.lua | 38 ++++++++- src/Modules/Data.lua | 7 ++ src/Modules/ModTools.lua | 27 ++++++ 30 files changed, 449 insertions(+), 14 deletions(-) diff --git a/src/Classes/BuildListControl.lua b/src/Classes/BuildListControl.lua index c40e68ff049..93d2e01bb7d 100644 --- a/src/Classes/BuildListControl.lua +++ b/src/Classes/BuildListControl.lua @@ -20,6 +20,7 @@ local buildListHelpers = require("Modules.BuildListHelpers") ---@class BuildListMode: ControlHost ---@field subPath string ---@field list BuildListEntry[] +---@field BuildList fun(self: BuildListMode) ---@class BuildListControl: ListControl ---@field listMode BuildListMode diff --git a/src/Classes/CompareBuySimilar.lua b/src/Classes/CompareBuySimilar.lua index 5030832a115..c692ad7e6ba 100644 --- a/src/Classes/CompareBuySimilar.lua +++ b/src/Classes/CompareBuySimilar.lua @@ -52,6 +52,14 @@ local M = {} ---@field value? string|number ---@field needsExactValue? boolean +---@class CompareBuySimilarControls: table +---@field ilvlMin EditControl +---@field ilvlMax EditControl +---@field baseTypeCheck CheckBoxControl +---@field listedDrop DropDownControl +---@field realmDrop DropDownControl +---@field leagueDrop DropDownControl + -- Realm display name to API id mapping local REALM_API_IDS = { ["PC"] = "pc", @@ -75,7 +83,7 @@ end -- Build the trade search URL based on popup selections ---@param item Item ---@param slotName string ----@param controls table +---@param controls CompareBuySimilarControls ---@param modEntries CompareBuySimilarModEntry[] ---@param defenceEntries CompareBuySimilarDefenceEntry[] ---@param isUnique boolean diff --git a/src/Classes/CompareEntry.lua b/src/Classes/CompareEntry.lua index 53649765397..2c5dfd46d6a 100644 --- a/src/Classes/CompareEntry.lua +++ b/src/Classes/CompareEntry.lua @@ -10,6 +10,7 @@ local m_min = math.min local m_max = math.max ---@class CompareEntry: ControlHost +---@field spec? PassiveSpec ---@field label string ---@field buildName string ---@field xmlText string diff --git a/src/Classes/CompareTab.lua b/src/Classes/CompareTab.lua index eeb348cab3a..f97d45fe468 100644 --- a/src/Classes/CompareTab.lua +++ b/src/Classes/CompareTab.lua @@ -206,7 +206,32 @@ end ---@field y number ---@field group? CompareSocketGroup +---@class CompareTabControls: table +---@field cmpStageCount EditControl +---@field cmpMineCount EditControl +---@field primCalcsStageCount EditControl +---@field primCalcsMineCount EditControl +---@field cmpCalcsStageCount EditControl +---@field cmpCalcsMineCount EditControl +---@field overlayTreeSearch EditControl +---@field leftTreeSearch EditControl +---@field rightTreeSearch EditControl +---@field configSearchEdit EditControl +---@field calcsScrollBar ScrollBarControl +---@field viewScrollBar ScrollBarControl +---@field itemsHScrollBar ScrollBarControl +---@field skillsHScrollBar ScrollBarControl +---@field cmpSocketGroup DropDownControl +---@field cmpMainSkill DropDownControl +---@field cmpSkillPart DropDownControl +---@field cmpMinion DropDownControl +---@field cmpMinionSkill DropDownControl +---@field primCalcsMode DropDownControl +---@field cmpCalcsMode DropDownControl +---@field comparePowerStatSelect DropDownControl + ---@class CompareTab: ControlHost, Control +---@field controls CompareTabControls ---@field build Build ---@field primaryBuild Build ---@field compareEntries CompareEntry[] @@ -2060,7 +2085,7 @@ 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 contentVP Viewport ---@param compareEntry CompareEntry function CompareTabClass:LayoutTreeView(contentVP, compareEntry) self.treeLayout = nil @@ -2227,7 +2252,7 @@ local function syncControlValue(ctrl, varData, val) end -- Position config controls and build section-grouped display when in CONFIG view. ----@param contentVP Rect +---@param contentVP Viewport ---@param compareEntry CompareEntry function CompareTabClass:LayoutConfigView(contentVP, compareEntry) if self.compareViewMode ~= "CONFIG" or not compareEntry then return end @@ -2606,7 +2631,7 @@ local function layoutRow(control, x, currentY, width) end -- Handle scroll events for scrollable views. ----@param contentVP Rect +---@param contentVP Viewport ---@param inputEvents InputEvent[] function CompareTabClass:HandleScrollInput(contentVP, inputEvents) local cursorX, cursorY = GetCursorPos() diff --git a/src/Classes/Control.lua b/src/Classes/Control.lua index 0301eb18677..48d633682fb 100644 --- a/src/Classes/Control.lua +++ b/src/Classes/Control.lua @@ -36,6 +36,25 @@ local rect = { ---@class Control ---@field enabled Prop +---@field state boolean? +---@field list table? +---@field selValue any +---@field selIndex integer? +---@field offset number? +---@field labelWidth number? +---@field realDraw fun(self: Control, x: number, y: number, width: number, height: number, viewPort: Viewport)? +---@field IsMouseOver? fun(self: Control): boolean +---@field lines table +---@field onClick? fun(...) +---@field buf string +---@field dropped boolean +---@field Click? fun(self: Control, ...) +---@field IsScrollDownKey? fun(key: string): boolean +---@field IsScrollUpKey? fun(key: string): boolean +---@field SelectIndex? fun(self: Control, index: integer): boolean? +---@field CanDragToValue? fun(self: Control, index: integer, value: any, source: Control): boolean +---@field SetBreakdownData? fun(self: Control, displayData?: table, pinned?: boolean, forceActor?: Actor) +---@field GetColumnProperty? fun(self: Control, column: table, property: string): any ---@field anchor AnchorState ---@field rectStart Rect ---@field hasFocus? boolean @@ -48,6 +67,14 @@ local rect = { ---@field y Prop? ---@field width Prop? ---@field height Prop? +---@field Draw? fun(self: Control, viewPort: Viewport, noTooltip?: boolean) +---@field OnKeyUp? fun(self: Control, key: string): Control? +---@field SetText? fun(self: Control, text: string, notify?: boolean) +---@field SetList? fun(self: Control, textList: table) +---@field SetSel? fun(self: Control, newSel: integer, noCallSelFunc?: boolean) +---@field SelByValue? fun(self: Control, value: any, key?: string): integer? +---@field Scroll? fun(self: Control, mult: number) +---@field SetContentDimension? fun(self: Control, conDim: number, viewDim: number) ---@field collapseY number? An additional offset which is applied when this control uses a collapsed anchor. ---@field collapseX number? An additional offset which is applied when this control uses a collapsed anchor. local ControlClass = newClass("Control") diff --git a/src/Classes/DropDownControl.lua b/src/Classes/DropDownControl.lua index f983d6f88ed..65c5c652de8 100644 --- a/src/Classes/DropDownControl.lua +++ b/src/Classes/DropDownControl.lua @@ -8,7 +8,11 @@ local m_min = math.min local m_max = math.max local m_floor = math.floor +---@class DropDownControlControls: table +---@field scrollBar ScrollBarControl + ---@class DropDownControl: Control, ControlHost, TooltipHost, SearchHost +---@field controls DropDownControlControls ---@field list T[] ---@field selFunc? fun(index: integer, data: T, doubleClick?: boolean) ---@field selIndex? integer diff --git a/src/Classes/EditControl.lua b/src/Classes/EditControl.lua index f51db1f05c3..bf072198b9c 100644 --- a/src/Classes/EditControl.lua +++ b/src/Classes/EditControl.lua @@ -40,7 +40,13 @@ local function newlineCount(str) end end +---@class EditControlControls: table +---@field scrollBarH ScrollBarControl +---@field scrollBarV ScrollBarControl + ---@class EditControl: ControlHost, Control, UndoHandler, TooltipHost +---@field controls EditControlControls +---@field SetText fun(self: EditControl, text: string, notify?: boolean) ---@field inactiveText (fun(buf: string?): string)|string ---@field buf string ---@field caret integer diff --git a/src/Classes/GemSelectControl.lua b/src/Classes/GemSelectControl.lua index aaa8ef46854..636c8abbe00 100644 --- a/src/Classes/GemSelectControl.lua +++ b/src/Classes/GemSelectControl.lua @@ -15,11 +15,15 @@ local gemTooltip = require("Classes.GemTooltip") local toolTipText = "Prefix tag searches with a colon and exclude tags with a dash. e.g. :fire:lightning:-cold:area" local imbuedTooltipText = "\"Socketed in\" item must be set in order to add an imbued support.\nOnly one imbued support is allowed per item." +---@class GemSelectEntry +---@field name string +---@field grantedEffect table + ---@class GemSelectControl: EditControl ---@field skillsTab SkillsTab ---@field index integer ---@field imbuedSelect boolean ----@field gems table +---@field gems table ---@field list string[] ---@field mode string ---@field forceTooltip? boolean diff --git a/src/Classes/ImportTab.lua b/src/Classes/ImportTab.lua index 0823e781f27..032d7dc8baf 100644 --- a/src/Classes/ImportTab.lua +++ b/src/Classes/ImportTab.lua @@ -22,6 +22,17 @@ local influenceInfo = itemLib.influenceInfo.all ---@field hostName string ---@field profileURL string +---@class ImportTabControls: table +---@field accountRealm DropDownControl +---@field charSelectLeague DropDownControl +---@field siteAccountName EditControl +---@field siteAccountHistory DropDownControl +---@field siteCharSelectLeague DropDownControl
+---@field generateCodeOut EditControl +---@field exportFrom DropDownControl
+---@field importCodeIn EditControl +---@field importCodeMode DropDownControl + ---@type RealmInfo[] local realmList = { { label = "PC", id = "PC", realmCode = "pc", hostName = "https://www.pathofexile.com/", profileURL = "account/view-profile/" }, @@ -505,6 +516,10 @@ local function addAccountNameControls(self) end ---@class ImportTab: ControlHost, Control +---@field isAuthorized? fun(): boolean +---@field usingOauth? boolean +---@field characterList? table +---@field controls ImportTabControls ---@field build Build ---@field modFlag boolean ---@field [string] unknown diff --git a/src/Classes/Item.lua b/src/Classes/Item.lua index 467c5a7f7b6..fe50b268947 100644 --- a/src/Classes/Item.lua +++ b/src/Classes/Item.lua @@ -170,6 +170,7 @@ local influenceInfo = itemLib.influenceInfo.all ---@field base? ItemBase ---@field baseName string ---@field itemLevel integer +---@field matchesSocket? boolean ---@field quality integer ---@field corrupted boolean ---@field crafted boolean diff --git a/src/Classes/ItemDBControl.lua b/src/Classes/ItemDBControl.lua index da723e57343..32e92c37d20 100644 --- a/src/Classes/ItemDBControl.lua +++ b/src/Classes/ItemDBControl.lua @@ -11,6 +11,7 @@ local m_floor = math.floor ---@class ItemDBControl: ListControl +---@field controls ItemDBControls ---@field itemsTab ItemsTab ---@field db ItemDBData ---@field dbType "RARE"|"UNIQUE" @@ -32,8 +33,21 @@ local ItemDBClass = newClass("ItemDBControl", "ListControl") ---@field byTitle? table ---@field loading boolean? ----@param anchor Anchor? ----@param rect Rect? +---@class UniqueItemDBData: ItemDBData +---@field byTitle table + +---@class ItemDBControls: ListControlControls +---@field slot DropDownControl +---@field type DropDownControl +---@field league? DropDownControl +---@field requirement? DropDownControl +---@field obtainable? DropDownControl +---@field sort? DropDownControl
+---@field search EditControl +---@field searchMode DropDownControl + +---@param anchor? Anchor +---@param rect? Rect ---@param itemsTab ItemsTab ---@param db ItemDBData ---@param dbType "RARE"|"UNIQUE" diff --git a/src/Classes/ItemSetListControl.lua b/src/Classes/ItemSetListControl.lua index adee1a57c7b..1f4c7928eb3 100644 --- a/src/Classes/ItemSetListControl.lua +++ b/src/Classes/ItemSetListControl.lua @@ -12,6 +12,7 @@ local s_format = string.format ---@field id integer ---@field title? string ---@field slots? table +---@field useSecondWeaponSet boolean ---@class ItemSetListControl: ListControl ---@field itemsTab ItemsTab diff --git a/src/Classes/ItemSlotControl.lua b/src/Classes/ItemSlotControl.lua index 357adb59562..7eff6912d08 100644 --- a/src/Classes/ItemSlotControl.lua +++ b/src/Classes/ItemSlotControl.lua @@ -15,6 +15,8 @@ local itemSlotHelper = require("Modules.ItemSlotHelper") ---@field nodeId? integer ---@field selItemId integer ---@field active boolean +---@field inactive? boolean +---@field weaponSet? integer ---@field items table local ItemSlotClass = newClass("ItemSlotControl", "DropDownControl") diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index f6ae8e9c13a..0793f8c2d19 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -43,6 +43,24 @@ local forbiddenJewelCounterpart = { local influenceInfo = itemLib.influenceInfo.all +---@class ItemsTabControls: table +---@field setSelect DropDownControl +---@field specSelect DropDownControl +---@field selectDB DropDownControl +---@field itemList ItemListControl +---@field displayItemVersion DropDownControl +---@field displayItemVariant DropDownControl +---@field displayItemAltVariant DropDownControl +---@field displayItemAltVariant2 DropDownControl +---@field displayItemAltVariant3 DropDownControl +---@field displayItemAltVariant4 DropDownControl +---@field displayItemAltVariant5 DropDownControl +---@field displayItemInfluence DropDownControl +---@field displayItemInfluence2 DropDownControl +---@field displayItemRangeLine DropDownControl +---@field scrollBarH ScrollBarControl +---@field scrollBarV ScrollBarControl + ---@type string[] local catalystQualityFormat = { "^x7F7F7FQuality (Attack Modifiers): "..colorCodes.MAGIC.."+%d%% (augmented)", @@ -91,6 +109,7 @@ local function buildModSortList() end ---@class ItemsTab: UndoHandler, ControlHost, Control +---@field controls ItemsTabControls ---@field build Build ---@field modFlag boolean ---@field socketViewer PassiveTreeView diff --git a/src/Classes/ListControl.lua b/src/Classes/ListControl.lua index e556e19b4ed..b43dfa1812c 100644 --- a/src/Classes/ListControl.lua +++ b/src/Classes/ListControl.lua @@ -30,7 +30,14 @@ local m_min = math.min local m_max = math.max local m_floor = math.floor +---@class ListControlControls: table +---@field scrollBar? ScrollBarControl +---@field scrollBarH? ScrollBarControl +---@field scrollBarV? ScrollBarControl + ---@class ListControl: Control, ControlHost +---@field controls ListControlControls +---@field ListControl fun(self: ListControl, anchor?: Anchor, rect?: Rect, rowHeight: number, scroll?: string|boolean, isMutable?: boolean, list?: T[], forceTooltip?: boolean): ListControl ---@field list T[] ---@field rowHeight number ---@field scroll "HORIZONTAL"|"VERTICAL"|boolean|nil diff --git a/src/Classes/ModStore.lua b/src/Classes/ModStore.lua index 73f4d9d98a0..552e12114be 100644 --- a/src/Classes/ModStore.lua +++ b/src/Classes/ModStore.lua @@ -29,10 +29,60 @@ end }) -- TODO: very incomplete ---@class ModCfg ----@field flags number? bit mask ----@field keywordFlags number? +---@field [string] any +---@field flags integer? bit mask +---@field keywordFlags integer? ---@field skillName string? +---@field summonSkillName string? +---@field skillGem any? +---@field skillGrantedEffect any? +---@field grantedEffect? table +---@field srcInstance? table +---@field matchesSocket? boolean +---@field skillPart integer? +---@field skillTypes table? +---@field skillCond table? +---@field skillDist number? +---@field slotName string? +---@field socketColor any? +---@field socketNum integer? ---@field source string? +---@field actor string? +---@field skillStats table? +---@field baseFlags table? +---@field dexterityGems integer? +---@field intelligenceGems integer? +---@field strengthGems integer? +---@field neg? boolean +---@field var? string +---@field varList? table +---@field base? number +---@field ramp? number +---@field limitVar? string +---@field reservedPercent? number +---@field reservedFlat? number +---@field efficiency? number +---@field corruptedRange? number +---@field newModId? string +---@field modList? ModList +---@field sockets? table +---@field minionData? table +---@field globalLimitKey? string +---@field partyMembers? table +---@field item Item? +---@field varList table? +---@field sockets table? +---@field minionData table? +---@field globalLimitKey string? + +---@class SkillCfg: ModCfg +---@field flags integer +---@field keywordFlags integer +---@field skillName string +---@field skillGrantedEffect any +---@field skillPart integer +---@field skillTypes table +---@field skillCond table ---@class ModStore ---@field parent? ModStore diff --git a/src/Classes/NotesTab.lua b/src/Classes/NotesTab.lua index 04f07f8d2a9..74f47551e89 100644 --- a/src/Classes/NotesTab.lua +++ b/src/Classes/NotesTab.lua @@ -5,7 +5,11 @@ -- local t_insert = table.insert +---@class NotesTabControls: table +---@field edit EditControl + ---@class NotesTab: ControlHost, Control +---@field controls NotesTabControls ---@field build Build ---@field modFlag boolean ---@field lastContent? string diff --git a/src/Classes/PartyTab.lua b/src/Classes/PartyTab.lua index 1aea86e9a2c..3f16bf4c9bd 100644 --- a/src/Classes/PartyTab.lua +++ b/src/Classes/PartyTab.lua @@ -22,7 +22,19 @@ local m_max = math.max ---@field EnableExportBuffs boolean ---@field showAdvancedTools boolean +---@class PartyTabControls: table +---@field importCodeIn EditControl +---@field importCodeDestination DropDownControl +---@field editAuras EditControl +---@field editCurses EditControl +---@field editWarcries EditControl +---@field editLinks EditControl +---@field editPartyMemberStats EditControl +---@field enemyCond EditControl +---@field enemyMods EditControl + ---@class PartyTab: ControlHost, Control +---@field controls PartyTabControls ---@field build Build ---@field actor table ---@field enemyModList ModList diff --git a/src/Classes/PassiveTree.lua b/src/Classes/PassiveTree.lua index eb2adadd3e1..a0b14e5b6c7 100644 --- a/src/Classes/PassiveTree.lua +++ b/src/Classes/PassiveTree.lua @@ -97,6 +97,7 @@ end ---@field connectedToStart? boolean ---@field name string ---@field ascendancyName? string +---@field reminderText? string[] ---@field classStartIndex? integer ---@field masteryCache? Node ---@field isAscendancyStart? boolean diff --git a/src/Classes/ScrollBarControl.lua b/src/Classes/ScrollBarControl.lua index 7c5faadcb44..67cb8bcd1b8 100644 --- a/src/Classes/ScrollBarControl.lua +++ b/src/Classes/ScrollBarControl.lua @@ -9,6 +9,8 @@ local m_ceil = math.ceil local m_floor = math.floor ---@class ScrollBarControl: Control +---@field Scroll fun(self: ScrollBarControl, mult: number) +---@field SetContentDimension fun(self: ScrollBarControl, conDim: number, viewDim: number) ---@field step number ---@field dir "HORIZONTAL"|"VERTICAL" ---@field offset number diff --git a/src/Classes/SkillsTab.lua b/src/Classes/SkillsTab.lua index 48061570ae8..334045975a1 100644 --- a/src/Classes/SkillsTab.lua +++ b/src/Classes/SkillsTab.lua @@ -87,7 +87,18 @@ local sortGemTypeList = { ---@field activeSocketGroup integer? ---@field activeSocketGroup2 integer? +---@class SkillsTabControls: table +---@field setSelect DropDownControl +---@field defaultLevel DropDownControl +---@field defaultQuality EditControl +---@field showSupportGemTypes DropDownControl +---@field groupLabel EditControl +---@field groupSlot DropDownControl +---@field groupCount EditControl +---@field scrollBarH ScrollBarControl + ---@class SkillsTab: UndoHandler, ControlHost, Control +---@field controls SkillsTabControls ---@field build Build ---@field socketGroupList table[] ---@field skillSets table diff --git a/src/Classes/Tooltip.lua b/src/Classes/Tooltip.lua index c52700f08c4..03776aa4c79 100644 --- a/src/Classes/Tooltip.lua +++ b/src/Classes/Tooltip.lua @@ -71,6 +71,8 @@ end ---@field influenceHeader2? string ---@field foilType? string ---@field _bgHandles? table +---@field Draw? fun(self: Tooltip, x: number, y: number, width: number, height: number, viewPort: Viewport) +---@field realDraw? fun(self: Tooltip, x: number, y: number, width: number, height: number, viewPort: Viewport) local TooltipClass = newClass("Tooltip") ---@return Tooltip diff --git a/src/Data/Global.lua b/src/Data/Global.lua index c675233afcd..61aea5c98de 100644 --- a/src/Data/Global.lua +++ b/src/Data/Global.lua @@ -353,7 +353,22 @@ SkillType = { Pact = 135, } +---@class CachedCalc +---@field Env Env +---@field ActiveSkill ActiveSkill +---@field HitSpeed number? +---@field Speed number? +---@field TotalDPS number? +---@field HitChance number? +---@field CritChance number? + +---@class GlobalCacheData +---@field MAIN table +---@field CALCS table +---@field CALCULATOR table + +---@class GlobalCache +---@field cachedData GlobalCacheData GlobalCache = { cachedData = { MAIN = {}, CALCS = {}, CALCULATOR = {} }, } - diff --git a/src/Modules/Build.lua b/src/Modules/Build.lua index 3ad9d8eafff..abb7e3d032f 100644 --- a/src/Modules/Build.lua +++ b/src/Modules/Build.lua @@ -16,6 +16,7 @@ local m_abs = math.abs local s_format = string.format ---@class Build: ControlHost +---@field controls table ---@field spec PassiveSpec added by TreeTab ---@field powerBuilderProgressCallback fun(progress: number)? ---@field powerBuilderCallback fun() diff --git a/src/Modules/BuildList.lua b/src/Modules/BuildList.lua index 7648110d0ce..e6b852c7709 100644 --- a/src/Modules/BuildList.lua +++ b/src/Modules/BuildList.lua @@ -10,6 +10,16 @@ local t_insert = table.insert local buildListHelpers = require("Modules.BuildListHelpers") local buildSortDropList = buildListHelpers.buildSortDropList +---@class BuildListMode: ControlHost +---@field controls table +---@field subPath string +---@field list BuildListEntry[] +---@field buildIndex table +---@field BuildList fun(self: BuildListMode) +---@field FilterBuildList fun(self: BuildListMode) +---@field SortList fun(self: BuildListMode) + +---@type BuildListMode local listMode = new("ControlHost"):ControlHost() function listMode:Init(selBuildName, subPath) diff --git a/src/Modules/CalcBase.lua b/src/Modules/CalcBase.lua index 150dde9f188..fbd6ff87816 100644 --- a/src/Modules/CalcBase.lua +++ b/src/Modules/CalcBase.lua @@ -15,6 +15,102 @@ return calcs ---@field BattleMageCryDuration number? ---@field BattlemageUpTimeRatio number? ---@field BleedDamage number? +---@field BleedDPS number? +---@field IgniteDPS number? +---@field PoisonDPS number? +---@field IntimidatingUpTimeRatio number? +---@field IntimidatingAvgDmg number? +---@field IntimidatingHitEffect number? +---@field IntimidatingMaxHitEffect number? +---@field RallyingUpTimeRatio number? +---@field ExertedAttackUptimeRatio number? +---@field SeismicUpTimeRatio number? +---@field AncestralUpTimeRatio number? +---@field ActionSpeedMod number? +---@field AilmentWarcryEffect number? +---@field AncestralCryCalculated boolean? +---@field AncestralCryCastTime number? +---@field AncestralCryCooldown number? +---@field AncestralCryDuration number? +---@field AncestralExertsCount number? +---@field AverageBurstDamage number? +---@field AverageBurstHits number? +---@field AvgFistOfWarDamage number? +---@field AvgFistOfWarDamageEffect number? +---@field BattleMageCryCalculated boolean? +---@field BeidatAdditionalBeamChains number? +---@field BeidatAdditionalCascades number? +---@field BeidatAdditionalProjectiles number? +---@field BleedDuration number? +---@field BleedRollAverage number? +---@field BleedStackPotential number? +---@field BleedStacks number? +---@field BleedStacksMax number? +---@field BurningGroundDPS number? +---@field BurningGroundFromIgnite boolean? +---@field CausticGroundFromPoison boolean? +---@field Chain number? +---@field ChainMax number? +---@field ChainMaxString string? +---@field ChainRemaining number? +---@field Cooldown number? +---@field CritChance number? +---@field CullPercent number? +---@field EnergyShield number? +---@field ExertedAttackAvgDmg number? +---@field ExertedAttackHitEffect number? +---@field ExertedAttackMaxHitEffect number? +---@field ExertedAttackUptimeRatioCalculated boolean? +---@field FistOfWarCooldown number? +---@field FistOfWarDamageMultiplier number? +---@field FistOfWarUptimeRatio number? +---@field HitSpeed number? +---@field HitTime number? +---@field IgniteChancePerHit number? +---@field IgniteDamage number? +---@field IgniteDuration number? +---@field IgniteRollAverage number? +---@field IgniteStackPotential number? +---@field IgniteStacksMax number? +---@field ImpaleStacks number? +---@field ImpaleStacksMax number? +---@field InfernalCryCalculated boolean? +---@field IntimidatingCryCalculated boolean? +---@field IntimidatingCryCastTime number? +---@field IntimidatingCryCooldown number? +---@field IntimidatingCryDuration number? +---@field IntimidatingExertsCount number? +---@field KtashSoulRefundChance number? +---@field Life number? +---@field Mana number? +---@field MaxOffensiveWarcryEffect number? +---@field OffensiveWarcryEffect number? +---@field PoisonDuration number? +---@field PoisonStacks number? +---@field ProjectileCount number? +---@field RallyingAvgDmg number? +---@field RallyingCryCalculated boolean? +---@field RallyingCryCastTime number? +---@field RallyingCryCooldown number? +---@field RallyingCryDuration number? +---@field RallyingExertsCount number? +---@field RallyingHitEffect number? +---@field RallyingMaxHitEffect number? +---@field Repeats number? +---@field SeismicCryCalculated boolean? +---@field SeismicCryCastTime number? +---@field SeismicCryCooldown number? +---@field SeismicCryDuration number? +---@field SeismicExertsCount number? +---@field ShowBurst boolean? +---@field SoulGainPreventionDuration number? +---@field Speed number? +---@field TheoreticalMaxOffensiveWarcryEffect number? +---@field TheoreticalOffensiveWarcryEffect number? +---@field TotemPlacementTime number? +---@field enemyHasSpellBlock boolean? +---@field TotalDPS number? +---@field TotalDot number? ---@field ChaosHitAverage number? ---@field ChaosResist number? ---@field ColdHitAverage number? @@ -79,6 +175,7 @@ return calcs ---@field TotemLife number? ---@class Breakdown +---@field [string] table|string|fun(...: any) ---@field MainHand Breakdown? ---@field OffHand Breakdown? @@ -86,9 +183,37 @@ return calcs ---@field output Output ---@field modDB ModDB ---@field enemy Actor? +---@field player Actor? ---@field breakdown? Breakdown? ---@field activeSkillList? ActiveSkill[] +---@field mainSkill? ActiveSkill +---@field itemList? Item[] +---@field weaponData1? table +---@field weaponData2? table +---@field level? integer +---@field enemyLevel? integer +---@field weapon1Cfg? table +---@field weapon2Cfg? table +---@field minionData? table +---@field sockets? table ---@class ActiveSkill ---@field skillModList ModList ----@field skillCfg ModCfg +---@field mirage? ActiveSkill +---@field count? number +---@field output? Output +---@field grantedEffect? table +---@field activeEffect? ActiveEffect +---@field skillData table +---@field skillFlags table +---@field skillTypes table +---@field actor? Actor +---@field minion? Actor +---@field skillCfg SkillCfg +---@field srcInstance? table +---@field matchesSocket? boolean + +---@class ActiveEffect +---@field grantedEffect table +---@field srcInstance table +---@field matchesSocket? boolean diff --git a/src/Modules/CalcSections.lua b/src/Modules/CalcSections.lua index f2429119bfb..e9f55ffcd58 100644 --- a/src/Modules/CalcSections.lua +++ b/src/Modules/CalcSections.lua @@ -94,6 +94,7 @@ local rageCost = { ---@class CalcSectionSubsection ---@field defaultCollapsed boolean +---@field collapsed? boolean ---@field label string ---@field data CalcSectionData diff --git a/src/Modules/CalcSetup.lua b/src/Modules/CalcSetup.lua index 57d84764c8a..188064778b7 100644 --- a/src/Modules/CalcSetup.lua +++ b/src/Modules/CalcSetup.lua @@ -6,6 +6,39 @@ ---@class Calcs local calcs = require("Modules.CalcBase") +---@class Env +---@field build Build +---@field data Data +---@field configInput table +---@field configPlaceholder table +---@field calcsInput table +---@field mode string +---@field mode_effective boolean +---@field buildBreakdown boolean +---@field spec PassiveSpec +---@field override CalcOverride +---@field classId integer +---@field modDB ModDB +---@field enemyDB ModDB +---@field itemModDB ModDB +---@field player Actor +---@field enemy Actor +---@field minion Actor? +---@field enemyLevel integer +---@field requirementsTableItems table +---@field requirementsTableGems table +---@field radiusJewelList table +---@field extraRadiusNodeList table +---@field grantedSkills table +---@field grantedSkillsNodes table +---@field grantedSkillsItems table +---@field explodeSources table +---@field itemWarnings table +---@field flasks table +---@field tinctures table +---@field grantedPassives table +---@field auxSkillList ActiveSkill[] + local pairs = pairs local ipairs = ipairs local t_insert = table.insert @@ -488,9 +521,6 @@ function calcs.initEnv(build, mode, override, specEnv) local classStats = nil if not env then - ---@class Env - ---@field minion Actor? - ---@field grantedPassives table env = { } env.build = build env.data = build.data @@ -512,11 +542,13 @@ function calcs.initEnv(build, mode, override, specEnv) env.enemyLevel = build.configTab.enemyLevel or m_min(data.misc.MaxEnemyLevel, build.characterLevel) -- Create player/enemy actors + ---@type Actor env.player = { modDB = env.modDB, level = build.characterLevel, } env.modDB.actor = env.player + ---@type Actor env.enemy = { modDB = env.enemyDB, level = env.enemyLevel, diff --git a/src/Modules/Data.lua b/src/Modules/Data.lua index 3c44b0476c2..b85fd7ff939 100644 --- a/src/Modules/Data.lua +++ b/src/Modules/Data.lua @@ -105,9 +105,16 @@ end ---@diagnostic disable-next-line: lowercase-global ---@class Data : MiscDataExport +---@field characterConstants table +---@field monsterConstants table +---@field misc table +---@field powerStatList PowerStatList ---@field bosses BossData data = { } +---@class PowerStatList: PowerStat[] +---@field GetFromOutput fun(self: PowerStatList, output: Output, statTable: PowerStat, skipTransform?: boolean): number? + -- Misc data tables local miscData = LoadModule("Data/Misc") for k, v in pairs(miscData) do diff --git a/src/Modules/ModTools.lua b/src/Modules/ModTools.lua index c70fe526ece..f0b0a321860 100644 --- a/src/Modules/ModTools.lua +++ b/src/Modules/ModTools.lua @@ -26,6 +26,23 @@ modLib = { } ---@class ModTag ---@field type string +---@class (partial) Mod +---@field [string] any +---@field actor? Actor +---@field neg? boolean +---@field var? string +---@field varList? table +---@field base? number +---@field ramp? number +---@field partyMembers? table +---@field newModId? string +---@field modList? ModList +---@field limitVar? string +---@field reservedPercent? number +---@field efficiency? number +---@field efficiencyMore? number +---@field inc? number + ---@overload fun(modName: string, modType: NumericModTypes, modVal?: number, sourceOrTag: string|ModTag?, flagsOrModTag: number|ModTag?, keywordFlagsOrModTag: number|ModTag?, ...: ModTag) ---@overload fun(modName: string, modType: "FLAG", modVal: boolean, sourceOrModTag: string|ModTag?, flagsOrModTag: number|ModTag?, keywordFlagsOrModTag: number|ModTag?, ...: ModTag) ---@overload fun(modName: string, modType: "LIST", modVal: any[]|any, sourceOrModTag: string|ModTag?, flagsOrModTag: number|ModTag?, keywordFlagsOrModTag: number|ModTag?, ...: ModTag) @@ -54,6 +71,16 @@ function modLib.createMod(modName, modType, modVal, ...) ---@field flags number ---@field keywordFlags number ---@field source? string + ---@field neg? boolean + ---@field var? string + ---@field base? number + ---@field ramp? number + ---@field limitVar? string + ---@field reservedPercent? number + ---@field partyMembers? table + ---@field newModId? string + ---@field modList? ModList + ---@field limit? number ---@field [integer] ModTag return { name = modName, From aa68c9afd1f6300154688b5d5e7ead16577d455c Mon Sep 17 00:00:00 2001 From: Theodore Di Pietro Date: Sat, 3 Oct 2026 06:51:30 +0000 Subject: [PATCH 4/5] fix mana regression --- src/Classes/CompareCalcsHelpers.lua | 1 - src/Classes/ModStore.lua | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Classes/CompareCalcsHelpers.lua b/src/Classes/CompareCalcsHelpers.lua index cdad0ad5db3..46a34334263 100644 --- a/src/Classes/CompareCalcsHelpers.lua +++ b/src/Classes/CompareCalcsHelpers.lua @@ -119,7 +119,6 @@ function M.TabulateMods(sectionData, actor) local modStore, cfg = M.GetModStoreAndCfg(sectionData, actor) if not modStore then return {} end local modType = sectionData.modType - if not modType then return {} end local modName = sectionData.modName if not modName then return {} end diff --git a/src/Classes/ModStore.lua b/src/Classes/ModStore.lua index 552e12114be..bea029e859a 100644 --- a/src/Classes/ModStore.lua +++ b/src/Classes/ModStore.lua @@ -305,7 +305,7 @@ function ModStoreClass:List(cfg, ...) return result end ----@param modType NumericModTypes|string +---@param modType? NumericModTypes|string ---@param cfg? ModCfg ---@param ... string ---@return { value: unknown, mod: Mod }[] From b41e6c156cfef626185a597c71a22faaa0a3fa73 Mon Sep 17 00:00:00 2001 From: Theodore Di Pietro Date: Sat, 3 Oct 2026 08:50:21 +0000 Subject: [PATCH 5/5] fix return annotation --- src/Classes/CalcsTab.lua | 6 +++++- src/Classes/CompareCalcsHelpers.lua | 2 +- src/Classes/ConfigTab.lua | 4 +++- src/Classes/Control.lua | 4 ++-- src/Classes/DropDownControl.lua | 6 +++--- src/Classes/EditControl.lua | 6 +++--- src/Classes/Item.lua | 9 ++++----- src/Classes/ItemSlotControl.lua | 4 ++-- src/Classes/ItemsTab.lua | 19 ++++++++++--------- src/Classes/ModDB.lua | 8 ++++---- src/Classes/ModList.lua | 8 ++++---- src/Classes/ModStore.lua | 8 ++++++-- src/Classes/NotableDBControl.lua | 6 +++--- src/Classes/PassiveSpec.lua | 10 +++++----- src/Classes/PoBArchivesProvider.lua | 5 +++-- src/Classes/SkillsTab.lua | 2 +- src/Classes/TradeHelpers.lua | 21 +++++++++++---------- src/Classes/TradeQuery.lua | 2 +- src/Classes/TreeTab.lua | 2 +- 19 files changed, 72 insertions(+), 60 deletions(-) diff --git a/src/Classes/CalcsTab.lua b/src/Classes/CalcsTab.lua index 058c10f5e30..01ed2127768 100644 --- a/src/Classes/CalcsTab.lua +++ b/src/Classes/CalcsTab.lua @@ -410,7 +410,7 @@ end ---@param obj table ---@param actor? Actor ---@param player? Actor ----@return boolean +---@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 @@ -543,6 +543,10 @@ function CalcsTabClass:PowerBuilder() id = node.id, type = node.type, name = node.name, + linked = node.linked, + linkedId = node.linkedId, + oidx = node.oidx, + intuitiveLeapLikesAffecting = node.intuitiveLeapLikesAffecting, sd = { }, } for i, sd in ipairs(effect.sd or { }) do diff --git a/src/Classes/CompareCalcsHelpers.lua b/src/Classes/CompareCalcsHelpers.lua index 46a34334263..f22237a06de 100644 --- a/src/Classes/CompareCalcsHelpers.lua +++ b/src/Classes/CompareCalcsHelpers.lua @@ -161,7 +161,7 @@ function M.FormatModRow(row, sectionData, build) end local sourceType = row.mod.source and row.mod.source:match("[^:]+") or "?" - local sourceName = M.ResolveSourceName(row.mod, build) + local sourceName = M.ResolveSourceName(row.mod, build) or "" local modName = "" if type(sectionData.modName) == "table" then modName = " " .. M.FormatCalcModName(row.mod.name) diff --git a/src/Classes/ConfigTab.lua b/src/Classes/ConfigTab.lua index ca83bd1c5cd..f77b1f9045b 100644 --- a/src/Classes/ConfigTab.lua +++ b/src/Classes/ConfigTab.lua @@ -444,6 +444,7 @@ function ConfigTabClass:ConfigTab(build) end if varData.ifNode then + ---@return boolean? t_insert(shownFuncs, listOrSingleIfOption(varData.ifNode, function(ifOption) if self.build.spec.allocNodes[ifOption] then return true @@ -452,6 +453,7 @@ function ConfigTabClass:ConfigTab(build) if node and node.type == "Keystone" then return self.build.calcsTab.mainEnv.keystonesAdded[node.dn] end + return false end)) t_insert(tooltipFuncs, listOrSingleIfTooltip(varData.ifNode, function(ifOption) return "This option is specific to '"..self.build.spec.nodes[ifOption].dn.."'." @@ -1374,7 +1376,7 @@ end ---@param title? string ---@return ConfigSet function ConfigTabClass:NewConfigSet(configSetId, title) - local configSet = { id = configSetId, title = title, input = { }, placeholder = { }, customModsList = { { title = "Default", enabled = true, text = "" } } } + local configSet = { id = configSetId or 1, title = title, input = { }, placeholder = { }, customModsList = { { title = "Default", enabled = true, text = "" } } } if not configSetId then configSet.id = 1 while self.configSets[configSet.id] do diff --git a/src/Classes/Control.lua b/src/Classes/Control.lua index 48d633682fb..f2b41357c2b 100644 --- a/src/Classes/Control.lua +++ b/src/Classes/Control.lua @@ -178,7 +178,7 @@ function ControlClass:GetSize() return self:GetProperty("width"), self:GetProperty("height") end ----@return boolean +---@return boolean? function ControlClass:IsShown() return (not self.anchor.other or self.anchor.collapse or self.anchor.other:IsShown()) and self:GetProperty("shown") end @@ -219,7 +219,7 @@ function ControlClass:AddToTabGroup(master) end ---@param step integer ----@return Control +---@return Control? function ControlClass:TabAdvance(step) if self.tabOrder then local index = isValueInArray(self.tabOrder, self) diff --git a/src/Classes/DropDownControl.lua b/src/Classes/DropDownControl.lua index 65c5c652de8..39866843ef2 100644 --- a/src/Classes/DropDownControl.lua +++ b/src/Classes/DropDownControl.lua @@ -109,7 +109,7 @@ end -- maps the original (unfiltered) list index to the actual dropdown row index (after eventual filtering) ---@param listIndex integer ---@param default? integer ----@return integer +---@return integer? function DropDownClass:ListIndexToDropIndex(listIndex, default) -- 1:1 if not self:IsSearchActive() then @@ -491,7 +491,7 @@ function DropDownClass:OnChar(key) end ---@param key string ----@return DropDownControl? +---@return DropDownControl|false? function DropDownClass:OnKeyDown(key) if not self:IsShown() or not self:IsEnabled() then return @@ -525,7 +525,7 @@ function DropDownClass:OnKeyDown(key) end ---@param key string ----@return Control? +---@return Control|false? function DropDownClass:OnKeyUp(key) if not self:IsShown() or not self:IsEnabled() then return diff --git a/src/Classes/EditControl.lua b/src/Classes/EditControl.lua index bf072198b9c..7192bb50c7c 100644 --- a/src/Classes/EditControl.lua +++ b/src/Classes/EditControl.lua @@ -508,7 +508,7 @@ end ---@param key string ---@param doubleClick? boolean ----@return EditControl? +---@return EditControl|false? function EditClass:OnKeyDown(key, doubleClick) if not self:IsShown() or not self:IsEnabled() then return @@ -735,7 +735,7 @@ function EditClass:OnKeyDown(key, doubleClick) end ---@param key string ----@return EditControl? +---@return EditControl|false? function EditClass:OnKeyUp(key) if not self:IsShown() or not self:IsEnabled() then return @@ -813,7 +813,7 @@ function EditClass:OnChar(key) return self end ----@return string +---@return { buf: string, caret: integer } function EditClass:CreateUndoState() local state = { buf = self.buf, diff --git a/src/Classes/Item.lua b/src/Classes/Item.lua index fe50b268947..c30445ef5c4 100644 --- a/src/Classes/Item.lua +++ b/src/Classes/Item.lua @@ -1702,7 +1702,7 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) ---@param modId string ---@param newModId string ---@param mutated? boolean - ---@return boolean + ---@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 @@ -1869,7 +1869,7 @@ function ItemClass:GetModSpawnWeight(mod, includeTags, excludeTags, baseTags) end ---@param modAffix? string - ---@return boolean + ---@return string|false local function HasMavenInfluence(modAffix) return modAffix and modAffix:match("Elevated") or false end @@ -2010,7 +2010,6 @@ function ItemClass:BuildRaw() t_insert(rawLines, "Memory Strands: " .. self.memoryStrands) end ---@param modLine ModLine - ---@return string local function writeModLine(modLine) local line = modLine.line ---@param prefix string @@ -2403,7 +2402,7 @@ 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 +---@return Mod[] function ItemClass:BuildModListForSlotNum(baseList, slotNum) local slotName = self:GetPrimarySlot() or "" if slotNum ~= 1 then @@ -2711,7 +2710,7 @@ end ---@param item Item ---@param modLine ModLine ----@return Mod[] +---@return Mod[]? local function getRangedModList(item, modLine) if not modLine.range or not modLine.line:find("%((%-?%d+%.?%d*)%-(%-?%d+%.?%d*)%)") then return diff --git a/src/Classes/ItemSlotControl.lua b/src/Classes/ItemSlotControl.lua index 7eff6912d08..4bd78b74e69 100644 --- a/src/Classes/ItemSlotControl.lua +++ b/src/Classes/ItemSlotControl.lua @@ -135,7 +135,7 @@ end ---@param type string ---@param value Item ----@return boolean +---@return boolean? function ItemSlotClass:CanReceiveDrag(type, value) return type == "Item" and self.itemsTab:IsItemValidForSlot(value, self.slotName) end @@ -204,4 +204,4 @@ function ItemSlotClass:OnHoverKeyUp(key) end end end -end \ No newline at end of file +end diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index 0793f8c2d19..99c92b55312 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -87,7 +87,7 @@ for _, entry in pairs(data.flavourText) do end ---@param item? Item ----@return boolean +---@return boolean? local function isAnointable(item) return item and item.base and not item.base.cannotBeAnointed and item.base.subType ~= "Talisman" @@ -1660,7 +1660,7 @@ end ---@param itemSetId? integer ---@return ItemSet function ItemsTabClass:NewItemSet(itemSetId) - local itemSet = { id = itemSetId } + local itemSet = { id = itemSetId or 1, useSecondWeaponSet = false } if not itemSetId then itemSet.id = 1 while self.itemSets[itemSet.id] do @@ -2449,7 +2449,7 @@ function ItemsTabClass:UpdateAffixControl(control, item, affixType, outputTable, controlPowerCache = { } end ---@param modList string[] - ---@return string + ---@return string? local function pickModifierFromList(modList) -- pick mid tier modifier from a group if #modList == 1 then @@ -2510,7 +2510,7 @@ function ItemsTabClass:UpdateAffixControl(control, item, affixType, outputTable, end end controlPowerCache[modId] = power - return power + return power or 0 end table.sort(control.list, function(a, b) -- keep "None" as the first option @@ -2689,7 +2689,7 @@ function ItemsTabClass:GetEquippedSlotForItem(item) end ---@param item Item ----@return string +---@return string? function ItemsTabClass:GetComparisonSlotNameForItem(item) local equippedSlot = self:GetEquippedSlotForItem(item) if equippedSlot then @@ -4294,10 +4294,11 @@ end ---@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) - :gsub("currency{([^}]+)}",colorCodes.CURRENCY.."%1"..colorCodes.SOURCE) - :gsub("prophecy{([^}]+)}",colorCodes.PROPHECY.."%1"..colorCodes.SOURCE) + local formattedText, replacementCount = text:gsub("unique{([^}]+)}", colorCodes.UNIQUE.."%1"..colorCodes.SOURCE) + formattedText = formattedText:gsub("normal{([^}]+)}", colorCodes.NORMAL.."%1"..colorCodes.SOURCE) + formattedText = formattedText:gsub("currency{([^}]+)}", colorCodes.CURRENCY.."%1"..colorCodes.SOURCE) + formattedText, replacementCount = formattedText:gsub("prophecy{([^}]+)}", colorCodes.PROPHECY.."%1"..colorCodes.SOURCE) + return formattedText, replacementCount end ---@param item? Item diff --git a/src/Classes/ModDB.lua b/src/Classes/ModDB.lua index 9299b8a1a52..1a3833651d1 100644 --- a/src/Classes/ModDB.lua +++ b/src/Classes/ModDB.lua @@ -171,9 +171,9 @@ function ModDBClass:SumInternal(context, modType, cfg, flags, keywordFlags, sour end end if self.parent then - result = result + self.parent:SumInternal(context, modType, cfg, flags, keywordFlags, source, ...) + result = result + (self.parent:SumInternal(context, modType, cfg, flags, keywordFlags, source, ...) or 0) end - return result + return tonumber(result) or 0 end ---@param context ModStore @@ -222,9 +222,9 @@ function ModDBClass:MoreInternal(context, cfg, flags, keywordFlags, source, ...) end end if self.parent then - result = result * self.parent:MoreInternal(context, cfg, flags, keywordFlags, source, ...) + result = result * (self.parent:MoreInternal(context, cfg, flags, keywordFlags, source, ...) or 1) end - return result + return tonumber(result) or 1 end ---@param context ModStore diff --git a/src/Classes/ModList.lua b/src/Classes/ModList.lua index 80f72bd7b27..64f131bf384 100644 --- a/src/Classes/ModList.lua +++ b/src/Classes/ModList.lua @@ -132,9 +132,9 @@ function ModListClass:SumInternal(context, modType, cfg, flags, keywordFlags, so end end if self.parent then - result = result + self.parent:SumInternal(context, modType, cfg, flags, keywordFlags, source, ...) + result = result + (self.parent:SumInternal(context, modType, cfg, flags, keywordFlags, source, ...) or 0) end - return result + return tonumber(result) or 0 end ---@param context ModStore @@ -176,9 +176,9 @@ function ModListClass:MoreInternal(context, cfg, flags, keywordFlags, source, .. end end if self.parent then - result = result * self.parent:MoreInternal(context, cfg, flags, keywordFlags, source, ...) + result = result * (self.parent:MoreInternal(context, cfg, flags, keywordFlags, source, ...) or 1) end - return result + return tonumber(result) or 1 end ---@param context ModStore diff --git a/src/Classes/ModStore.lua b/src/Classes/ModStore.lua index bea029e859a..51c8474cff4 100644 --- a/src/Classes/ModStore.lua +++ b/src/Classes/ModStore.lua @@ -382,7 +382,11 @@ end ---@param noMod? boolean ---@return number function ModStoreClass:GetMultiplier(var, cfg, noMod) - return (not noMod and self:Override(cfg, multiplierName[var])) or (self.multipliers[var] or 0) + (self.parent and self.parent:GetMultiplier(var, cfg, true) or 0) + (not noMod and self:Sum("BASE", cfg, multiplierName[var]) or 0) + local override = not noMod and self:Override(cfg, multiplierName[var]) + if override ~= nil then + return tonumber(override) or 0 + end + return (self.multipliers[var] or 0) + (self.parent and self.parent:GetMultiplier(var, cfg, true) or 0) + (not noMod and self:Sum("BASE", cfg, multiplierName[var]) or 0) end ---@param stat string @@ -432,7 +436,7 @@ function ModStoreClass:GetStat(stat, cfg) -- if ReservationEfficiency is -100, ManaUnreserved is nan which breaks everything if Arcane Cloak is enabled if stat == "ManaUnreserved" and self.actor.output[stat] ~= self.actor.output[stat] then -- 0% reserved = total mana - return self.actor.output["Mana"] + return tonumber(self.actor.output["Mana"]) or 0 elseif stat == "ManaUnreserved" and self.actor.output[stat] ~= nil and self.actor.output[stat] < 0 then -- This reverse engineers how much mana is unreserved before efficiency for accurate Arcane Cloak calcs local reservedPercentBeforeEfficiency = (math.abs(self.actor.output["ManaUnreservedPercent"]) + 100) * ((100 + self.actor["ManaEfficiency"]) / 100) diff --git a/src/Classes/NotableDBControl.lua b/src/Classes/NotableDBControl.lua index 40440c6b363..d6b8a7d8ae8 100644 --- a/src/Classes/NotableDBControl.lua +++ b/src/Classes/NotableDBControl.lua @@ -13,7 +13,7 @@ local m_huge = math.huge local s_format = string.format ---@param node Node ----@return boolean +---@return boolean? local function IsAnointableNode(node) return node.recipe and #node.recipe >= 1 end @@ -314,5 +314,5 @@ end ---@param index integer ---@param node Node function NotableDBClass:OnSelCopy(index, node) - Copy(item.dn) -end \ No newline at end of file + Copy(node.dn) +end diff --git a/src/Classes/PassiveSpec.lua b/src/Classes/PassiveSpec.lua index 8ad1c3b8387..580bb32ed1e 100644 --- a/src/Classes/PassiveSpec.lua +++ b/src/Classes/PassiveSpec.lua @@ -417,7 +417,7 @@ function PassiveSpecClass:DecodePoePlannerURL(url, return_tree_version_only) -- both error messages begin with 'Invalid' ---@param bytes string ---@param start integer - ---@return integer + ---@return number local function byteToInt(bytes, start) -- get a little endian number from two bytes return bytes:byte(start) + bytes:byte(start + 1) * 256 @@ -518,7 +518,7 @@ end -- Decode the given GGG passive tree URL ---@param url string ----@return boolean? +---@return string? function PassiveSpecClass:DecodeURL(url) local b = common.base64.decode(url:gsub("^.+/",""):gsub("-","+"):gsub("_","/")) if not b or #b < 6 then @@ -933,7 +933,7 @@ end ---@param node Node ---@param visited Node[] ---@param noAscend? boolean ----@return Node? +---@return boolean? function PassiveSpecClass:FindStartFromNode(node, visited, noAscend) -- Mark the current node as visited so we don't go around in circles node.visited = true @@ -1038,7 +1038,7 @@ end -- Determine the shortest path from the given node to the class' start -- Only allocated nodes can be traversed ---@param rootId integer ----@return Node[]? +---@return table? function PassiveSpecClass:GetShortestPathToClassStart(rootId) local root = self.nodes[rootId] if not root or not root.alloc then @@ -2012,7 +2012,7 @@ end ---@param srcOidx integer ---@param srcNodesPerOrbit integer ---@param destNodesPerOrbit integer ----@return integer +---@return integer? function PassiveSpecClass:TranslateClusterOrbitIndex(srcOidx, srcNodesPerOrbit, destNodesPerOrbit) if srcNodesPerOrbit == destNodesPerOrbit then return srcOidx diff --git a/src/Classes/PoBArchivesProvider.lua b/src/Classes/PoBArchivesProvider.lua index b6e58da275b..693227eaf25 100644 --- a/src/Classes/PoBArchivesProvider.lua +++ b/src/Classes/PoBArchivesProvider.lua @@ -139,6 +139,7 @@ function PoBArchivesProviderClass:ParseBuilds(message) end end +---@return nil function PoBArchivesProviderClass:GetBuilds() self.statusMsg = "Loading.." wipeTable(self.buildList) @@ -148,12 +149,12 @@ function PoBArchivesProviderClass:GetBuilds() if self.mode == 'similar' then ---@cast self.importCode string self:GetRecommendations(self.importCode,self:GetApiUrl()) - return + return nil else launch:DownloadPage(self:GetApiUrl(), function(response, errMsg) if errMsg then self.statusMsg = errMsg - return + return nil end self:ParseBuilds(response.body) diff --git a/src/Classes/SkillsTab.lua b/src/Classes/SkillsTab.lua index 334045975a1..dae0c792c4d 100644 --- a/src/Classes/SkillsTab.lua +++ b/src/Classes/SkillsTab.lua @@ -1520,7 +1520,7 @@ end ---@param skillSetId? integer ---@return SkillSet function SkillsTabClass:NewSkillSet(skillSetId) - local skillSet = { id = skillSetId, socketGroupList = {} } + local skillSet = { id = skillSetId or 1, socketGroupList = {} } if not skillSetId then skillSet.id = 1 while self.skillSets[skillSet.id] do diff --git a/src/Classes/TradeHelpers.lua b/src/Classes/TradeHelpers.lua index 8cde0c9bbae..74b3e352ee1 100644 --- a/src/Classes/TradeHelpers.lua +++ b/src/Classes/TradeHelpers.lua @@ -86,7 +86,8 @@ end ---@return integer replacements function M.modLineTemplate(line) -- Replace decimal numbers first (e.g. "1.5"), then integers - return line:gsub("%-?[%d]+%.?[%d]*", "#") + local template, replacements = line:gsub("%-?[%d]+%.?[%d]*", "#") + return template, replacements end -- Helper: extract the first number from a mod line for value comparison, or in the case of # to # @@ -589,15 +590,15 @@ local ITEM_BOX_H = 20 ---@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 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 diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index 740f113f86c..1ecbaac0e81 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -289,7 +289,7 @@ 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 +---@return boolean? local function isSameAsDefaultList(list) return list and #list == 2 and list[1].stat == "FullDPS" and list[1].weightMult == 1.0 diff --git a/src/Classes/TreeTab.lua b/src/Classes/TreeTab.lua index 120b98f1b6a..d7f405d8706 100644 --- a/src/Classes/TreeTab.lua +++ b/src/Classes/TreeTab.lua @@ -1204,7 +1204,7 @@ function TreeTabClass:BuildPowerReportList(currentStat) 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 + return math.floor(node.power.distance or (#(node.path or { }) == 0 and 1 or #node.path)) or 0 end ---@param node Node ---@param name string