diff --git a/spec/System/TestImportTab_spec.lua b/spec/System/TestImportTab_spec.lua index 94270f95a..8c367398c 100644 --- a/spec/System/TestImportTab_spec.lua +++ b/spec/System/TestImportTab_spec.lua @@ -43,6 +43,59 @@ describe("ImportTab", function() assert.are.equals(1, #importTab.controls.charSelect.list) assert.True(importTab.controls.charSelect.list[1].detail:match("Future Ascendancy") ~= nil) end) + + it("imports Split Personality alternate class start from character JSON", function() + local spec = build.spec + local socketNode = spec.nodes[60735] + local rangerStartPassive = spec.nodes[56651] + assert.is_not_nil(socketNode) + assert.is_not_nil(rangerStartPassive) + + local hashes = { socketNode.id, rangerStartPassive.id } + for _, pathNode in ipairs(socketNode.path or { }) do + table.insert(hashes, pathNode.id) + end + + local rangerStart = spec.nodes[spec.tree.classes[spec.tree.classNameMap.Ranger].startNodeId] + local importPayload = { + name = "Split Import Test", + class = "Witch2", + league = "Test", + level = 90, + jewels = { + { + id = "split-personality-test", + frameType = 3, + name = "Split Personality", + typeLine = "Ruby", + inventoryId = "PassiveJewels", + x = 4, + ilvl = 84, + properties = { }, + explicitMods = { + "Can Allocate Passive Skills from the Ranger's starting point", + }, + }, + }, + passives = { + hashes = hashes, + specialisations = { }, + skill_overrides = { }, + jewel_data = { }, + quest_stats = { }, + }, + } + + build.importTab:ImportPassiveTreeAndJewels(importPayload) + + local importedSpec = build.spec + local importedJewel = build.itemsTab.items[importedSpec.jewels[socketNode.id]] + assert.are.equals("Ranger", importedJewel.jewelData.alternateClassStart) + + assert.are.equals(0, importedSpec.nodes[rangerStart.id].pathDist) + assert.True(importedSpec.nodes[rangerStartPassive.id].alloc) + assert.True(importedSpec.nodes[rangerStartPassive.id].connectedToStart) + end) end) describe("ImportTab quest reward import", function() diff --git a/src/Classes/Item.lua b/src/Classes/Item.lua index 897a1ae8e..75fe9bf89 100644 --- a/src/Classes/Item.lua +++ b/src/Classes/Item.lua @@ -1895,6 +1895,9 @@ function ItemClass:BuildModListForSlotNum(baseList, slotNum) for _, value in ipairs(modList:List(nil, "JewelData")) do jewelData[value.key] = value.value end + for _, className in ipairs(modList:List(nil, "AlternateClassStart")) do + jewelData.alternateClassStart = className + end if modList:List(nil, "FromNothingKeystones") then jewelData.fromNothingKeystones = { } for _, value in ipairs(modList:List(nil, "FromNothingKeystones")) do diff --git a/src/Classes/PassiveSpec.lua b/src/Classes/PassiveSpec.lua index 86310d575..81991d5fa 100644 --- a/src/Classes/PassiveSpec.lua +++ b/src/Classes/PassiveSpec.lua @@ -1018,7 +1018,7 @@ end -- Attempt to find a class start node starting from the given node -- Unless noAscent == true it will also look for an ascendancy class start node -function PassiveSpecClass:FindStartFromNode(node, visited, noAscend, allocMode) +function PassiveSpecClass:FindStartFromNode(node, visited, noAscend, allocMode, alternateClassStartNodes) allocMode = allocMode or node.allocMode or 0 -- Mark the current node as visited so we don't go around in circles node.visited = true @@ -1029,9 +1029,10 @@ function PassiveSpecClass:FindStartFromNode(node, visited, noAscend, allocMode) -- - the other node is a start node, or -- - there is a path to a start node through the other node which didn't pass through any nodes which have already been visited local startIndex = #visited + 1 - if other.alloc and self:CanPathThroughAllocMode(allocMode, other) and + local otherAlloc = other.alloc or (alternateClassStartNodes and alternateClassStartNodes[other.id]) + if otherAlloc and self:CanPathThroughAllocMode(allocMode, other) and (other.type == "ClassStart" or other.type == "AscendClassStart" or - (not other.visited and node.type ~= "Mastery" and self:FindStartFromNode(other, visited, noAscend, allocMode)) + (not other.visited and node.type ~= "Mastery" and self:FindStartFromNode(other, visited, noAscend, allocMode, alternateClassStartNodes)) ) then if node.ascendancyName and not other.ascendancyName then -- Pathing out of Ascendant, un-visit the outside nodes @@ -1225,6 +1226,18 @@ function PassiveSpecClass:BuildAllDependsAndPaths() -- First check for mods that affect intuitive leap-like properties of other nodes local processed = { } local intuitiveLeapLikeNodes = self.intuitiveLeapLikeNodes + local alternateClassStartNodes = { } + for socketNodeId, itemId in pairs(self.jewels) do + if self.allocNodes[socketNodeId] then + local item = self:GetJewel(itemId) + local className = item and item.jewelData.alternateClassStart + local classData = className and self.tree.classes[self.tree.classNameMap[className]] + local startNode = classData and self.nodes[classData.startNodeId] + if startNode then + alternateClassStartNodes[startNode.id] = startNode + end + end + end wipeTable(intuitiveLeapLikeNodes) for id, node in pairs(self.allocNodes) do if node.ascendancyName then -- avoid processing potentially replaceable nodes @@ -1577,13 +1590,14 @@ function PassiveSpecClass:BuildAllDependsAndPaths() node.connectedToStart = false local anyStartFound = (node.type == "ClassStart" or node.type == "AscendClassStart") for _, other in ipairs(node.linked) do - if other.alloc and self:CanPathThroughAllocMode(node.allocMode or 0, other) and not isValueInArray(node.depends, other) then + 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 -- The other node is allocated and isn't already dependent on this node, so try and find a path to a start node through it if other.type == "ClassStart" or other.type == "AscendClassStart" then -- Well that was easy! anyStartFound = true node.connectedToStart = true - elseif self:FindStartFromNode(other, visited, nil, node.allocMode or 0) then + elseif self:FindStartFromNode(other, visited, nil, node.allocMode or 0, alternateClassStartNodes) then -- We found a path through the other node, therefore the other node cannot be dependent on this node anyStartFound = true node.connectedToStart = true @@ -1795,6 +1809,9 @@ function PassiveSpecClass:BuildAllDependsAndPaths() end end end + for _, node in pairs(alternateClassStartNodes) do + self:BuildPathFromNode(node) + end end function PassiveSpecClass:ReplaceNode(old, newNode) diff --git a/src/Classes/PassiveTreeView.lua b/src/Classes/PassiveTreeView.lua index 5c9e91301..2728b190e 100644 --- a/src/Classes/PassiveTreeView.lua +++ b/src/Classes/PassiveTreeView.lua @@ -912,8 +912,9 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) local socket, jewel = build.itemsTab:GetSocketAndJewelForNodeID(nodeId) if isAlloc and jewel then - if jewel.rarity == "UNIQUE" and jewel.title ~= "Grand Spectrum" then - overlay = jewel.title + if jewel.rarity == "UNIQUE" then + local hasUniqueJewelArt = tree.ddsMap[jewel.title] or tree.assets[jewel.title] or tree.spriteMap[jewel.title] + overlay = hasUniqueJewelArt and jewel.title or jewel.baseName else overlay = jewel.baseName end diff --git a/src/Data/ModCache.lua b/src/Data/ModCache.lua index b846e2a14..faaa91e91 100644 --- a/src/Data/ModCache.lua +++ b/src/Data/ModCache.lua @@ -4701,10 +4701,8 @@ c["Buffs on you expire 10% slower"]={{[1]={[1]={skillType=5,type="SkillType"},fl c["Bulwark"]={{[1]={flags=0,keywordFlags=0,name="Keystone",type="LIST",value="Bulwark"}},nil} c["Burning Enemies you kill have a 5% chance to Explode, dealing a"]={nil,"Burning Enemies you kill have a 5% chance to Explode, dealing a "} c["Burning Enemies you kill have a 5% chance to Explode, dealing a tenth of their maximum Life as Fire Damage"]={{[1]={[1]={actor="enemy",type="ActorCondition",var="Burning"},flags=0,keywordFlags=0,name="ExplodeMod",type="LIST",value={amount=10,keyOfScaledMod="value",type="Fire",value=5}},[2]={flags=0,keywordFlags=0,name="CanExplode",type="FLAG",value=true}},nil} -c["Can Allocate Passive Skills from the Sorceress's starting point"]={nil,"Can Allocate Passive Skills from the Sorceress's starting point "} -c["Can Allocate Passive Skills from the Sorceress's starting point Grants 4 Passive Skill Point"]={nil,"Can Allocate Passive Skills from the Sorceress's starting point Grants 4 Passive Skill Point "} -c["Can Allocate Passive Skills from the Warrior's starting point"]={nil,"Can Allocate Passive Skills from the Warrior's starting point "} -c["Can Allocate Passive Skills from the Warrior's starting point Grants 4 Passive Skill Point"]={nil,"Can Allocate Passive Skills from the Warrior's starting point Grants 4 Passive Skill Point "} +c["Can Allocate Passive Skills from the Sorceress's starting point"]={{[1]={flags=0,keywordFlags=0,name="AlternateClassStart",type="LIST",value="Sorceress"}},nil} +c["Can Allocate Passive Skills from the Warrior's starting point"]={{[1]={flags=0,keywordFlags=0,name="AlternateClassStart",type="LIST",value="Warrior"}},nil} c["Can Attack as though using a One Handed Mace while both of your hand slots are empty"]={{[1]={flags=0,keywordFlags=0,name="CanAttackAsOneHandMaceUnarmed",type="FLAG",value=true}},nil} c["Can Attack as though using a Quarterstaff while both of your hand slots are empty"]={nil,"Can Attack as though using a Quarterstaff while both of your hand slots are empty "} c["Can Attack as though using a Quarterstaff while both of your hand slots are empty Unarmed Attacks that would use an Equipped Quarterstaff's damage have:"]={nil,"Can Attack as though using a Quarterstaff while both of your hand slots are empty Unarmed Attacks that would use an Equipped Quarterstaff's damage have: "} diff --git a/src/Modules/ModParser.lua b/src/Modules/ModParser.lua index 4dc7443fd..a084db6b0 100644 --- a/src/Modules/ModParser.lua +++ b/src/Modules/ModParser.lua @@ -2727,7 +2727,8 @@ local specialModList = { ["evasion rating from equipped body armour is halved"] = { mod("Evasion", "MORE", -50, { type = "SlotName", slotName = "Body Armour" }) }, -- Ascendant ["grants (%d+) passive skill points?"] = function(num) return { mod("ExtraPoints", "BASE", num) } end, - ["can allocate passives from the %a+'s starting point"] = { }, + ["can allocate passives from the (%a+)'s starting point"] = function(_, className) return { mod("AlternateClassStart", "LIST", firstToUpper(className)) } end, + ["can allocate passive skills from the (%a+)'s starting point"] = function(_, className) return { mod("AlternateClassStart", "LIST", firstToUpper(className)) } end, ["projectiles gain damage as they travel farther, dealing up to (%d+)%% increased damage with hits to targets"] = function(num) return { mod("Damage", "INC", num, nil, bor(ModFlag.Hit, ModFlag.Projectile), { type = "DistanceRamp", ramp = { {35,0},{70,1} } }) } end, ["(%d+)%% chance to gain elusive on kill"] = { flag("Condition:CanBeElusive"),