From eb91bcbf66d32aa216077b938ecc2b4285a77179 Mon Sep 17 00:00:00 2001 From: Openarl Date: Tue, 16 May 2017 19:05:02 +1000 Subject: [PATCH] Release 1.4.11 - Fixed stack overflow in copyTable() - Fixed interaction between weapon swap and skilsl granted by items - Consolidated list controls using a new base class --- Classes/BuildListControl.lua | 265 ++++++++----------- Classes/CalcsTab.lua | 14 +- Classes/ConfigTab.lua | 18 +- Classes/ItemDBControl.lua | 258 ++++-------------- Classes/ItemListControl.lua | 324 ++++++----------------- Classes/ItemSlotControl.lua | 33 ++- Classes/ItemsTab.lua | 393 +++++++++++++++------------- Classes/ListControl.lua | 331 +++++++++++++++++++++++ Classes/MinionListControl.lua | 286 +++++--------------- Classes/ModDB.lua | 2 +- Classes/ModList.lua | 2 +- Classes/PassiveSpecListControl.lua | 280 +++++--------------- Classes/PassiveTreeView.lua | 12 +- Classes/SkillListControl.lua | 404 +++++++++-------------------- Classes/SkillsTab.lua | 37 +-- Classes/TreeTab.lua | 1 + Modules/Build.lua | 6 +- Modules/BuildList.lua | 161 +----------- Modules/CalcActiveSkill.lua | 48 ++-- Modules/CalcSetup.lua | 113 ++++---- Modules/Common.lua | 25 +- Modules/ItemTools.lua | 17 +- Modules/Main.lua | 5 +- Modules/ModParser.lua | 22 +- PathOfBuilding.sln | 1 + README.md | 7 + changelog.txt | 6 + manifest.xml | 51 ++-- 28 files changed, 1282 insertions(+), 1840 deletions(-) create mode 100644 Classes/ListControl.lua diff --git a/Classes/BuildListControl.lua b/Classes/BuildListControl.lua index b0650694..b3b9c693 100644 --- a/Classes/BuildListControl.lua +++ b/Classes/BuildListControl.lua @@ -5,181 +5,124 @@ -- local launch, main = ... -local t_insert = table.insert -local t_remove = table.remove -local m_min = math.min -local m_max = math.max -local m_floor = math.floor +local s_format = string.format -local BuildListClass = common.NewClass("BuildList", "Control", "ControlHost", function(self, anchor, x, y, width, height, listMode) - self.Control(anchor, x, y, width, height) - self.ControlHost() +local BuildListClass = common.NewClass("BuildList", "ListControl", function(self, anchor, x, y, width, height, listMode) + self.ListControl(anchor, x, y, width, height, 20, false, listMode.list) self.listMode = listMode - self.controls.scrollBar = common.New("ScrollBarControl", {"RIGHT",self,"RIGHT"}, -1, 0, 18, 0, 40) - self.controls.scrollBar.height = function() - local width, height = self:GetSize() - return height - 2 - end - self.controls.scrollBar.locked = function() - return self.listMode.edit - end - self.controls.nameEdit = common.New("EditControl", {"TOPLEFT",self,"TOPLEFT"}, 0, 0, 0, 20, nil, nil, "\\/:%*%?\"<>|%c", 100) - self.controls.nameEdit.shown = function() - return self.listMode.edit - end - self.controls.nameEdit.width = function() - local width, height = self:GetSize() - return width - 20 - end + self.showRowSeparators = true end) -function BuildListClass:ScrollSelIntoView() - if self.listMode.sel then - local width, height = self:GetSize() - self.controls.scrollBar:SetContentDimension(#self.listMode.list * 20, height - 4) - self.controls.scrollBar:ScrollIntoView((self.listMode.sel - 2) * 20, 60) +function BuildListClass:SelByFileName(selFileName) + for index, build in ipairs(self.list) do + if build.fileName == selFileName then + self:SelectIndex(index) + break + end end end -function BuildListClass:SelectIndex(index) - if self.listMode.list[index] then - self.listMode.sel = index - self:ScrollSelIntoView() - end +function BuildListClass:LoadBuild(build) + main:SetMode("BUILD", main.buildPath..build.fileName, build.buildName) end -function BuildListClass:IsMouseOver() - if not self:IsShown() then - return - end - return self:IsMouseInBounds() or self:GetMouseOverControl() -end - -function BuildListClass:Draw(viewPort) - local x, y = self:GetPos() - local width, height = self:GetSize() - local list = self.listMode.list - local scrollBar = self.controls.scrollBar - scrollBar:SetContentDimension(#list * 20, height - 4) - if self.hasFocus then - SetDrawColor(1, 1, 1) - else - SetDrawColor(0.5, 0.5, 0.5) - end - DrawImage(nil, x, y, width, height) - SetDrawColor(0, 0, 0) - DrawImage(nil, x + 1, y + 1, width - 2, height - 2) - SetViewport(x + 2, y + 2, width - 22, height - 4) - local selBuildIndex = self.listMode.sel - local minIndex = m_floor(scrollBar.offset / 20 + 1) - local maxIndex = m_min(m_floor((scrollBar.offset + height) / 20 + 1), #list) - for index = minIndex, maxIndex do - local build = list[index] - local lineY = 20 * (index - 1) - scrollBar.offset - if index == selBuildIndex then - self.controls.nameEdit.y = lineY + 2 - end - local mOverLine - if not scrollBar.dragging then - local cursorX, cursorY = GetCursorPos() - local relX = cursorX - (x + 2) - local relY = cursorY - (y + 2) - if relX >= 0 and relX < width - 19 and relY >= 0 and relY >= lineY and relY < height - 2 and relY < lineY + 20 then - mOverLine = true - end - end - if index == selBuildIndex then - SetDrawColor(1, 1, 1) - else - SetDrawColor(0.5, 0.5, 0.5) - end - DrawImage(nil, 0, lineY, width - 22, 20) - if mOverLine or index == selBuildIndex then - SetDrawColor(0.33, 0.33, 0.33) - elseif index % 2 == 0 then - SetDrawColor(0.05, 0.05, 0.05) - else - SetDrawColor(0, 0, 0) - end - DrawImage(nil, 0, lineY + 1, width - 22, 18) - if self.listMode.edit ~= index then - DrawString(0, lineY + 2, "LEFT", 16, "VAR", "^7"..(build.buildName or "?")) - if mOverLine or index == selBuildIndex then - SetDrawColor(0.33, 0.33, 0.33) - elseif index % 2 == 0 then - SetDrawColor(0.05, 0.05, 0.05) +function BuildListClass:RenameBuild(build, copyOnName) + local controls = { } + controls.label = common.New("LabelControl", nil, 0, 20, 0, 16, "^7Enter the new name for this build:") + controls.edit = common.New("EditControl", nil, 0, 40, 350, 20, build.buildName, nil, "\\/:%*%?\"<>|%c", 100, function(buf) + controls.save.enabled = false + if buf:match("%S") and buf:lower() ~= build.buildName:lower() then + local newName = buf..".xml" + local newFile = io.open(main.buildPath..newName, "r") + if newFile then + newFile:close() else - SetDrawColor(0, 0, 0) - end - DrawImage(nil, width - 162, lineY + 2, 162, 16) - SetDrawColor(build.className and data.colorCodes[build.className:upper()] or "^7") - DrawString(width - 160, lineY + 2, "LEFT", 16, "VAR", string.format("Level %d %s", build.level or 1, (build.ascendClassName ~= "None" and build.ascendClassName) or build.className or "?")) - end - end - SetViewport() - if self.listMode.edit then - self.listMode:SelectControl(self.controls.nameEdit) - end - self:DrawControls(viewPort) -end - -function BuildListClass:OnKeyDown(key, doubleClick) - if not self:IsShown() or not self:IsEnabled() then - return - end - local mOverControl = self:GetMouseOverControl() - if mOverControl and mOverControl.OnKeyDown then - return mOverControl:OnKeyDown(key) - end - if not self:IsMouseOver() and key:match("BUTTON") then - return - end - if self.listMode.edit then - return self - end - if key == "LEFTBUTTON" then - self.listMode.sel = nil - local x, y = self:GetPos() - local width, height = self:GetSize() - local cursorX, cursorY = GetCursorPos() - if cursorX >= x + 2 and cursorY >= y + 2 and cursorX < x + width - 20 and cursorY < y + height - 2 then - local index = math.floor((cursorY - y - 2 + self.controls.scrollBar.offset) / 20) + 1 - local selBuild = self.listMode.list[index] - if selBuild then - self.listMode.sel = index - if doubleClick then - self.listMode:LoadSel() - end + controls.save.enabled = true end end - elseif key == "UP" then - self:SelectIndex(((self.listMode.sel or 1) - 2) % #self.listMode.list + 1) - elseif key == "DOWN" then - self:SelectIndex((self.listMode.sel or #self.listMode.list) % #self.listMode.list + 1) - elseif key == "HOME" then - self:SelectIndex(1) - elseif key == "END" then - self:SelectIndex(#self.listMode.list) - elseif self.listMode.sel then - if key == "BACK" or key == "DELETE" then - self.listMode:DeleteSel() - elseif key == "F2" then - self.listMode:RenameSel() - elseif key == "RETURN" then - self.listMode:LoadSel() + end) + controls.save = common.New("ButtonControl", nil, -45, 70, 80, 20, "Save", function() + local newBuildName = controls.edit.buf + local newFileName = newBuildName..".xml" + if copyOnName then + local inFile, msg = io.open(main.buildPath..build.fileName, "r") + if not inFile then + main:OpenMessagePopup("Error", "Couldn't open '"..build.fileName.."': "..msg) + return + end + local outFile, msg = io.open(main.buildPath..newFileName, "w") + if not outFile then + main:OpenMessagePopup("Error", "Couldn't create '"..newFileName.."': "..msg) + return + end + outFile:write(inFile:read("*a")) + inFile:close() + outFile:close() + else + local res, msg = os.rename(main.buildPath..build.fileName, main.buildPath..newFileName) + if not res then + main:OpenMessagePopup("Error", "Couldn't rename '"..build.fileName.."' to '"..newFileName.."': "..msg) + return + end + build.buildName = newBuildName + build.fileName = newFileName end - end - return self + self.listMode:BuildList() + self:SelByFileName(newFileName) + main:ClosePopup() + self.listMode:SelectControl(self) + end) + controls.save.enabled = false + controls.cancel = common.New("ButtonControl", nil, 45, 70, 80, 20, "Cancel", function() + main:ClosePopup() + self.listMode:SelectControl(self) + end) + main:OpenPopup(370, 100, copyOnName and "Copy Build" or "Rename Build", controls, "save", "edit") end -function BuildListClass:OnKeyUp(key) - if not self:IsShown() or not self:IsEnabled() then - return +function BuildListClass:DeleteBuild(build) + main:OpenConfirmPopup("Confirm Delete", "Are you sure you want to delete build:\n"..build.buildName.."\nThis cannot be undone.", "Delete", function() + os.remove(main.buildPath..build.fileName) + self.listMode:BuildList() + self.selIndex = nil + self.selValue = nil + end) +end + +function BuildListClass:GetColumnOffset(column) + if column == 1 then + return 0 + elseif column == 2 then + return self:GetProperty("width") - 164 end - if key == "WHEELDOWN" then - self.controls.scrollBar:Scroll(1) - elseif key == "WHEELUP" then - self.controls.scrollBar:Scroll(-1) +end + +function BuildListClass:GetRowValue(column, index, build) + if column == 1 then + return build.buildName or "?" + elseif column == 2 then + return s_format("%sLevel %d %s", + build.className and data.colorCodes[build.className:upper()] or "^7", + build.level or 1, + (build.ascendClassName ~= "None" and build.ascendClassName) or build.className or "?" + ) end - return self -end \ No newline at end of file +end + +function BuildListClass:OnSelClick(index, build, doubleClick) + if doubleClick then + self:LoadBuild(build) + end +end + +function BuildListClass:OnSelDelete(index, build) + self:DeleteBuild(build) +end + +function BuildListClass:OnSelKeyDown(index, build, key) + if key == "RETURN" then + self:LoadBuild(build) + elseif key == "F2" then + self:RenameBuild(build) + end +end diff --git a/Classes/CalcsTab.lua b/Classes/CalcsTab.lua index 80b81c91..8680952f 100644 --- a/Classes/CalcsTab.lua +++ b/Classes/CalcsTab.lua @@ -405,8 +405,8 @@ function CalcsTabClass:PowerBuilder() local calcFunc, calcBase = self:GetNodeCalculator() local cache = { } local newPowerMax = { - dps = 0, - def = 0 + offence = 0, + defence = 0 } if not self.powerMax then self.powerMax = newPowerMax @@ -423,19 +423,19 @@ function CalcsTabClass:PowerBuilder() end local output = cache[node.modKey] if calcBase.Minion then - node.power.dps = (output.Minion.CombinedDPS - calcBase.Minion.CombinedDPS) / calcBase.Minion.CombinedDPS + node.power.offence = (output.Minion.CombinedDPS - calcBase.Minion.CombinedDPS) / calcBase.Minion.CombinedDPS else - node.power.dps = (output.CombinedDPS - calcBase.CombinedDPS) / calcBase.CombinedDPS + node.power.offence = (output.CombinedDPS - calcBase.CombinedDPS) / calcBase.CombinedDPS end - node.power.def = (output.LifeUnreserved - calcBase.LifeUnreserved) / m_max(3000, calcBase.Life) + + node.power.defence = (output.LifeUnreserved - calcBase.LifeUnreserved) / m_max(3000, calcBase.Life) + (output.Armour - calcBase.Armour) / m_max(10000, calcBase.Armour) + (output.EnergyShield - calcBase.EnergyShield) / m_max(3000, calcBase.EnergyShield) + (output.Evasion - calcBase.Evasion) / m_max(10000, calcBase.Evasion) + (output.LifeRegen - calcBase.LifeRegen) / 500 + (output.EnergyShieldRegen - calcBase.EnergyShieldRegen) / 1000 if node.path then - newPowerMax.dps = m_max(newPowerMax.dps, node.power.dps) - newPowerMax.def = m_max(newPowerMax.def, node.power.def) + newPowerMax.offence = m_max(newPowerMax.offence, node.power.offence) + newPowerMax.defence = m_max(newPowerMax.defence, node.power.defence) end end if coroutine.running() and GetTime() - start > 100 then diff --git a/Classes/ConfigTab.lua b/Classes/ConfigTab.lua index b97d77cb..41ab80a5 100644 --- a/Classes/ConfigTab.lua +++ b/Classes/ConfigTab.lua @@ -574,15 +574,17 @@ end function ConfigTabClass:Save(xml) for k, v in pairs(self.input) do - local child = { elem = "Input", attrib = {name = k} } - if type(v) == "number" then - child.attrib.number = tostring(v) - elseif type(v) == "boolean" then - child.attrib.boolean = tostring(v) - else - child.attrib.string = tostring(v) + if v then + local child = { elem = "Input", attrib = {name = k} } + if type(v) == "number" then + child.attrib.number = tostring(v) + elseif type(v) == "boolean" then + child.attrib.boolean = tostring(v) + else + child.attrib.string = tostring(v) + end + t_insert(xml, child) end - t_insert(xml, child) end self.modFlag = false end diff --git a/Classes/ItemDBControl.lua b/Classes/ItemDBControl.lua index bdaff6e7..54165b9e 100644 --- a/Classes/ItemDBControl.lua +++ b/Classes/ItemDBControl.lua @@ -5,25 +5,21 @@ -- local launch, main = ... +local pairs = pairs +local ipairs = ipairs local t_insert = table.insert -local t_remove = table.remove -local m_min = math.min -local m_max = math.max -local m_floor = math.floor -local ItemDBClass = common.NewClass("ItemDB", "Control", "ControlHost", function(self, anchor, x, y, width, height, itemsTab, db) - self.Control(anchor, x, y, width, height) - self.ControlHost() +local ItemDBClass = common.NewClass("ItemDB", "ListControl", function(self, anchor, x, y, width, height, itemsTab, db) + self.ListControl(anchor, x, y, width, height, 16, false) self.itemsTab = itemsTab self.db = db + self.dragTargetList = { itemsTab.controls.itemList } + for _, slot in pairs(itemsTab.slots) do + t_insert(self.dragTargetList, slot) + end self.sortControl = { NAME = { key = "name", order = 1, dir = "ASCEND", func = function(a,b) return a:gsub("^The ","") < b:gsub("^The ","") end } } - self.controls.scrollBar = common.New("ScrollBarControl", {"RIGHT",self,"RIGHT"}, -1, 0, 16, 0, 32) - self.controls.scrollBar.height = function() - local width, height = self:GetSize() - return height - 2 - end local leagueFlag = { } local typeFlag = { } for _, item in pairs(db.list) do @@ -49,25 +45,25 @@ local ItemDBClass = common.NewClass("ItemDB", "Control", "ControlHost", function t_insert(self.typeList, 1, "Any type") self.slotList = { "Any slot", "Weapon 1", "Weapon 2", "Helmet", "Body Armour", "Gloves", "Boots", "Amulet", "Ring", "Belt", "Jewel" } self.controls.slot = common.New("DropDownControl", {"BOTTOMLEFT",self,"TOPLEFT"}, 0, -22, 95, 18, self.slotList, function() - self:BuildOrderList() + self:BuildList() end) self.controls.type = common.New("DropDownControl", {"LEFT",self.controls.slot,"RIGHT"}, 2, 0, 135, 18, self.typeList, function() - self:BuildOrderList() + self:BuildList() end) self.controls.league = common.New("DropDownControl", {"LEFT",self.controls.type,"RIGHT"}, 2, 0, 126, 18, self.leagueList, function() - self:BuildOrderList() + self:BuildList() end) self.controls.league.shown = function() return #self.leagueList > 2 end self.controls.search = common.New("EditControl", {"BOTTOMLEFT",self,"TOPLEFT"}, 0, -2, 258, 18, "", "Search", "%c", 100, function() - self:BuildOrderList() + self:BuildList() end) self.controls.searchMode = common.New("DropDownControl", {"LEFT",self.controls.search,"RIGHT"}, 2, 0, 100, 18, { "Anywhere", "Names", "Modifiers" }, function() - self:BuildOrderList() + self:BuildList() end) self:BuildSortOrder() - self:BuildOrderList() + self:BuildList() end) function ItemDBClass:DoesItemMatchFilters(item) @@ -105,7 +101,7 @@ function ItemDBClass:DoesItemMatchFilters(item) end if not found then searchStr = searchStr:gsub(" ","") - for i, mod in pairs(item.baseModList) do + for i, mod in ipairs(item.baseModList) do if mod.name:lower():find(searchStr, 1, true) then found = true break @@ -130,14 +126,14 @@ function ItemDBClass:BuildSortOrder() end) end -function ItemDBClass:BuildOrderList() - self.orderList = wipeTable(self.orderList) +function ItemDBClass:BuildList() + wipeTable(self.list) for id, item in pairs(self.db.list) do if self:DoesItemMatchFilters(item) then - t_insert(self.orderList, item) + t_insert(self.list, item) end end - table.sort(self.orderList, function(a, b) + table.sort(self.list, function(a, b) for _, data in ipairs(self.sortOrder) do local aVal = a[data.key] local bVal = b[data.key] @@ -160,195 +156,53 @@ function ItemDBClass:BuildOrderList() end) end -function ItemDBClass:SelectIndex(index) - self.selItem = self.orderList[index] - if self.selItem then - self.selIndex = index - self.controls.scrollBar:ScrollIntoView((index - 2) * 16, 48) +function ItemDBClass:GetRowValue(column, index, item) + if column == 1 then + return data.colorCodes[item.rarity] .. item.name end end -function ItemDBClass:IsMouseOver() - if not self:IsShown() then - return - end - return self:IsMouseInBounds() or self:GetMouseOverControl() +function ItemDBClass:AddValueTooltip(index, item) + self.itemsTab:AddItemTooltip(item, nil, true) + return data.colorCodes[item.rarity], true end -function ItemDBClass:Draw(viewPort) - local x, y = self:GetPos() - local width, height = self:GetSize() - local list = self.db.list - local orderList = self.orderList - local scrollBar = self.controls.scrollBar - scrollBar:SetContentDimension(#orderList * 16, height - 4) - if self.selItem and self.selDragging then - local cursorX, cursorY = GetCursorPos() - if not self.selDragActive and (cursorX-self.selCX)*(cursorX-self.selCX)+(cursorY-self.selCY)*(cursorY-self.selCY) > 100 then - self.selDragActive = true - end - end - if self.hasFocus then - SetDrawColor(1, 1, 1) - else - SetDrawColor(0.5, 0.5, 0.5) - end - DrawImage(nil, x, y, width, height) - SetDrawColor(0, 0, 0) - DrawImage(nil, x + 1, y + 1, width - 2, height - 2) - self:DrawControls(viewPort) - SetViewport(x + 2, y + 2, width - 20, height - 4) - local ttItem, ttY, ttWidth - local minIndex = m_floor(scrollBar.offset / 16 + 1) - local maxIndex = m_min(m_floor((scrollBar.offset + height) / 16 + 1), #orderList) - for index = minIndex, maxIndex do - local item = orderList[index] - local itemY = 16 * (index - 1) - scrollBar.offset - local nameWidth = DrawStringWidth(16, "VAR", item.name) - if not scrollBar.dragging and (not self.itemsTab.selControl or self.hasFocus or self.controls.search.hasFocus) and not main.popups[1] then - local cursorX, cursorY = GetCursorPos() - local relX = cursorX - (x + 2) - local relY = cursorY - (y + 2) - if relX >= 0 and relX < width - 17 and relY >= 0 and relY >= itemY and relY < height - 2 and relY < itemY + 16 then - ttItem = item - ttWidth = m_max(nameWidth + 8, relX) - ttY = itemY + y + 2 +function ItemDBClass:GetDragValue(index, item) + return "Item", item +end + +function ItemDBClass:OnSelClick(index, item, doubleClick) + if IsKeyDown("CTRL") then + -- Add item + local newItem = itemLib.makeItemFromRaw(item.raw) + itemLib.normaliseQuality(newItem) + self.itemsTab:AddItem(newItem, true) + + -- Equip item if able + local slotName = itemLib.getPrimarySlotForItem(newItem) + if slotName and self.itemsTab.slots[slotName] then + if self.itemsTab.slots[slotName].weaponSet == 1 and self.itemsTab.useSecondWeaponSet then + -- Redirect to second weapon set + slotName = slotName .. " Swap" end - end - if item == ttItem or item == self.selItem then - if self.hasFocus then - SetDrawColor(1, 1, 1) - else - SetDrawColor(0.5, 0.5, 0.5) - end - DrawImage(nil, 0, itemY, width - 20, 16) - SetDrawColor(0.15, 0.15, 0.15) - DrawImage(nil, 0, itemY + 1, width - 20, 14) - end - SetDrawColor(data.colorCodes[item.rarity]) - DrawString(0, itemY, "LEFT", 16, "VAR", item.name) - end - SetViewport() - if ttItem then - self.itemsTab:AddItemTooltip(ttItem, nil, true) - SetDrawLayer(nil, 100) - main:DrawTooltip(x + 2, ttY, ttWidth, 16, viewPort, data.colorCodes[ttItem.rarity], true) - SetDrawLayer(nil, 0) - end -end - -function ItemDBClass:OnKeyDown(key, doubleClick) - if not self:IsShown() or not self:IsEnabled() then - return - end - local mOverControl = self:GetMouseOverControl() - if mOverControl and mOverControl.OnKeyDown then - return mOverControl:OnKeyDown(key) - end - if not self:IsMouseOver() and key:match("BUTTON") then - return - end - if key == "LEFTBUTTON" then - self.selItem = nil - self.selIndex = nil - local x, y = self:GetPos() - local width, height = self:GetSize() - local cursorX, cursorY = GetCursorPos() - if cursorX >= x + 2 and cursorY >= y + 2 and cursorX < x + width - 18 and cursorY < y + height - 2 then - local index = m_floor((cursorY - y - 2 + self.controls.scrollBar.offset) / 16) + 1 - local selItem = self.orderList[index] - if selItem then - self.selItem = selItem - self.selIndex = index - if IsKeyDown("CTRL") then - -- Immediately add and equip it - self.itemsTab:CreateDisplayItemFromRaw(selItem.raw, true) - local newItem = self.itemsTab.displayItem - self.itemsTab:AddDisplayItem(true) - itemLib.buildItemModList(newItem) - local slotName = itemLib.getPrimarySlotForItem(newItem) - if slotName and self.itemsTab.slots[slotName] then - if IsKeyDown("SHIFT") then - local altSlot = slotName:gsub("1","2") - if self.itemsTab:IsItemValidForSlot(newItem, altSlot) then - slotName = altSlot - end - end - if self.itemsTab.slots[slotName].selItemId ~= newItem.id then - self.itemsTab.slots[slotName]:SetSelItemId(newItem.id) - self.itemsTab:PopulateSlots() - self.itemsTab:AddUndoState() - self.itemsTab.build.buildFlag = true - end - end - elseif doubleClick then - self.itemsTab:CreateDisplayItemFromRaw(selItem.raw, true) + if IsKeyDown("SHIFT") then + -- Redirect to second slot if possible + local altSlot = slotName:gsub("1","2") + if self.itemsTab:IsItemValidForSlot(newItem, altSlot) then + slotName = altSlot end end + self.itemsTab.slots[slotName]:SetSelItemId(newItem.id) end - if self.selItem then - self.selCX = cursorX - self.selCY = cursorY - self.selDragging = true - self.selDragActive = false - end - elseif key == "UP" then - self:SelectIndex(((self.selIndex or 1) - 2) % #self.orderList + 1) - elseif key == "DOWN" then - self:SelectIndex((self.selIndex or #self.orderList) % #self.orderList + 1) - elseif key == "HOME" then - self:SelectIndex(1) - elseif key == "END" then - self:SelectIndex(#self.orderList) - elseif key == "c" and IsKeyDown("CTRL") then - if self.selItem then - Copy(self.selItem.raw:gsub("\n","\r\n")) - end + + self.itemsTab:PopulateSlots() + self.itemsTab:AddUndoState() + self.itemsTab.build.buildFlag = true + elseif doubleClick then + self.itemsTab:CreateDisplayItemFromRaw(item.raw, true) end - return self end -function ItemDBClass:OnKeyUp(key) - if not self:IsShown() or not self:IsEnabled() then - return - end - if key == "WHEELDOWN" then - self.controls.scrollBar:Scroll(1) - elseif key == "WHEELUP" then - self.controls.scrollBar:Scroll(-1) - elseif self.selItem then - if key == "LEFTBUTTON" then - self.selDragging = false - if self.selDragActive then - self.selDragActive = false - if self.itemsTab.controls.itemList:IsMouseOver() and self.itemsTab.controls.itemList.selDragIndex then - self.itemsTab:CreateDisplayItemFromRaw(self.selItem.raw, true) - local newItem = self.itemsTab.displayItem - self.itemsTab:AddDisplayItem() - itemLib.buildItemModList(newItem) - t_remove(self.itemsTab.orderList, #self.itemsTab.orderList) - t_insert(self.itemsTab.orderList, self.itemsTab.controls.itemList.selDragIndex, newItem.id) - else - for slotName, slot in pairs(self.itemsTab.slots) do - if not slot.inactive and slot:IsMouseOver() then - if self.itemsTab:IsItemValidForSlot(self.selItem, slotName) then - self.itemsTab:CreateDisplayItemFromRaw(self.selItem.raw, true) - local newItem = self.itemsTab.displayItem - self.itemsTab:AddDisplayItem(true) - itemLib.buildItemModList(newItem) - slot:SetSelItemId(newItem.id) - self.itemsTab:PopulateSlots() - self.itemsTab:AddUndoState() - self.itemsTab.build.buildFlag = true - end - self.selItem = nil - self.selIndex = nil - return - end - end - end - end - end - end - return self +function ItemDBClass:OnSelCopy(index, item) + Copy(item.raw:gsub("\n","\r\n")) end \ No newline at end of file diff --git a/Classes/ItemListControl.lua b/Classes/ItemListControl.lua index 770566e1..d469126d 100644 --- a/Classes/ItemListControl.lua +++ b/Classes/ItemListControl.lua @@ -5,281 +5,107 @@ -- local launch, main = ... +local pairs = pairs local t_insert = table.insert -local t_remove = table.remove -local m_min = math.min -local m_max = math.max -local m_floor = math.floor -local ItemListClass = common.NewClass("ItemList", "Control", "ControlHost", function(self, anchor, x, y, width, height, itemsTab) - self.Control(anchor, x, y, width, height) - self.ControlHost() +local ItemListClass = common.NewClass("ItemList", "ListControl", function(self, anchor, x, y, width, height, itemsTab) + self.ListControl(anchor, x, y, width, height, 16, true, itemsTab.orderList) self.itemsTab = itemsTab - self.controls.scrollBar = common.New("ScrollBarControl", {"RIGHT",self,"RIGHT"}, -1, 0, 16, 0, 32) - self.controls.scrollBar.height = function() - local width, height = self:GetSize() - return height - 2 + self.label = "^7All items:" + self.dragTargetList = { } + for _, slot in pairs(itemsTab.slots) do + t_insert(self.dragTargetList, slot) end self.controls.sort = common.New("ButtonControl", {"BOTTOMRIGHT",self,"TOPRIGHT"}, -64, -2, 60, 18, "Sort", function() - table.sort(itemsTab.orderList, function(a, b) - local itemA = itemsTab.list[a] - local itemB = itemsTab.list[b] - local primSlotA = itemLib.getPrimarySlotForItem(itemA) - local primSlotB = itemLib.getPrimarySlotForItem(itemB) - if primSlotA ~= primSlotB then - if not itemsTab.slotOrder[primSlotA] then - return false - elseif not itemsTab.slotOrder[primSlotB] then - return true - end - return itemsTab.slotOrder[primSlotA] < itemsTab.slotOrder[primSlotB] - end - local equipSlotA = itemsTab:GetEquippedSlotForItem(itemA) - local equipSlotB = itemsTab:GetEquippedSlotForItem(itemB) - if equipSlotA and equipSlotB then - return itemsTab.slotOrder[equipSlotA.slotName] < itemsTab.slotOrder[equipSlotB.slotName] - elseif not equipSlotA then - return false - elseif not equipSlotB then - return true - end - return itemA.name < itemB.name - end) - itemsTab:AddUndoState() + itemsTab:SortItemList() end) self.controls.delete = common.New("ButtonControl", {"BOTTOMRIGHT",self,"TOPRIGHT"}, 0, -2, 60, 18, "Delete", function() - self:OnKeyUp("DELETE") + self:OnSelDelete(self.selIndex, self.selValue) end) self.controls.delete.enabled = function() - return self.selItem ~= nil + return self.selValue ~= nil end end) -function ItemListClass:SelectIndex(index) - local selItemId = self.itemsTab.orderList[index] - if selItemId then - self.selItem = self.itemsTab.list[selItemId] - self.selIndex = index - self.controls.scrollBar:ScrollIntoView((index - 2) * 16, 48) +function ItemListClass:GetRowValue(column, index, itemId) + local item = self.itemsTab.list[itemId] + if column == 1 then + return data.colorCodes[item.rarity] .. item.name .. (not self.itemsTab:GetEquippedSlotForItem(item) and " ^9(Unused)" or "") end end -function ItemListClass:IsMouseOver() - if not self:IsShown() then - return - end - return self:IsMouseInBounds() or self:GetMouseOverControl() +function ItemListClass:AddValueTooltip(index, itemId) + local item = self.itemsTab.list[itemId] + self.itemsTab:AddItemTooltip(item, nil, true) + return data.colorCodes[item.rarity], true end -function ItemListClass:Draw(viewPort) - local x, y = self:GetPos() - local width, height = self:GetSize() - local list = self.itemsTab.list - local orderList = self.itemsTab.orderList - local scrollBar = self.controls.scrollBar - scrollBar:SetContentDimension(#orderList * 16, height - 4) - local cursorX, cursorY = GetCursorPos() - self.selDragIndex = nil - if self.selItem and self.selDragging then - if not self.selDragActive and (cursorX-self.selCX)*(cursorX-self.selCX)+(cursorY-self.selCY)*(cursorY-self.selCY) > 100 then - self.selDragActive = true - end - elseif (self.itemsTab.controls.uniqueDB:IsShown() and self.itemsTab.controls.uniqueDB.selDragActive) or (self.itemsTab.controls.rareDB:IsShown() and self.itemsTab.controls.rareDB.selDragActive) then - self.selDragActive = true - else - self.selDragActive = false +function ItemListClass:GetDragValue(index, itemId) + return "Item", self.itemsTab.list[itemId] +end + +function ItemListClass:ReceiveDrag(type, value, source) + if type == "Item" then + local newItem = itemLib.makeItemFromRaw(value.raw) + itemLib.normaliseQuality(newItem) + self.itemsTab:AddItem(newItem, true, self.selDragIndex) + self.itemsTab:PopulateSlots() + self.itemsTab:AddUndoState() + self.itemsTab.build.buildFlag = true end - if self.selDragActive then - if cursorX >= x + 2 and cursorY >= y + 2 and cursorX < x + width - 18 and cursorY < y + height - 2 then - local index = math.floor((cursorY - y - 2 + scrollBar.offset) / 16 + 0.5) + 1 - if not self.selDragging or index < self.selIndex or index > self.selIndex + 1 then - self.selDragIndex = m_min(index, #orderList + 1) +end + +function ItemListClass:OnOrderChange() + self.itemsTab:AddUndoState() +end + +function ItemListClass:OnSelClick(index, itemId, doubleClick) + local item = self.itemsTab.list[itemId] + if IsKeyDown("CTRL") then + local slotName = itemLib.getPrimarySlotForItem(item) + if slotName and self.itemsTab.slots[slotName] then + if self.itemsTab.slots[slotName].weaponSet == 1 and self.itemsTab.useSecondWeaponSet then + -- Redirect to second weapon set + slotName = slotName .. " Swap" end - end - end - DrawString(x, y - 20, "LEFT", 16, "VAR", "^7All items:") - if self.hasFocus then - SetDrawColor(1, 1, 1) - else - SetDrawColor(0.5, 0.5, 0.5) - end - DrawImage(nil, x, y, width, height) - SetDrawColor(0, 0, 0) - DrawImage(nil, x + 1, y + 1, width - 2, height - 2) - self:DrawControls(viewPort) - SetViewport(x + 2, y + 2, width - 20, height - 4) - local ttItem, ttY, ttWidth - local minIndex = m_floor(scrollBar.offset / 16 + 1) - local maxIndex = m_min(m_floor((scrollBar.offset + height) / 16 + 1), #orderList) - for index = minIndex, maxIndex do - local item = list[orderList[index]] - local itemY = 16 * (index - 1) - scrollBar.offset - local nameWidth = DrawStringWidth(16, "VAR", item.name) - if not scrollBar.dragging and not self.selDragActive and (not self.itemsTab.selControl or self.hasFocus) and not main.popups[1] then - local cursorX, cursorY = GetCursorPos() - local relX = cursorX - (x + 2) - local relY = cursorY - (y + 2) - if relX >= 0 and relX < width - 17 and relY >= 0 and relY >= itemY and relY < height - 2 and relY < itemY + 16 then - ttItem = item - ttWidth = m_max(nameWidth + 8, relX) - ttY = itemY + y + 2 + if IsKeyDown("SHIFT") then + -- Redirect to second slot if possible + local altSlot = slotName:gsub("1","2") + if self.itemsTab:IsItemValidForSlot(item, altSlot) then + slotName = altSlot + end end - end - if item == ttItem or item == self.selItem then - if self.hasFocus then - SetDrawColor(1, 1, 1) + if self.itemsTab.slots[slotName].selItemId == item.id then + self.itemsTab.slots[slotName]:SetSelItemId(0) else - SetDrawColor(0.5, 0.5, 0.5) + self.itemsTab.slots[slotName]:SetSelItemId(item.id) end - DrawImage(nil, 0, itemY, width - 20, 16) - SetDrawColor(0.15, 0.15, 0.15) - DrawImage(nil, 0, itemY + 1, width - 20, 14) + self.itemsTab:PopulateSlots() + self.itemsTab:AddUndoState() + self.itemsTab.build.buildFlag = true end - SetDrawColor(data.colorCodes[item.rarity]) - DrawString(0, itemY, "LEFT", 16, "VAR", item.name) - if not self.itemsTab:GetEquippedSlotForItem(item) then - DrawString(nameWidth + 8, itemY, "LEFT", 16, "VAR", "^9(Unused)") - end - end - if self.selDragIndex then - local itemY = 16 * (self.selDragIndex - 1) - scrollBar.offset - SetDrawColor(1, 1, 1) - DrawImage(nil, 0, itemY - 1, width - 20, 3) - SetDrawColor(0, 0, 0) - DrawImage(nil, 0, itemY, width - 20, 1) - end - SetViewport() - if ttItem then - self.itemsTab:AddItemTooltip(ttItem) - SetDrawLayer(nil, 100) - main:DrawTooltip(x + 2, ttY, ttWidth, 16, viewPort, data.colorCodes[ttItem.rarity], true) - SetDrawLayer(nil, 0) + elseif doubleClick then + self.itemsTab:SetDisplayItem(copyTable(item)) end end -function ItemListClass:OnKeyDown(key, doubleClick) - if not self:IsShown() or not self:IsEnabled() then - return - end - local mOverControl = self:GetMouseOverControl() - if mOverControl and mOverControl.OnKeyDown then - return mOverControl:OnKeyDown(key) - end - if not self:IsMouseOver() and key:match("BUTTON") then - return - end - if key == "LEFTBUTTON" then - self.selItem = nil +function ItemListClass:OnSelCopy(index, itemId) + local item = self.itemsTab.list[itemId] + Copy(itemLib.createItemRaw(item):gsub("\n","\r\n")) +end + +function ItemListClass:OnSelDelete(index, itemId) + local item = self.itemsTab.list[itemId] + local equipSlot = self.itemsTab:GetEquippedSlotForItem(item) + if equipSlot then + main:OpenConfirmPopup("Delete Item", item.name.." is currently equipped in "..equipSlot.label..".\nAre you sure you want to delete it?", "Delete", function() + self.itemsTab:DeleteItem(item) + self.selIndex = nil + self.selValue = nil + end) + else + self.itemsTab:DeleteItem(item) self.selIndex = nil - local x, y = self:GetPos() - local width, height = self:GetSize() - local cursorX, cursorY = GetCursorPos() - if cursorX >= x + 2 and cursorY >= y + 2 and cursorX < x + width - 18 and cursorY < y + height - 2 then - local index = math.floor((cursorY - y - 2 + self.controls.scrollBar.offset) / 16) + 1 - local selItemId = self.itemsTab.orderList[index] - if selItemId then - self.selItem = self.itemsTab.list[selItemId] - self.selIndex = index - if IsKeyDown("CTRL") then - -- Equip it - local slotName = itemLib.getPrimarySlotForItem(self.selItem) - if slotName and self.itemsTab.slots[slotName] then - if IsKeyDown("SHIFT") then - local altSlot = slotName:gsub("1","2") - if self.itemsTab:IsItemValidForSlot(self.selItem, altSlot) then - slotName = altSlot - end - end - if self.itemsTab.slots[slotName].selItemId == selItemId then - self.itemsTab.slots[slotName]:SetSelItemId(0) - else - self.itemsTab.slots[slotName]:SetSelItemId(selItemId) - end - self.itemsTab:PopulateSlots() - self.itemsTab:AddUndoState() - self.itemsTab.build.buildFlag = true - end - elseif doubleClick then - self.itemsTab:SetDisplayItem(copyTable(self.selItem)) - end - end - end - if self.selItem then - self.selCX = cursorX - self.selCY = cursorY - self.selDragging = true - self.selDragActive = false - end - elseif key == "UP" then - self:SelectIndex(((self.selIndex or 1) - 2) % #self.itemsTab.orderList + 1) - elseif key == "DOWN" then - self:SelectIndex((self.selIndex or #self.itemsTab.orderList) % #self.itemsTab.orderList + 1) - elseif key == "HOME" then - self:SelectIndex(1) - elseif key == "END" then - self:SelectIndex(#self.itemsTab.orderList) - elseif key == "c" and IsKeyDown("CTRL") then - if self.selItem then - Copy(itemLib.createItemRaw(self.selItem):gsub("\n","\r\n")) - end + self.selValue = nil end - return self end - -function ItemListClass:OnKeyUp(key) - if not self:IsShown() or not self:IsEnabled() then - return - end - if key == "WHEELDOWN" then - self.controls.scrollBar:Scroll(1) - elseif key == "WHEELUP" then - self.controls.scrollBar:Scroll(-1) - elseif self.selItem then - if key == "BACK" or key == "DELETE" then - local equipSlot = self.itemsTab:GetEquippedSlotForItem(self.selItem) - if equipSlot then - main:OpenConfirmPopup("Delete Item", self.selItem.name.." is currently equipped in "..equipSlot.label..".\nAre you sure you want to delete it?", "Delete", function() - self.itemsTab:DeleteItem(self.selItem) - self.selItem = nil - self.selIndex = nil - end) - else - self.itemsTab:DeleteItem(self.selItem) - self.selItem = nil - self.selIndex = nil - end - elseif key == "LEFTBUTTON" then - self.selDragging = false - if self.selDragActive then - self.selDragActive = false - if self.selDragIndex then - if self.selDragIndex ~= self.selIndex then - t_remove(self.itemsTab.orderList, self.selIndex) - if self.selDragIndex > self.selIndex then - self.selDragIndex = self.selDragIndex - 1 - end - t_insert(self.itemsTab.orderList, self.selDragIndex, self.selItem.id) - self.itemsTab:AddUndoState() - self.selItem = nil - self.selIndex = nil - end - else - for slotName, slot in pairs(self.itemsTab.slots) do - if not slot.inactive and slot:IsMouseOver() then - if self.itemsTab:IsItemValidForSlot(self.selItem, slotName) and slot.selItemId ~= self.selItem.id then - slot:SetSelItemId(self.selItem.id) - self.itemsTab:PopulateSlots() - self.itemsTab:AddUndoState() - self.itemsTab.build.buildFlag = true - end - self.selItem = nil - self.selIndex = nil - return - end - end - end - end - end - end - return self -end \ No newline at end of file diff --git a/Classes/ItemSlotControl.lua b/Classes/ItemSlotControl.lua index 8d35ab45..07210814 100644 --- a/Classes/ItemSlotControl.lua +++ b/Classes/ItemSlotControl.lua @@ -5,7 +5,7 @@ -- local launch, main = ... -local ipairs = ipairs +local pairs = pairs local t_insert = table.insert local m_min = math.min @@ -76,22 +76,35 @@ function ItemSlotClass:Populate() end end +function ItemSlotClass:CanReceiveDrag(type, value) + return type == "Item" and self.itemsTab:IsItemValidForSlot(value, self.slotName) +end + +function ItemSlotClass:ReceiveDrag(type, value, source) + if value.id and self.itemsTab.list[value.id] then + self:SetSelItemId(value.id) + else + local newItem = itemLib.makeItemFromRaw(value.raw) + itemLib.normaliseQuality(newItem) + self.itemsTab:AddItem(newItem, true) + self:SetSelItemId(newItem.id) + end + self.itemsTab:PopulateSlots() + self.itemsTab:AddUndoState() + self.itemsTab.build.buildFlag = true +end + function ItemSlotClass:Draw(viewPort) local x, y = self:GetPos() local width, height = self:GetSize() DrawString(x + self.labelOffset, y + 2, "RIGHT_X", height - 4, "VAR", "^7"..self.label..":") self.DropDownControl:Draw(viewPort) self:DrawControls(viewPort) - local highlight = false - for _, control in pairs({self.itemsTab.controls.itemList, self.itemsTab.controls.uniqueDB, self.itemsTab.controls.rareDB}) do - if control:IsShown() and control.selDragging and control.selDragActive and control.selItem and self.itemsTab:IsItemValidForSlot(control.selItem, self.slotName) then - highlight = true - SetDrawColor(0, 1, 0, 0.25) - DrawImage(nil, x, y, width, height) - break - end + if self.otherDragSource then + SetDrawColor(0, 1, 0, 0.25) + DrawImage(nil, x, y, width, height) end - if self.nodeId and (self.dropped or (self:IsMouseOver() and (highlight or not self.itemsTab.selControl))) then + if self.nodeId and (self.dropped or (self:IsMouseOver() and (self.otherDragSource or not self.itemsTab.selControl))) then SetDrawLayer(nil, 15) local viewerX = x + width + 5 local viewerY = m_min(y, viewPort.y + viewPort.height - 304) diff --git a/Classes/ItemsTab.lua b/Classes/ItemsTab.lua index 04e5bb6d..823c7ef6 100644 --- a/Classes/ItemsTab.lua +++ b/Classes/ItemsTab.lua @@ -406,6 +406,213 @@ function ItemsTabClass:GetSocketAndJewelForNodeID(nodeId) return self.sockets[nodeId], self.list[self.sockets[nodeId].selItemId] end +-- Adds the given item to the build's item list +function ItemsTabClass:AddItem(item, noAutoEquip, index) + if not item.id then + -- Find an unused item ID + item.id = 1 + while self.list[item.id] do + item.id = item.id + 1 + end + + if index then + t_insert(self.orderList, index, item.id) + else + -- Add it to the end of the display order list + t_insert(self.orderList, item.id) + end + + if not noAutoEquip then + -- Autoequip it + for _, slot in ipairs(self.orderedSlots) do + if not slot.nodeId and slot.selItemId == 0 and slot:IsShown() and self:IsItemValidForSlot(item, slot.slotName) then + slot:SetSelItemId(item.id) + break + end + end + end + end + + -- Add it to the list + self.list[item.id] = item + itemLib.buildItemModList(item) +end + +-- Adds the current display item to the build's item list +function ItemsTabClass:AddDisplayItem(noAutoEquip) + -- Add it to the list and clear the current display item + self:AddItem(self.displayItem, noAutoEquip) + self:SetDisplayItem() + + self:PopulateSlots() + self:AddUndoState() + self.build.buildFlag = true +end + +-- Sorts the build's item list +function ItemsTabClass:SortItemList() + table.sort(self.orderList, function(a, b) + local itemA = self.list[a] + local itemB = self.list[b] + local primSlotA = itemLib.getPrimarySlotForItem(itemA) + local primSlotB = itemLib.getPrimarySlotForItem(itemB) + if primSlotA ~= primSlotB then + if not self.slotOrder[primSlotA] then + return false + elseif not self.slotOrder[primSlotB] then + return true + end + return self.slotOrder[primSlotA] < self.slotOrder[primSlotB] + end + local equipSlotA = self:GetEquippedSlotForItem(itemA) + local equipSlotB = self:GetEquippedSlotForItem(itemB) + if equipSlotA and equipSlotB then + return self.slotOrder[equipSlotA.slotName] < self.slotOrder[equipSlotB.slotName] + elseif equipSlotA then + return true + elseif equipSlotB then + return false + end + return itemA.name < itemB.name + end) + self:AddUndoState() +end + +-- Deletes an item +function ItemsTabClass:DeleteItem(item) + for _, slot in pairs(self.slots) do + if slot.selItemId == item.id then + slot:SetSelItemId(0) + self.build.buildFlag = true + end + end + for index, id in pairs(self.orderList) do + if id == item.id then + t_remove(self.orderList, index) + break + end + end + for _, spec in pairs(self.build.treeTab.specList) do + for nodeId, itemId in pairs(spec.jewels) do + if itemId == item.id then + spec.jewels[nodeId] = 0 + end + end + end + self.list[item.id] = nil + self:PopulateSlots() + self:AddUndoState() +end + +-- Attempt to create a new item from the given item raw text and sets it as the new display item +function ItemsTabClass:CreateDisplayItemFromRaw(itemRaw, normalise) + local newItem = itemLib.makeItemFromRaw(itemRaw) + if newItem then + if normalise then + itemLib.normaliseQuality(newItem) + end + self:SetDisplayItem(newItem) + end +end + +-- Sets the display item to the given item +function ItemsTabClass:SetDisplayItem(item) + self.displayItem = item + if item then + -- Update the display item controls + self.controls.displayItemVariant.list = item.variantList + self.controls.displayItemVariant.sel = item.variant + self:UpdateDisplayItemRangeLines() + self.controls.scrollBarH:SetOffset(self.controls.scrollBarH.offsetMax) + item.craftable = item.crafted and item.affixes and item.affixLimit > 0 + if item.craftable then + local prefixList = { } + local suffixList = { } + for name, data in pairs(item.affixes) do + if not data.exclude or (not data.exclude[item.base.subType] and not data.exclude[item.baseName]) then + if data.type == "Prefix" then + t_insert(prefixList, name) + elseif data.type == "Suffix" then + t_insert(suffixList, name) + end + end + end + table.sort(prefixList) + t_insert(prefixList, 1, "None") + table.sort(suffixList) + t_insert(suffixList, 1, "None") + local prefixTable = { } + local suffixTable = { } + for list, out in pairs({[prefixList] = prefixTable, [suffixList] = suffixTable}) do + for i, name in pairs(list) do + out[i] = { + label = name, + value = name, + } + if item.affixes[name] then + out[i].label = out[i].label .. " ^8[" .. table.concat(item.affixes[name], "/") .. "]" + end + end + end + for i = 1, item.affixLimit/2 do + local pre = self.controls["displayItemAffix"..i] + pre.list = prefixTable + pre.outputTable = "prefixes" + pre.outputIndex = i + pre.sel = isValueInArray(prefixList, item.prefixes[i] or "None") or 1 + local suf = self.controls["displayItemAffix"..(i+item.affixLimit/2)] + suf.list = suffixTable + suf.outputTable = "suffixes" + suf.outputIndex = i + suf.sel = isValueInArray(suffixList, item.suffixes[i] or "None") or 1 + end + end + else + self.controls.scrollBarH:SetOffset(0) + end +end + +-- Updates the range line dropdown and range slider for the current display item +function ItemsTabClass:UpdateDisplayItemRangeLines() + if self.displayItem and self.displayItem.rangeLineList[1] then + wipeTable(self.controls.displayItemRangeLine.list) + for _, modLine in ipairs(self.displayItem.rangeLineList) do + t_insert(self.controls.displayItemRangeLine.list, modLine.line) + end + self.controls.displayItemRangeLine.sel = 1 + self.controls.displayItemRangeSlider.val = self.displayItem.rangeLineList[1].range + end +end + +-- Returns the first slot in which the given item is equipped +function ItemsTabClass:GetEquippedSlotForItem(item) + for _, slot in ipairs(self.orderedSlots) do + if not slot.inactive and slot.selItemId == item.id then + return slot + end + end +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 +function ItemsTabClass:IsItemValidForSlot(item, slotName) + if item.type == slotName:gsub(" %d+","") then + return true + elseif slotName == "Weapon 1" or slotName == "Weapon 1 Swap" or slotName == "Weapon" then + return item.base.weapon ~= nil + elseif slotName == "Weapon 2" or slotName == "Weapon 2 Swap" then + local weapon1Sel = self.slots[slotName == "Weapon 2" and "Weapon 1" or "Weapon 1 Swap"].selItemId or 0 + local weapon1Type = weapon1Sel > 0 and self.list[weapon1Sel].base.type or "None" + if weapon1Type == "None" then + return item.type == "Quiver" or item.type == "Shield" or (data.weaponTypeInfo[item.type] and data.weaponTypeInfo[item.type].oneHand) + elseif weapon1Type == "Bow" then + return item.type == "Quiver" + elseif data.weaponTypeInfo[weapon1Type].oneHand then + return item.type == "Shield" or (data.weaponTypeInfo[item.type] and data.weaponTypeInfo[item.type].oneHand and ((weapon1Type == "Wand" and item.type == "Wand") or (weapon1Type ~= "Wand" and item.type ~= "Wand"))) + end + end +end + -- Opens the item crafting popup function ItemsTabClass:CraftItem() local controls = { } @@ -638,179 +845,6 @@ function ItemsTabClass:EnchantDisplayItem() main:OpenPopup(550, 130, "Enchant Item", controls) end --- Attempt to create a new item from the given item raw text and sets it as the new display item -function ItemsTabClass:CreateDisplayItemFromRaw(itemRaw, normalise) - local newItem = itemLib.makeItemFromRaw(itemRaw) - if newItem then - if normalise then - itemLib.normaliseQuality(newItem) - end - self:SetDisplayItem(newItem) - end -end - --- Sets the display item to the given item -function ItemsTabClass:SetDisplayItem(item) - self.displayItem = item - if item then - -- Update the display item controls - self.controls.displayItemVariant.list = item.variantList - self.controls.displayItemVariant.sel = item.variant - self:UpdateDisplayItemRangeLines() - self.controls.scrollBarH:SetOffset(self.controls.scrollBarH.offsetMax) - item.craftable = item.crafted and item.affixes and item.affixLimit > 0 - if item.craftable then - local prefixList = { } - local suffixList = { } - for name, data in pairs(item.affixes) do - if not data.exclude or (not data.exclude[item.base.subType] and not data.exclude[item.baseName]) then - if data.type == "Prefix" then - t_insert(prefixList, name) - elseif data.type == "Suffix" then - t_insert(suffixList, name) - end - end - end - table.sort(prefixList) - t_insert(prefixList, 1, "None") - table.sort(suffixList) - t_insert(suffixList, 1, "None") - local prefixTable = { } - local suffixTable = { } - for list, out in pairs({[prefixList] = prefixTable, [suffixList] = suffixTable}) do - for i, name in pairs(list) do - out[i] = { - label = name, - value = name, - } - if item.affixes[name] then - out[i].label = out[i].label .. " ^8[" .. table.concat(item.affixes[name], "/") .. "]" - end - end - end - for i = 1, item.affixLimit/2 do - local pre = self.controls["displayItemAffix"..i] - pre.list = prefixTable - pre.outputTable = "prefixes" - pre.outputIndex = i - pre.sel = isValueInArray(prefixList, item.prefixes[i] or "None") or 1 - local suf = self.controls["displayItemAffix"..(i+item.affixLimit/2)] - suf.list = suffixTable - suf.outputTable = "suffixes" - suf.outputIndex = i - suf.sel = isValueInArray(suffixList, item.suffixes[i] or "None") or 1 - end - end - else - self.controls.scrollBarH:SetOffset(0) - end -end - --- Updates the range line dropdown and range slider for the current display item -function ItemsTabClass:UpdateDisplayItemRangeLines() - if self.displayItem and self.displayItem.rangeLineList[1] then - wipeTable(self.controls.displayItemRangeLine.list) - for _, modLine in ipairs(self.displayItem.rangeLineList) do - t_insert(self.controls.displayItemRangeLine.list, modLine.line) - end - self.controls.displayItemRangeLine.sel = 1 - self.controls.displayItemRangeSlider.val = self.displayItem.rangeLineList[1].range - end -end - --- Adds the given item to the build's item list -function ItemsTabClass:AddItem(item, noAutoEquip) - if not item.id then - -- Find an unused item ID - item.id = 1 - while self.list[item.id] do - item.id = item.id + 1 - end - - -- Add it to the end of the display order list - t_insert(self.orderList, item.id) - - if not noAutoEquip then - -- Autoequip it - for _, slotName in ipairs(baseSlots) do - if self.slots[slotName].selItemId == 0 and self:IsItemValidForSlot(item, slotName) then - self.slots[slotName]:SetSelItemId(item.id) - break - end - end - end - end - - -- Add it to the list - self.list[item.id] = item - itemLib.buildItemModList(item) -end - --- Adds the current display item to the build's item list -function ItemsTabClass:AddDisplayItem(noAutoEquip) - -- Add it to the list and clear the current display item - self:AddItem(self.displayItem, noAutoEquip) - self:SetDisplayItem() - - self:PopulateSlots() - self:AddUndoState() - self.build.buildFlag = true -end - -function ItemsTabClass:DeleteItem(item) - for _, slot in pairs(self.slots) do - if slot.selItemId == item.id then - slot:SetSelItemId(0) - self.build.buildFlag = true - end - end - for index, id in pairs(self.orderList) do - if id == item.id then - t_remove(self.orderList, index) - break - end - end - for _, spec in pairs(self.build.treeTab.specList) do - for nodeId, itemId in pairs(spec.jewels) do - if itemId == item.id then - spec.jewels[nodeId] = 0 - end - end - end - self.list[item.id] = nil - self:PopulateSlots() - self:AddUndoState() -end - --- Returns the first slot in which the given item is equipped -function ItemsTabClass:GetEquippedSlotForItem(item) - for _, slot in ipairs(self.orderedSlots) do - if not slot.inactive and slot.selItemId == item.id then - return slot - end - end -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 -function ItemsTabClass:IsItemValidForSlot(item, slotName) - if item.type == slotName:gsub(" %d+","") then - return true - elseif slotName == "Weapon 1" or slotName == "Weapon 1 Swap" or slotName == "Weapon" then - return item.base.weapon ~= nil - elseif slotName == "Weapon 2" or slotName == "Weapon 2 Swap" then - local weapon1Sel = self.slots[slotName == "Weapon 2" and "Weapon 1" or "Weapon 1 Swap"].selItemId or 0 - local weapon1Type = weapon1Sel > 0 and self.list[weapon1Sel].base.type or "None" - if weapon1Type == "None" then - return item.type == "Quiver" or item.type == "Shield" or (data.weaponTypeInfo[item.type] and data.weaponTypeInfo[item.type].oneHand) - elseif weapon1Type == "Bow" then - return item.type == "Quiver" - elseif data.weaponTypeInfo[weapon1Type].oneHand then - return item.type == "Shield" or (data.weaponTypeInfo[item.type] and data.weaponTypeInfo[item.type].oneHand and ((weapon1Type == "Wand" and item.type == "Wand") or (weapon1Type ~= "Wand" and item.type ~= "Wand"))) - end - end -end - function ItemsTabClass:AddItemTooltip(item, slot, dbMode) -- Item name local rarityCode = data.colorCodes[item.rarity] @@ -888,10 +922,10 @@ function ItemsTabClass:AddItemTooltip(item, slot, dbMode) if base.armour.blockChance and armourData.BlockChance > 0 then main:AddTooltipLine(16, s_format("^x7F7F7FChance to Block: %s%d%%", armourData.BlockChance ~= base.armour.blockChance and data.colorCodes.MAGIC or "^7", armourData.BlockChance)) end - for _, def in ipairs({{var="Armour",label="Armour"},{var="Evasion",label="Evasion Rating"},{var="EnergyShield",label="Energy Shield"}}) do - local itemVal = armourData[def.var] + for _, defence in ipairs({{var="Armour",label="Armour"},{var="Evasion",label="Evasion Rating"},{var="EnergyShield",label="Energy Shield"}}) do + local itemVal = armourData[defence.var] if itemVal and itemVal > 0 then - main:AddTooltipLine(16, s_format("^x7F7F7F%s: %s%d", def.label, itemVal ~= base.armour[def.var:sub(1,1):lower()..def.var:sub(2,-1).."Base"] and data.colorCodes.MAGIC or "^7", itemVal)) + main:AddTooltipLine(16, s_format("^x7F7F7F%s: %s%d", defence.label, itemVal ~= base.armour[defence.var:sub(1,1):lower()..defence.var:sub(2,-1).."Base"] and data.colorCodes.MAGIC or "^7", itemVal)) end end elseif base.flask then @@ -978,7 +1012,7 @@ function ItemsTabClass:AddItemTooltip(item, slot, dbMode) end main:AddTooltipSeparator(14) - -- Mod differences + -- Stat differences local calcFunc, calcBase = self.build.calcsTab:GetMiscCalculator() if calcFunc then if base.flask then @@ -1120,7 +1154,10 @@ end function ItemsTabClass:RestoreUndoState(state) self.useSecondWeaponSet = state.useSecondWeaponSet self.list = state.list - self.orderList = state.orderList + wipeTable(self.orderList) + for k, v in pairs(state.orderList) do + self.orderList[k] = v + end for slotName, selItemId in pairs(state.slotSelItemId) do self.slots[slotName]:SetSelItemId(selItemId) end diff --git a/Classes/ListControl.lua b/Classes/ListControl.lua new file mode 100644 index 00000000..ab36d970 --- /dev/null +++ b/Classes/ListControl.lua @@ -0,0 +1,331 @@ +-- Path of Building +-- +-- Class: List Control +-- Basic list control. +-- +-- This is an abstract base class; derived classes can supply these properties and methods to configure the list control: +-- .label [Adds a label above the top left corner] +-- .dragTargetList [List of controls that can receive drag events from this list control] +-- .showRowSeparators [Shows separators between rows] +-- :GetColumnOffset(column) [Called to get the offset of the given column] +-- :GetRowValue(column, index, value) [Required; called to retrieve the text for the given column of the given list value] +-- :AddValueTooltip(index, value) [Called to add the tooltip for the given list value] +-- :GetDragValue(index, value) [Called to retrieve the drag type and object for the given list value] +-- :CanReceiveDrag(type, value) [Called on drag target to determine if it can receive this value] +-- :ReceiveDrag(type, value, source) [Called on the drag target when a drag completes] +-- :OnDragSend(index, value, target) [Called after a drag event] +-- :OnOrderChange() [Called after list order is changed through dragging] +-- :OnSelect(index, value) [Called when a list value is selected] +-- :OnSelClick(index, value, doubleClick) [Called when a list value is clicked] +-- :OnSelCopy(index, value) [Called when Ctrl+C is pressed while a list value is selected] +-- :OnSelDelete(index, value) [Called when backspace or delete is pressed while a list value is selected] +-- :OnSelKeyDown(index, value) [Called when any other key is pressed while a list value is selected] +-- +local launch, main = ... + +local ipairs = ipairs +local t_insert = table.insert +local t_remove = table.remove +local m_min = math.min +local m_max = math.max +local m_floor = math.floor + +local ListClass = common.NewClass("ListControl", "Control", "ControlHost", function(self, anchor, x, y, width, height, rowHeight, isMutable, list) + self.Control(anchor, x, y, width, height) + self.ControlHost() + self.rowHeight = rowHeight + self.isMutable = isMutable + self.list = list or { } + self.controls.scrollBar = common.New("ScrollBarControl", {"RIGHT",self,"RIGHT"}, -1, 0, 16, 0, rowHeight * 2) + self.controls.scrollBar.height = function() + local width, height = self:GetSize() + return height - 2 + end +end) + +function ListClass:SelectIndex(index) + self.selValue = self.list[index] + if self.selValue then + self.selIndex = index + local width, height = self:GetSize() + self.controls.scrollBar:SetContentDimension(#self.list * self.rowHeight, height - 4) + self.controls.scrollBar:ScrollIntoView((index - 2) * self.rowHeight, self.rowHeight * 3) + if self.OnSelect then + self:OnSelect(self.selIndex, self.selValue) + end + end +end + +function ListClass:GetColumnOffset(column) + if column == 1 then + return 0 + end +end + +function ListClass:IsMouseOver() + if not self:IsShown() then + return + end + return self:IsMouseInBounds() or self:GetMouseOverControl() +end + +function ListClass:Draw(viewPort) + local x, y = self:GetPos() + local width, height = self:GetSize() + local rowHeight = self.rowHeight + local list = self.list + local scrollBar = self.controls.scrollBar + scrollBar:SetContentDimension(#list * rowHeight, height - 4) + local cursorX, cursorY = GetCursorPos() + if self.selValue and self.selDragging and not self.selDragActive and (cursorX-self.selCX)*(cursorX-self.selCX)+(cursorY-self.selCY)*(cursorY-self.selCY) > 100 then + self.selDragActive = true + if self.dragTargetList then + self.dragType, self.dragValue = self:GetDragValue(self.selIndex, self.selValue) + for _, target in ipairs(self.dragTargetList) do + if not target.CanReceiveDrag or target:CanReceiveDrag(self.dragType, self.dragValue) then + target.otherDragSource = self + end + end + end + end + self.selDragIndex = nil + if (self.selDragActive or self.otherDragSource) and self.isMutable then + if cursorX >= x + 2 and cursorY >= y + 2 and cursorX < x + width - 18 and cursorY < y + height - 2 then + local index = math.floor((cursorY - y - 2 + scrollBar.offset) / rowHeight + 0.5) + 1 + if not self.selIndex or index < self.selIndex or index > self.selIndex + 1 then + self.selDragIndex = m_min(index, #list + 1) + end + end + end + if self.selDragActive and self.dragTargetList then + self.dragTarget = nil + for _, target in ipairs(self.dragTargetList) do + if not self.dragTarget and target.otherDragSource == self and target:IsMouseOver() then + self.dragTarget = target + target.otherDragTargeting = true + else + target.otherDragTargeting = false + end + end + end + local label = self:GetProperty("label") + if label then + DrawString(x, y - 20, "LEFT", 16, "VAR", label) + end + if self.otherDragSource then + SetDrawColor(0.2, 0.6, 0.2) + elseif self.hasFocus then + SetDrawColor(1, 1, 1) + else + SetDrawColor(0.5, 0.5, 0.5) + end + DrawImage(nil, x, y, width, height) + if self.otherDragSource then + SetDrawColor(0, 0.05, 0) + else + SetDrawColor(0, 0, 0) + end + DrawImage(nil, x + 1, y + 1, width - 2, height - 2) + self:DrawControls(viewPort) + SetViewport(x + 2, y + 2, width - 20, height - 4) + local textOffsetY = self.showRowSeparators and 2 or 0 + local textHeight = rowHeight - textOffsetY * 2 + local ttIndex, ttValue, ttX, ttY, ttWidth + local minIndex = m_floor(scrollBar.offset / rowHeight + 1) + local maxIndex = m_min(m_floor((scrollBar.offset + height) / rowHeight + 1), #list) + local column = 1 + local elipWidth = DrawStringWidth(textHeight, "VAR", "...") + while true do + local colOffset = self:GetColumnOffset(column) + if not colOffset then + break + end + local colWidth = (self:GetColumnOffset(column + 1) or width - 20) - colOffset + for index = minIndex, maxIndex do + local lineY = rowHeight * (index - 1) - scrollBar.offset + local value = list[index] + local text = self:GetRowValue(column, index, value) + local textWidth = DrawStringWidth(textHeight, "VAR", text) + if textWidth > colWidth - 2 then + local clipIndex = DrawStringCursorIndex(textHeight, "VAR", text, colWidth - elipWidth - 2, 0) + text = text:sub(1, clipIndex - 1) .. "..." + textWidth = DrawStringWidth(textHeight, "VAR", text) + end + if not scrollBar.dragging and not self.selDragActive then + local cursorX, cursorY = GetCursorPos() + local relX = cursorX - (x + 2) + local relY = cursorY - (y + 2) + if relX >= colOffset and relX < width - 20 and relY >= 0 and relY >= lineY and relY < height - 2 and relY < lineY + rowHeight then + ttIndex = index + ttValue = value + ttX = x + 2 + colOffset + ttY = lineY + y + 2 + ttWidth = m_max(textWidth + 8, relX - colOffset) + end + end + if self.showRowSeparators then + if self.hasFocus and value == self.selValue then + SetDrawColor(1, 1, 1) + elseif value == ttValue then + SetDrawColor(0.8, 0.8, 0.8) + else + SetDrawColor(0.5, 0.5, 0.5) + end + DrawImage(nil, colOffset, lineY, colWidth, rowHeight) + if (value == self.selValue or value == ttValue) then + SetDrawColor(0.33, 0.33, 0.33) + elseif index % 2 == 0 then + SetDrawColor(0.05, 0.05, 0.05) + else + SetDrawColor(0, 0, 0) + end + DrawImage(nil, colOffset, lineY + 1, colWidth, rowHeight - 2) + elseif value == self.selValue or value == ttValue then + if self.hasFocus and value == self.selValue then + SetDrawColor(1, 1, 1) + elseif value == ttValue then + SetDrawColor(0.8, 0.8, 0.8) + else + SetDrawColor(0.5, 0.5, 0.5) + end + DrawImage(nil, colOffset, lineY, colWidth, rowHeight) + SetDrawColor(0.15, 0.15, 0.15) + DrawImage(nil, colOffset, lineY + 1, colWidth, rowHeight - 2) + end + SetDrawColor(1, 1, 1) + DrawString(colOffset, lineY + textOffsetY, "LEFT", textHeight, "VAR", text) + end + column = column + 1 + end + if self.selDragIndex then + local lineY = rowHeight * (self.selDragIndex - 1) - scrollBar.offset + SetDrawColor(1, 1, 1) + DrawImage(nil, 0, lineY - 1, width - 20, 3) + SetDrawColor(0, 0, 0) + DrawImage(nil, 0, lineY, width - 20, 1) + end + SetViewport() + if self.selDragActive and self.dragTargetList and (not self.isMutable or not self:IsMouseOver()) then + local text = self:GetRowValue(1, self.selIndex, self.selValue) + local strWidth = DrawStringWidth(16, "VAR", text) + SetDrawLayer(nil, 90) + SetDrawColor(0.15, 0.15, 0.15, 0.75) + DrawImage(nil, cursorX, cursorY - 8, strWidth + 2, 18) + SetDrawColor(1, 1, 1) + DrawString(cursorX + 1, cursorY - 7, "LEFT", 16, "VAR", text) + SetDrawLayer(nil, 0) + end + if ttIndex and self.AddValueTooltip then + local col, center = self:AddValueTooltip(ttIndex, ttValue) + SetDrawLayer(nil, 100) + main:DrawTooltip(ttX, ttY, ttWidth, rowHeight, viewPort, col, center) + SetDrawLayer(nil, 0) + end +end + +function ListClass:OnKeyDown(key, doubleClick) + if not self:IsShown() or not self:IsEnabled() then + return + end + local mOverControl = self:GetMouseOverControl() + if mOverControl and mOverControl.OnKeyDown then + return mOverControl:OnKeyDown(key) + end + if not self:IsMouseOver() and key:match("BUTTON") then + return + end + if key == "LEFTBUTTON" then + self.selValue = nil + self.selIndex = nil + local x, y = self:GetPos() + local width, height = self:GetSize() + local cursorX, cursorY = GetCursorPos() + if cursorX >= x + 2 and cursorY >= y + 2 and cursorX < x + width - 18 and cursorY < y + height - 2 then + local index = math.floor((cursorY - y - 2 + self.controls.scrollBar.offset) / self.rowHeight) + 1 + self.selValue = self.list[index] + if self.selValue then + self.selIndex = index + if self.isMutable or self.dragTargetList then + self.selCX = cursorX + self.selCY = cursorY + self.selDragging = true + self.selDragActive = false + end + if self.OnSelect then + self:OnSelect(self.selIndex, self.selValue) + end + if self.OnSelClick then + self:OnSelClick(self.selIndex, self.selValue, doubleClick) + end + end + end + elseif #self.list > 0 then + if key == "UP" then + self:SelectIndex(((self.selIndex or 1) - 2) % #self.list + 1) + elseif key == "DOWN" then + self:SelectIndex((self.selIndex or #self.list) % #self.list + 1) + elseif key == "HOME" then + self:SelectIndex(1) + elseif key == "END" then + self:SelectIndex(#self.list) + elseif self.selValue then + if key == "c" and IsKeyDown("CTRL") then + if self.OnSelCopy then + self:OnSelCopy(self.selIndex, self.selValue) + end + elseif key == "BACK" or key == "DELETE" then + if self.OnSelDelete then + self:OnSelDelete(self.selIndex, self.selValue) + end + elseif self.OnSelKeyDown then + self:OnSelKeyDown(self.selIndex, self.selValue, key) + end + end + end + return self +end + +function ListClass:OnKeyUp(key) + if not self:IsShown() or not self:IsEnabled() then + return + end + if key == "WHEELDOWN" then + self.controls.scrollBar:Scroll(1) + elseif key == "WHEELUP" then + self.controls.scrollBar:Scroll(-1) + elseif self.selValue then + if key == "LEFTBUTTON" then + self.selDragging = false + if self.selDragActive then + self.selDragActive = false + if self.selDragIndex and self.selDragIndex ~= self.selIndex then + t_remove(self.list, self.selIndex) + if self.selDragIndex > self.selIndex then + self.selDragIndex = self.selDragIndex - 1 + end + t_insert(self.list, self.selDragIndex, self.selValue) + if self.OnOrderChange then + self:OnOrderChange() + end + self.selValue = nil + elseif self.dragTarget then + self.dragTarget:ReceiveDrag(self.dragType, self.dragValue, self) + if self.OnDragSend then + self:OnDragSend(self.selIndex, self.selValue, self.dragTarget) + end + self.selValue = nil + end + if self.dragTargetList then + for _, target in ipairs(self.dragTargetList) do + target.otherDragSource = nil + target.otherDragTargeting = false + end + end + self.dragType = nil + self.dragValue = nil + self.dragTarget = nil + end + end + end + return self +end \ No newline at end of file diff --git a/Classes/MinionListControl.lua b/Classes/MinionListControl.lua index c269d1cf..9a98883d 100644 --- a/Classes/MinionListControl.lua +++ b/Classes/MinionListControl.lua @@ -5,247 +5,91 @@ -- local launch, main = ... +local ipairs = ipairs local t_insert = table.insert local t_remove = table.remove -local m_min = math.min -local m_max = math.max -local m_floor = math.floor local s_format = string.format -local MinionListClass = common.NewClass("MinionList", "Control", "ControlHost", function(self, anchor, x, y, width, height, list, mutable, dest) - self.Control(anchor, x, y, width, height) - self.ControlHost() - self.list = list - self.mutable = mutable +local MinionListClass = common.NewClass("MinionList", "ListControl", function(self, anchor, x, y, width, height, list, dest) + self.ListControl(anchor, x, y, width, height, 16, not dest, list) self.dest = dest - self.controls.scrollBar = common.New("ScrollBarControl", {"RIGHT",self,"RIGHT"}, -1, 0, 16, 0, 32) - self.controls.scrollBar.height = function() - local width, height = self:GetSize() - return height - 2 - end - if mutable then - self.controls.delete = common.New("ButtonControl", {"BOTTOMRIGHT",self,"TOPRIGHT"}, 0, -2, 60, 18, "Remove", function() - self:DeleteSel() - end) - self.controls.delete.enabled = function() - return self.selValue ~= nil - end - else + if dest then + self.dragTargetList = { dest } + self.label = "^7Available Spectres:" self.controls.add = common.New("ButtonControl", {"BOTTOMRIGHT",self,"TOPRIGHT"}, 0, -2, 60, 18, "Add", function() self:AddSel() end) self.controls.add.enabled = function() + return self.selValue ~= nil and not isValueInArray(dest.list, self.selValue) + end + else + self.label = "^7Spectres in Build:" + self.controls.delete = common.New("ButtonControl", {"BOTTOMRIGHT",self,"TOPRIGHT"}, 0, -2, 60, 18, "Remove", function() + self:OnSelDelete(self.selIndex, self.selValue) + end) + self.controls.delete.enabled = function() return self.selValue ~= nil end end end) -function MinionListClass:SelectIndex(index) - self.selValue = self.list[index] - if self.selValue then - self.selIndex = index - self.controls.scrollBar:ScrollIntoView((index - 2) * 16, 48) - end -end - function MinionListClass:AddSel() - if self.selValue and self.dest and not isValueInArray(self.dest.list, self.selValue) then + if self.dest and not isValueInArray(self.dest.list, self.selValue) then t_insert(self.dest.list, self.selValue) end end -function MinionListClass:DeleteSel() - if self.selIndex and self.mutable then - t_remove(self.list, self.selIndex) +function MinionListClass:GetRowValue(column, index, minionId) + local minion = data.minions[minionId] + if column == 1 then + return minion.name + end +end + +function MinionListClass:AddValueTooltip(index, minionId) + local minion = data.minions[minionId] + main:AddTooltipLine(18, "^7"..minion.name) + main:AddTooltipLine(14, s_format("^7Life multiplier: x%.2f", minion.life)) + if minion.energyShield then + main:AddTooltipLine(14, s_format("^7Energy Shield: %d%% of base Life", minion.energyShield * 100)) + end + main:AddTooltipLine(14, s_format("^7Resistances: %s%d^7/%s%d^7/%s%d^7/%s%d", + data.colorCodes.FIRE, minion.fireResist, + data.colorCodes.COLD, minion.coldResist, + data.colorCodes.LIGHTNING, minion.lightningResist, + data.colorCodes.CHAOS, minion.chaosResist + )) + main:AddTooltipLine(14, s_format("^7Base damage: x%.2f", minion.damage)) + main:AddTooltipLine(14, s_format("^7Base attack speed: %.2f", 1 / minion.attackTime)) + for _, skillId in ipairs(minion.skillList) do + if data.skills[skillId] then + main:AddTooltipLine(14, "^7Skill: "..data.skills[skillId].name) + end + end +end + +function MinionListClass:GetDragValue(index, value) + return "MinionId", value +end + +function MinionListClass:CanReceiveDrag(type, value) + return type == "MinionId" and not isValueInArray(self.list, value) +end + +function MinionListClass:ReceiveDrag(type, value, source) + t_insert(self.list, self.selDragIndex or #self.list + 1, value) +end + +function MinionListClass:OnSelClick(index, minionId, doubleClick) + if doubleClick and self.dest then + self:AddSel() + end +end + +function MinionListClass:OnSelDelete(index, minionId) + if not self.dest then + t_remove(self.list, index) self.selIndex = nil self.selValue = nil end end - -function MinionListClass:IsMouseOver() - if not self:IsShown() then - return - end - return self:IsMouseInBounds() or self:GetMouseOverControl() -end - -function MinionListClass:Draw(viewPort) - local x, y = self:GetPos() - local width, height = self:GetSize() - local list = self.list - local scrollBar = self.controls.scrollBar - scrollBar:SetContentDimension(#list * 16, height - 4) - self.selDragIndex = nil - if (self.selValue and self.selDragging) or self.otherDragActive then - local cursorX, cursorY = GetCursorPos() - if not self.selDragActive and not self.otherDragActive and (cursorX-self.selCX)*(cursorX-self.selCX)+(cursorY-self.selCY)*(cursorY-self.selCY) > 100 then - self.selDragActive = true - end - if (self.selDragActive or self.otherDragActive) and self.mutable then - if cursorX >= x + 2 and cursorY >= y + 2 and cursorX < x + width - 18 and cursorY < y + height - 2 then - local index = math.floor((cursorY - y - 2 + scrollBar.offset) / 16 + 0.5) + 1 - if not self.selIndex or index < self.selIndex or index > self.selIndex + 1 then - self.selDragIndex = m_min(index, #list + 1) - end - end - end - if self.dest then - self.dest.otherDragActive = self.dest:IsMouseOver() - end - end - DrawString(x, y - 20, "LEFT", 16, "VAR", self.mutable and "^7Spectres in Build:" or "^7Available Spectres:") - if self.hasFocus then - SetDrawColor(1, 1, 1) - else - SetDrawColor(0.5, 0.5, 0.5) - end - DrawImage(nil, x, y, width, height) - SetDrawColor(0, 0, 0) - DrawImage(nil, x + 1, y + 1, width - 2, height - 2) - self:DrawControls(viewPort) - SetViewport(x + 2, y + 2, width - 20, height - 4) - local ttValue, ttY, ttWidth - local minIndex = m_floor(scrollBar.offset / 16 + 1) - local maxIndex = m_min(m_floor((scrollBar.offset + height) / 16 + 1), #list) - for index = minIndex, maxIndex do - local value = list[index] - local minion = data.minions[value] - local lineY = 16 * (index - 1) - scrollBar.offset - local label = minion.name - local nameWidth = DrawStringWidth(16, "VAR", label) - if not scrollBar.dragging and not self.selDragActive then - local cursorX, cursorY = GetCursorPos() - local relX = cursorX - (x + 2) - local relY = cursorY - (y + 2) - if relX >= 0 and relX < width - 17 and relY >= 0 and relY >= lineY and relY < height - 2 and relY < lineY + 16 then - ttValue = value - ttWidth = m_max(nameWidth + 8, relX) - ttY = lineY + y + 2 - end - end - if value == ttValue or value == self.selValue then - if self.hasFocus then - SetDrawColor(1, 1, 1) - else - SetDrawColor(0.5, 0.5, 0.5) - end - DrawImage(nil, 0, lineY, width - 20, 16) - SetDrawColor(0.15, 0.15, 0.15) - DrawImage(nil, 0, lineY + 1, width - 20, 14) - end - SetDrawColor(1, 1, 1) - DrawString(0, lineY, "LEFT", 16, "VAR", label) - end - if self.selDragIndex then - local lineY = 16 * (self.selDragIndex - 1) - scrollBar.offset - SetDrawColor(1, 1, 1) - DrawImage(nil, 0, lineY - 1, width - 20, 3) - SetDrawColor(0, 0, 0) - DrawImage(nil, 0, lineY, width - 20, 1) - end - SetViewport() - if ttValue then - local minion = data.minions[ttValue] - main:AddTooltipLine(18, "^7"..minion.name) - main:AddTooltipLine(14, s_format("^7Life multiplier: x%.2f", minion.life)) - if minion.energyShield then - main:AddTooltipLine(14, s_format("^7Energy Shield: %d%% of base Life", minion.energyShield * 100)) - end - main:AddTooltipLine(14, s_format("^7Resistances: %s%d^7/%s%d^7/%s%d^7/%s%d", - data.colorCodes.FIRE, minion.fireResist, data.colorCodes.COLD, minion.coldResist, - data.colorCodes.LIGHTNING, minion.lightningResist, data.colorCodes.CHAOS, minion.chaosResist)) - main:AddTooltipLine(14, s_format("^7Base damage: x%.2f", minion.damage)) - main:AddTooltipLine(14, s_format("^7Base attack speed: %.2f", 1 / minion.attackTime)) - for _, skillId in ipairs(minion.skillList) do - if data.skills[skillId] then - main:AddTooltipLine(14, "^7Skill: "..data.skills[skillId].name) - end - end - SetDrawLayer(nil, 100) - main:DrawTooltip(x + 2, ttY, ttWidth, 16, viewPort) - SetDrawLayer(nil, 0) - end -end - -function MinionListClass:OnKeyDown(key, doubleClick) - if not self:IsShown() or not self:IsEnabled() then - return - end - local mOverControl = self:GetMouseOverControl() - if mOverControl and mOverControl.OnKeyDown then - return mOverControl:OnKeyDown(key) - end - if not self:IsMouseOver() and key:match("BUTTON") then - return - end - if key == "LEFTBUTTON" then - self.selValue = nil - self.selIndex = nil - local x, y = self:GetPos() - local width, height = self:GetSize() - local cursorX, cursorY = GetCursorPos() - if cursorX >= x + 2 and cursorY >= y + 2 and cursorX < x + width - 18 and cursorY < y + height - 2 then - local index = math.floor((cursorY - y - 2 + self.controls.scrollBar.offset) / 16) + 1 - local selValue = self.list[index] - if selValue then - self.selValue = selValue - self.selIndex = index - if doubleClick then - self:AddSel() - end - end - end - if self.selValue then - self.selCX = cursorX - self.selCY = cursorY - self.selDragging = true - self.selDragActive = false - end - elseif #self.list > 0 then - if key == "UP" then - self:SelectIndex(((self.selIndex or 1) - 2) % #self.list + 1) - elseif key == "DOWN" then - self:SelectIndex((self.selIndex or #self.list) % #self.list + 1) - elseif key == "HOME" then - self:SelectIndex(1) - elseif key == "END" then - self:SelectIndex(#list) - end - end - return self -end - -function MinionListClass:OnKeyUp(key) - if not self:IsShown() or not self:IsEnabled() then - return - end - if key == "WHEELDOWN" then - self.controls.scrollBar:Scroll(1) - elseif key == "WHEELUP" then - self.controls.scrollBar:Scroll(-1) - elseif self.selValue then - if key == "BACK" or key == "DELETE" then - self:DeleteSel() - elseif key == "LEFTBUTTON" then - self.selDragging = false - if self.selDragActive then - self.selDragActive = false - if self.selDragIndex and self.selDragIndex ~= self.selIndex then - t_remove(self.list, self.selIndex) - if self.selDragIndex > self.selIndex then - self.selDragIndex = self.selDragIndex - 1 - end - t_insert(self.list, self.selDragIndex, self.selValue) - self.selValue = nil - elseif self.dest and self.dest.otherDragActive then - if self.dest.selDragIndex and not isValueInArray(self.dest.list, self.selValue) then - t_insert(self.dest.list, self.dest.selDragIndex, self.selValue) - end - self.dest.otherDragActive = false - self.selValue = nil - end - end - end - end - return self -end \ No newline at end of file diff --git a/Classes/ModDB.lua b/Classes/ModDB.lua index 4d7a528f..fac705ba 100644 --- a/Classes/ModDB.lua +++ b/Classes/ModDB.lua @@ -97,7 +97,7 @@ function ModDBClass:Sum(modType, cfg, ...) source = cfg.source tabulate = cfg.tabulate if tabulate then - cfg = copyTable(cfg) + cfg = copyTable(cfg, true) cfg.tabulate = false end end diff --git a/Classes/ModList.lua b/Classes/ModList.lua index 3892ffc0..72b01781 100644 --- a/Classes/ModList.lua +++ b/Classes/ModList.lua @@ -73,7 +73,7 @@ function ModListClass:Sum(modType, cfg, ...) source = cfg.source tabulate = cfg.tabulate if tabulate then - cfg = copyTable(cfg) + cfg = copyTable(cfg, true) cfg.tabulate = false end end diff --git a/Classes/PassiveSpecListControl.lua b/Classes/PassiveSpecListControl.lua index 9e80d5fa..b2eec1ae 100644 --- a/Classes/PassiveSpecListControl.lua +++ b/Classes/PassiveSpecListControl.lua @@ -7,244 +7,98 @@ local launch, main = ... local t_insert = table.insert local t_remove = table.remove -local m_min = math.min local m_max = math.max -local m_floor = math.floor -local PassiveSpecListClass = common.NewClass("PassiveSpecList", "Control", "ControlHost", function(self, anchor, x, y, width, height, treeTab) - self.Control(anchor, x, y, width, height) - self.ControlHost() +local PassiveSpecListClass = common.NewClass("PassiveSpecList", "ListControl", function(self, anchor, x, y, width, height, treeTab) + self.ListControl(anchor, x, y, width, height, 16, true, treeTab.specList) self.treeTab = treeTab - self.controls.scrollBar = common.New("ScrollBarControl", {"RIGHT",self,"RIGHT"}, -1, 0, 16, 0, 32) - self.controls.scrollBar.height = function() - local width, height = self:GetSize() - return height - 2 - end self.controls.copy = common.New("ButtonControl", {"BOTTOMLEFT",self,"TOP"}, 2, -4, 60, 18, "Copy", function() - local prevSel = self.selSpec - self.selSpec = common.New("PassiveSpec", treeTab.build) - self.selSpec.title = prevSel.title - self.selSpec.jewels = copyTable(prevSel.jewels) - self.selSpec:DecodeURL(prevSel:EncodeURL()) - self:RenameSel(true) + local newSpec = common.New("PassiveSpec", treeTab.build) + newSpec.title = self.selValue.title + newSpec.jewels = copyTable(self.selValue.jewels) + newSpec:DecodeURL(self.selValue:EncodeURL()) + self:RenameSpec(newSpec, true) end) self.controls.copy.enabled = function() - return self.selSpec ~= nil + return self.selValue ~= nil end self.controls.delete = common.New("ButtonControl", {"LEFT",self.controls.copy,"RIGHT"}, 4, 0, 60, 18, "Delete", function() - self:OnKeyUp("DELETE") + self:OnSelDelete(self.selIndex, self.selValue) end) self.controls.delete.enabled = function() - return self.selSpec ~= nil and #treeTab.specList > 1 + return self.selValue ~= nil and #self.list > 1 end self.controls.rename = common.New("ButtonControl", {"BOTTOMRIGHT",self,"TOP"}, -2, -4, 60, 18, "Rename", function() - self:RenameSel() + self:RenameSpec(self.selValue) end) self.controls.rename.enabled = function() - return self.selSpec ~= nil + return self.selValue ~= nil end self.controls.new = common.New("ButtonControl", {"RIGHT",self.controls.rename,"LEFT"}, -4, 0, 60, 18, "New", function() - self.selSpec = common.New("PassiveSpec", treeTab.build) - self:RenameSel(true) + local newSpec = common.New("PassiveSpec", treeTab.build) + self:RenameSpec(newSpec, true) end) end) -function PassiveSpecListClass:SelectIndex(index) - self.selSpec = self.treeTab.specList[index] - if self.selSpec then - self.selSpec = index - self.controls.scrollBar:ScrollIntoView((index - 2) * 16, 48) - end -end - -function PassiveSpecListClass:RenameSel(addOnName) - local popup - popup = main:OpenPopup(370, 100, self.selSpec.title and "Rename" or "Set Name", { - common.New("LabelControl", nil, 0, 20, 0, 16, "^7Enter name for this passive tree:"), - edit = common.New("EditControl", nil, 0, 40, 350, 20, self.selSpec.title, nil, nil, 100, function(buf) - popup.controls.save.enabled = buf:match("%S") - end), - save = common.New("ButtonControl", nil, -45, 70, 80, 20, "Save", function() - self.selSpec.title = popup.controls.edit.buf - self.treeTab.modFlag = true - if addOnName then - t_insert(self.treeTab.specList, self.selSpec) - self.selIndex = #self.treeTab.specList - end - main:ClosePopup() - end), - cancel = common.New("ButtonControl", nil, 45, 70, 80, 20, "Cancel", function() - if addOnName then - self.selSpec = nil - end - main:ClosePopup() - end), - }, "save", "edit", "cancel") - popup.controls.save.enabled = false -end - -function PassiveSpecListClass:IsMouseOver() - if not self:IsShown() then - return - end - return self:IsMouseInBounds() or self:GetMouseOverControl() -end - -function PassiveSpecListClass:Draw(viewPort) - local x, y = self:GetPos() - local width, height = self:GetSize() - local list = self.treeTab.specList - local scrollBar = self.controls.scrollBar - scrollBar:SetContentDimension(#list * 16, height - 4) - self.selDragIndex = nil - if self.selSpec and self.selDragging then - local cursorX, cursorY = GetCursorPos() - if not self.selDragActive and (cursorX-self.selCX)*(cursorX-self.selCX)+(cursorY-self.selCY)*(cursorY-self.selCY) > 100 then - self.selDragActive = true +function PassiveSpecListClass:RenameSpec(spec, addOnName) + local controls = { } + controls.label = common.New("LabelControl", nil, 0, 20, 0, 16, "^7Enter name for this passive tree:") + controls.edit = common.New("EditControl", nil, 0, 40, 350, 20, spec.title, nil, nil, 100, function(buf) + controls.save.enabled = buf:match("%S") + end) + controls.save = common.New("ButtonControl", nil, -45, 70, 80, 20, "Save", function() + spec.title = controls.edit.buf + self.treeTab.modFlag = true + if addOnName then + t_insert(self.list, spec) + self.selIndex = #self.list + self.selValue = spec end - if self.selDragActive then - if cursorX >= x + 2 and cursorY >= y + 2 and cursorX < x + width - 18 and cursorY < y + height - 2 then - local index = math.floor((cursorY - y - 2 + scrollBar.offset) / 16 + 0.5) + 1 - if index < self.selIndex or index > self.selIndex + 1 then - self.selDragIndex = m_min(index, #list + 1) - end - end - end - end - if self.hasFocus then - SetDrawColor(1, 1, 1) - else - SetDrawColor(0.5, 0.5, 0.5) - end - DrawImage(nil, x, y, width, height) - SetDrawColor(0, 0, 0) - DrawImage(nil, x + 1, y + 1, width - 2, height - 2) - self:DrawControls(viewPort) - SetViewport(x + 2, y + 2, width - 20, height - 4) - local ttSpec, ttY, ttWidth - local minIndex = m_floor(scrollBar.offset / 16 + 1) - local maxIndex = m_min(m_floor((scrollBar.offset + height) / 16 + 1), #list) - for index = minIndex, maxIndex do - local spec = list[index] - local lineY = 16 * (index - 1) - scrollBar.offset + main:ClosePopup() + end) + controls.save.enabled = false + controls.cancel = common.New("ButtonControl", nil, 45, 70, 80, 20, "Cancel", function() + main:ClosePopup() + end) + main:OpenPopup(370, 100, spec.title and "Rename" or "Set Name", controls, "save", "edit") +end + +function PassiveSpecListClass:GetRowValue(column, index, spec) + if column == 1 then local used = spec:CountAllocNodes() - local label = (spec.title or "Default") .. " (" .. (spec.curAscendClassName ~= "None" and spec.curAscendClassName or spec.curClassName) .. ", " .. used .. " points)" - local nameWidth = DrawStringWidth(16, "VAR", label) - if not scrollBar.dragging and not self.selDragActive then - local cursorX, cursorY = GetCursorPos() - local relX = cursorX - (x + 2) - local relY = cursorY - (y + 2) - if relX >= 0 and relX < width - 17 and relY >= 0 and relY >= lineY and relY < height - 2 and relY < lineY + 16 then - ttSpec = spec - ttWidth = m_max(nameWidth + 8, relX) - ttY = lineY + y + 2 - end - end - if spec == ttSpec or spec == self.selSpec then - if self.hasFocus then - SetDrawColor(1, 1, 1) + return (spec.title or "Default") .. " (" .. (spec.curAscendClassName ~= "None" and spec.curAscendClassName or spec.curClassName) .. ", " .. used .. " points)" .. (index == self.treeTab.activeSpec and " ^9(Current)" or "") + end +end + +function PassiveSpecListClass:OnOrderChange() + self.treeTab.activeSpec = isValueInArray(self.list, self.treeTab.build.spec) + self.treeTab.modFlag = true +end + +function PassiveSpecListClass:OnSelClick(index, spec, doubleClick) + if doubleClick and index ~= self.treeTab.activeSpec then + self.treeTab:SetActiveSpec(index) + end +end + +function PassiveSpecListClass:OnSelDelete(index, spec) + if #self.list > 1 then + main:OpenConfirmPopup("Delete Spec", "Are you sure you want to delete '"..(spec.title or "Default").."'?", "Delete", function() + t_remove(self.list, index) + self.selIndex = nil + self.selValue = nil + if index == self.treeTab.activeSpec then + self.treeTab:SetActiveSpec(m_max(1, index - 1)) else - SetDrawColor(0.5, 0.5, 0.5) + self.treeTab.activeSpec = isValueInArray(self.list, self.treeTab.build.spec) end - DrawImage(nil, 0, lineY, width - 20, 16) - SetDrawColor(0.15, 0.15, 0.15) - DrawImage(nil, 0, lineY + 1, width - 20, 14) - end - SetDrawColor(1, 1, 1) - DrawString(0, lineY, "LEFT", 16, "VAR", label) + self.treeTab.modFlag = true + end) end - if self.selDragIndex then - local lineY = 16 * (self.selDragIndex - 1) - scrollBar.offset - SetDrawColor(1, 1, 1) - DrawImage(nil, 0, lineY - 1, width - 20, 3) - SetDrawColor(0, 0, 0) - DrawImage(nil, 0, lineY, width - 20, 1) - end - SetViewport() end -function PassiveSpecListClass:OnKeyDown(key, doubleClick) - if not self:IsShown() or not self:IsEnabled() then - return +function PassiveSpecListClass:OnSelKeyDown(index, spec, key) + if key == "F2" then + self:RenameSpec(spec) end - local mOverControl = self:GetMouseOverControl() - if mOverControl and mOverControl.OnKeyDown then - return mOverControl:OnKeyDown(key) - end - if not self:IsMouseOver() and key:match("BUTTON") then - return - end - if key == "LEFTBUTTON" then - self.selSpec = nil - self.selIndex = nil - local x, y = self:GetPos() - local width, height = self:GetSize() - local cursorX, cursorY = GetCursorPos() - if cursorX >= x + 2 and cursorY >= y + 2 and cursorX < x + width - 18 and cursorY < y + height - 2 then - local index = math.floor((cursorY - y - 2 + self.controls.scrollBar.offset) / 16) + 1 - local selSpec = self.treeTab.specList[index] - if selSpec then - self.selSpec = selSpec - self.selIndex = index - end - end - if self.selSpec then - self.selCX = cursorX - self.selCY = cursorY - self.selDragging = true - self.selDragActive = false - end - elseif #self.treeTab.specList > 0 then - if key == "UP" then - self:SelectIndex(((self.selIndex or 1) - 2) % #self.treeTab.specList + 1) - elseif key == "DOWN" then - self:SelectIndex((self.selIndex or #self.treeTab.specList) % #self.treeTab.specList + 1) - elseif key == "HOME" then - self:SelectIndex(1) - elseif key == "END" then - self:SelectIndex(#self.treeTab.specList) - end - end - return self -end - -function PassiveSpecListClass:OnKeyUp(key) - if not self:IsShown() or not self:IsEnabled() then - return - end - if key == "WHEELDOWN" then - self.controls.scrollBar:Scroll(1) - elseif key == "WHEELUP" then - self.controls.scrollBar:Scroll(-1) - elseif self.selSpec then - if key == "BACK" or key == "DELETE" then - if #self.treeTab.specList > 1 then - main:OpenConfirmPopup("Delete Spec", "Are you sure you want to delete '"..(self.selSpec.title or "Default").."'?", "Delete", function() - t_remove(self.treeTab.specList, self.selIndex) - self.selSpec = nil - if self.selIndex == self.treeTab.activeSpec then - self.treeTab:SetActiveSpec(m_max(1, self.selIndex - 1)) - end - end) - end - elseif key == "F2" then - self:RenameSel() - elseif key == "LEFTBUTTON" then - self.selDragging = false - if self.selDragActive then - self.selDragActive = false - if self.selDragIndex and self.selDragIndex ~= self.selIndex then - local activeSpec = self.treeTab.specList[self.treeTab.activeSpec] - t_remove(self.treeTab.specList, self.selIndex) - if self.selDragIndex > self.selIndex then - self.selDragIndex = self.selDragIndex - 1 - end - t_insert(self.treeTab.specList, self.selDragIndex, self.selSpec) - self.selSpec = nil - self.treeTab.activeSpec = isValueInArray(self.treeTab.specList, activeSpec) - end - end - end - end - return self end \ No newline at end of file diff --git a/Classes/PassiveTreeView.lua b/Classes/PassiveTreeView.lua index b34eb1c8..4879c36b 100644 --- a/Classes/PassiveTreeView.lua +++ b/Classes/PassiveTreeView.lua @@ -372,10 +372,10 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) if self.showHeatMap then if not node.alloc and node.type ~= "classStart" and node.type ~= "ascendClassStart" then -- Calculate color based on DPS and defensive powers - local dps = m_max(node.power.dps or 0, 0) - local def = m_max(node.power.def or 0, 0) - local dpsCol = (dps / build.calcsTab.powerMax.dps * 1.5) ^ 0.5 - local defCol = (def / build.calcsTab.powerMax.def * 1.5) ^ 0.5 + local offence = m_max(node.power.offence or 0, 0) + local defence = m_max(node.power.defence or 0, 0) + local dpsCol = (offence / build.calcsTab.powerMax.offence * 1.5) ^ 0.5 + local defCol = (defence / build.calcsTab.powerMax.defence * 1.5) ^ 0.5 SetDrawColor(dpsCol, (m_max(dpsCol - 0.5, 0) + m_max(defCol - 0.5, 0)) / 2, defCol) else SetDrawColor(1, 1, 1) @@ -554,9 +554,9 @@ function PassiveTreeViewClass:AddNodeTooltip(node, build) -- Node name self:AddNodeName(node) - if launch.devMode and IsKeyDown("ALT") and node.power and node.power.dps then + if launch.devMode and IsKeyDown("ALT") and node.power and node.power.offence then -- Power debugging info - main:AddTooltipLine(16, string.format("DPS power: %g Defence power: %g", node.power.dps, node.power.def)) + main:AddTooltipLine(16, string.format("DPS power: %g Defence power: %g", node.power.offence, node.power.defence)) end -- Node description diff --git a/Classes/SkillListControl.lua b/Classes/SkillListControl.lua index 2368d270..ef20abd9 100644 --- a/Classes/SkillListControl.lua +++ b/Classes/SkillListControl.lua @@ -5,41 +5,38 @@ -- local launch, main = ... +local ipairs = ipairs local t_insert = table.insert local t_remove = table.remove -local m_min = math.min -local m_max = math.max -local m_floor = math.floor -local SkillListClass = common.NewClass("SkillList", "Control", "ControlHost", function(self, anchor, x, y, width, height, skillsTab) - self.Control(anchor, x, y, width, height) - self.ControlHost() +local SkillListClass = common.NewClass("SkillList", "ListControl", function(self, anchor, x, y, width, height, skillsTab) + self.ListControl(anchor, x, y, width, height, 16, true, skillsTab.socketGroupList) self.skillsTab = skillsTab - self.controls.scrollBar = common.New("ScrollBarControl", {"RIGHT",self,"RIGHT"}, -1, 0, 16, 0, 32) - self.controls.scrollBar.height = function() - local width, height = self:GetSize() - return height - 2 - end + self.label = "^7Socket Groups:" self.controls.delete = common.New("ButtonControl", {"BOTTOMRIGHT",self,"TOPRIGHT"}, 0, -2, 60, 18, "Delete", function() - self:OnKeyUp("DELETE") + self:OnSelDelete(self.selIndex, self.selValue) end) self.controls.delete.enabled = function() - return self.selGroup ~= nil and self.selGroup.source == nil + return self.selValue ~= nil and self.selValue.source == nil end self.controls.paste = common.New("ButtonControl", {"RIGHT",self.controls.delete,"LEFT"}, -4, 0, 60, 18, "Paste", function() - self.skillsTab:PasteSocketGroup() + skillsTab:PasteSocketGroup() end) self.controls.copy = common.New("ButtonControl", {"RIGHT",self.controls.paste,"LEFT"}, -4, 0, 60, 18, "Copy", function() - self.skillsTab:CopySocketGroup(self.selGroup) + skillsTab:CopySocketGroup(self.selValue) end) self.controls.copy.enabled = function() - return self.selGroup ~= nil and self.selGroup.source == nil + return self.selValue ~= nil and self.selValue.source == nil end self.controls.new = common.New("ButtonControl", {"RIGHT",self.controls.copy,"LEFT"}, -4, 0, 60, 18, "New", function() - local newGroup = { label = "", enabled = true, gemList = { } } - t_insert(self.skillsTab.socketGroupList, newGroup) - self.selGroup = newGroup + local newGroup = { + label = "", + enabled = true, + gemList = { } + } + t_insert(self.list, newGroup) self.selIndex = #self.skillsTab.socketGroupList + self.selValue = newGroup self.skillsTab:SetDisplayGroup(newGroup) self.skillsTab:AddUndoState() self.skillsTab.build.buildFlag = true @@ -47,280 +44,133 @@ local SkillListClass = common.NewClass("SkillList", "Control", "ControlHost", fu end) end) -function SkillListClass:SelectIndex(index) - self.selGroup = self.skillsTab.socketGroupList[index] - if self.selGroup then - self.selIndex = index - self.skillsTab:SetDisplayGroup(self.selGroup) - self.controls.scrollBar:ScrollIntoView((index - 2) * 16, 48) - end -end - -function SkillListClass:IsMouseOver() - if not self:IsShown() then - return - end - return self:IsMouseInBounds() or self:GetMouseOverControl() -end - -function SkillListClass:Draw(viewPort) - local x, y = self:GetPos() - local width, height = self:GetSize() - local list = self.skillsTab.socketGroupList - local scrollBar = self.controls.scrollBar - scrollBar:SetContentDimension(#list * 16, height - 4) - self.selDragIndex = nil - if self.selGroup and self.selDragging then - local cursorX, cursorY = GetCursorPos() - if not self.selDragActive and (cursorX-self.selCX)*(cursorX-self.selCX)+(cursorY-self.selCY)*(cursorY-self.selCY) > 100 then - self.selDragActive = true - end - if self.selDragActive then - if cursorX >= x + 2 and cursorY >= y + 2 and cursorX < x + width - 18 and cursorY < y + height - 2 then - local index = math.floor((cursorY - y - 2 + scrollBar.offset) / 16 + 0.5) + 1 - if index < self.selIndex or index > self.selIndex + 1 then - self.selDragIndex = m_min(index, #list + 1) - end - end - end - end - DrawString(x, y - 20, "LEFT", 16, "VAR", "^7Socket Groups:") - if self.hasFocus then - SetDrawColor(1, 1, 1) - else - SetDrawColor(0.5, 0.5, 0.5) - end - DrawImage(nil, x, y, width, height) - SetDrawColor(0, 0, 0) - DrawImage(nil, x + 1, y + 1, width - 2, height - 2) - self:DrawControls(viewPort) - SetViewport(x + 2, y + 2, width - 20, height - 4) - local ttGroup, ttY, ttWidth - local minIndex = m_floor(scrollBar.offset / 16 + 1) - local maxIndex = m_min(m_floor((scrollBar.offset + height) / 16 + 1), #list) - for index = minIndex, maxIndex do - local socketGroup = list[index] - local lineY = 16 * (index - 1) - scrollBar.offset +function SkillListClass:GetRowValue(column, index, socketGroup) + if column == 1 then local label = socketGroup.displayLabel or "?" if not socketGroup.enabled or not socketGroup.slotEnabled then - label = label .. " (Disabled)" + label = "^x7F7F7F" .. label .. " (Disabled)" end - local nameWidth = DrawStringWidth(16, "VAR", label) - if not scrollBar.dragging and not self.selDragActive and (not self.skillsTab.selControl or self.hasFocus) then - local cursorX, cursorY = GetCursorPos() - local relX = cursorX - (x + 2) - local relY = cursorY - (y + 2) - if relX >= 0 and relX < width - 17 and relY >= 0 and relY >= lineY and relY < height - 2 and relY < lineY + 16 then - ttGroup = socketGroup - ttWidth = m_max(nameWidth + 8, relX) - ttY = lineY + y + 2 - end - end - if socketGroup == ttGroup or socketGroup == self.selGroup then - if self.hasFocus then - SetDrawColor(1, 1, 1) - else - SetDrawColor(0.5, 0.5, 0.5) - end - DrawImage(nil, 0, lineY, width - 20, 16) - SetDrawColor(0.15, 0.15, 0.15) - DrawImage(nil, 0, lineY + 1, width - 20, 14) - end - if socketGroup.enabled and socketGroup.slotEnabled then - SetDrawColor(1, 1, 1) - else - SetDrawColor(0.5, 0.5, 0.5) - end - DrawString(0, lineY, "LEFT", 16, "VAR", label) + return label end - if self.selDragIndex then - local lineY = 16 * (self.selDragIndex - 1) - scrollBar.offset - SetDrawColor(1, 1, 1) - DrawImage(nil, 0, lineY - 1, width - 20, 3) - SetDrawColor(0, 0, 0) - DrawImage(nil, 0, lineY, width - 20, 1) +end + +function SkillListClass:AddValueTooltip(index, socketGroup) + if not socketGroup.displaySkillList then + return end - SetViewport() - if ttGroup and ttGroup.displaySkillList then - if ttGroup.enabled and not ttGroup.slotEnabled then - main:AddTooltipLine(16, "^7Note: this group is disabled because it is socketed in the inactive weapon set.") - end - if ttGroup.sourceItem then - main:AddTooltipLine(18, "^7Source: "..data.colorCodes[ttGroup.sourceItem.rarity]..ttGroup.sourceItem.name) + if socketGroup.enabled and not socketGroup.slotEnabled then + main:AddTooltipLine(16, "^7Note: this group is disabled because it is socketed in the inactive weapon set.") + end + if socketGroup.sourceItem then + main:AddTooltipLine(18, "^7Source: "..data.colorCodes[socketGroup.sourceItem.rarity]..socketGroup.sourceItem.name) + main:AddTooltipSeparator(10) + end + local gemShown = { } + for index, activeSkill in ipairs(socketGroup.displaySkillList) do + if index > 1 then main:AddTooltipSeparator(10) end - local gemShown = { } - for index, activeSkill in ipairs(ttGroup.displaySkillList) do - if index > 1 then - main:AddTooltipSeparator(10) - end - main:AddTooltipLine(16, "^7Active Skill #"..index..":") - for _, gem in ipairs(activeSkill.gemList) do - main:AddTooltipLine(20, string.format("%s%s ^7%d%s/%d%s", - data.skillColorMap[gem.data.color], - gem.name, - gem.level, - (gem.srcGem and gem.level > gem.srcGem.level) and data.colorCodes.MAGIC.."+"..(gem.level - gem.srcGem.level).."^7" or "", - gem.quality, - (gem.srcGem and gem.quality > gem.srcGem.quality) and data.colorCodes.MAGIC.."+"..(gem.quality - gem.srcGem.quality).."^7" or "" - )) - if gem.srcGem then - gemShown[gem.srcGem] = true - end - end - if activeSkill.minion then - main:AddTooltipSeparator(10) - main:AddTooltipLine(16, "^7Active Skill #"..index.."'s Main Minion Skill:") - local gem = activeSkill.minion.mainSkill.gemList[1] - main:AddTooltipLine(20, string.format("%s%s ^7%d%s/%d%s", - data.skillColorMap[gem.data.color], - gem.name, - gem.level, - (gem.srcGem and gem.level > gem.srcGem.level) and data.colorCodes.MAGIC.."+"..(gem.level - gem.srcGem.level).."^7" or "", - gem.quality, - (gem.srcGem and gem.quality > gem.srcGem.quality) and data.colorCodes.MAGIC.."+"..(gem.quality - gem.srcGem.quality).."^7" or "" - )) - if gem.srcGem then - gemShown[gem.srcGem] = true - end + main:AddTooltipLine(16, "^7Active Skill #"..index..":") + for _, gem in ipairs(activeSkill.gemList) do + main:AddTooltipLine(20, string.format("%s%s ^7%d%s/%d%s", + data.skillColorMap[gem.data.color], + gem.name, + gem.level, + (gem.srcGem and gem.level > gem.srcGem.level) and data.colorCodes.MAGIC.."+"..(gem.level - gem.srcGem.level).."^7" or "", + gem.quality, + (gem.srcGem and gem.quality > gem.srcGem.quality) and data.colorCodes.MAGIC.."+"..(gem.quality - gem.srcGem.quality).."^7" or "" + )) + if gem.srcGem then + gemShown[gem.srcGem] = true end end - local showOtherHeader = true - for _, gem in ipairs(ttGroup.gemList) do - if not gemShown[gem] then - if showOtherHeader then - showOtherHeader = false - main:AddTooltipSeparator(10) - main:AddTooltipLine(16, "^7Inactive Gems:") - end - local reason = "" - local displayGem = gem.displayGem or gem - if not gem.data then - reason = "(Unsupported)" - elseif not gem.enabled then - reason = "(Disabled)" - elseif not ttGroup.enabled or not ttGroup.slotEnabled then - elseif gem.data.support then - if displayGem.superseded then - reason = "(Superseded)" - elseif not next(displayGem.isSupporting) and #ttGroup.displaySkillList > 0 then - reason = "(Cannot apply to any of the active skills)" - end - end - main:AddTooltipLine(20, string.format("%s%s ^7%d%s/%d%s %s", - gem.color, - gem.name or gem.nameSpec, - displayGem.level, - displayGem.level > gem.level and data.colorCodes.MAGIC.."+"..(displayGem.level - gem.level).."^7" or "", - displayGem.quality, - displayGem.quality > gem.quality and data.colorCodes.MAGIC.."+"..(displayGem.quality - gem.quality).."^7" or "", - reason - )) + if activeSkill.minion then + main:AddTooltipSeparator(10) + main:AddTooltipLine(16, "^7Active Skill #"..index.."'s Main Minion Skill:") + local gem = activeSkill.minion.mainSkill.gemList[1] + main:AddTooltipLine(20, string.format("%s%s ^7%d%s/%d%s", + data.skillColorMap[gem.data.color], + gem.name, + gem.level, + (gem.srcGem and gem.level > gem.srcGem.level) and data.colorCodes.MAGIC.."+"..(gem.level - gem.srcGem.level).."^7" or "", + gem.quality, + (gem.srcGem and gem.quality > gem.srcGem.quality) and data.colorCodes.MAGIC.."+"..(gem.quality - gem.srcGem.quality).."^7" or "" + )) + if gem.srcGem then + gemShown[gem.srcGem] = true end end - SetDrawLayer(nil, 100) - main:DrawTooltip(x + 2, ttY, ttWidth, 16, viewPort) - SetDrawLayer(nil, 0) + end + local showOtherHeader = true + for _, gem in ipairs(socketGroup.gemList) do + if not gemShown[gem] then + if showOtherHeader then + showOtherHeader = false + main:AddTooltipSeparator(10) + main:AddTooltipLine(16, "^7Inactive Gems:") + end + local reason = "" + local displayGem = gem.displayGem or gem + if not gem.data then + reason = "(Unsupported)" + elseif not gem.enabled then + reason = "(Disabled)" + elseif not socketGroup.enabled or not socketGroup.slotEnabled then + elseif gem.data.support then + if displayGem.superseded then + reason = "(Superseded)" + elseif not next(displayGem.isSupporting) and #socketGroup.displaySkillList > 0 then + reason = "(Cannot apply to any of the active skills)" + end + end + main:AddTooltipLine(20, string.format("%s%s ^7%d%s/%d%s %s", + gem.color, + gem.name or gem.nameSpec, + displayGem.level, + displayGem.level > gem.level and data.colorCodes.MAGIC.."+"..(displayGem.level - gem.level).."^7" or "", + displayGem.quality, + displayGem.quality > gem.quality and data.colorCodes.MAGIC.."+"..(displayGem.quality - gem.quality).."^7" or "", + reason + )) + end end end -function SkillListClass:OnKeyDown(key, doubleClick) - if not self:IsShown() or not self:IsEnabled() then - return - end - local mOverControl = self:GetMouseOverControl() - if mOverControl and mOverControl.OnKeyDown then - return mOverControl:OnKeyDown(key) - end - if not self:IsMouseOver() and key:match("BUTTON") then - return - end - if key == "LEFTBUTTON" then - self.selGroup = nil - self.selIndex = nil - local x, y = self:GetPos() - local width, height = self:GetSize() - local cursorX, cursorY = GetCursorPos() - if cursorX >= x + 2 and cursorY >= y + 2 and cursorX < x + width - 18 and cursorY < y + height - 2 then - local index = math.floor((cursorY - y - 2 + self.controls.scrollBar.offset) / 16) + 1 - local selGroup = self.skillsTab.socketGroupList[index] - if selGroup then - self.selGroup = selGroup - self.selIndex = index - self.skillsTab:SetDisplayGroup(selGroup) - end - end - if self.selGroup then - self.selCX = cursorX - self.selCY = cursorY - self.selDragging = true - self.selDragActive = false - end - elseif key == "c" and IsKeyDown("CTRL") then - if self.selGroup and not self.selGroup.source then - self.skillsTab:CopySocketGroup(self.selGroup) - end - elseif #self.skillsTab.socketGroupList > 0 then - if key == "UP" then - self:SelectIndex(((self.selIndex or 1) - 2) % #self.skillsTab.socketGroupList + 1) - elseif key == "DOWN" then - self:SelectIndex((self.selIndex or #self.skillsTab.socketGroupList) % #self.skillsTab.socketGroupList + 1) - elseif key == "HOME" then - self:SelectIndex(1) - elseif key == "END" then - self:SelectIndex(#self.skillsTab.socketGroupList) - end - end - return self +function SkillListClass:OnOrderChange() + self.skillsTab:AddUndoState() + self.skillsTab.build.buildFlag = true end -function SkillListClass:OnKeyUp(key) - if not self:IsShown() or not self:IsEnabled() then - return +function SkillListClass:OnSelect(index, socketGroup) + self.skillsTab:SetDisplayGroup(socketGroup) +end + +function SkillListClass:OnSelCopy(index, socketGroup) + if not socketGroup.source then + self.skillsTab:CopySocketGroup(socketGroup) end - if key == "WHEELDOWN" then - self.controls.scrollBar:Scroll(1) - elseif key == "WHEELUP" then - self.controls.scrollBar:Scroll(-1) - elseif self.selGroup then - if key == "BACK" or key == "DELETE" then - if self.selGroup.source then - main:OpenMessagePopup("Delete Socket Group", "This socket group cannot be deleted as it is created by an equipped item.") - elseif not self.selGroup.gemList[1] then - t_remove(self.skillsTab.socketGroupList, self.selIndex) - if self.skillsTab.displayGroup == self.selGroup then - self.skillsTab.displayGroup = nil - end - self.skillsTab:AddUndoState() - self.skillsTab.build.buildFlag = true - self.selGroup = nil - else - main:OpenConfirmPopup("Delete Socket Group", "Are you sure you want to delete '"..self.selGroup.displayLabel.."'?", "Delete", function() - t_remove(self.skillsTab.socketGroupList, self.selIndex) - if self.skillsTab.displayGroup == self.selGroup then - self.skillsTab.displayGroup = nil - end - self.skillsTab:AddUndoState() - self.skillsTab.build.buildFlag = true - self.selGroup = nil - end) - end - elseif key == "LEFTBUTTON" then - self.selDragging = false - if self.selDragActive then - self.selDragActive = false - if self.selDragIndex and self.selDragIndex ~= self.selIndex then - t_remove(self.skillsTab.socketGroupList, self.selIndex) - if self.selDragIndex > self.selIndex then - self.selDragIndex = self.selDragIndex - 1 - end - t_insert(self.skillsTab.socketGroupList, self.selDragIndex, self.selGroup) - self.skillsTab:AddUndoState() - self.skillsTab.build.buildFlag = true - self.selGroup = nil - end - end +end + +function SkillListClass:OnSelDelete(index, socketGroup) + if socketGroup.source then + main:OpenMessagePopup("Delete Socket Group", "This socket group cannot be deleted as it is created by an equipped item.") + elseif not socketGroup.gemList[1] then + t_remove(self.list, index) + if self.skillsTab.displayGroup == socketGroup then + self.skillsTab.displayGroup = nil end + self.skillsTab:AddUndoState() + self.skillsTab.build.buildFlag = true + self.selValue = nil + else + main:OpenConfirmPopup("Delete Socket Group", "Are you sure you want to delete '"..socketGroup.displayLabel.."'?", "Delete", function() + t_remove(self.list, index) + if self.skillsTab.displayGroup == socketGroup then + self.skillsTab.displayGroup = nil + end + self.skillsTab:AddUndoState() + self.skillsTab.build.buildFlag = true + self.selValue = nil + end) end - return self -end \ No newline at end of file +end diff --git a/Classes/SkillsTab.lua b/Classes/SkillsTab.lua index 4b5a8168..678d1232 100644 --- a/Classes/SkillsTab.lua +++ b/Classes/SkillsTab.lua @@ -220,8 +220,8 @@ function SkillsTabClass:PasteSocketGroup() end if #newGroup.gemList > 0 then t_insert(self.socketGroupList, newGroup) - self.controls.groupList.selGroup = newGroup self.controls.groupList.selIndex = #self.socketGroupList + self.controls.groupList.selValue = newGroup self:SetDisplayGroup(newGroup) self:AddUndoState() self.build.buildFlag = true @@ -487,31 +487,9 @@ function SkillsTabClass:CreateUndoState() local state = { } state.socketGroupList = { } for _, socketGroup in ipairs(self.socketGroupList) do - local newGroup = { - label = socketGroup.label, - slot = socketGroup.slot, - enabled = socketGroup.enabled, - source = socketGroup.source, - mainActiveSkill = socketGroup.mainActiveSkill, - mainActiveSkillCalcs = socketGroup.mainActiveSkillCalcs, - gemList = { } - } + local newGroup = copyTable(socketGroup, true) for index, gem in pairs(socketGroup.gemList) do - newGroup.gemList[index] = { - nameSpec = gem.nameSpec, - level = gem.level, - quality = gem.quality, - enabled = gem.enabled, - skillPart = gem.skillPart, - skillPartCalcs = gem.skillPartCalcs, - skillMinion = gem.skillMinion, - skillMinionCalcs = gem.skillMinionCalcs, - skillMinionSkill = gem.skillMinionSkill, - skillMinionSkillCalcs = gem.skillMinionSkillCalcs, - name = gem.name, - data = gem.data, - errMsg = gem.errMsg, - } + newGroup.gemList[index] = copyTable(gem, true) end t_insert(state.socketGroupList, newGroup) end @@ -520,9 +498,12 @@ end function SkillsTabClass:RestoreUndoState(state) local displayId = isValueInArray(self.socketGroupList, self.displayGroup) - self.socketGroupList = state.socketGroupList + wipeTable(self.socketGroupList) + for k, v in pairs(state.socketGroupList) do + self.socketGroupList[k] = v + end self:SetDisplayGroup(displayId and self.socketGroupList[displayId]) - if self.controls.groupList.selGroup then - self.controls.groupList.selGroup = self.socketGroupList[self.controls.groupList.selIndex] + if self.controls.groupList.selValue then + self.controls.groupList.selValue = self.socketGroupList[self.controls.groupList.selIndex] end end diff --git a/Classes/TreeTab.lua b/Classes/TreeTab.lua index c92ed587..53d53618 100644 --- a/Classes/TreeTab.lua +++ b/Classes/TreeTab.lua @@ -180,6 +180,7 @@ function TreeTabClass:Save(xml) spec:Save(child) t_insert(xml, child) end + self.modFlag = false end function TreeTabClass:SetActiveSpec(specId) diff --git a/Modules/Build.lua b/Modules/Build.lua index a24ca9db..94db2d3a 100644 --- a/Modules/Build.lua +++ b/Modules/Build.lua @@ -554,7 +554,7 @@ function buildMode:OnFrame(inputEvents) self.calcsTab:Draw(tabViewPort, inputEvents) end - self.unsaved = self.modFlag or self.notesTab.modFlag or self.configTab.modFlag or self.spec.modFlag or self.skillsTab.modFlag or self.itemsTab.modFlag or self.calcsTab.modFlag + self.unsaved = self.modFlag or self.notesTab.modFlag or self.configTab.modFlag or self.treeTab.modFlag or self.spec.modFlag or self.skillsTab.modFlag or self.itemsTab.modFlag or self.calcsTab.modFlag SetDrawLayer(5) @@ -651,8 +651,8 @@ function buildMode:OpenSpectreLibrary() return data.minions[a].name < data.minions[b].name end end) - controls.list = common.New("MinionList", nil, -100, 40, 190, 250, destList, true) - controls.source = common.New("MinionList", nil, 100, 40, 190, 250, sourceList, false, controls.list) + controls.list = common.New("MinionList", nil, -100, 40, 190, 250, destList) + controls.source = common.New("MinionList", nil, 100, 40, 190, 250, sourceList, controls.list) controls.save = common.New("ButtonControl", nil, -45, 300, 80, 20, "Save", function() self.spectreList = destList self.modFlag = true diff --git a/Modules/BuildList.lua b/Modules/BuildList.lua index 847cfc04..83c79690 100644 --- a/Modules/BuildList.lua +++ b/Modules/BuildList.lua @@ -7,7 +7,6 @@ local launch, main = ... local pairs = pairs local ipairs = ipairs -local t_insert = table.insert local listMode = common.New("ControlHost") @@ -17,25 +16,27 @@ function listMode:Init(selBuildName) return main.screenW / 2 end + self.list = { } + self.controls.new = common.New("ButtonControl", {"TOP",self.anchor,"TOP"}, -210, 0, 60, 20, "New", function() main:SetMode("BUILD", false, "Unnamed build") end) self.controls.open = common.New("ButtonControl", {"LEFT",self.controls.new,"RIGHT"}, 8, 0, 60, 20, "Open", function() - self:LoadSel() + self.controls.buildList:LoadBuild(self.controls.buildList.selValue) end) - self.controls.open.enabled = function() return self.sel ~= nil end + self.controls.open.enabled = function() return self.controls.buildList.selValue ~= nil end self.controls.copy = common.New("ButtonControl", {"LEFT",self.controls.open,"RIGHT"}, 8, 0, 60, 20, "Copy", function() - self:CopySel() + self.controls.buildList:RenameBuild(self.controls.buildList.selValue, true) end) - self.controls.copy.enabled = function() return self.sel ~= nil end + self.controls.copy.enabled = function() return self.controls.buildList.selValue ~= nil end self.controls.rename = common.New("ButtonControl", {"LEFT",self.controls.copy,"RIGHT"}, 8, 0, 60, 20, "Rename", function() - self:RenameSel() + self.controls.buildList:RenameBuild(self.controls.buildList.selValue) end) - self.controls.rename.enabled = function() return self.sel ~= nil end + self.controls.rename.enabled = function() return self.controls.buildList.selValue ~= nil end self.controls.delete = common.New("ButtonControl", {"LEFT",self.controls.rename,"RIGHT"}, 8, 0, 60, 20, "Delete", function() - self:DeleteSel() + self.controls.buildList:DeleteBuild(self.controls.buildList.selValue) end) - self.controls.delete.enabled = function() return self.sel ~= nil end + self.controls.delete.enabled = function() return self.controls.buildList.selValue ~= nil end self.controls.sort = common.New("DropDownControl", {"LEFT",self.controls.delete,"RIGHT"}, 8, 0, 140, 20, {{val="NAME",label="Sort by Name"},{val="CLASS",label="Sort by Class"},{val="EDITED",label="Sort by Last Edited"}}, function(sel, val) main.buildSortMode = val.val self:SortList() @@ -48,7 +49,7 @@ function listMode:Init(selBuildName) end self:BuildList() - self:SelByFileName(selBuildName and selBuildName..".xml") + self.controls.buildList:SelByFileName(selBuildName and selBuildName..".xml") self:SelectControl(self.controls.buildList) end @@ -56,19 +57,6 @@ function listMode:Shutdown() end function listMode:OnFrame(inputEvents) - for id, event in ipairs(inputEvents) do - if event.type == "KeyDown" then - if self.edit then - if event.key == "RETURN" then - self:EditFinish() - inputEvents[id] = nil - elseif event.key == "ESCAPE" then - self:EditCancel() - inputEvents[id] = nil - end - end - end - end self:ProcessControlsInput(inputEvents, main.viewPort) main:DrawBackground(main.viewPort) @@ -107,7 +95,7 @@ function listMode:BuildList() end function listMode:SortList() - local oldSelFileName = self.sel and self.list[self.sel] and self.list[self.sel].fileName + local oldSelFileName = self.controls.buildList.selValue and self.controls.buildList.selValue.fileName table.sort(self.list, function(a, b) if main.buildSortMode == "EDITED" then return a.modified > b.modified @@ -125,131 +113,8 @@ function listMode:SortList() return a.fileName:upper() < b.fileName:upper() end) if oldSelFileName then - self:SelByFileName(oldSelFileName) + self.controls.buildList:SelByFileName(oldSelFileName) end - self.controls.buildList:ScrollSelIntoView() -end - -function listMode:SelByFileName(selFileName) - self.sel = nil - for index, build in ipairs(self.list) do - if build.fileName == selFileName then - self.sel = index - self.controls.buildList:ScrollSelIntoView() - break - end - end -end - -function listMode:EditInit(prompt, finFunc) - self.edit = self.sel - self.editFinFunc = finFunc - self.controls.buildList:ScrollSelIntoView() - self.controls.buildList.controls.nameEdit.prompt = prompt - self.controls.buildList.controls.nameEdit:SetText(self.list[self.sel].buildName or "") -end - -function listMode:EditFinish() - if not self.edit then - return - end - local msg = self.editFinFunc(self.controls.buildList.controls.nameEdit.buf) - if msg then - main:OpenMessagePopup("Message", msg) - return - end - self.edit = nil - self:SelectControl(self.controls.buildList) -end - -function listMode:EditCancel() - self.sel = nil - self.edit = nil - self:BuildList() - self:SelectControl(self.controls.buildList) -end - -function listMode:LoadSel() - if self.edit or not self.sel or not self.list[self.sel] then - return - end - main:SetMode("BUILD", main.buildPath..self.list[self.sel].fileName, self.list[self.sel].buildName) -end - -function listMode:CopySel() - if self.edit or not self.sel or not self.list[self.sel] then - return - end - local srcName = self.list[self.sel].fileName - table.insert(self.list, self.sel + 1, copyTable(self.list[self.sel])) - self.sel = self.sel + 1 - self.list[self.sel].fileName = srcName:gsub("%.xml$","") .. " (copy)" - self:EditInit("Enter new name", function(buf) - if #buf < 1 then - return "No name entered" - end - local inFile, msg = io.open(main.buildPath..srcName, "r") - if not inFile then - return "Couldn't copy '"..srcName.."': "..msg - end - local dstName = buf .. ".xml" - local outFile, msg = io.open(main.buildPath..dstName, "r") - if outFile then - outFile:close() - return "'"..dstName.."' already exists" - end - outFile, msg = io.open(main.buildPath..dstName, "w") - if not outFile then - return "Couldn't create '"..dstName.."': "..msg - end - outFile:write(inFile:read("*a")) - inFile:close() - outFile:close() - self.list[self.edit].fileName = dstName - self.list[self.edit].buildName = buf - self:BuildList() - end) -end - -function listMode:RenameSel() - if self.edit or not self.sel or not self.list[self.sel] then - return - end - local oldName = self.list[self.sel].fileName - self:EditInit("Enter new name", function(buf) - if #buf < 1 then - return "No name entered" - end - local newName = buf .. ".xml" - if newName == oldName then - return - end - if newName:lower() ~= oldName:lower() then - local newFile = io.open(main.buildPath..newName, "r") - if newFile then - newFile:close() - return "'"..newName.."' already exists" - end - end - local res, msg = os.rename(main.buildPath..oldName, main.buildPath..newName) - if not res then - return "Couldn't rename '"..oldName.."' to '"..newName.."': "..msg - end - self.list[self.edit].fileName = newName - self.list[self.edit].buildName = buf - self:SortList() - end) -end - -function listMode:DeleteSel() - if self.edit or not self.sel or not self.list[self.sel] then - return - end - main:OpenConfirmPopup("Confirm Delete", "Are you sure you want to delete build:\n"..self.list[self.sel].buildName.."\nThis cannot be undone.", "Delete", function() - os.remove(main.buildPath..self.list[self.sel].fileName) - self:BuildList() - self.sel = nil - end) end return listMode \ No newline at end of file diff --git a/Modules/CalcActiveSkill.lua b/Modules/CalcActiveSkill.lua index d344218c..fb71b5e2 100644 --- a/Modules/CalcActiveSkill.lua +++ b/Modules/CalcActiveSkill.lua @@ -69,44 +69,46 @@ end -- Create an active skill using the given active gem and list of support gems -- It will determine the base flag set, and check which of the support gems can support this skill function calcs.createActiveSkill(activeGem, supportList, summonSkill) - local activeSkill = { } - activeSkill.activeGem = activeGem - activeSkill.gemList = { activeSkill.activeGem } - activeSkill.supportList = supportList - activeSkill.summonSkill = summonSkill + local activeSkill = { + activeGem = activeGem, + supportList = supportList, + summonSkill = summonSkill, + skillData = { }, + } + -- Initialise skill types activeSkill.skillTypes = copyTable(activeGem.data.skillTypes) if activeGem.data.minionSkillTypes then activeSkill.minionSkillTypes = copyTable(activeGem.data.minionSkillTypes) end - activeSkill.skillData = { } - -- Initialise skill flag set ('attack', 'projectile', etc) local skillFlags = copyTable(activeGem.data.baseFlags) activeSkill.skillFlags = skillFlags skillFlags.hit = activeSkill.skillTypes[SkillType.Attack] or activeSkill.skillTypes[SkillType.Hit] or activeSkill.skillTypes[SkillType.Projectile] - for _, gem in ipairs(supportList) do - if calcLib.gemCanSupport(gem, activeSkill) then - if gem.data.addFlags then - -- Support gem adds flags to supported skills (eg. Remote Mine adds 'mine') - for k in pairs(gem.data.addFlags) do - skillFlags[k] = true - end - end - for _, skillType in pairs(gem.data.addSkillTypes) do + -- Process support skills + activeSkill.gemList = { activeGem } + for _, supportGem in ipairs(supportList) do + -- Pass 1: Add skill types from compatible supports + if calcLib.gemCanSupport(supportGem, activeSkill) then + for _, skillType in pairs(supportGem.data.addSkillTypes) do activeSkill.skillTypes[skillType] = true end end end - - -- Process support gems - for _, gem in ipairs(supportList) do - if calcLib.gemCanSupport(gem, activeSkill) then - t_insert(activeSkill.gemList, gem) - if gem.isSupporting then - gem.isSupporting[activeGem.name] = true + for _, supportGem in ipairs(supportList) do + -- Pass 2: Add all compatible supports + if calcLib.gemCanSupport(supportGem, activeSkill) then + t_insert(activeSkill.gemList, supportGem) + if supportGem.isSupporting then + supportGem.isSupporting[activeGem.name] = true + end + if supportGem.data.addFlags and not summonSkill then + -- Support skill adds flags to supported skills (eg. Remote Mine adds 'mine') + for k in pairs(supportGem.data.addFlags) do + skillFlags[k] = true + end end end end diff --git a/Modules/CalcSetup.lua b/Modules/CalcSetup.lua index 8614b3e6..8f857a45 100644 --- a/Modules/CalcSetup.lua +++ b/Modules/CalcSetup.lua @@ -231,19 +231,30 @@ function calcs.initEnv(build, mode, override) -- Build and merge item modifiers, and create list of radius jewels env.radiusJewelList = wipeTable(env.radiusJewelList) env.player.itemList = { } + env.itemGrantedSkills = { } env.flasks = { } env.modDB.conditions["UsingAllCorruptedItems"] = true for slotName, slot in pairs(build.itemsTab.slots) do local item - if slot.weaponSet and slot.weaponSet ~= (build.itemsTab.useSecondWeaponSet and 2 or 1) then - item = nil - elseif slotName == override.repSlotName then + if slotName == override.repSlotName then item = override.repItem elseif slot.nodeId and override.spec then item = build.itemsTab.list[env.spec.jewels[slot.nodeId]] else item = build.itemsTab.list[slot.selItemId] end + if item then + -- Find skills granted by this item + for _, skill in ipairs(item.grantedSkills) do + local grantedSkill = copyTable(skill) + grantedSkill.sourceItem = item + grantedSkill.slotName = slotName + t_insert(env.itemGrantedSkills, grantedSkill) + end + end + if slot.weaponSet and slot.weaponSet ~= (build.itemsTab.useSecondWeaponSet and 2 or 1) then + item = nil + end if slot.weaponSet == 2 and build.itemsTab.useSecondWeaponSet then slotName = slotName:gsub(" Swap","") end @@ -337,64 +348,53 @@ function calcs.initEnv(build, mode, override) if env.mode == "MAIN" then -- Process extra skills granted by items - local markList = { } - for _, mod in ipairs(modDB.mods["ExtraSkill"] or { }) do - if mod.value.name ~= "Unknown" then - -- Extract the name of the slot containing the item this skill was granted by - local slotName - for _, tag in ipairs(mod.tagList) do - if tag.type == "SocketedIn" then - slotName = tag.slotName + local markList = wipeTable(tempTable1) + for _, grantedSkill in ipairs(env.itemGrantedSkills) do + -- Check if a matching group already exists + local group + for index, socketGroup in pairs(build.skillsTab.socketGroupList) do + if socketGroup.source == grantedSkill.source and socketGroup.slot == grantedSkill.slotName then + if socketGroup.gemList[1] and socketGroup.gemList[1].nameSpec == grantedSkill.name then + group = socketGroup + markList[socketGroup] = true break end end + end + if not group then + -- Create a new group for this skill + group = { label = "", enabled = true, gemList = { }, source = grantedSkill.source, slot = grantedSkill.slotName } + t_insert(build.skillsTab.socketGroupList, group) + markList[group] = true + end - -- Check if a matching group already exists - local group - for index, socketGroup in pairs(build.skillsTab.socketGroupList) do - if socketGroup.source == mod.source and socketGroup.slot == slotName then - if socketGroup.gemList[1] and socketGroup.gemList[1].nameSpec == mod.value.name then - group = socketGroup - markList[socketGroup] = true - break - end - end - end - if not group then - -- Create a new group for this skill - group = { label = "", enabled = true, gemList = { }, source = mod.source, slot = slotName } - t_insert(build.skillsTab.socketGroupList, group) - markList[group] = true - end - - -- Update the group - group.sourceItem = build.itemsTab.list[tonumber(mod.source:match("Item:(%d+):"))] - local activeGem = group.gemList[1] or { - nameSpec = mod.value.name, - quality = 0, - enabled = true, - fromItem = true, - } - activeGem.level = mod.value.level - wipeTable(group.gemList) - t_insert(group.gemList, activeGem) - if mod.value.noSupports then - group.noSupports = true - else - for _, socketGroup in pairs(build.skillsTab.socketGroupList) do - -- Look for other groups that are socketed in the item - if socketGroup.slot == slotName and not socketGroup.source then - -- Add all support gems to the skill's group - for _, gem in ipairs(socketGroup.gemList) do - if gem.data and gem.data.support then - t_insert(group.gemList, gem) - end + -- Update the group + group.sourceItem = grantedSkill.sourceItem + local activeGem = group.gemList[1] or { + nameSpec = grantedSkill.name, + quality = 0, + enabled = true, + fromItem = true, + } + activeGem.level = grantedSkill.level + wipeTable(group.gemList) + t_insert(group.gemList, activeGem) + if grantedSkill.noSupports then + group.noSupports = true + else + for _, socketGroup in pairs(build.skillsTab.socketGroupList) do + -- Look for other groups that are socketed in the item + if socketGroup.slot == grantedSkill.slotName and not socketGroup.source then + -- Add all support gems to the skill's group + for _, gem in ipairs(socketGroup.gemList) do + if gem.data and gem.data.support then + t_insert(group.gemList, gem) end end end end - build.skillsTab:ProcessSocketGroup(group) end + build.skillsTab:ProcessSocketGroup(group) end -- Remove any socket groups that no longer have a matching item @@ -434,12 +434,12 @@ function calcs.initEnv(build, mode, override) -- Build list of active skills env.activeSkillList = { } + local groupCfg = wipeTable(tempTable1) for index, socketGroup in pairs(build.skillsTab.socketGroupList) do local socketGroupSkillList = { } local slot = socketGroup.slot and build.itemsTab.slots[socketGroup.slot] socketGroup.slotEnabled = not slot or not slot.weaponSet or slot.weaponSet == (build.itemsTab.useSecondWeaponSet and 2 or 1) if index == env.mainSocketGroup or (socketGroup.enabled and socketGroup.slotEnabled) then - local groupCfg = wipeTable(tempTable1) groupCfg.slotName = socketGroup.slot local propertyModList = env.modDB:Sum("LIST", groupCfg, "GemProperty") @@ -454,14 +454,17 @@ function calcs.initEnv(build, mode, override) name = gemData.name, data = gemData, level = value.level, - quality = 0, - enabled = true, + quality = 0, + enabled = true, }) end end end for _, gem in ipairs(socketGroup.gemList) do -- Add support gems from this group + if env.mode == "MAIN" then + gem.displayGem = nil + end if gem.enabled and gem.data and gem.data.support then local supportGem = copyTable(gem, true) supportGem.srcGem = gem diff --git a/Modules/Common.lua b/Modules/Common.lua index 02d4cd34..1a9991ae 100644 --- a/Modules/Common.lua +++ b/Modules/Common.lua @@ -145,16 +145,25 @@ function isMouseInRegion(region) end -- Make a copy of a table and all subtables -function copyTable(tbl, noRecurse) - local out = {} - for k, v in pairs(tbl) do - if not noRecurse and type(v) == "table" then - out[k] = copyTable(v) - else - out[k] = v +do + local subTableMap = { } + function copyTable(tbl, noRecurse, isSubTable) + local out = {} + if not noRecurse then + subTableMap[tbl] = out end + for k, v in pairs(tbl) do + if not noRecurse and type(v) == "table" then + out[k] = subTableMap[v] or copyTable(v, false, true) + else + out[k] = v + end + end + if not noRecurse and not isSubTable then + wipeTable(subTableMap) + end + return out end - return out end -- Wipe all keys from the table and return it, or return a new table if no table provided diff --git a/Modules/ItemTools.lua b/Modules/ItemTools.lua index b3a33c3b..780f12a2 100644 --- a/Modules/ItemTools.lua +++ b/Modules/ItemTools.lua @@ -620,7 +620,7 @@ function itemLib.buildItemModList(item) if not item.base then return end - local baseList = { } + local baseList = common.New("ModList") if item.base.weapon then item.weaponData = { } elseif item.base.armour then @@ -652,14 +652,25 @@ function itemLib.buildItemModList(item) if modLine.buff then t_insert(item.buffModList, mod) else - t_insert(baseList, mod) + baseList:AddMod(mod) end end end end + item.grantedSkills = { } + for _, skill in ipairs(baseList:Sum("LIST", nil, "ExtraSkill")) do + if skill.name ~= "Unknown" then + t_insert(item.grantedSkills, { + name = skill.name, + level = skill.level, + noSupports = skill.noSupports, + source = item.modSource, + }) + end + end if item.name == "Tabula Rasa, Simple Robe" or item.name == "Skin of the Loyal, Simple Robe" or item.name == "Skin of the Lords, Simple Robe" then -- Hack to remove the energy shield - t_insert(baseList, { name = "ArmourData", type = "LIST", value = { key = "EnergyShield" }, flags = 0, keywordFlags = 0, tagList = { } }) + baseList:NewMod("ArmourData", "LIST", { key = "EnergyShield" }) end if item.base.weapon or item.type == "Ring" then item.slotModList = { } diff --git a/Modules/Main.lua b/Modules/Main.lua index 09ed0db9..b7aa8de2 100644 --- a/Modules/Main.lua +++ b/Modules/Main.lua @@ -38,6 +38,7 @@ local classList = { "ScrollBarControl", "SliderControl", "TextListControl", + "ListControl", -- Misc "PopupDialog", -- Mode: Build list @@ -327,7 +328,7 @@ function main:OpenUpdatePopup() end end local controls = { } - controls.changeLog = common.New("TextListControl", nil, 0, 20, 780, 190, nil, changeList) + controls.changeLog = common.New("TextListControl", nil, 0, 20, 780, 192, nil, changeList) controls.update = common.New("ButtonControl", nil, -45, 220, 80, 20, "Update", function() self:ClosePopup() local ret = self:CallMode("CanExit", "UPDATE") @@ -446,7 +447,7 @@ function main:RenderCircle(x, y, width, height, oX, oY, radius) end end for ly = minY, maxY do - DrawImage(nil, x + minX[ly], y + ly, maxX[ly] - minX[ly], 1) + DrawImage(nil, x + minX[ly], y + ly, maxX[ly] - minX[ly] + 1, 1) end end diff --git a/Modules/ModParser.lua b/Modules/ModParser.lua index 301d4041..81f616ab 100644 --- a/Modules/ModParser.lua +++ b/Modules/ModParser.lua @@ -712,17 +712,17 @@ local specialModList = { ["socketed gems gain (%d+)%% of physical damage as extra lightning damage"] = function(num) return { mod("PhysicalDamageGainAsLightning", "BASE", num, { type = "SocketedIn" }) } end, ["socketed red gems get (%d+)%% physical damage as extra fire damage"] = function(num) return { mod("PhysicalDamageGainAsFire", "BASE", num, { type = "SocketedIn", keyword = "strength" }) } end, -- Extra skill/support - ["grants level (%d+) (.+)"] = function(num, _, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill:gsub(" skill","")] or "Unknown", level = num }, { type = "SocketedIn" }) } end, - ["casts level (%d+) (.+) when equipped"] = function(num, _, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill:gsub(" skill","")] or "Unknown", level = num }, { type = "SocketedIn" }) } end, - ["cast level (%d+) (.+) when you deal a critical strike"] = function(num, _, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill:gsub(" skill","")] or "Unknown", level = num }, { type = "SocketedIn" }) } end, - ["cast level (%d+) (.+) when hit"] = function(num, _, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill:gsub(" skill","")] or "Unknown", level = num }, { type = "SocketedIn" }) } end, - ["cast level (%d+) (.+) when you kill an enemy"] = function(num, _, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill:gsub(" skill","")] or "Unknown", level = num }, { type = "SocketedIn" }) } end, - ["%d+%% chance to attack with level (%d+) (.+) on melee hit"] = function(num, _, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill:gsub(" skill","")] or "Unknown", level = num }, { type = "SocketedIn" }) } end, - ["%d+%% chance to cast level (%d+) (.+) on hit"] = function(num, _, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill:gsub(" skill","")] or "Unknown", level = num }, { type = "SocketedIn" }) } end, - ["%d+%% chance to cast level (%d+) (.+) on kill"] = function(num, _, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill:gsub(" skill","")] or "Unknown", level = num }, { type = "SocketedIn" }) } end, - ["attack with level (%d+) (.+) when you kill a bleeding enemy"] = function(num, _, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill:gsub(" skill","")] or "Unknown", level = num }, { type = "SocketedIn" }) } end, - ["curse enemies with (%D+) on %a+"] = function(_, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill] or "Unknown", level = 1, noSupports = true }, { type = "SocketedIn" }) } end, - ["curse enemies with level (%d+) (.+) on %a+"] = function(num, _, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill] or "Unknown", level = num, noSupports = true }, { type = "SocketedIn" }) } end, + ["grants level (%d+) (.+)"] = function(num, _, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill:gsub(" skill","")] or "Unknown", level = num }) } end, + ["casts level (%d+) (.+) when equipped"] = function(num, _, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill:gsub(" skill","")] or "Unknown", level = num }) } end, + ["cast level (%d+) (.+) when you deal a critical strike"] = function(num, _, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill:gsub(" skill","")] or "Unknown", level = num }) } end, + ["cast level (%d+) (.+) when hit"] = function(num, _, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill:gsub(" skill","")] or "Unknown", level = num }) } end, + ["cast level (%d+) (.+) when you kill an enemy"] = function(num, _, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill:gsub(" skill","")] or "Unknown", level = num }) } end, + ["%d+%% chance to attack with level (%d+) (.+) on melee hit"] = function(num, _, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill:gsub(" skill","")] or "Unknown", level = num }) } end, + ["%d+%% chance to cast level (%d+) (.+) on hit"] = function(num, _, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill:gsub(" skill","")] or "Unknown", level = num }) } end, + ["%d+%% chance to cast level (%d+) (.+) on kill"] = function(num, _, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill:gsub(" skill","")] or "Unknown", level = num }) } end, + ["attack with level (%d+) (.+) when you kill a bleeding enemy"] = function(num, _, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill:gsub(" skill","")] or "Unknown", level = num }) } end, + ["curse enemies with (%D+) on %a+"] = function(_, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill] or "Unknown", level = 1, noSupports = true }) } end, + ["curse enemies with level (%d+) (.+) on %a+"] = function(num, _, skill) return { mod("ExtraSkill", "LIST", { name = gemNameLookup[skill] or "Unknown", level = num, noSupports = true }) } end, ["socketed [%a+]* ?gems a?r?e? ?supported by level (%d+) (.+)"] = function(num, _, support) return { mod("ExtraSupport", "LIST", { name = gemNameLookup[support] or gemNameLookup[support:gsub("^increased ","")] or "Unknown", level = num }, { type = "SocketedIn" }) } end, -- Conversion ["increases and reductions to minion damage also affects? you"] = { flag("MinionDamageAppliesToPlayer") }, diff --git a/PathOfBuilding.sln b/PathOfBuilding.sln index c74d9410..7e5a0729 100644 --- a/PathOfBuilding.sln +++ b/PathOfBuilding.sln @@ -76,6 +76,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Classes", "Classes", "{7EE4 Classes\ItemSlotControl.lua = Classes\ItemSlotControl.lua Classes\ItemsTab.lua = Classes\ItemsTab.lua Classes\LabelControl.lua = Classes\LabelControl.lua + Classes\ListControl.lua = Classes\ListControl.lua Classes\MinionListControl.lua = Classes\MinionListControl.lua Classes\ModDB.lua = Classes\ModDB.lua Classes\ModList.lua = Classes\ModList.lua diff --git a/README.md b/README.md index 495a2938..fc93cbf0 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,13 @@ If you'd like to help support the development of Path of Building, I have a [Pat ![ss3](https://cloud.githubusercontent.com/assets/19189971/18089780/f0ff234a-6f04-11e6-8c88-6193fe59a5c4.png) ## Changelog +### 1.4.11 - 2017/05/16 + * Fixed a stack overflow error that could occur when trying to view breakdowns in the Calcs tab + * Fixed interaction between weapon swap and skills granted by items + * Consolidated the program's various list controls; their appearence and behaviour should be largely unchanged, + aside from some minor enhancements + * Various minor tweaks and fixes + ### 1.4.10 - 2017/05/12 * Added support for weapon swap: * You can switch between the two weapon sets using the new buttons above the Weapon 1 slot on the Items tab diff --git a/changelog.txt b/changelog.txt index 388809d8..2bc85cd2 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,9 @@ +VERSION[1.4.11][2017/05/16] + * Fixed a stack overflow error that could occur when trying to view breakdowns in the Calcs tab + * Fixed interaction between weapon swap and skills granted by items + * Consolidated the program's various list controls; their appearence and behaviour should be largely unchanged, + aside from some minor enhancements + * Various minor tweaks and fixes VERSION[1.4.10][2017/05/12] * Added support for weapon swap: * You can switch between the two weapon sets using the new buttons above the Weapon 1 slot on the Items tab diff --git a/manifest.xml b/manifest.xml index a66a5039..7fed03c7 100644 --- a/manifest.xml +++ b/manifest.xml @@ -1,64 +1,65 @@ - + - - + + - + - + - - - - + + + + - - - + + + + - + - + - - + + - + - - - + + + - + - + - - - + + +