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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ manifest-updated.xml
# GGPK Export
src/Export/ggpk/metadata/
src/Export/ggpk/data/
src/Export/ggpk/art/
src/Export/ggpk/*.exe
src/Export/ggpk/*.dll

Expand Down
495 changes: 495 additions & 0 deletions spec/System/TestAbyssTimelessJewel_spec.lua

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions src/Classes/ItemsTab.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3990,13 +3990,14 @@ function ItemsTabClass:FormatItemSource(text)
:gsub("prophecy{([^}]+)}",colorCodes.PROPHECY.."%1"..colorCodes.SOURCE)
end

local function itemChangesPassiveTreeRadius(item)
return not not (item and item.type == "Jewel" and item.jewelData and item.jewelRadiusIndex
and (item.jewelData.conqueredBy or item.jewelData.intuitiveLeapLike or item.jewelData.impossibleEscapeKeystone))
local function itemChangesPassiveTree(item)
return not not (item and item.type == "Jewel" and item.jewelData
and (item.jewelData.conqueredBy or item.jewelRadiusIndex
and (item.jewelData.intuitiveLeapLike or item.jewelData.impossibleEscapeKeystone)))
end

-- Radius jewels can change conquered nodes and orphaned allocations, so compare
-- against a rebuilt spec instead of approximating the diff with removeNodes.
-- These jewels can replace passive nodes or disconnect allocated passives, so
-- rebuild the passive tree before comparing their stats.
-- Keep this list in sync with PassiveSpec's constructor, Init, and Select*
-- methods; omitted fields fail safe as nil on the comparison spec.
local sharedSpecKeysForJewelComparison = {
Expand Down Expand Up @@ -4804,7 +4805,7 @@ function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode, maxWidth)
local function getReplacedItemAndOutput(compareSlot)
local selItem = self.items[compareSlot.selItemId]
local override = { repSlotName = compareSlot.slotName, repItem = item ~= selItem and item or nil }
if compareSlot.nodeId and (itemChangesPassiveTreeRadius(selItem) or itemChangesPassiveTreeRadius(item)) then
if compareSlot.nodeId and (itemChangesPassiveTree(selItem) or itemChangesPassiveTree(item)) then
override.spec = buildSpecForJewelComparison(self, compareSlot, override.repItem)
end
local output = calcFunc(override)
Expand Down
82 changes: 56 additions & 26 deletions src/Classes/PassiveSpec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -990,7 +990,7 @@ end
-- Only allocated nodes can be traversed
function PassiveSpecClass:GetShortestPathToClassStart(rootId)
local root = self.nodes[rootId]
if not root or not root.alloc or not root.connectedToStart then
if not root or not root.alloc then
return nil
end

Expand Down Expand Up @@ -1096,9 +1096,40 @@ end

-- Rebuilds dependencies and paths for all nodes
function PassiveSpecClass:BuildAllDependsAndPaths()
local timelessJewelTypeByConqueror = {
vaal = 1,
karui = 2,
maraketh = 3,
templar = 4,
eternal = 5,
kalguur = 6,
abyss_murderous = 7,
abyss_searching = 8,
abyss_hypnotic = 9,
abyss_ghastly = 10,
abyss_special = 11,
}
-- This table will keep track of which nodes have been visited during each path-finding attempt
local visited = { }
local attributes = { "Dexterity", "Intelligence", "Strength" }
-- Read Abyss changes before resetting the nodes. Zorath needs the currently
-- allocated path from its socket to the class starting node.
local abyssConquests = { }
for socketId, itemId in pairs(self.jewels) do
local item = self.build.itemsTab.items[itemId]
local conqueredBy = item and item.jewelData and item.jewelData.conqueredBy
local jewelType = conqueredBy and timelessJewelTypeByConqueror[conqueredBy.conqueror.type]
if jewelType and jewelType >= 7 and self.allocNodes[socketId] and not item.jewelData.limitDisabled then
local path = jewelType == 11 and self:GetShortestPathToClassStart(socketId)
for nodeId, modification in pairs(data.readAbyssJewelLUT(conqueredBy.id, socketId, jewelType, path)) do
abyssConquests[nodeId] = {
id = conqueredBy.id,
conqueror = conqueredBy.conqueror,
modification = modification,
}
end
end
end
-- Check all nodes for other nodes which depend on them (i.e. are only connected to the tree through that node)
for id, node in pairs(self.nodes) do
node.depends = wipeTable(node.depends)
Expand All @@ -1109,6 +1140,7 @@ function PassiveSpecClass:BuildAllDependsAndPaths()
if self.tree.nodes[id] then
self:ReplaceNode(node,self.tree.nodes[id])
end
node.conqueredBy = abyssConquests[id]

if node.type ~= "ClassStart" and node.type ~= "Socket" and not node.ascendancyName then
for nodeId, itemId in pairs(self.jewels) do
Expand All @@ -1125,7 +1157,10 @@ function PassiveSpecClass:BuildAllDependsAndPaths()
t_insert(node.intuitiveLeapLikesAffecting, self.nodes[nodeId])
end
if item.jewelData.conqueredBy then
node.conqueredBy = item.jewelData.conqueredBy
local radiusJewelType = timelessJewelTypeByConqueror[item.jewelData.conqueredBy.conqueror.type]
if not radiusJewelType or radiusJewelType < 7 then
node.conqueredBy = item.jewelData.conqueredBy
end
end
end
end
Expand Down Expand Up @@ -1159,29 +1194,7 @@ function PassiveSpecClass:BuildAllDependsAndPaths()
local legionNodes = self.tree.legion.nodes
local legionAdditions = self.tree.legion.additions

-- FIXME - continue implementing
local jewelType = 5
if conqueredBy.conqueror.type == "vaal" then
jewelType = 1
elseif conqueredBy.conqueror.type == "karui" then
jewelType = 2
elseif conqueredBy.conqueror.type == "maraketh" then
jewelType = 3
elseif conqueredBy.conqueror.type == "templar" then
jewelType = 4
elseif conqueredBy.conqueror.type == "kalguur" then
jewelType = 6
elseif conqueredBy.conqueror.type == "abyssTecrod" then
jewelType = 7
elseif conqueredBy.conqueror.type == "abyssUlaman" then
jewelType = 8
elseif conqueredBy.conqueror.type == "abyssKurgal" then
jewelType = 9
elseif conqueredBy.conqueror.type == "abyssAmanamu" then
jewelType = 10
elseif conqueredBy.conqueror.type == "abyssZorath" then
jewelType = 11
end
local jewelType = timelessJewelTypeByConqueror[conqueredBy.conqueror.type] or 5
local seed = conqueredBy.id
if jewelType == 5 then
seed = seed / 20
Expand All @@ -1206,7 +1219,24 @@ function PassiveSpecClass:BuildAllDependsAndPaths()
return statToFix -- if it doesn't need to be changed
end

if node.type == "Notable" then
if jewelType >= 7 then
for _, component in ipairs(conqueredBy.modification) do
local changedNode, replacesNode = data.resolveAbyssJewelComponent(component, self.tree.legion)
if changedNode then
if replacesNode then
self:ReplaceNode(node, changedNode)
end
for statIndex, statLine in ipairs(changedNode.sd) do
for statKey, statMod in pairs(changedNode.stats) do
statLine = replaceHelperFunc(statLine, statKey, statMod, component.rolls[statMod.index])
end
self:NodeAdditionOrReplacementFromString(node, (replacesNode and "" or " \n") .. statLine, replacesNode and statIndex == 1)
end
else
ConPrintf("Unhandled Abyss component ID: " .. component.id)
end
end
elseif node.type == "Notable" then
local jewelDataTbl = { }
if seed ~= m_max(m_min(seed, data.timelessJewelSeedMax[jewelType]), data.timelessJewelSeedMin[jewelType]) then
ConPrintf("ERROR: Seed " .. seed .. " is outside of valid range [" .. data.timelessJewelSeedMin[jewelType] .. " - " .. data.timelessJewelSeedMax[jewelType] .. "] for jewel type: " .. data.timelessJewelTypes[jewelType])
Expand Down
50 changes: 39 additions & 11 deletions src/Classes/PassiveTree.lua
Original file line number Diff line number Diff line change
Expand Up @@ -365,31 +365,51 @@ local PassiveTreeClass = newClass("PassiveTree", function(self, treeVersion)

-- Load legion sprite sheets and build sprite map
local legionSprites = LoadModule("TreeData/legion/tree-legion.lua")
for type, data in pairs(legionSprites) do
local maxZoom = data[#data]
local sheet = spriteSheets[maxZoom.filename]
local function loadLegionSheet(data)
local sheet = spriteSheets[data.filename]
if not sheet then
sheet = { }
sheet.handle = NewImageHandle()
sheet.handle:Load("TreeData/legion/"..maxZoom.filename)
sheet.handle:Load("TreeData/legion/"..data.filename)
sheet.width, sheet.height = sheet.handle:ImageSize()
spriteSheets[maxZoom.filename] = sheet
spriteSheets[data.filename] = sheet
end
for name, coords in pairs(maxZoom.coords) do
if not self.spriteMap[name] then
self.spriteMap[name] = { }
end
self.spriteMap[name][type] = {
return sheet
end
for _, data in ipairs(legionSprites.treeAssets or { }) do
local sheet = loadLegionSheet(data)
for name, coords in pairs(data.coords) do
self.assets[name] = {
handle = sheet.handle,
width = coords.w,
height = coords.h,
[1] = coords.x / sheet.width,
[2] = coords.y / sheet.height,
[3] = (coords.x + coords.w) / sheet.width,
[4] = (coords.y + coords.h) / sheet.height
[4] = (coords.y + coords.h) / sheet.height,
}
end
end
for type, data in pairs(legionSprites) do
if type ~= "treeAssets" then
local maxZoom = data[#data]
local sheet = loadLegionSheet(maxZoom)
for name, coords in pairs(maxZoom.coords) do
if not self.spriteMap[name] then
self.spriteMap[name] = { }
end
self.spriteMap[name][type] = {
handle = sheet.handle,
width = coords.w,
height = coords.h,
[1] = coords.x / sheet.width,
[2] = coords.y / sheet.height,
[3] = (coords.x + coords.w) / sheet.width,
[4] = (coords.y + coords.h) / sheet.height
}
end
end
end

local classArt = {
[0] = "centerscion",
Expand Down Expand Up @@ -676,6 +696,14 @@ local PassiveTreeClass = newClass("PassiveTree", function(self, treeVersion)
end
end

-- The game only gives these notables numbered names. Use the manual name for
-- their first stat, or show the full stat description when no name exists yet.
for _, addition in pairs(self.legion.additions) do
if addition.id:match("^abyss_.+_notable_%d+$") and addition.dn:match("^Notable %d+$") then
addition.dn = data.abyssNotableNames[addition.sortedStats[1]] or addition.sd[1]
end
end

-- Build ModList for legion jewels
for _, node in pairs(self.legion.nodes) do
-- Determine node type
Expand Down
37 changes: 36 additions & 1 deletion src/Classes/PassiveTreeView.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ local JEWEL_RADIUS_TINT_PRIMARY_ONLY = { 1, 0, 0, 0.7 }
local JEWEL_RADIUS_TINT_COMPARE_ONLY = { 0, 1, 0, 0.7 }

local gemTooltip = LoadModule("Classes/GemTooltip")

local function isAbyssConquered(node)
local conqueror = node and node.conqueredBy and node.conqueredBy.conqueror
return conqueror and conqueror.type and conqueror.type:match("^abyss_")
end

local PassiveTreeViewClass = newClass("PassiveTreeView", function(self)
self.ring = NewImageHandle()
self.ring:Load("Assets/ring.png", "CLAMP")
Expand Down Expand Up @@ -707,7 +713,22 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents)
setConnectorColor(0.75, 0.75, 0.75)
end
SetDrawColor(unpack(connectorColor))
DrawImageQuad(tree.assets[connector.type..state].handle, unpack(connector.c))
local assetName = connector.type .. state
-- The game uses Abyss connector art only when both connected nodes are conquered.
if isAbyssConquered(node1) and isAbyssConquered(node2) then
assetName = "Abyss" .. assetName
end
local asset = tree.assets[assetName] or tree.assets[connector.type..state]
-- Atlas assets provide bounds that map the usual connector coordinates into one sprite.
local left, top = asset[1] or 0, asset[2] or 0
local width, height = (asset[3] or 1) - left, (asset[4] or 1) - top
local c = connector.c
DrawImageQuad(asset.handle,
c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8],
left + c[9] * width, top + c[10] * height,
left + c[11] * width, top + c[12] * height,
left + c[13] * width, top + c[14] * height,
left + c[15] * width, top + c[16] * height)
end

-- Draw the connecting lines between nodes
Expand Down Expand Up @@ -856,6 +877,16 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents)
base = node.sprites[node.type:lower()..(isAlloc and "Active" or "Inactive")]
local overlayKey = state .. (node.ascendancyName and "Ascend" or "") .. (node.isBlighted and "Blighted" or "")
local overlayName = node.overlay[overlayKey]
if isAbyssConquered(node) and (node.type == "Notable" or node.type == "Keystone") then
-- AlternateTreeArt gives Abyss replacements their own notable,
-- keystone, and ascendancy-notable frames.
local frameType = node.ascendancyName and "Ascendancy" or node.type
local frameState = state == "alloc" and "Allocated" or state == "path" and "CanAllocate" or "Unallocated"
local abyssOverlay = "Abyss" .. frameType .. "Frame" .. frameState
if tree.assets[abyssOverlay] then
overlayName = abyssOverlay
end
end
if node.ascendancyName then
local prefix = node.bloodlineOverlayPrefix or (tree.bloodlineSpritePrefixes and tree.bloodlineSpritePrefixes[node.ascendancyName])
if prefix and overlayName then
Expand Down Expand Up @@ -1125,6 +1156,10 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents)

-- Draw ring overlays for jewel sockets
local function drawJewelRadius(jewel, scrX, scrY, tint)
-- Abyss jewels do not show radius art in game.
if isAbyssConquered(jewel.jewelData) then
return
end
local radData = build.data.jewelRadius[jewel.jewelRadiusIndex]
local outerSize = radData.outer * scale
local innerSize = radData.inner * scale * 1.06
Expand Down
Loading
Loading