Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
474 changes: 474 additions & 0 deletions spec/System/TestPassiveSpec_spec.lua

Large diffs are not rendered by default.

19 changes: 14 additions & 5 deletions src/Classes/ItemsTab.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2254,6 +2254,9 @@ function ItemsTabClass:IsItemValidForSlot(item, slotName, itemSet, flagState)
local node = self.build.spec.tree.nodes[tonumber(slotId)] or self.build.spec.nodes[tonumber(slotId)]
if not node or item.type ~= "Jewel" then
return false
elseif node.sinister and (item.rarity == "UNIQUE" or item.rarity == "RELIC") then
-- Sinister Jewel Sockets can only accept non-unique jewels
return false
elseif node.containJewelSocket then
if item.rarity == "UNIQUE" or item.rarity == "RELIC" or (item.base and item.base.subType ~= nil) then
-- Lich socket can only accept basic non-unique jewels
Expand Down Expand Up @@ -3635,12 +3638,18 @@ function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode, maxWidth)
tooltip:AddLine(fontSizeBig, formattedModLine, "FONTIN SC", bg)
end

-- Show mods from granted Notables
-- Show mods from granted passives
if modLine.modList[1] and modLine.modList[1].name == "GrantedPassive" then
local node = self.build.spec.tree.notableMap[modLine.modList[1].value]
if node then
for _, stat in ipairs(node.sd) do
tooltip:AddLine(fontSizeBig, "^x7F7F7F"..stat, "FONTIN SC")
for _, node in ipairs(self.build.spec:ResolveGrantedPassiveNodes(modLine.modList[1].value)) do
local displayed = false
if node.sd then
for _, stat in ipairs(node.sd) do
tooltip:AddLine(fontSizeBig, "^x7F7F7F"..stat, "FONTIN SC")
displayed = true
end
end
if not displayed and node.isJewelSocket then
tooltip:AddLine(fontSizeBig, "^x7F7F7F"..(node.name or "Jewel Socket"), "FONTIN SC")
end
end
-- Add separator only for anoints
Expand Down
183 changes: 180 additions & 3 deletions src/Classes/PassiveSpec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,10 @@ end
function PassiveSpecClass:Save(xml)
local allocNodeIdList = { }
local weaponSets = {}
for nodeId in pairs(self.allocNodes) do
t_insert(allocNodeIdList, nodeId)
for nodeId, node in pairs(self.allocNodes) do
if not (node.isGrantedPassive and node.isFreeAllocate) then
t_insert(allocNodeIdList, nodeId)
end
if self.nodes[nodeId].allocMode and self.nodes[nodeId].allocMode ~= 0 then
local weaponSet = self.nodes[nodeId].allocMode
if not weaponSets[weaponSet] then
Expand Down Expand Up @@ -1058,6 +1060,178 @@ function PassiveSpecClass:GetJewel(itemId)
return item
end

local function normalisePassiveName(name)
return type(name) == "string" and name:lower():gsub("^%s+", ""):gsub("%s+$", "") or nil
end

local voicesSinisterSocketAliases = {
"voices_jewel_slot1",
"voices_jewel_slot2",
"voices_jewel_slot3__",
"voices_jewel_slot4",
"voices_jewel_slot5",
}

function PassiveSpecClass:ResolveGrantedPassiveNodes(passive)
local out = { }
if type(passive) == "table" then
if passive.type == "SinisterJewelSockets" then
local byAlias = { }
for _, node in pairs(self.tree.sockets) do
if node.sinister and node.aliasPassiveSocket then
byAlias[node.aliasPassiveSocket] = node
end
end
for i = 1, m_min(passive.count or 0, #voicesSinisterSocketAliases) do
local node = byAlias[voicesSinisterSocketAliases[i]]
if node then
t_insert(out, self.nodes[node.id] or node)
end
end
end
return out
end

local passiveName = normalisePassiveName(passive)
if not passiveName then
return out
end

local notable = self.tree.notableMap[passiveName]
if notable then
t_insert(out, self.nodes[notable.id] or notable)
return out
end

local node = self.tree.keystoneMap[passiveName]
if not node then
for _, socket in pairs(self.tree.sockets) do
if normalisePassiveName(socket.dn) == passiveName then
node = socket
break
end
end
end
if node then
t_insert(out, self.nodes[node.id] or node)
end
return out
end

local function getItemForGrantedPassiveSlot(spec, itemsTab, slot, allocNodes, override, activeWeaponSet, nodesModsList)
local slotName = slot.slotName
if slot.nodeId then
if not allocNodes[slot.nodeId] then
return
end
if slotName == override.repSlotName then
return override.repItem
end
local itemId = spec.jewels[slot.nodeId]
if (not itemId or itemId == 0) and itemsTab.sockets[slot.nodeId] then
itemId = itemsTab.sockets[slot.nodeId].selItemId
end
return itemsTab.items[itemId]
end

if slot.weaponSet and slot.weaponSet ~= activeWeaponSet then
return
end
if slotName == "Ring 3" and not nodesModsList:Flag(nil, "AdditionalRingSlot") then
return
end
if slotName == override.repSlotName then
return override.repItem
end
return itemsTab.items[slot.selItemId]
end

function PassiveSpecClass:SetGrantedPassiveNodes(grantedNodeMap)
local changed = false
grantedNodeMap = grantedNodeMap or { }

for nodeId, node in pairs(self.allocNodes) do
if node.isGrantedPassive and node.isFreeAllocate and not grantedNodeMap[nodeId] then
node.alloc = false
node.isGrantedPassive = nil
node.isFreeAllocate = nil
self.allocNodes[nodeId] = nil
changed = true
end
end

for nodeId, node in pairs(grantedNodeMap) do
local specNode = self.nodes[nodeId] or node
if not self.allocNodes[nodeId] then
specNode.alloc = true
specNode.allocMode = 0
specNode.isGrantedPassive = true
specNode.isFreeAllocate = true
self.allocNodes[nodeId] = specNode
changed = true
end
end

if changed then
self:BuildAllDependsAndPaths()
end
return changed
end

function PassiveSpecClass:CollectGrantedPassiveNodesFromItems(itemsTab, baseAllocNodes, ignoreJewelLimits, override, nodesModsList)
override = override or { }
local granted = { }
local allocNodes = { }
for nodeId, node in pairs(baseAllocNodes or self.allocNodes) do
if not (node.isGrantedPassive and node.isFreeAllocate) then
allocNodes[nodeId] = node
end
end
local activeWeaponSet = itemsTab.activeItemSet.useSecondWeaponSet and 2 or 1
local jewelLimits = { }
local changed = true
local safety = 0

while changed and safety < 8 do
changed = false
safety = safety + 1

for _, slot in pairs(itemsTab.orderedSlots) do
local item = getItemForGrantedPassiveSlot(self, itemsTab, slot, allocNodes, override, activeWeaponSet, nodesModsList)

if not item or not item.modList then
goto continue
end
if slot.nodeId and not itemsTab:IsItemValidForSlot(item, slot.slotName) then
goto continue
end
if slot.nodeId and item.limit and not ignoreJewelLimits then
local limitKey = item.base.subType == "Timeless" and "Historic" or item.title
if jewelLimits[limitKey] and jewelLimits[limitKey] >= item.limit then
goto continue
end
jewelLimits[limitKey] = (jewelLimits[limitKey] or 0) + 1
end
for _, mod in ipairs(item.modList) do
if mod.name == "GrantedPassive" then
local passive = mod.value
for _, node in ipairs(self:ResolveGrantedPassiveNodes(passive)) do
if node.type == "Socket" and not granted[node.id] then
local specNode = self.nodes[node.id] or node
granted[node.id] = specNode
allocNodes[node.id] = specNode
changed = true
end
end
end
end
::continue::
end
end

return granted
end

-- Perform a breadth-first search of the tree, starting from this node, and determine if it is the closest node to any other nodes
function PassiveSpecClass:BuildPathFromNode(root)
root.pathDist = 0
Expand Down Expand Up @@ -1588,7 +1762,10 @@ function PassiveSpecClass:BuildAllDependsAndPaths()
for id, node in pairs(self.allocNodes) do
node.visited = true
node.connectedToStart = false
local anyStartFound = (node.type == "ClassStart" or node.type == "AscendClassStart")
local anyStartFound = (node.type == "ClassStart" or node.type == "AscendClassStart" or node.isFreeAllocate)
if node.isFreeAllocate then
node.connectedToStart = true
end
for _, other in ipairs(node.linked) do
local otherAlloc = other.alloc or alternateClassStartNodes[other.id]
if otherAlloc and self:CanPathThroughAllocMode(node.allocMode or 0, other) and not isValueInArray(node.depends, other) then
Expand Down
6 changes: 3 additions & 3 deletions src/Data/ModCache.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3862,9 +3862,9 @@ c["Allies in your Presence have 50% increased Critical Hit Chance"]={{[1]={flags
c["Allies in your Presence have 6% increased Attack Speed"]={{[1]={flags=0,keywordFlags=0,name="ExtraAura",type="LIST",value={mod={flags=1,keywordFlags=0,name="Speed",type="INC",value=6},onlyAllies=true}}},nil}
c["Allies in your Presence have 6% increased Cast Speed"]={{[1]={flags=0,keywordFlags=0,name="ExtraAura",type="LIST",value={mod={flags=16,keywordFlags=0,name="Speed",type="INC",value=6},onlyAllies=true}}},nil}
c["Allies in your Presence have Block Chance equal to yours"]={nil,"Block Chance equal to yours "}
c["Allocates 2 Sinister Jewel sockets"]={{[1]={flags=0,keywordFlags=0,name="GrantedPassive",type="LIST",value="2 sinister jewel sockets"}},nil}
c["Allocates 3 Sinister Jewel sockets"]={{[1]={flags=0,keywordFlags=0,name="GrantedPassive",type="LIST",value="3 sinister jewel sockets"}},nil}
c["Allocates 4 Sinister Jewel sockets"]={{[1]={flags=0,keywordFlags=0,name="GrantedPassive",type="LIST",value="4 sinister jewel sockets"}},nil}
c["Allocates 2 Sinister Jewel sockets"]={{[1]={flags=0,keywordFlags=0,name="GrantedPassive",type="LIST",value={count=2,type="SinisterJewelSockets"}}},nil}
c["Allocates 3 Sinister Jewel sockets"]={{[1]={flags=0,keywordFlags=0,name="GrantedPassive",type="LIST",value={count=3,type="SinisterJewelSockets"}}},nil}
c["Allocates 4 Sinister Jewel sockets"]={{[1]={flags=0,keywordFlags=0,name="GrantedPassive",type="LIST",value={count=4,type="SinisterJewelSockets"}}},nil}
c["Allocates Abasement"]={{[1]={flags=0,keywordFlags=0,name="GrantedPassive",type="LIST",value="abasement"}},nil}
c["Allocates Acceleration"]={{[1]={flags=0,keywordFlags=0,name="GrantedPassive",type="LIST",value="acceleration"}},nil}
c["Allocates Adaptable Assault"]={{[1]={flags=0,keywordFlags=0,name="GrantedPassive",type="LIST",value="adaptable assault"}},nil}
Expand Down
1 change: 1 addition & 0 deletions src/Export/Scripts/passivetree.lua
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,7 @@ for i, group in ipairs(psg.groups) do
-- Sinister jewel support
if passiveRow.JewelSocket and passiveRow.AnointOnly then
node["aliasPassiveSocket"] = passiveRow.Id
node["sinister"] = true
node["noRadius"] = true
end

Expand Down
43 changes: 31 additions & 12 deletions src/Modules/CalcSetup.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ local band = AND64

local tempTable1 = { }

local function addGrantedPassiveNode(env, node)
env.allocNodes[node.id] = env.spec.nodes[node.id] or node -- use the conquered node data, if available
env.grantedPassives[node.id] = true
env.extraRadiusNodeList[node.id] = nil
end

-- Initialise modifier database with stats and conditions common to all actors
function calcs.initModDB(env, modDB)
modDB:NewMod("FireResistMax", "BASE", data.characterConstants["base_maximum_all_resistances_%"], "Base")
Expand Down Expand Up @@ -771,6 +777,11 @@ function calcs.initEnv(build, mode, override, specEnv)
nodes = copyTable(env.spec.allocNodes, true)
end
env.allocNodes = nodes
for nodeId, node in pairs(env.allocNodes) do
if node.isGrantedPassive and node.isFreeAllocate then
env.allocNodes[nodeId] = nil
end
end
end

local nodesModsList = calcs.buildModListForNodeList(env, env.allocNodes, true, true)
Expand Down Expand Up @@ -813,6 +824,16 @@ function calcs.initEnv(build, mode, override, specEnv)

-- Build and merge item modifiers, and create list of radius jewels
if not accelerate.requirementsItems then
local grantedNodes = env.spec:CollectGrantedPassiveNodesFromItems(build.itemsTab, env.allocNodes, env.configInput.ignoreJewelLimits, override, nodesModsList)
if mode == "MAIN" then
if build.spec:SetGrantedPassiveNodes(grantedNodes) then
build.itemsTab:UpdateSockets()
end
end
for _, node in pairs(grantedNodes) do
addGrantedPassiveNode(env, node)
end

local items = {}
local jewelLimits = {}
local giantsBlood = weaponFlagState.giantsBlood
Expand Down Expand Up @@ -861,10 +882,13 @@ function calcs.initEnv(build, mode, override, specEnv)
if slot.weaponSet == 2 and build.itemsTab.activeItemSet.useSecondWeaponSet then
slotName = slotName:gsub(" Swap","")
end
local node = slot.nodeId and env.spec.nodes[slot.nodeId]
if slot.nodeId then
-- Slot is a jewel socket, check if socket is allocated
if not env.allocNodes[slot.nodeId] then
goto continue
elseif item and not build.itemsTab:IsItemValidForSlot(item, slot.slotName) then
goto continue
elseif item then
if item.jewelData then
item.jewelData.limitDisabled = nil
Expand All @@ -890,7 +914,7 @@ function calcs.initEnv(build, mode, override, specEnv)
jewelLimits[limitKey] = (jewelLimits[limitKey] or 0) + 1
end
end
if item and ( item.jewelRadiusIndex or (override and override.extraJewelFuncs and #override.extraJewelFuncs > 0) ) then
if item and not (node and node.sinister) and ( item.jewelRadiusIndex or (override and override.extraJewelFuncs and #override.extraJewelFuncs > 0) ) then
-- Jewel has a radius, add it to the list
local funcList = (item.jewelData and item.jewelData.funcList) or { { type = "Self", func = function(node, out, data)
-- Default function just tallies all stats in radius
Expand All @@ -901,7 +925,6 @@ function calcs.initEnv(build, mode, override, specEnv)
end
end } }
for _, func in ipairs(funcList) do
local node = env.spec.nodes[slot.nodeId]
t_insert(env.radiusJewelList, {
nodes = node.nodesInRadius and node.nodesInRadius[item.jewelRadiusIndex] or { },
func = func.func,
Expand All @@ -923,7 +946,6 @@ function calcs.initEnv(build, mode, override, specEnv)
end
end
for _, funcData in ipairs(override and override.extraJewelFuncs and override.extraJewelFuncs:List({item = item}, "ExtraJewelFunc") or {}) do
local node = env.spec.nodes[slot.nodeId]
local radius
for index, data in pairs(data.jewelRadius) do
if funcData.radius == data.label then
Expand Down Expand Up @@ -1043,16 +1065,14 @@ function calcs.initEnv(build, mode, override, specEnv)
scale = parentItem.socketedJewelEffectModifier
end
end
if slot.nodeId and item and item.type == "Jewel" and item.jewelData and item.jewelData.jewelIncEffectFromClassStart then
local node = env.spec.nodes[slot.nodeId]
if slot.nodeId and item and item.type == "Jewel" and item.jewelData and item.jewelData.jewelIncEffectFromClassStart and not (node and node.sinister) then
if node and node.distanceToClassStart then
scale = scale + node.distanceToClassStart * (item.jewelData.jewelIncEffectFromClassStart / 100)
end
end

local addSourceSlotNum = false
if slot.nodeId and item and item.type == "Jewel" then
local node = env.spec.nodes[slot.nodeId]
if node and node.containJewelSocket then
addSourceSlotNum = true
local inc = node.modList:Sum("INC", nil, "SocketedJewelEffect")
Expand Down Expand Up @@ -1261,7 +1281,7 @@ function calcs.initEnv(build, mode, override, specEnv)
env.itemModDB:ScaleAddMod(mod, scale)
end
end
elseif env.modDB.multipliers["Corrupted" .. item.rarity:gsub("(%a)(%u*)", function(a, b) return a..string.lower(b) end) .. "JewelEffect"] and item.type == "Jewel" and item.corrupted and slot.nodeId and item.base.subType ~= "Charm" and not env.spec.nodes[slot.nodeId].containJewelSocket then
elseif env.modDB.multipliers["Corrupted" .. item.rarity:gsub("(%a)(%u*)", function(a, b) return a..string.lower(b) end) .. "JewelEffect"] and item.type == "Jewel" and item.corrupted and slot.nodeId and item.base.subType ~= "Charm" and node and not node.containJewelSocket and not node.sinister then
scale = scale + env.modDB.multipliers["Corrupted" .. item.rarity:gsub("(%a)(%u*)", function(a, b) return a..string.lower(b) end) .. "JewelEffect"]
local combinedList = new("ModList")
for _, mod in ipairs(srcList) do
Expand Down Expand Up @@ -1327,11 +1347,10 @@ function calcs.initEnv(build, mode, override, specEnv)
-- Add granted passives (e.g., amulet anoints)
if not accelerate.nodeAlloc then
for _, passive in pairs(env.modDB:List(nil, "GrantedPassive")) do
local node = env.spec.tree.notableMap[passive]
if node and (not override.removeNodes or not override.removeNodes[node.id]) then
env.allocNodes[node.id] = env.spec.nodes[node.id] or node -- use the conquered node data, if available
env.grantedPassives[node.id] = true
env.extraRadiusNodeList[node.id] = nil
for _, node in ipairs(env.spec:ResolveGrantedPassiveNodes(passive)) do
if node and (not override.removeNodes or not override.removeNodes[node.id]) then
addGrantedPassiveNode(env, node)
end
end
end
end
Expand Down
1 change: 1 addition & 0 deletions src/Modules/ModParser.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5848,6 +5848,7 @@ local specialModList = {
mod("CritChanceCap", "OVERRIDE", num),
} end,
["allocates (.+) if you have the matching modifiers? on forbidden (.+)"] = function(_, ascendancy, side) return { mod("GrantedAscendancyNode", "LIST", { side = side, name = ascendancy }) } end,
["allocates (%d+) sinister jewel sockets?"] = function(num) return { mod("GrantedPassive", "LIST", { type = "SinisterJewelSockets", count = num }) } end,
["allocates (.+)"] = function(_, passive) return { mod("GrantedPassive", "LIST", passive) } end,
["battlemage"] = { flag("Battlemage"), mod("MainHandWeaponDamageAppliesToSpells", "MAX", 100) },
["transfiguration of body"] = { flag("TransfigurationOfBody") },
Expand Down
2 changes: 1 addition & 1 deletion src/TreeData/0_5/tree.json

Large diffs are not rendered by default.

Loading
Loading