diff --git a/Assets/patreon_logo.png b/Assets/patreon_logo.png new file mode 100644 index 00000000..9c0c3efc Binary files /dev/null and b/Assets/patreon_logo.png differ diff --git a/Classes/ButtonControl.lua b/Classes/ButtonControl.lua index 49104f7b..8010e4f5 100644 --- a/Classes/ButtonControl.lua +++ b/Classes/ButtonControl.lua @@ -23,6 +23,15 @@ function ButtonClass:Click() end end +function ButtonClass:SetImage(path) + if path then + self.image = NewImageHandle() + self.image:Load(path) + else + self.image = nil + end +end + function ButtonClass:IsMouseOver() if not self:IsShown() then return false @@ -54,6 +63,18 @@ function ButtonClass:Draw(viewPort) SetDrawColor(0, 0, 0) end DrawImage(nil, x + 1, y + 1, width - 2, height - 2) + if self.image then + if enabled then + SetDrawColor(1, 1, 1) + else + SetDrawColor(0.33, 0.33, 0.33) + end + DrawImage(self.image, x + 2, y + 2, width - 4, height - 4) + if self.clicked and mOver then + SetDrawColor(1, 1, 1, 0.5) + DrawImage(nil, x + 1, y + 1, width - 2, height - 2) + end + end if enabled then SetDrawColor(1, 1, 1) else diff --git a/Classes/CalcBreakdownControl.lua b/Classes/CalcBreakdownControl.lua index 4616c99a..36ae9623 100644 --- a/Classes/CalcBreakdownControl.lua +++ b/Classes/CalcBreakdownControl.lua @@ -198,7 +198,7 @@ function CalcBreakdownClass:AddModSection(sectionData) local build = self.calcsTab.build -- Build list of modifiers to display - local cfg = (sectionData.cfg and actor.mainSkill[sectionData.cfg.."Cfg"] and copyTable(actor.mainSkill[sectionData.cfg.."Cfg"])) or { } + local cfg = (sectionData.cfg and actor.mainSkill[sectionData.cfg.."Cfg"] and copyTable(actor.mainSkill[sectionData.cfg.."Cfg"], true)) or { } cfg.source = sectionData.modSource cfg.tabulate = true local rowList diff --git a/Classes/ConfigTab.lua b/Classes/ConfigTab.lua index f480704a..b97d77cb 100644 --- a/Classes/ConfigTab.lua +++ b/Classes/ConfigTab.lua @@ -356,6 +356,9 @@ local varList = { { var = "conditionEnemyShocked", type = "check", label = "Is the enemy Shocked?", tooltip = "In addition to allowing any 'against Shocked Enemies' modifiers to apply,\nthis will apply Shock's Damage Taken modifier to the enemy.", apply = function(val, modList, enemyModList) enemyModList:NewMod("Condition:Shocked", "FLAG", true, "Config", { type = "Condition", var = "Effective" }) end }, + { var = "multiplierFreezeShockIgniteOnEnemy", type = "number", label = "# of Freeze/Shock/Ignite on Enemy:", ifMult = "FreezeShockIgniteOnEnemy", apply = function(val, modList, enemyModList) + modList:NewMod("Multiplier:FreezeShockIgniteOnEnemy", "BASE", val, "Config", { type = "Condition", var = "Effective" }) + end }, { var = "conditionEnemyIntimidated", type = "check", label = "Is the enemy Intimidated?", tooltip = "This adds the following modifiers:\n10% increased Damage Taken by enemy", apply = function(val, modList, enemyModList) enemyModList:NewMod("DamageTaken", "INC", 10, "Intimidate") end }, @@ -497,6 +500,21 @@ local ConfigTabClass = common.NewClass("ConfigTab", "UndoHandler", "ControlHost" return varData.tooltip end end + elseif varData.ifMult then + control.shown = function() + return self.build.calcsTab.mainEnv.multipliersUsed[varData.ifMult] + end + control.tooltip = function() + if launch.devMode and IsKeyDown("ALT") then + local out = varData.tooltip or "" + for _, mod in ipairs(self.build.calcsTab.mainEnv.multipliersUsed[varData.ifMult]) do + out = (#out > 0 and out.."\n" or out) .. modLib.formatMod(mod) .. "|" .. mod.source + end + return out + else + return varData.tooltip + end + end elseif varData.ifFlag then control.shown = function() return self.build.calcsTab.mainEnv.player.mainSkill.skillFlags[varData.ifFlag] -- O_O diff --git a/Classes/GemSelectControl.lua b/Classes/GemSelectControl.lua index 1b88b578..877cedc1 100644 --- a/Classes/GemSelectControl.lua +++ b/Classes/GemSelectControl.lua @@ -197,19 +197,30 @@ function GemSelectClass:Draw(viewPort) local calcFunc, calcBase = self.skillsTab.build.calcsTab:GetMiscCalculator(self.build) if calcFunc then local gemList = self.skillsTab.displayGroup.gemList - local oldGem = gemList[self.index] - gemList[self.index] = copyTable(oldGem or { level = 20, quality = 0, enabled = true }, true) - gemList[self.index].name = self.list[self.hoverSel] - gemList[self.index].data = data.gems[self.list[self.hoverSel]] - if gemList[self.index].data.low_max_level and not gemList[self.index].data.levels[gemList[self.index].level] then - if gemList[self.index].data.levels[3][1] then - gemList[self.index].level = 3 + local oldGem + if gemList[self.index] then + oldGem = copyTable(gemList[self.index], true) + else + gemList[self.index] = { level = 20, quality = 0, enabled = true } + end + local gem = gemList[self.index] + gem.name = self.list[self.hoverSel] + gem.data = data.gems[self.list[self.hoverSel]] + if gem.data.low_max_level and not gem.data.levels[gem.level] then + if gem.data.levels[3][1] then + gem.level = 3 else - gemList[self.index].level = 1 + gem.level = 1 end end local output = calcFunc() - gemList[self.index] = oldGem + if oldGem then + gem.name = oldGem.name + gem.data = oldGem.data + gem.level = oldGem.level + else + gemList[self.index] = nil + end self.skillsTab.build:AddStatComparesToTooltip(calcBase, output, "^7Selecting this gem will give you:") main:DrawTooltip(x, y + height + 2 + (self.hoverSel - 1) * (height - 4) - scrollBar.offset, width, height - 4, viewPort) end @@ -226,8 +237,8 @@ function GemSelectClass:Draw(viewPort) local thisGem = self.skillsTab.displayGroup.gemList[self.index] local hoverGem = self.skillsTab.displayGroup.gemList[hoverControl.index] if thisGem and hoverGem and thisGem.enabled and hoverGem.enabled and thisGem.data and hoverGem.data and - ((hoverGem.data.support and not thisGem.data.support and hoverGem.isSupporting and hoverGem.isSupporting[thisGem.name]) or - (thisGem.data.support and not hoverGem.data.support and thisGem.isSupporting and thisGem.isSupporting[hoverGem.name])) then + ((hoverGem.data.support and not thisGem.data.support and hoverGem.displayGem and hoverGem.displayGem.isSupporting[thisGem.name]) or + (thisGem.data.support and not hoverGem.data.support and thisGem.displayGem and thisGem.displayGem.isSupporting[hoverGem.name])) then SetDrawColor(0.33, 1, 0.33, 0.25) DrawImage(nil, x, y, width, height) end diff --git a/Classes/ImportTab.lua b/Classes/ImportTab.lua index 5ebeb2a9..daae9870 100644 --- a/Classes/ImportTab.lua +++ b/Classes/ImportTab.lua @@ -34,6 +34,10 @@ local ImportTabClass = common.NewClass("ImportTab", "ControlHost", "Control", fu self.controls.accountNameGo.enabled = function() return self.controls.accountName.buf:match("%S") end + self.controls.accountNameUnicode = common.New("LabelControl", {"TOPLEFT",self.controls.accountName,"BOTTOMLEFT"}, 0, 16, 0, 14, "^7Note: if the account name contains non-ASCII characters then it must be URL encoded first.") + self.controls.accountNameURLEncoder = common.New("ButtonControl", {"TOPLEFT",self.controls.accountNameUnicode,"BOTTOMLEFT"}, 0, 4, 170, 18, "^x4040FFhttps://www.urlencoder.org/", function() + OpenURL("https://www.urlencoder.org/") + end) -- Stage: input POESESSID self.controls.sessionHeader = common.New("LabelControl", {"TOPLEFT",self.controls.sectionCharImport,"TOPLEFT"}, 6, 40, 200, 14) @@ -455,7 +459,7 @@ end local rarityMap = { [0] = "NORMAL", "MAGIC", "RARE", "UNIQUE", [9] = "RELIC" } local colorMap = { S = "R", D = "G", I = "B", G = "W" } -local slotMap = { ["Weapon"] = "Weapon 1", ["Offhand"] = "Weapon 2", ["Helm"] = "Helmet", ["BodyArmour"] = "Body Armour", ["Gloves"] = "Gloves", ["Boots"] = "Boots", ["Amulet"] = "Amulet", ["Ring"] = "Ring 1", ["Ring2"] = "Ring 2", ["Belt"] = "Belt" } +local slotMap = { ["Weapon"] = "Weapon 1", ["Offhand"] = "Weapon 2", ["Weapon2"] = "Weapon 1 Swap", ["Offhand2"] = "Weapon 2 Swap", ["Helm"] = "Helmet", ["BodyArmour"] = "Body Armour", ["Gloves"] = "Gloves", ["Boots"] = "Boots", ["Amulet"] = "Amulet", ["Ring"] = "Ring 1", ["Ring2"] = "Ring 2", ["Belt"] = "Belt" } function ImportTabClass:ImportItem(itemData, sockets) local slotName diff --git a/Classes/ItemsTab.lua b/Classes/ItemsTab.lua index 399ca286..04e5bb6d 100644 --- a/Classes/ItemsTab.lua +++ b/Classes/ItemsTab.lua @@ -65,14 +65,29 @@ local ItemsTabClass = common.NewClass("ItemsTab", "UndoHandler", "ControlHost", self.slots = { } self.orderedSlots = { } self.slotOrder = { } + self.slotAnchor = common.New("Control", {"TOPLEFT",self,"TOPLEFT"}, 96, 24, 310, 0) for index, slotName in ipairs(baseSlots) do - t_insert(self.controls, common.New("ItemSlot", {"TOPLEFT",self,"TOPLEFT"}, 96, (index - 1) * 20 + 24, self, slotName)) - self.slotOrder[slotName] = index + local slot = common.New("ItemSlot", {"TOPLEFT",self.slotAnchor,"TOPLEFT"}, 0, (index - 1) * 20, self, slotName) + t_insert(self.controls, slot) + self.slotOrder[slotName] = #self.orderedSlots + if slotName:match("Weapon") then + slot.weaponSet = 1 + slot.shown = function() + return not self.useSecondWeaponSet + end + local swapSlot = common.New("ItemSlot", {"TOPLEFT",self.slotAnchor,"TOPLEFT"}, 0, (index - 1) * 20, self, slotName.." Swap", slotName) + t_insert(self.controls, swapSlot) + self.slotOrder[swapSlot.slotName] = #self.orderedSlots + swapSlot.weaponSet = 2 + swapSlot.shown = function() + return not slot:IsShown() + end + end end self.sockets = { } for _, node in pairs(main.tree.nodes) do if node.type == "socket" then - local socketControl = common.New("ItemSlot", {"TOPLEFT",self,"TOPLEFT"}, 96, 0, self, "Jewel "..node.id, "Socket", node.id) + local socketControl = common.New("ItemSlot", {"TOPLEFT",self.slotAnchor,"TOPLEFT"}, 0, 0, self, "Jewel "..node.id, "Socket", node.id) self.controls["socket"..node.id] = socketControl self.sockets[node.id] = socketControl self.slotOrder["Jewel "..node.id] = #baseSlots + 1 + node.id @@ -81,11 +96,52 @@ local ItemsTabClass = common.NewClass("ItemsTab", "UndoHandler", "ControlHost", table.sort(self.orderedSlots, function(a, b) return self.slotOrder[a.slotName] < self.slotOrder[b.slotName] end) - self.controls.slotHeader = common.New("LabelControl", {"BOTTOMLEFT",self.orderedSlots[1],"TOPLEFT"}, 0, -4, 0, 16, "^7Equipped items:") + self.controls.slotHeader = common.New("LabelControl", {"BOTTOMLEFT",self.slotAnchor,"TOPLEFT"}, 0, -4, 0, 16, "^7Equipped items:") + self.controls.weaponSwap1 = common.New("ButtonControl", {"BOTTOMRIGHT",self.slotAnchor,"TOPRIGHT"}, -20, -2, 18, 18, "I", function() + if self.useSecondWeaponSet then + self.useSecondWeaponSet = false + self:AddUndoState() + self.build.buildFlag = true + local mainSocketGroup = self.build.skillsTab.socketGroupList[self.build.mainSocketGroup] + if mainSocketGroup and mainSocketGroup.slot and self.slots[mainSocketGroup.slot].weaponSet == 2 then + for index, socketGroup in ipairs(self.build.skillsTab.socketGroupList) do + if socketGroup.slot and self.slots[socketGroup.slot].weaponSet == 1 then + self.build.mainSocketGroup = index + break + end + end + end + end + end) + self.controls.weaponSwap1.overSizeText = 3 + self.controls.weaponSwap1.locked = function() + return not self.useSecondWeaponSet + end + self.controls.weaponSwap2 = common.New("ButtonControl", {"BOTTOMRIGHT",self.slotAnchor,"TOPRIGHT"}, 0, -2, 18, 18, "II", function() + if not self.useSecondWeaponSet then + self.useSecondWeaponSet = true + self:AddUndoState() + self.build.buildFlag = true + local mainSocketGroup = self.build.skillsTab.socketGroupList[self.build.mainSocketGroup] + if mainSocketGroup and mainSocketGroup.slot and self.slots[mainSocketGroup.slot].weaponSet == 1 then + for index, socketGroup in ipairs(self.build.skillsTab.socketGroupList) do + if socketGroup.slot and self.slots[socketGroup.slot].weaponSet == 2 then + self.build.mainSocketGroup = index + break + end + end + end + end + end) + self.controls.weaponSwap2.overSizeText = 3 + self.controls.weaponSwap2.locked = function() + return self.useSecondWeaponSet + end + self.controls.weaponSwapLabel = common.New("LabelControl", {"RIGHT",self.controls.weaponSwap1,"LEFT"}, -4, 0, 0, 14, "^7Weapon Set:") self:PopulateSlots() -- Build item list - self.controls.itemList = common.New("ItemList", {"TOPLEFT",self.orderedSlots[1],"TOPRIGHT"}, 20, 0, 360, 308, self) + self.controls.itemList = common.New("ItemList", {"TOPLEFT",self.slotAnchor,"TOPRIGHT"}, 20, 0, 360, 308, self) -- Database selector self.controls.selectDBLabel = common.New("LabelControl", {"TOPLEFT",self.controls.itemList,"BOTTOMLEFT"}, 0, 14, 0, 16, "^7Import from:") @@ -201,6 +257,7 @@ If there's 2 slots an item can go in, holding Shift will put it in the second.]] end) function ItemsTabClass:Load(xml, dbFileName) + self.useSecondWeaponSet = xml.attrib.useSecondWeaponSet == "true" for _, node in ipairs(xml) do if node.elem == "Item" then local item = { } @@ -239,6 +296,9 @@ function ItemsTabClass:Load(xml, dbFileName) end function ItemsTabClass:Save(xml) + xml.attrib = { + useSecondWeaponSet = tostring(self.useSecondWeaponSet) + } for _, id in ipairs(self.orderList) do local item = self.list[id] local child = { elem = "Item", attrib = { id = tostring(id), variant = item.variant and tostring(item.variant) } } @@ -337,7 +397,7 @@ function ItemsTabClass:UpdateSockets() -- Update the position of the active socket controls for index, nodeId in pairs(activeSocketList) do self.sockets[nodeId].label = "Socket #"..index - self.sockets[nodeId].y = (#baseSlots + index - 1) * 20 + 24 + self.sockets[nodeId].y = (#baseSlots + index - 1) * 20 end end @@ -736,10 +796,10 @@ end function ItemsTabClass:IsItemValidForSlot(item, slotName) if item.type == slotName:gsub(" %d+","") then return true - elseif slotName == "Weapon 1" or slotName == "Weapon" then + elseif slotName == "Weapon 1" or slotName == "Weapon 1 Swap" or slotName == "Weapon" then return item.base.weapon ~= nil - elseif slotName == "Weapon 2" then - local weapon1Sel = self.slots["Weapon 1"].selItemId or 0 + 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) @@ -1000,8 +1060,7 @@ function ItemsTabClass:AddItemTooltip(item, slot, dbMode) -- Build sorted list of slots to compare with local compareSlots = { } for slotName, slot in pairs(self.slots) do - local selItem = self.list[slot.selItemId] - if self:IsItemValidForSlot(item, slotName) and not slot.inactive then + if self:IsItemValidForSlot(item, slotName) and not slot.inactive and (not slot.weaponSet or slot.weaponSet == (self.useSecondWeaponSet and 2 or 1)) then t_insert(compareSlots, slot) end end @@ -1048,6 +1107,7 @@ end function ItemsTabClass:CreateUndoState() local state = { } + state.useSecondWeaponSet = self.useSecondWeaponSet state.list = copyTable(self.list) state.orderList = copyTable(self.orderList) state.slotSelItemId = { } @@ -1058,6 +1118,7 @@ function ItemsTabClass:CreateUndoState() end function ItemsTabClass:RestoreUndoState(state) + self.useSecondWeaponSet = state.useSecondWeaponSet self.list = state.list self.orderList = state.orderList for slotName, selItemId in pairs(state.slotSelItemId) do diff --git a/Classes/SkillListControl.lua b/Classes/SkillListControl.lua index 6681b7dd..2368d270 100644 --- a/Classes/SkillListControl.lua +++ b/Classes/SkillListControl.lua @@ -102,7 +102,7 @@ function SkillListClass:Draw(viewPort) local socketGroup = list[index] local lineY = 16 * (index - 1) - scrollBar.offset local label = socketGroup.displayLabel or "?" - if not socketGroup.enabled then + if not socketGroup.enabled or not socketGroup.slotEnabled then label = label .. " (Disabled)" end local nameWidth = DrawStringWidth(16, "VAR", label) @@ -126,7 +126,7 @@ function SkillListClass:Draw(viewPort) SetDrawColor(0.15, 0.15, 0.15) DrawImage(nil, 0, lineY + 1, width - 20, 14) end - if socketGroup.enabled then + if socketGroup.enabled and socketGroup.slotEnabled then SetDrawColor(1, 1, 1) else SetDrawColor(0.5, 0.5, 0.5) @@ -142,12 +142,14 @@ function SkillListClass:Draw(viewPort) end SetViewport() if ttGroup and ttGroup.displaySkillList then - local count = 0 - local gemShown = { } + 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) main:AddTooltipSeparator(10) end + local gemShown = { } for index, activeSkill in ipairs(ttGroup.displaySkillList) do if index > 1 then main:AddTooltipSeparator(10) @@ -156,14 +158,15 @@ function SkillListClass:Draw(viewPort) 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.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 "" )) - gemShown[gem.srcGem] = true - count = count + 1 + if gem.srcGem then + gemShown[gem.srcGem] = true + end end if activeSkill.minion then main:AddTooltipSeparator(10) @@ -177,8 +180,9 @@ function SkillListClass:Draw(viewPort) gem.quality, (gem.srcGem and gem.quality > gem.srcGem.quality) and data.colorCodes.MAGIC.."+"..(gem.quality - gem.srcGem.quality).."^7" or "" )) - gemShown[gem.srcGem] = true - count = count + 1 + if gem.srcGem then + gemShown[gem.srcGem] = true + end end end local showOtherHeader = true @@ -190,28 +194,33 @@ function SkillListClass:Draw(viewPort) 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 gem.data.support and gem.isSupporting and not next(gem.isSupporting) then - reason = "(Cannot apply to any of the active skills)" + 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/%d %s", + main:AddTooltipLine(20, string.format("%s%s ^7%d%s/%d%s %s", gem.color, gem.name or gem.nameSpec, - gem.level, - gem.quality, + 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 )) - count = count + 1 end end - if count > 0 then - SetDrawLayer(nil, 100) - main:DrawTooltip(x + 2, ttY, ttWidth, 16, viewPort) - SetDrawLayer(nil, 0) - end + SetDrawLayer(nil, 100) + main:DrawTooltip(x + 2, ttY, ttWidth, 16, viewPort) + SetDrawLayer(nil, 0) end end diff --git a/Classes/SkillsTab.lua b/Classes/SkillsTab.lua index 7562a1fc..4b5a8168 100644 --- a/Classes/SkillsTab.lua +++ b/Classes/SkillsTab.lua @@ -36,7 +36,7 @@ local SkillsTabClass = common.NewClass("SkillsTab", "UndoHandler", "ControlHost" self.build.buildFlag = true end) self.controls.groupSlotLabel = common.New("LabelControl", {"TOPLEFT",self.anchorGroupDetail,"TOPLEFT"}, 0, 30, 0, 16, "^7Socketed in:") - self.controls.groupSlot = common.New("DropDownControl", {"TOPLEFT",self.anchorGroupDetail,"TOPLEFT"}, 85, 28, 110, 20, { "None", "Weapon 1", "Weapon 2", "Helmet", "Body Armour", "Gloves", "Boots", "Amulet", "Ring 1", "Ring 2" }, function(sel, selVal) + self.controls.groupSlot = common.New("DropDownControl", {"TOPLEFT",self.anchorGroupDetail,"TOPLEFT"}, 85, 28, 130, 20, { "None", "Weapon 1", "Weapon 2", "Weapon 1 Swap", "Weapon 2 Swap", "Helmet", "Body Armour", "Gloves", "Boots", "Amulet", "Ring 1", "Ring 2" }, function(sel, selVal) if sel > 1 then self.displayGroup.slot = selVal else diff --git a/Classes/TreeTab.lua b/Classes/TreeTab.lua index 7b091371..c92ed587 100644 --- a/Classes/TreeTab.lua +++ b/Classes/TreeTab.lua @@ -46,6 +46,21 @@ local TreeTabClass = common.NewClass("TreeTab", "ControlHost", function(self, bu local output = calcFunc({ spec = spec }) self.build:AddStatComparesToTooltip(calcBase, output, "^7Switching to this tree will give you:") end + if spec.curClassId == self.build.spec.curClassId then + local respec = 0 + for nodeId, node in pairs(self.build.spec.allocNodes) do + if node.type ~= "classStart" and node.type ~= "ascendClassStart" and not spec.allocNodes[nodeId] then + if node.ascendancyName then + respec = respec + 5 + else + respec = respec + 1 + end + end + end + if respec > 0 then + main:AddTooltipLine(16, "^7Switching to this tree requires "..respec.." refund points.") + end + end end end end diff --git a/Data/Uniques/jewel.lua b/Data/Uniques/jewel.lua index bad453a0..c6a376df 100644 --- a/Data/Uniques/jewel.lua +++ b/Data/Uniques/jewel.lua @@ -437,7 +437,7 @@ Crimson Jewel Limited to: 2 Radius: Medium (4-12)% increased Physical Damage -With at least 40 Strength in Radius, Heavy Strike has a 20% chance to deal Double Damage. +With at least 40 Strength in Radius, Heavy Strike has a 20% chance to deal Double Damage ]],[[ Winter's Bounty Cobalt Jewel diff --git a/Modules/CalcActiveSkill.lua b/Modules/CalcActiveSkill.lua index 057a7f19..d344218c 100644 --- a/Modules/CalcActiveSkill.lua +++ b/Modules/CalcActiveSkill.lua @@ -70,19 +70,11 @@ end -- 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.summonSkill = summonSkill - activeSkill.supportList = supportList - - activeSkill.activeGem = { - name = activeGem.name, - data = activeGem.data, - level = activeGem.level, - quality = activeGem.quality, - fromItem = activeGem.fromItem, - srcGem = activeGem, - } + activeSkill.activeGem = activeGem activeSkill.gemList = { activeSkill.activeGem } - + activeSkill.supportList = supportList + activeSkill.summonSkill = summonSkill + activeSkill.skillTypes = copyTable(activeGem.data.skillTypes) if activeGem.data.minionSkillTypes then activeSkill.minionSkillTypes = copyTable(activeGem.data.minionSkillTypes) @@ -112,14 +104,7 @@ function calcs.createActiveSkill(activeGem, supportList, summonSkill) -- Process support gems for _, gem in ipairs(supportList) do if calcLib.gemCanSupport(gem, activeSkill) then - t_insert(activeSkill.gemList, { - name = gem.name, - data = gem.data, - level = gem.level, - quality = gem.quality, - fromItem = gem.fromItem, - srcGem = gem, - }) + t_insert(activeSkill.gemList, gem) if gem.isSupporting then gem.isSupporting[activeGem.name] = true end @@ -339,15 +324,6 @@ function calcs.buildActiveSkillModList(env, actor, activeSkill) activeSkill.weapon2Cfg.flags = bor(skillModFlags, activeSkill.weapon2Flags) end - -- Apply gem property modifiers from the item this skill is socketed into - for _, value in ipairs(env.modDB:Sum("LIST", activeSkill.skillCfg, "GemProperty")) do - for _, gem in pairs(activeSkill.gemList) do - if not gem.fromItem and calcLib.gemIsType(gem, value.keyword) then - gem[value.key] = (gem[value.key] or 0) + value.value - end - end - end - -- Initialise skill modifier list local skillModList = common.New("ModList") activeSkill.skillModList = skillModList diff --git a/Modules/CalcOffence.lua b/Modules/CalcOffence.lua index 5b846c76..0cc09a03 100644 --- a/Modules/CalcOffence.lua +++ b/Modules/CalcOffence.lua @@ -741,15 +741,23 @@ function calcs.offence(env, actor) end min, max = calcHitDamage(actor, source, cfg, breakdown and breakdown[damageType], damageType) local convMult = actor.conversionTable[damageType].mult + local doubleChance = m_min(modDB:Sum("BASE", cfg, "DoubleDamageChance"), 100) if breakdown then t_insert(breakdown[damageType], "Hit damage:") t_insert(breakdown[damageType], s_format("%d to %d ^8(total damage)", min, max)) if convMult ~= 1 then t_insert(breakdown[damageType], s_format("x %g ^8(%g%% converted to other damage types)", convMult, (1-convMult)*100)) end + if doubleChance > 0 then + t_insert(breakdown[damageType], s_format("x %.2f ^8(chance to deal double damage)", 1 + doubleChance / 100)) + end end min = min * convMult max = max * convMult + if doubleChance > 0 then + min = min * (1 + doubleChance / 100) + max = max * (1 + doubleChance / 100) + end if pass == 1 then -- Apply crit multiplier min = min * output.CritMultiplier diff --git a/Modules/CalcSetup.lua b/Modules/CalcSetup.lua index 8d9e5cdc..8614b3e6 100644 --- a/Modules/CalcSetup.lua +++ b/Modules/CalcSetup.lua @@ -235,13 +235,18 @@ function calcs.initEnv(build, mode, override) env.modDB.conditions["UsingAllCorruptedItems"] = true for slotName, slot in pairs(build.itemsTab.slots) do local item - if slotName == override.repSlotName then + if slot.weaponSet and slot.weaponSet ~= (build.itemsTab.useSecondWeaponSet and 2 or 1) then + item = nil + elseif 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 slot.weaponSet == 2 and build.itemsTab.useSecondWeaponSet then + slotName = slotName:gsub(" Swap","") + end if slot.nodeId then -- Slot is a jewel socket, check if socket is allocated if not nodes[slot.nodeId] then @@ -415,7 +420,7 @@ function calcs.initEnv(build, mode, override) env.player.weaponData2 = env.player.itemList["Weapon 2"] and env.player.itemList["Weapon 2"].weaponData and env.player.itemList["Weapon 2"].weaponData[2] or { } end - -- Build and merge modifiers for allocated passives + -- Merge modifiers for allocated passives env.modDB:AddList(calcs.buildNodeModList(env, nodes, true)) -- Determine main skill group @@ -429,16 +434,20 @@ 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 = { } - if socketGroup.enabled or index == env.mainSocketGroup then + 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") + -- Build list of supports for this socket group local supportList = { } if not socketGroup.source then - groupCfg.slotName = socketGroup.slot + -- Add extra supports from the item this group is socketed in for _, value in ipairs(env.modDB:Sum("LIST", groupCfg, "ExtraSupport")) do - -- Add extra supports from the item this group is socketed in local gemData = data.gems[value.name] or data.skills[value.name] if gemData then t_insert(supportList, { @@ -447,31 +456,43 @@ function calcs.initEnv(build, mode, override) level = value.level, quality = 0, enabled = true, - fromItem = true }) end end end for _, gem in ipairs(socketGroup.gemList) do + -- Add support gems from this group if gem.enabled and gem.data and gem.data.support then - -- Add support gems from this group + local supportGem = copyTable(gem, true) + supportGem.srcGem = gem + supportGem.superseded = false + supportGem.isSupporting = { } + if env.mode == "MAIN" then + gem.displayGem = supportGem + end + for _, value in ipairs(propertyModList) do + if calcLib.gemIsType(supportGem, value.keyword) then + supportGem[value.key] = (supportGem[value.key] or 0) + value.value + end + end local add = true - for _, otherGem in pairs(supportList) do + for index, otherGem in ipairs(supportList) do -- Check if there's another support with the same name already present - if gem.data == otherGem.data then + if supportGem.data == otherGem.data then add = false - if gem.level > otherGem.level then - otherGem.level = gem.level - otherGem.quality = gem.quality - elseif gem.level == otherGem.level then - otherGem.quality = m_max(gem.quality, otherGem.quality) + if supportGem.level > otherGem.level or (supportGem.level == otherGem.level and supportGem.quality > otherGem.quality) then + if env.mode == "MAIN" then + otherGem.superseded = true + end + supportList[index] = supportGem + else + supportGem.superseded = true end break end end if add then - gem.isSupporting = { } - t_insert(supportList, gem) + t_insert(supportList, supportGem) end end end @@ -479,7 +500,16 @@ function calcs.initEnv(build, mode, override) -- Create active skills for _, gem in ipairs(socketGroup.gemList) do if gem.enabled and gem.data and not gem.data.support and not gem.data.unsupported then - local activeSkill = calcs.createActiveSkill(gem, supportList) + local activeGem = copyTable(gem, true) + activeGem.srcGem = gem + if not gem.fromItem then + for _, value in ipairs(propertyModList) do + if calcLib.gemIsType(activeGem, value.keyword) then + activeGem[value.key] = (activeGem[value.key] or 0) + value.value + end + end + end + local activeSkill = calcs.createActiveSkill(activeGem, supportList) activeSkill.slotName = socketGroup.slot t_insert(socketGroupSkillList, activeSkill) t_insert(env.activeSkillList, activeSkill) diff --git a/Modules/Calcs.lua b/Modules/Calcs.lua index feda5740..614efd60 100644 --- a/Modules/Calcs.lua +++ b/Modules/Calcs.lua @@ -199,6 +199,20 @@ function calcs.buildOutput(build, mode) end end end + + env.multipliersUsed = { } + for modName, modList in pairs(env.player.modDB.mods) do + for _, mod in ipairs(modList) do + for _, tag in ipairs(mod.tagList) do + if tag.type == "Multiplier" or tag.type == "MultiplierThreshold" then + if not env.multipliersUsed[tag.var] then + env.multipliersUsed[tag.var] = { } + end + t_insert(env.multipliersUsed[tag.var], mod) + end + end + end + end elseif mode == "CALCS" then local buffList = { } local combatList = { } diff --git a/Modules/Main.lua b/Modules/Main.lua index 0ad587d7..09ed0db9 100644 --- a/Modules/Main.lua +++ b/Modules/Main.lua @@ -118,34 +118,7 @@ function main:Init() return self.screenH - 4 end self.controls.applyUpdate = common.New("ButtonControl", {"BOTTOMLEFT",self.anchorUpdate,"BOTTOMLEFT"}, 0, 0, 110, 20, "^x50E050Update Ready", function() - local changeList = { } - for line in io.lines("changelog.txt") do - local ver, date = line:match("^VERSION%[(.+)%]%[(.+)%]$") - if ver then - if ver == launch.versionNumber then - break - end - if #changeList > 0 then - t_insert(changeList, { height = 12 }) - end - t_insert(changeList, { height = 20, "^7Version "..ver.." ("..date..")" }) - else - t_insert(changeList, { height = 14, "^7"..line }) - end - end - self:OpenPopup(800, 250, "Update Available", { - common.New("TextListControl", nil, 0, 20, 780, 190, nil, changeList), - common.New("ButtonControl", nil, -45, 220, 80, 20, "Update", function() - main:ClosePopup() - local ret = self:CallMode("CanExit", "UPDATE") - if ret == nil or ret == true then - launch:ApplyUpdate(launch.updateAvailable) - end - end), - common.New("ButtonControl", nil, 45, 220, 80, 20, "Cancel", function() - main:ClosePopup() - end), - }) + self:OpenUpdatePopup() end) self.controls.applyUpdate.shown = function() return launch.updateAvailable and launch.updateAvailable ~= "none" @@ -167,36 +140,7 @@ function main:Init() return "^8Version: "..launch.versionNumber..(launch.versionBranch == "dev" and " (Dev)" or "") end self.controls.about = common.New("ButtonControl", {"BOTTOMLEFT",self.anchorUpdate,"BOTTOMLEFT"}, 250, 0, 50, 20, "About", function() - local changeList = { } - for line in io.lines("changelog.txt") do - local ver, date = line:match("^VERSION%[(.+)%]%[(.+)%]$") - if ver then - if #changeList > 0 then - t_insert(changeList, { height = 10 }) - end - t_insert(changeList, { height = 18, "^7Version "..ver.." ("..date..")" }) - else - t_insert(changeList, { height = 12, "^7"..line }) - end - end - self:OpenPopup(650, 400, "About", { - common.New("ButtonControl", {"TOPRIGHT",nil,"TOPRIGHT"}, -10, 10, 50, 20, "Close", function() - main:ClosePopup() - end), - common.New("LabelControl", nil, 0, 18, 0, 18, "Path of Building v"..launch.versionNumber.." by Openarl"), - common.New("ButtonControl", nil, 0, 42, 420, 18, "Forum Thread: ^x4040FFhttps://www.pathofexile.com/forum/view-thread/1716360", function(control) - if OpenURL then - OpenURL("https://www.pathofexile.com/forum/view-thread/1716360") - end - end), - common.New("ButtonControl", nil, 0, 64, 340, 18, "GitHub page: ^x4040FFhttps://github.com/Openarl/PathOfBuilding", function(control) - if OpenURL then - OpenURL("https://github.com/Openarl/PathOfBuilding") - end - end), - common.New("LabelControl", {"TOPLEFT",nil,"TOPLEFT"}, 10, 82, 0, 18, "^7Version history:"), - common.New("TextListControl", nil, 0, 100, 630, 290, nil, changeList), - }) + self:OpenAboutPopup() end) self.controls.devMode = common.New("LabelControl", {"BOTTOMLEFT",self.anchorUpdate,"BOTTOMLEFT"}, 0, 0, 0, 18, "^1Dev Mode") self.controls.devMode.shown = function() @@ -366,6 +310,76 @@ function main:SaveSettings() end end +function main:OpenUpdatePopup() + local changeList = { } + for line in io.lines("changelog.txt") do + local ver, date = line:match("^VERSION%[(.+)%]%[(.+)%]$") + if ver then + if ver == launch.versionNumber then + break + end + if #changeList > 0 then + t_insert(changeList, { height = 12 }) + end + t_insert(changeList, { height = 20, "^7Version "..ver.." ("..date..")" }) + else + t_insert(changeList, { height = 14, "^7"..line }) + end + end + local controls = { } + controls.changeLog = common.New("TextListControl", nil, 0, 20, 780, 190, nil, changeList) + controls.update = common.New("ButtonControl", nil, -45, 220, 80, 20, "Update", function() + self:ClosePopup() + local ret = self:CallMode("CanExit", "UPDATE") + if ret == nil or ret == true then + launch:ApplyUpdate(launch.updateAvailable) + end + end) + controls.cancel = common.New("ButtonControl", nil, 45, 220, 80, 20, "Cancel", function() + self:ClosePopup() + end) + controls.patreon = common.New("ButtonControl", {"BOTTOMLEFT",nil,"BOTTOMLEFT"}, 10, -10, 82, 22, "", function() + OpenURL("https://www.patreon.com/openarl") + end) + controls.patreon:SetImage("Assets/patreon_logo.png") + controls.patreon.tooltip = "Help support the development of Path of Building by pledging a monthly donation!" + self:OpenPopup(800, 250, "Update Available", controls) +end + +function main:OpenAboutPopup() + local changeList = { } + for line in io.lines("changelog.txt") do + local ver, date = line:match("^VERSION%[(.+)%]%[(.+)%]$") + if ver then + if #changeList > 0 then + t_insert(changeList, { height = 10 }) + end + t_insert(changeList, { height = 18, "^7Version "..ver.." ("..date..")" }) + else + t_insert(changeList, { height = 12, "^7"..line }) + end + end + local controls = { } + controls.close = common.New("ButtonControl", {"TOPRIGHT",nil,"TOPRIGHT"}, -10, 10, 50, 20, "Close", function() + self:ClosePopup() + end) + controls.version = common.New("LabelControl", nil, 0, 18, 0, 18, "Path of Building v"..launch.versionNumber.." by Openarl") + controls.forum = common.New("ButtonControl", nil, 0, 42, 420, 18, "Forum Thread: ^x4040FFhttps://www.pathofexile.com/forum/view-thread/1716360", function(control) + OpenURL("https://www.pathofexile.com/forum/view-thread/1716360") + end) + controls.github = common.New("ButtonControl", nil, 0, 64, 340, 18, "GitHub page: ^x4040FFhttps://github.com/Openarl/PathOfBuilding", function(control) + OpenURL("https://github.com/Openarl/PathOfBuilding") + end) + controls.patreon = common.New("ButtonControl", {"TOPLEFT",nil,"TOPLEFT"}, 10, 10, 82, 22, "", function() + OpenURL("https://www.patreon.com/openarl") + end) + controls.patreon:SetImage("Assets/patreon_logo.png") + controls.patreon.tooltip = "Help support the development of Path of Building by pledging a monthly donation!" + controls.verLabel = common.New("LabelControl", {"TOPLEFT",nil,"TOPLEFT"}, 10, 82, 0, 18, "^7Version history:") + controls.changelog = common.New("TextListControl", nil, 0, 100, 630, 290, nil, changeList) + self:OpenPopup(650, 400, "About", controls) +end + function main:DrawBackground(viewPort) SetDrawLayer(nil, -100) SetDrawColor(0.5, 0.5, 0.5) diff --git a/Modules/ModParser.lua b/Modules/ModParser.lua index fca593e6..301d4041 100644 --- a/Modules/ModParser.lua +++ b/Modules/ModParser.lua @@ -503,6 +503,7 @@ local modTagList = { ["while ignited"] = { tag = { type = "Condition", var = "Ignited" } }, ["while frozen"] = { tag = { type = "Condition", var = "Frozen" } }, ["while shocked"] = { tag = { type = "Condition", var = "Shocked" } }, + ["while not ignited, frozen or shocked"] = { tag = { type = "Condition", varList = { "Ignited", "Frozen", "Shocked" }, neg = true } }, ["if you have hit recently"] = { tag = { type = "Condition", var = "HitRecently" } }, ["if you've crit recently"] = { tag = { type = "Condition", var = "CritRecently" } }, ["if you've dealt a critical strike recently"] = { tag = { type = "Condition", var = "CritRecently" } }, @@ -561,6 +562,7 @@ local modTagList = { ["against enemies affected by elemental status ailments"] = { tag = { type = "EnemyCondition", varList = {"Frozen","Chilled","Shocked","Ignited"} }, flags = ModFlag.Hit }, ["against enemies that are affected by elemental status ailments"] = { tag = { type = "EnemyCondition", varList = {"Frozen","Chilled","Shocked","Ignited"} }, flags = ModFlag.Hit }, ["against enemies that are affected by no elemental status ailments"] = { tagList = { { type = "EnemyCondition", varList = {"Frozen","Chilled","Shocked","Ignited"}, neg = true }, { type = "Condition", var = "Effective" } }, flags = ModFlag.Hit }, + ["per freeze, shock and ignite on enemy"] = { tag = { type = "Multiplier", var = "FreezeShockIgniteOnEnemy" }, flags = ModFlag.Hit }, } local mod = modLib.createMod @@ -705,6 +707,7 @@ local specialModList = { ["socketed gems fire an additional projectile"] = { mod("ProjectileCount", "BASE", 1, { type = "SocketedIn" }) }, ["socketed gems fire (%d+) additional projectiles"] = function(num) return { mod("ProjectileCount", "BASE", num, { type = "SocketedIn" }) } end, ["socketed gems reserve no mana"] = { mod("ManaReserved", "MORE", -100, { type = "SocketedIn" }) }, + ["socketed skill gems get a (%d+)%% mana multiplier"] = function(num) return { mod("ExtraSkillMod", "LIST", { mod = mod("ManaCost", "MORE", num - 100) }, { type = "SocketedIn" }) } end, ["socketed gems have blood magic"] = { flag("SkillBloodMagic", { type = "SocketedIn" }) }, ["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, @@ -1091,11 +1094,15 @@ local jewelFuncs = { ["With at least 40 Intelligence in Radius, Frostbolt fires 2 additional Projectiles"] = getThreshold("Int", "ProjectileCount", "BASE", 2, { type = "SkillName", skillName = "Frostbolt" }), ["With at least 40 Intelligence in Radius, Magma Orb fires an additional Projectile"] = getThreshold("Int", "ProjectileCount", "BASE", 1, { type = "SkillName", skillName = "Magma Orb" }), ["With at least 40 Dexterity in Radius, Shrapnel Shot has 25% increased Area of Effect"] = getThreshold("Dex", "AreaOfEffect", "INC", 25, { type = "SkillName", skillName = "Shrapnel Shot" }), + ["With at least 40 Dexterity in Radius, Shrapnel Shot's cone has a 50% chance to deal Double Damage"] = getThreshold("Dex", "DoubleDamageChance", "BASE", 50, { type = "SkillName", skillName = "Shrapnel Shot" }, { type = "SkillPart", skillPart = 2 }), ["With at least 40 Intelligence in Radius, Freezing Pulse fires 2 additional Projectiles"] = getThreshold("Int", "ProjectileCount", "BASE", 2, { type = "SkillName", skillName = "Freezing Pulse" }), ["With at least 40 Dexterity in Radius, Ethereal Knives fires 10 additional Projectiles"] = getThreshold("Dex", "ProjectileCount", "BASE", 10, { type = "SkillName", skillName = "Ethereal Knives" }), ["With at least 40 Strength in Radius, Molten Strike fires 2 additional Projectiles"] = getThreshold("Str", "ProjectileCount", "BASE", 2, { type = "SkillName", skillName = "Molten Strike" }), ["With at least 40 Strength in Radius, Molten Strike has 25% increased Area of Effect"] = getThreshold("Str", "AreaOfEffect", "INC", 25, { type = "SkillName", skillName = "Molten Strike" }), ["With at least 40 Strength in Radius, 25% of Glacial Hammer Physical Damage converted to Cold Damage"] = getThreshold("Str", "PhysicalDamageConvertToCold", "BASE", 25, { type = "SkillName", skillName = "Glacial Hammer" }), + ["With at least 40 Strength in Radius, Heavy Strike has a 20% chance to deal Double Damage"] = getThreshold("Str", "DoubleDamageChance", "BASE", 20, { type = "SkillName", skillName = "Heavy Strike" }), + ["With at least 40 Strength in Radius, Heavy Strike has a 20% chance to deal Double Damage."] = getThreshold("Str", "DoubleDamageChance", "BASE", 20, { type = "SkillName", skillName = "Heavy Strike" }), + ["With at least 40 Dexterity in Radius, Dual Strike has a 20% chance to deal Double Damage with the Main-Hand Weapon"] = getThreshold("Dex", "DoubleDamageChance", "BASE", 20, { type = "SkillName", skillName = "Dual Strike" }, { type = "Condition", var = "MainHandAttack" }), ["With at least 40 Intelligence in Radius, Raised Zombies' Slam Attack has 100% increased Cooldown Recovery Speed"] = getThreshold("Int", "MinionModifier", "LIST", { mod = mod("CooldownRecovery", "INC", 100, { type = "SkillId", skillId = "ZombieSlam" }) }), ["With at least 40 Intelligence in Radius, Raised Zombies' Slam Attack deals 30% increased Damage"] = getThreshold("Int", "MinionModifier", "LIST", { mod = mod("Damage", "INC", 30, { type = "SkillId", skillId = "ZombieSlam" }) }), --[""] = getThreshold("", "", "", , { type = "SkillName", skillName = "" }), diff --git a/README.md b/README.md index fcc73ebf..495a2938 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Welcome to Path of Building, an offline build planner for Path of Exile! * Calculate your skill DPS, damage over time, life/mana/ES totals and much more! * Can factor in auras, buffs, charges, curses, monster resistances and more, to estimate your effective DPS * Also calculates life/mana reservations - * Shows a summary of character stats in the side bar, as well as a detailed calcs breakdown tab which can show you how the stats were derived + * Shows a summary of character stats in the side bar, as well as a detailed calculations breakdown tab which can show you how the stats were derived * Supports most skills, support gems, passives and item modifiers * Throughout the program, supported modifiers will show in blue and unsupported ones in red * Full support for minions @@ -27,6 +27,9 @@ Welcome to Path of Building, an offline build planner for Path of Exile! * Contains a searchable database of all uniques that are currently in game (and some that aren't yet!) * You can choose the modifier rolls when you add a unique to your build * Includes all league-specific items and legacy variants + * Features a basic item crafting system: + * You can select from any of the game's base item types + * For Flasks and Jewels you can select modifiers from lists; for other item types modifiers must be added manually at present * Also contains a database of rare item templates: * Allows you to create rare items for your build to approximate the gear you will be using * Choose which modifiers appear on each item, and the rolls for each modifier, to suit your needs @@ -41,12 +44,29 @@ Welcome to Path of Building, an offline build planner for Path of Exile! ## Download Head over to the [Releases](https://github.com/Openarl/PathOfBuilding/releases) page to download the installer. +## Donate +If you'd like to help support the development of Path of Building, I have a [Patreon page](https://www.patreon.com/openarl). + ## Screenshots ![ss1](https://cloud.githubusercontent.com/assets/19189971/18089779/f0fe23fa-6f04-11e6-8ed7-ff7d5b9f867a.png) ![ss2](https://cloud.githubusercontent.com/assets/19189971/18089778/f0f923f0-6f04-11e6-89c2-b2c1410d3583.png) ![ss3](https://cloud.githubusercontent.com/assets/19189971/18089780/f0ff234a-6f04-11e6-8c88-6193fe59a5c4.png) ## Changelog +### 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 + * Skills in the inactive weapon set are automatically disabled + * Switching weapon sets will automatically update the main skill selection if the current main skill is socketed in the + set being deactivated and there is a skill socketed in the set being activated + * Importing character items will now import both weapon sets + * Added support for "X% chance to deal Double Damage" modifiers + * The comparison tooltip for passive trees now displays the number of refund points needed to switch to that tree + * Added an option to the Configuration tab for "# of Freeze/Shock/Ignite on Enemy" (for The Taming) + * Fixed several anomalies in the handling of duplicate support gems +Also, for those interested in supporting the development of the program I now have a Patreon page. +You can find the link in the About window. + ### 1.4.9 - 2017/05/08 * AoE Radius and Weapon Range are now calculated and displayed in the "Skill type-specific Stats" section of the Calcs tab * The breakdowns for those calculations feature a visual display of the area size diff --git a/changelog.txt b/changelog.txt index eff151e6..388809d8 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,16 @@ +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 + * Skills in the inactive weapon set are automatically disabled + * Switching weapon sets will automatically update the main skill selection if the current main skill is socketed in the + set being deactivated and there is a skill socketed in the set being activated + * Importing character items will now import both weapon sets + * Added support for "X% chance to deal Double Damage" modifiers + * The comparison tooltip for passive trees now displays the number of refund points needed to switch to that tree + * Added an option to the Configuration tab for "# of Freeze/Shock/Ignite on Enemy" (for The Taming) + * Fixed several anomalies in the handling of duplicate support gems +Also, for those interested in supporting the development of the program I now have a Patreon page. +You can find the link in the About window. VERSION[1.4.9][2017/05/08] * AoE Radius and Weapon Range are now calculated and displayed in the "Skill type-specific Stats" section of the Calcs tab * The breakdowns for those calculations feature a visual display of the area size diff --git a/manifest.xml b/manifest.xml index 95dafb7c..a66a5039 100644 --- a/manifest.xml +++ b/manifest.xml @@ -1,30 +1,30 @@ - + - + - - + + - + - - + + - + @@ -37,30 +37,31 @@ - - + + - + - + - + - + - + - - + + + @@ -111,7 +112,7 @@ - +