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
This commit is contained in:
Openarl
2017-05-16 19:05:02 +10:00
parent 8a1c666b2d
commit eb91bcbf66
28 changed files with 1282 additions and 1840 deletions

View File

@@ -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
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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

331
Classes/ListControl.lua Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
end

View File

@@ -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

View File

@@ -180,6 +180,7 @@ function TreeTabClass:Save(xml)
spec:Save(child)
t_insert(xml, child)
end
self.modFlag = false
end
function TreeTabClass:SetActiveSpec(specId)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 = { }

View File

@@ -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

View File

@@ -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") },

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,64 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<PoBVersion>
<Version number="1.4.10"/>
<Version number="1.4.11"/>
<Source part="program" url="https://raw.githubusercontent.com/Openarl/PathOfBuilding/{branch}/"/>
<Source part="tree" url="https://raw.githubusercontent.com/Openarl/PathOfBuilding/{branch}/tree.zip"/>
<Source url="https://raw.githubusercontent.com/Openarl/PathOfBuilding/{branch}/runtime-win32.zip" part="runtime" platform="win32"/>
<File sha1="7912b22666567d5f0ff46378f5cb22e589e84efd" name="Launch.lua" part="program"/>
<File sha1="d8e42beeb38baabcc197d658e4c0af33419eeff3" name="UpdateCheck.lua" part="program"/>
<File sha1="4f17937f2b37784e169a3792b235f2a0a3961e61" name="UpdateApply.lua" part="program"/>
<File sha1="735c96cf5f40f0d1b6fcf5f299d9b291cb0f3893" name="changelog.txt" part="program"/>
<File sha1="26815100a18550b05f99cec9ca8ebff8e0e2a567" name="Classes/BuildListControl.lua" part="program"/>
<File sha1="b0e42a8db9250e9b5be96b4471a3745f9079c9bb" name="changelog.txt" part="program"/>
<File sha1="dd954f662b98ee5c2e5a1a3559a30a3578662188" name="Classes/BuildListControl.lua" part="program"/>
<File sha1="dc2ffb55b4aae04b86886b25bbf219a301b21340" name="Classes/ButtonControl.lua" part="program"/>
<File sha1="9391d403c4ad8e59fd02fba76158acb4159e0562" name="Classes/CalcBreakdownControl.lua" part="program"/>
<File sha1="f7bced0e554bd4d99ef56bd87cf43294957cd179" name="Classes/CalcSectionControl.lua" part="program"/>
<File sha1="125e891b36ba51345662be93e53a10d2733e63c8" name="Classes/CalcsTab.lua" part="program"/>
<File sha1="e43c0c851f7cfb022baba23bcbdc4c96b4078a53" name="Classes/CalcsTab.lua" part="program"/>
<File sha1="573b791867ada18da1e93de8fc33ce6cf9ec9ab3" name="Classes/CheckBoxControl.lua" part="program"/>
<File sha1="aaf31303ab6d70877517d53db9673faa131514e3" name="Classes/ConfigTab.lua" part="program"/>
<File sha1="4a83db8e6a6c83756795192e81c3237839ef3cb3" name="Classes/ConfigTab.lua" part="program"/>
<File sha1="bbb08f183746d6ec023e2bd08fb7a89d365381da" name="Classes/Control.lua" part="program"/>
<File sha1="45be3d636d1eaff18bed7095478c22abfd0590ef" name="Classes/ControlHost.lua" part="program"/>
<File sha1="9f05f72260f896eea09c1a8fb28f58973ce4d3ff" name="Classes/DropDownControl.lua" part="program"/>
<File sha1="4fadcbb94abdd8793c8041fab9269e0f41ebc73a" name="Classes/EditControl.lua" part="program"/>
<File sha1="f3de897d524a19b7838ada898aae9af23d12cd21" name="Classes/GemSelectControl.lua" part="program"/>
<File sha1="d6c2758c03ef6b052678210396d8c2f5cb7a0946" name="Classes/ImportTab.lua" part="program"/>
<File sha1="973a8e8aef65bcaf71812707fb671ee090b67793" name="Classes/ItemDBControl.lua" part="program"/>
<File sha1="5ecb01b4b82c19420b15a116027e00530f673045" name="Classes/ItemListControl.lua" part="program"/>
<File sha1="bda86df3dca469c831eb6d0bc2721d73cf3096b1" name="Classes/ItemSlotControl.lua" part="program"/>
<File sha1="9094c1f5a94d7bb919e144af6c5f939520db477b" name="Classes/ItemsTab.lua" part="program"/>
<File sha1="7bde7b84e4925162ab869ed788c52453d7ad1c44" name="Classes/ItemDBControl.lua" part="program"/>
<File sha1="9ddb90cd201b691298ec2fd97004f5807271b153" name="Classes/ItemListControl.lua" part="program"/>
<File sha1="0d8d46af9ea346867e2a4f324baa835022a42267" name="Classes/ItemSlotControl.lua" part="program"/>
<File sha1="d1d5d7a65e6c860bf112da90f5d267313b1da3d9" name="Classes/ItemsTab.lua" part="program"/>
<File sha1="62138c7db82d57d638a16610a26acd0de75d3486" name="Classes/LabelControl.lua" part="program"/>
<File sha1="f1b5e6a85fff1677c6cdc27a431b9ddbea21f9b4" name="Classes/MinionListControl.lua" part="program"/>
<File sha1="91a2c0ef3e7f097f5f9ff3fd6d849ab784fbf3c2" name="Classes/ModDB.lua" part="program"/>
<File sha1="00c1906b36492a5906d84e33966b763ac1ca88bd" name="Classes/ModList.lua" part="program"/>
<File sha1="c0df07903c315e415fd2690763437f93aadd09f7" name="Classes/ListControl.lua" part="program"/>
<File sha1="75c943489d007c34793f6568c28cee2bc1843e75" name="Classes/MinionListControl.lua" part="program"/>
<File sha1="ed86df273035636fea3589ad48f9bcc18aba5346" name="Classes/ModDB.lua" part="program"/>
<File sha1="d4911a6780f4f8cf2b30cd88565a801fa9563fef" name="Classes/ModList.lua" part="program"/>
<File sha1="9bc0d8791e7825e52070e96e7894d29fad70cf98" name="Classes/NotesTab.lua" part="program"/>
<File sha1="cf400bb69d6668bdb44f595572b84194ac728b72" name="Classes/PassiveSpec.lua" part="program"/>
<File sha1="178c1dcab43b126240b029d3d29721f0af6cd84c" name="Classes/PassiveSpecListControl.lua" part="program"/>
<File sha1="2802110b05a2ebdf39e62c1e9cfcf72d9538f359" name="Classes/PassiveSpecListControl.lua" part="program"/>
<File sha1="bcd74b6409a4d39d27b2f4db8106daf763d97f47" name="Classes/PassiveTree.lua" part="program"/>
<File sha1="6d0093e408730224046d4e5499e2b89bda5d6f9d" name="Classes/PassiveTreeView.lua" part="program"/>
<File sha1="c66811dc709dc18d88ce9fcaec53161c7ca650fe" name="Classes/PassiveTreeView.lua" part="program"/>
<File sha1="9d91ef81ac4fd8d5a1e16be17bdf199545209d87" name="Classes/PopupDialog.lua" part="program"/>
<File sha1="86fee3127d9520144fc741f6fccc3c1d9f1aa532" name="Classes/ScrollBarControl.lua" part="program"/>
<File sha1="261dcf54a4542e6160fd7024d8edf4fc095d9c71" name="Classes/SectionControl.lua" part="program"/>
<File sha1="9848d55768c97c189e001014db6ce606dbed3e46" name="Classes/SkillListControl.lua" part="program"/>
<File sha1="0dbb779f1e3f38f37645328c480942bc7a5f82f0" name="Classes/SkillsTab.lua" part="program"/>
<File sha1="0d6551c10866549a2f808c5b11be05e2cfa70fe3" name="Classes/SkillListControl.lua" part="program"/>
<File sha1="560bd7cfeb4b354e4b7c9b8b28862714569b0683" name="Classes/SkillsTab.lua" part="program"/>
<File sha1="6317bd9ba391832dccafcb62409a5ce2988d1928" name="Classes/SliderControl.lua" part="program"/>
<File sha1="ada27b91a3c466689111429105edd4ee28ad0c3f" name="Classes/TextListControl.lua" part="program"/>
<File sha1="e3361a87d74bb7c6febad7d43c30868ab21ec1fd" name="Classes/TreeTab.lua" part="program"/>
<File sha1="73f7b9a0657e616be4252dcc5e57e8bb057d2606" name="Classes/TreeTab.lua" part="program"/>
<File sha1="4b7675c8b4fe71cade7dd3d70793df1ed8022d01" name="Classes/UndoHandler.lua" part="program"/>
<File sha1="50b76f0db57d437329987ff08481ad65446129ce" name="Modules/Build.lua" part="program"/>
<File sha1="9407e4de58446eaaa420bd5e7a324ae827a3b50a" name="Modules/BuildList.lua" part="program"/>
<File sha1="c77abfd2020a3cec7f270ff8bbb1f1e10896c08a" name="Modules/CalcActiveSkill.lua" part="program"/>
<File sha1="c01971cdd5bc9ed758cacdace3403bbeda71aef9" name="Modules/Build.lua" part="program"/>
<File sha1="e5fba9a6bf560a005e7b756ac5b703e36eb8ba59" name="Modules/BuildList.lua" part="program"/>
<File sha1="403311a61d926adb1150a75d8100032f45c2ef17" name="Modules/CalcActiveSkill.lua" part="program"/>
<File sha1="b2b8d55258d9763d7c7a4a9ca1f99be3973528a8" name="Modules/CalcBreakdown.lua" part="program"/>
<File sha1="8647664477fef0e771500b96342496c83ab3fe4f" name="Modules/CalcDefence.lua" part="program"/>
<File sha1="a8758ac6817d509cc3c5913402d38fb022185651" name="Modules/CalcOffence.lua" part="program"/>
<File sha1="a91717945f21ce2c65d12dc8e5573c7c21a2f489" name="Modules/CalcPerform.lua" part="program"/>
<File sha1="23c918fa7005af4413a9875571fe56946c76a931" name="Modules/Calcs.lua" part="program"/>
<File sha1="39a28dc4f69724dfda868d8253fd65943bd9e325" name="Modules/CalcSections.lua" part="program"/>
<File sha1="54b6e97cb2fe4e7e619ce3382dd7745561e8b5b9" name="Modules/CalcSetup.lua" part="program"/>
<File sha1="5308683d3c078886b6217418b12e6766b75623f4" name="Modules/CalcSetup.lua" part="program"/>
<File sha1="d15500aeb71f04993850bc9401d1e20e3436c795" name="Modules/CalcTools.lua" part="program"/>
<File sha1="1333f0be1dec8f99e971d4175574296f82691fac" name="Modules/Common.lua" part="program"/>
<File sha1="1a3f16372f27e7b94d373e6c61affb4ebe478b13" name="Modules/Common.lua" part="program"/>
<File sha1="b87a64e3b2d14246dcc2e19fa65bb44eed1428f8" name="Modules/Data.lua" part="program"/>
<File sha1="a2143caa5362ecb9a85d88ba497b042269038b0a" name="Modules/ItemTools.lua" part="program"/>
<File sha1="a739ed77abd79ef4972d6c130ba4779b45ae2191" name="Modules/Main.lua" part="program"/>
<File sha1="0361406ebcf01f1d8f8de0f5a943d24a2886ef4c" name="Modules/ModParser.lua" part="program"/>
<File sha1="d5d22f2fca12cd9148ec17016aa2741ee844a692" name="Modules/ItemTools.lua" part="program"/>
<File sha1="ae4b307aceb643ac23d6fd2ea5ec02e5234a43e9" name="Modules/Main.lua" part="program"/>
<File sha1="6f572a9c9c5b6d85739f4c0cfda8386b4874af34" name="Modules/ModParser.lua" part="program"/>
<File sha1="08bfd94f291b10dbdc75c94d7f2928c8bbd1a1d7" name="Modules/ModTools.lua" part="program"/>
<File sha1="c345cdcf374d271411aa424ab150c0edbb5a362d" name="Assets/game_ui_small.png" part="program"/>
<File sha1="97b020d8213e09c313536a91528ba5d5ebc4ca0a" name="Assets/patreon_logo.png" part="program"/>