local gimpBatch = require("Tree/GimpBatch/gimp_batch")
local nvtt = LoadModule("Tree/nvtt")
local json = require("dkjson")
main.treeCacheExtract = main.treeCacheExtract or { }
local cacheExtract = main.treeCacheExtract
local ignoreFilter = "^%[DNT"
if not loadStatFile then
dofile("statdesc.lua")
end
loadStatFile("passive_skill_stat_descriptions.csd")
local function CalcOrbitAngles(nodesInOrbit)
local orbitAngles = {}
if nodesInOrbit == 16 then
orbitAngles = { 0, 30, 45, 60, 90, 120, 135, 150, 180, 210, 225, 240, 270, 300, 315, 330 }
elseif nodesInOrbit == 40 then
orbitAngles = { 0, 10, 20, 30, 40, 45, 50, 60, 70, 80, 90, 100, 110, 120, 130, 135, 140, 150, 160, 170, 180, 190, 200, 210, 220, 225, 230, 240, 250, 260, 270, 280, 290, 300, 310, 315, 320, 330, 340, 350 }
else
for i = 0, nodesInOrbit do
orbitAngles[i + 1] = 360 * i / nodesInOrbit
end
end
for i, degrees in ipairs(orbitAngles) do
orbitAngles[i] = math.rad(degrees)
end
return orbitAngles
end
local function bits(int, s, e)
return bit.band(bit.rshift(int, s), 2 ^ (e - s + 1) - 1)
end
local function toFloat(int)
local s = (-1) ^ bits(int, 31, 31)
local e = bits(int, 23, 30) - 127
if e == -127 then
return 0 * s
end
local m = 1
for i = 0, 22 do
m = m + bits(int, i, i) * 2 ^ (i - 23)
end
return s * m * 2 ^ e
end
local function getInt(f)
local int = f:read(4)
return bytesToInt(int)
end
local function getLong(f)
local bytes = f:read(8)
local a, b, c, d, e, f, g, h = bytes:byte(1, 8)
return a + b * 256 + c * 65536 + d * 16777216 + e * 4294967296 + f * 1099511627776 + g * 281474976710656 + h * 72057594037927936
end
local function getFloat(f)
return toFloat(getInt(f))
end
local function getUint16(f)
local bytes = f:read(2)
local b1, b2 = bytes:byte(1, 2)
local uint16 = b1 + b2 * 256
return uint16
end
local function round_to(num, decimal_places)
local multiplier = 10 ^ decimal_places
return math.floor(num * multiplier + 0.5) / multiplier
end
local function extractSheetFiles(allSheets, is4kEnabled, extraFiles)
local filesToExtract = {}
local seen = {}
for _, sheet in ipairs(allSheets) do
for icon in pairs(sheet.files) do
if not seen[icon] then
seen[icon] = true
table.insert(filesToExtract, icon)
end
if is4kEnabled then
local icon4k = icon:gsub("(.*/)([^/]+)$", "%14k/%2")
if not seen[icon4k] then
seen[icon4k] = true
table.insert(filesToExtract, icon4k)
end
end
end
end
if extraFiles then
for _, file in ipairs(extraFiles) do
if not seen[file] then
seen[file] = true
table.insert(filesToExtract, file)
end
end
end
if #filesToExtract > 0 then
main.ggpk:ExtractList(filesToExtract, cacheExtract)
end
end
local function print_table(t, indent)
indent = indent or 0
local prefix = string.rep(" ", indent)
if type(t) ~= "table" then
print(prefix .. tostring(t))
return
end
for key, value in pairs(t) do
if type(value) == "table" then
print(prefix .. tostring(key) .. ": {")
print_table(value, indent + 1)
print(prefix .. "}")
else
print(prefix .. tostring(key) .. ": " .. tostring(value))
end
end
end
local function newSheet(name, startWidth, saturation)
return {
name = name,
startWidth = startWidth,
saturation = saturation,
sprite = { },
files = {}
}
end
local function addToSheet(sheet, icon, section, metadata)
sheet.files[icon] = sheet.files[icon] or {}
if sheet.files[icon][section] then
if metadata.alias then
for _, meta in pairs(sheet.files[icon][section]) do
if meta.alias == metadata.alias then
return
end
end
else
for _, meta in pairs(sheet.files[icon][section]) do
if meta.alias == nil then
return
end
end
end
end
sheet.files[icon][section] = sheet.files[icon][section] or {}
table.insert(sheet.files[icon][section], metadata)
end
local function calculateDDSPack(sheet, from_base, to_base, is4kEnabled)
local stackTextures = {}
local ddsCoords = {}
for icon, sections in pairsSortByKey(sheet.files) do
local tex = Texture.new()
local rc
if is4kEnabled then
local icon4k = icon:gsub("(.*/)([^/]+)$", "%14k/%2")
rc = tex:Load(from_base .. string.lower(icon4k))
end
if not rc then
rc = tex:Load(from_base .. string.lower(icon))
end
local info = tex:Info()
local ident = string.format("%d_%d_%s", info.width, info.height, info.formatStr)
if not stackTextures[ident] then
stackTextures[ident] = {}
end
table.insert(stackTextures[ident], {
tex = tex,
icon = icon,
sections = sections
})
end
for iden, stackInfo in pairsSortByKey(stackTextures) do
local stacks = {}
local file = sheet.name .. "_" .. iden .. ".dds.zst"
ddsCoords[file] = {}
for position, stack in ipairs(stackInfo) do
for _, metadata in pairs(stack.sections) do
for _, meta in ipairs(metadata) do
local icon = meta.alias or stack.icon
ddsCoords[file][icon] = position
end
end
table.insert(stacks, stack.tex)
end
local stackTex = Texture.new()
local rc = stackTex:StackTextures(stacks)
rc = stackTex:Save(to_base .. file)
end
sheet.ddsCoords = ddsCoords
end
local function parseUIImages(file)
local text
if main.ggpk.txt[file] then
text = main.ggpk.txt[file]
else
text = convertUTF16to8(getFile(file))
main.ggpk.txt[file] = text
end
local images = {}
for line in text:gmatch("[^\r\n]+") do
local index = 0
local name = ""
for field in line:gmatch('"?([^%s"]+)"?') do
if index == 0 then
name = string.lower(field)
images[name] = {}
elseif index ==1 then
images[name]["path"] = string.lower(field)
elseif index == 2 then
images[name]["x"] = tonumber(field)
elseif index == 3 then
images[name]["y"] = tonumber(field)
elseif index == 4 then
images[name]["width"] = tonumber(field)
elseif index == 5 then
images[name]["height"] = tonumber(field)
end
index = index + 1
end
end
printf("UI Images parsed")
return images
end
===== Extraction =====
Extraction of passives tree from psg file
workflow:
- read data file
- get psg file
- parse psg file
- check version (only support version 3 for now)
--]]
local generateAssets = false
local use4kIfPossible = false
local idPassiveTree = 'Default'
local basePath = GetWorkDir() .. "/../TreeData/"
local version = "0_4"
local path = basePath .. version .. "/"
local fileTree = path .. "tree.lua"
printf("Getting passives tree...")
local rowPassiveTree = dat("passiveskilltrees"):GetRow("Id", idPassiveTree)
if rowPassiveTree == nil then
printf("Passive tree not found")
return
end
local psgFile = rowPassiveTree.PassiveSkillGraph .. ".psg"
local uiImagesFile = "art/uiimages1.txt"
printf("Extracting required assets (psg + ui images)...")
main.ggpk:ExtractList({psgFile, uiImagesFile}, cacheExtract)
local uiImages = parseUIImages(uiImagesFile)
local f = io.open(main.ggpk.oozPath .. psgFile, "rb")
local pgb_version = getUint16(f)
if pgb_version ~= 3 then
printf("Version " .. version .. " not supported")
return
end
f:read(11)
local psg = {
passives = { },
groups = { },
}
local passivesCount = getInt(f)
for i = 1 , passivesCount do
table.insert(psg.passives, getLong(f))
end
local groupCount = getInt(f)
for i = 1 , groupCount do
local group = {
x = getFloat(f),
y = getFloat(f),
flags = getInt(f),
unk1 = getInt(f),
unk2 = f:read(1):byte(),
passives = { },
}
local passiveCount = getInt(f)
for j = 1, passiveCount do
local passive = {
id = getInt(f),
radius = getInt(f),
position = getInt(f),
connections = { },
}
local connectionCount = getInt(f)
for k = 1, connectionCount do
table.insert(passive.connections, {
id = getInt(f),
radius = getInt(f),
})
end
table.insert(group.passives, passive)
end
table.insert(psg.groups, group)
end
f:close()
===== Generation =====
Generation of passives tree from psg file
workflow:
- generate classes
- generate groups
- generate nodes
- generate sprites (with sprite sheet)
- generate zoom levels
- generate constants
--]]
local function commonMetadata(alias)
local metadata = {
alias = alias,
}
return metadata
end
local defaultMaxWidth = 1500
local sheets = {
newSheet("skills", defaultMaxWidth, 100),
newSheet("skills-disabled", defaultMaxWidth, 60),
newSheet("background", defaultMaxWidth, 100),
newSheet("group-background", defaultMaxWidth, 100),
newSheet("mastery-active-effect", defaultMaxWidth, 100),
newSheet("ascendancy-background", defaultMaxWidth, 100),
newSheet("oils", defaultMaxWidth, 100),
newSheet("lines", defaultMaxWidth, 100),
newSheet("jewel-sockets", defaultMaxWidth, 100),
newSheet("legion", defaultMaxWidth, 100),
newSheet("monster-categories", defaultMaxWidth, 100),
}
local sheetLocations = {
["skills"] = 1,
["skills-disabled"] = 2,
["background"] = 3,
["group-background"] = 4,
["mastery-active-effect"] = 5,
["ascendancy-background"] = 6,
["oils"] = 7,
["lines"] = 8,
["jewel-sockets"] = 9,
["legion"] = 10,
["monster-categories"] = 11,
}
local connectionArtToDecompose = {
Character = true,
CharacterAscendancy = true,
CharacterPlanned = true,
}
local linesFiles = {}
local linesDds = {}
do
local typeOfConnections = {
"Normal", "Intermediate", "IntermediateActive"
}
for connectionArtId in pairs(connectionArtToDecompose) do
local connectionArt = dat("PassiveSkillTreeConnectionArt"):GetRow("Id", connectionArtId)
if connectionArt == nil then
printf("Connection art " .. connectionArtId .. " not found")
else
for _, typeName in ipairs(typeOfConnections) do
local linesFile = {
file = connectionArt[typeName],
mask = connectionArt.Mask,
extension = ".png",
basename = connectionArtId .. "_orbit_" .. string.lower(typeName),
first = connectionArtId .. "LineConnector",
prefix = connectionArtId .. "Orbit",
postfix = typeName == "IntermediateActive" and "Active" or typeName,
total = 10
}
table.insert(linesFiles, linesFile)
end
end
end
for _, lines in ipairs(linesFiles) do
table.insert(linesDds, lines.file)
table.insert(linesDds, lines.mask)
end
end
local function getSheet(sheetLocation)
return sheets[sheetLocations[sheetLocation]]
end
local bg2 = uiImages["art/2dart/uiimages/common/background2"]
if not bg2 then
printf("Background2 not found")
goto final
end
addToSheet(getSheet("background"), bg2.path, "background", commonMetadata("Background2"))
local uIArt = rowPassiveTree.UIArt
local gBgSmall = uiImages[string.lower(uIArt.GroupBackgroundSmall)].path
addToSheet(getSheet("group-background"), gBgSmall, "groupBackground", commonMetadata("PSGroupBackground1"))
local gBgMedium = uiImages[string.lower(uIArt.GroupBackgroundMedium)].path
addToSheet(getSheet("group-background"), gBgMedium, "groupBackground", commonMetadata("PSGroupBackground2"))
local gBgLarge = uiImages[string.lower(uIArt.GroupBackgroundLarge)].path
addToSheet(getSheet("group-background"), gBgLarge, "groupBackground", commonMetadata("PSGroupBackground3"))
local pFrameNormal = uiImages[string.lower(uIArt.PassiveFrame.Normal)].path
addToSheet(getSheet("group-background"), pFrameNormal, "frame", commonMetadata("PSSkillFrame"))
local pFrameActive = uiImages[string.lower(uIArt.PassiveFrame.Active)].path
addToSheet(getSheet("group-background"), pFrameActive, "frame", commonMetadata("PSSkillFrameActive"))
local pFrameCanAllocate = uiImages[string.lower(uIArt.PassiveFrame.CanAllocate)].path
addToSheet(getSheet("group-background"), pFrameCanAllocate, "frame", commonMetadata("PSSkillFrameHighlighted"))
local kFrameNormal = uiImages[string.lower(uIArt.KeystoneFrame.Normal)].path
addToSheet(getSheet("group-background"), kFrameNormal, "frame", commonMetadata("KeystoneFrameUnallocated"))
local kFrameActive = uiImages[string.lower(uIArt.KeystoneFrame.Active)].path
addToSheet(getSheet("group-background"), kFrameActive, "frame", commonMetadata("KeystoneFrameAllocated"))
local kFrameCanAllocate = uiImages[string.lower(uIArt.KeystoneFrame.CanAllocate)].path
addToSheet(getSheet("group-background"), kFrameCanAllocate, "frame", commonMetadata("KeystoneFrameCanAllocate"))
local nFrameNormal = uiImages[string.lower(uIArt.NotableFrame.Normal)].path
addToSheet(getSheet("group-background"), nFrameNormal, "frame", commonMetadata("NotableFrameUnallocated"))
local nFrameActive = uiImages[string.lower(uIArt.NotableFrame.Active)].path
addToSheet(getSheet("group-background"), nFrameActive, "frame", commonMetadata("NotableFrameAllocated"))
local nFrameCanAllocate = uiImages[string.lower(uIArt.NotableFrame.CanAllocate)].path
addToSheet(getSheet("group-background"), nFrameCanAllocate, "frame", commonMetadata("NotableFrameCanAllocate"))
local gBgSmallBlank = uiImages[string.lower(uIArt.GroupBackgroundSmall)].path
addToSheet(getSheet("group-background"), gBgSmallBlank, "groupBackground", commonMetadata("PSGroupBackgroundSmallBlank"))
local gBgMediumBlank = uiImages[string.lower(uIArt.GroupBackgroundMedium)].path
addToSheet(getSheet("group-background"), gBgMediumBlank, "groupBackground", commonMetadata("PSGroupBackgroundMediumBlank"))
local gBgLargeBlank = uiImages[string.lower(uIArt.GroupBackgroundLarge)].path
addToSheet(getSheet("group-background"), gBgLargeBlank, "groupBackground", commonMetadata("PSGroupBackgroundLargeBlank"))
local jFrameNormal = uiImages[string.lower(uIArt.JewelFrame.CanAllocate)].path
addToSheet(getSheet("group-background"), jFrameNormal, "frame", commonMetadata("JewelFrameCanAllocate"))
local jFrameActive = uiImages[string.lower(uIArt.JewelFrame.Active)].path
addToSheet(getSheet("group-background"), jFrameActive, "frame", commonMetadata("JewelFrameAllocated"))
local jFrameCanAllocate = uiImages[string.lower(uIArt.JewelFrame.Normal)].path
addToSheet(getSheet("group-background"), jFrameCanAllocate, "frame", commonMetadata("JewelFrameUnallocated"))
connectionArtToDecompose[uIArt.ConnectionsArt.Id] = true
local ascMiddle = uiImages[string.lower("Art/2DArt/UIImages/InGame/PassiveSkillScreenAscendancyMiddle")].path
addToSheet(getSheet("group-background"), ascMiddle, "frame", commonMetadata("AscendancyMiddle"))
local ascStart = uiImages[string.lower("Art/2DArt/UIImages/InGame/PassiveSkillScreenStartNodeBackgroundInactive")].path
addToSheet(getSheet("group-background"), ascStart, "startNode", commonMetadata("PSStartNodeBackgroundInactive"))
addToSheet(getSheet("ascendancy-background"), "art/textures/interface/2d/2dart/uiimages/ingame/passivetree/passivetreemaincircle.dds", "AscendancyBackground", commonMetadata("BGTree"))
addToSheet(getSheet("ascendancy-background"), "art/textures/interface/2d/2dart/uiimages/ingame/passivetree/passivetreemaincircleactive2.dds", "AscendancyBackground", commonMetadata("BGTreeActive"))
local jewelArt = dat("PassiveJewelArt")
for jewel in jewelArt:Rows() do
if jewel.Item.Name:find(ignoreFilter) ~= nil then
printf("Ignoring jewel socket " .. jewel.Item.Name)
goto nexttogo
end
local asset = uiImages[string.lower(jewel.JewelArt)]
local name = jewel.Item.Name
addToSheet(getSheet("jewel-sockets"), asset.path, "jewelpassive", commonMetadata(name))
:: nexttogo ::
end
local uniqueJewelArt = dat("PassiveJewelUniqueArt")
for jewel in uniqueJewelArt:Rows() do
if jewel.WordsKey.Text:find(ignoreFilter) ~= nil then
printf("Ignoring unique jewel socket " .. jewel.Item.Name)
goto nexttogo
end
local asset = uiImages[string.lower(jewel.JewelArt)]
local name = jewel.WordsKey.Text
addToSheet(getSheet("jewel-sockets"), asset.path, "jewelpassive", commonMetadata(name))
:: nexttogo ::
end
local monsterCategories = dat("MonsterCategories")
for category in monsterCategories:Rows() do
if category.Type:find(ignoreFilter) ~= nil then
printf("Ignoring category" .. category.Type)
goto nexttogo
end
local asset = uiImages[string.lower(category.HudImage)]
local name = category.Type
addToSheet(getSheet("monster-categories"), asset.path, "monster-categories", commonMetadata(name))
:: nexttogo ::
end
for legion in dat("AlternatePassiveSkills"):Rows() do
addToSheet(getSheet("legion"), legion.DDSIcon, "legion", commonMetadata(legion.DDSIcon))
end
local artsToLegion = {
"Art/2DArt/UIImages/InGame/Abyss/AbyssPassiveSkillScreenJewelCircle1",
"Art/2DArt/UIImages/InGame/PassiveSkillScreenEternalEmpireJewelCircle1",
"Art/2DArt/UIImages/InGame/PassiveSkillScreenEternalEmpireJewelCircle2",
"Art/2DArt/UIImages/InGame/PassiveSkillScreenKalguuranJewelCircle1",
"Art/2DArt/UIImages/InGame/PassiveSkillScreenKalguuranJewelCircle2",
"Art/2DArt/UIImages/InGame/PassiveSkillScreenKaruiJewelCircle1",
"Art/2DArt/UIImages/InGame/PassiveSkillScreenKaruiJewelCircle2",
"Art/2DArt/UIImages/InGame/PassiveSkillScreenMarakethJewelCircle1",
"Art/2DArt/UIImages/InGame/PassiveSkillScreenMarakethJewelCircle2",
"Art/2DArt/UIImages/InGame/PassiveSkillScreenTemplarJewelCircle1",
"Art/2DArt/UIImages/InGame/PassiveSkillScreenTemplarJewelCircle2",
"Art/2DArt/UIImages/InGame/PassiveSkillScreenVaalJewelCircle1",
"Art/2DArt/UIImages/InGame/PassiveSkillScreenVaalJewelCircle2",
}
for _, art in ipairs(artsToLegion) do
local asset = uiImages[string.lower(art)]
addToSheet(getSheet("legion"), asset.path, "legion", commonMetadata(asset.path))
end
local tree = {
["tree"] = idPassiveTree,
["min_x"]= 0,
["min_y"]= 0,
["max_x"]= 0,
["max_y"]= 0,
["classes"] = {},
["groups"] = { },
["nodes"]= { },
["assets"]={},
["ddsCoords"] = {},
["jewelSlots"] = {},
["constants"]= {
["classes"]= {
["StrDexIntClass"]= 0,
["StrClass"]= 1,
["DexClass"]= 2,
["IntClass"]= 3,
["StrDexClass"]= 4,
["StrIntClass"]= 5,
["DexIntClass"]= 6
},
["characterAttributes"]= {
["Strength"]= 0,
["Dexterity"]= 1,
["Intelligence"]= 2
},
["PSSCentreInnerRadius"]= 130,
["skillsPerOrbit"]= {},
["orbitAnglesByOrbit"] = {},
["orbitRadii"]= {
0, 82, 162, 335, 493, 662, 846, 251, 1080, 1322
},
},
["nodeOverlay"] = {
Normal = {
alloc = "PSSkillFrameActive",
path = "PSSkillFrameHighlighted",
unalloc = "PSSkillFrame",
},
Notable = {
alloc = "NotableFrameAllocated",
path = "NotableFrameCanAllocate",
unalloc = "NotableFrameUnallocated",
},
Keystone = {
alloc = "KeystoneFrameAllocated",
path = "KeystoneFrameCanAllocate",
unalloc = "KeystoneFrameUnallocated",
},
Socket = {
alloc = "JewelFrameAllocated",
path = "JewelFrameCanAllocate",
unalloc = "JewelFrameUnallocated",
},
},
["connectionArt"] = {
default = "Character",
ascendancy = "CharacterAscendancy",
},
}
printf("Generating classes...")
local ascedancyReplacements = {}
for i, classId in ipairs(psg.passives) do
local passiveRow = dat("passiveskills"):GetRow("PassiveSkillNodeId", classId)
if passiveRow == nil then
printf("Class " .. passiveRow.id .. " not found")
goto continue
end
if passiveRow.Name:find(ignoreFilter) ~= nil then
goto continue
end
local listCharacters = passiveRow.ClassStart
if listCharacters == nil then
printf("Characters not found")
goto continue
end
for j, character in ipairs(listCharacters) do
if character.Name:find(ignoreFilter) ~= nil then
goto continue2
end
local classDef = {
["name"] = character.Name,
["integerId"] = character.IntegerId,
["base_str"] = character.BaseStrength,
["base_dex"] = character.BaseDexterity,
["base_int"] = character.BaseIntelligence,
["ascendancies"] = {},
["background"] = {
["active"] = { width = 2000, height = 2000 },
["bg"] = { width = 2000, height = 2000 },
image = "Classes" .. character.Name,
section = "AscendancyBackground",
x = 0,
y = 0,
width = 1500,
height = 1500
}
}
addToSheet(getSheet("ascendancy-background"), character.PassiveTreeImage, "AscendancyBackground", commonMetadata( "Classes" .. character.Name))
local ascendancies = dat("ascendancy"):GetRowList("Class", character)
for k, ascendency in ipairs(ascendancies) do
if ascendency.Name:find(ignoreFilter) ~= nil or ascendency.isDisabled then
goto continue3
end
if ascendency.Replace then
ascedancyReplacements[ascendency.Replace.Name] = ascendency.Name
end
table.insert(classDef.ascendancies, {
["id"] = ascendency.Name,
["name"] = ascendency.Name,
["internalId"] = ascendency.Id,
["replace"] = ascendency.Replace and ascendency.Replace.Name or nil,
["background"] = {
image = "Classes" .. ascendency.Name,
section = "AscendancyBackground",
x = 0,
y = 0,
width = 1500,
height = 1500
}
})
addToSheet(getSheet("ascendancy-background"), ascendency.PassiveTreeImage, "AscendancyBackground", commonMetadata( "Classes" .. ascendency.Name))
local ascFrameNormal = uiImages[string.lower(ascendency.UIArt.PassiveFrame.CanAllocate)].path
addToSheet(getSheet("group-background"), ascFrameNormal, "frame", commonMetadata(ascendency.Name .. "FrameSmallCanAllocate"))
local ascFrameActive = uiImages[string.lower(ascendency.UIArt.PassiveFrame.Normal)].path
addToSheet(getSheet("group-background"), ascFrameActive, "frame", commonMetadata(ascendency.Name .. "FrameSmallNormal"))
local ascFrameCanAllocate = uiImages[string.lower(ascendency.UIArt.PassiveFrame.Active)].path
addToSheet(getSheet("group-background"), ascFrameCanAllocate, "frame", commonMetadata(ascendency.Name .. "FrameSmallAllocated"))
local ascFrameLargeNormal = uiImages[string.lower(ascendency.UIArt.NotableFrame.Normal)].path
addToSheet(getSheet("group-background"), ascFrameLargeNormal, "frame", commonMetadata(ascendency.Name .. "FrameLargeNormal"))
local ascFrameLargeCanAllocate = uiImages[string.lower(ascendency.UIArt.NotableFrame.CanAllocate)].path
addToSheet(getSheet("group-background"), ascFrameLargeCanAllocate, "frame", commonMetadata(ascendency.Name .. "FrameLargeCanAllocate"))
local ascFrameLargeAllocated = uiImages[string.lower(ascendency.UIArt.NotableFrame.Active)].path
addToSheet(getSheet("group-background"), ascFrameLargeAllocated, "frame", commonMetadata(ascendency.Name .. "FrameLargeAllocated"))
:: continue3 ::
end
if #classDef.ascendancies == 0 then
goto continue2
end
table.insert(tree.classes,classDef)
:: continue2 ::
end
:: continue ::
end
local base_attributes = {
[26297] = {},
[14927] = {},
[57022] = {}
}
for id, _ in pairs(base_attributes) do
local base = dat("passiveskills"):GetRow("PassiveSkillNodeId", id)
if base == nil then
printf("Base attribute " .. id .. " not found")
goto continue
end
if base.Name:find(ignoreFilter) ~= nil then
printf("Ignoring base attribute " .. base.Name)
goto continue
end
local attribute = {
["name"] = base.Name,
["icon"] = base.Icon,
["stats"] = {},
}
if base.Stats ~= nil then
local parseStats = {}
for k, stat in ipairs(base.Stats) do
parseStats[stat.Id] = { min = base["Stat" .. k], max = base["Stat" .. k] }
end
local out, orders = describeStats(parseStats)
for k, line in ipairs(out) do
table.insert(attribute["stats"], line)
end
end
addToSheet(getSheet("skills"), base.Icon, "normalActive", commonMetadata(nil))
base_attributes[id] = attribute
:: continue ::
end
printf("Generating tree groups...")
local orbitsConstants = { }
local ascendancyGroups = {}
local missingStatInfo = {}
for i, group in ipairs(psg.groups) do
local groupIsAscendancy = false
local treeGroup = {
["x"] = round_to(group.x, 2),
["y"] = round_to(group.y, 2),
["orbits"] ={},
["nodes"] = {}
}
local orbits = { }
for j, passive in ipairs(group.passives) do
local node = {
["skill"] = passive.id,
["group"] = i,
["orbit"] = passive.radius,
["orbitIndex"] = passive.position,
["connections"] = {},
}
local passiveRow = dat("passiveskills"):GetRow("PassiveSkillNodeId", passive.id)
if passiveRow == nil then
else
if passiveRow.Name == "" then
goto exitNode
end
if passiveRow.Name:find(ignoreFilter) ~= nil and passiveRow.Name ~= "[DNT] Kite Fisher" and passiveRow.Name ~= "[DNT] Troller" and passiveRow.Name ~= "[DNT] Spearfisher" and passiveRow.Name ~= "[DNT] Angler" and passiveRow.Name ~= "[DNT] Whaler" then
goto exitNode
end
node["name"] = escapeGGGString(passiveRow.Name)
node["icon"] = passiveRow.Icon
if passiveRow.FlavourText ~= "" then
node["flavourText"] = passiveRow.FlavourText:gsub('\r',''):gsub('\n','\\n')
end
if passiveRow.Keystone then
node["isKeystone"] = true
addToSheet(getSheet("skills"), passiveRow.Icon, "keystoneActive", commonMetadata(nil))
addToSheet(getSheet("skills-disabled"), passiveRow.Icon, "keystoneInactive", commonMetadata(nil))
elseif passiveRow.Notable then
node["isNotable"] = true
addToSheet(getSheet("skills"), passiveRow.Icon, "notableActive", commonMetadata(nil))
addToSheet(getSheet("skills-disabled"), passiveRow.Icon, "notableInactive", commonMetadata(nil))
elseif passiveRow.IsOnlyImage then
node["isOnlyImage"] = true
elseif passiveRow.JewelSocket then
node["isJewelSocket"] = true
addToSheet(getSheet("skills"), passiveRow.Icon, "socketActive", commonMetadata(nil))
addToSheet(getSheet("skills-disabled"), passiveRow.Icon, "socketInactive", commonMetadata(nil))
else
addToSheet(getSheet("skills"), passiveRow.Icon, "normalActive", commonMetadata(nil))
addToSheet(getSheet("skills-disabled"), passiveRow.Icon, "normalInactive", commonMetadata(nil))
end
if passiveRow.Ascendancy ~= nil then
groupIsAscendancy = true
if passiveRow.Ascendancy.Name:find(ignoreFilter) ~= nil or passiveRow.Ascendancy.isDisabled then
goto exitNode
end
node["ascendancyName"] = passiveRow.Ascendancy.Name
node["isAscendancyStart"] = passiveRow.AscendancyStart or nil
if node["isAscendancyStart"] then
node["name"] = passiveRow.Ascendancy.Name
end
if passiveRow.JewelSocket then
node["containJewelSocket"] = true
local uioverride = passiveRow.Ascendancy.UIArt.JewelFrame
if uioverride then
local uiSocketNormal = uiImages[string.lower(uioverride.Normal)]
addToSheet(getSheet("group-background"), uiSocketNormal.path, "frame", commonMetadata(nil))
local uiSocketActive = uiImages[string.lower(uioverride.Active)]
addToSheet(getSheet("group-background"), uiSocketActive.path, "frame", commonMetadata(nil))
local uiSocketCanAllocate = uiImages[string.lower(uioverride.CanAllocate)]
addToSheet(getSheet("group-background"), uiSocketCanAllocate.path, "frame", commonMetadata(nil))
node.nodeOverlay = {
alloc = uiSocketActive.path,
path = uiSocketCanAllocate.path,
unalloc = uiSocketNormal.path,
}
else
end
elseif node["isOnlyImage"] == nil then
local typeFrame = node["isNotable"] and "Large" or "Small"
node.nodeOverlay = {
alloc = passiveRow.Ascendancy.Name .. "Frame" .. typeFrame .. "Allocated",
path = passiveRow.Ascendancy.Name .. "Frame" .. typeFrame .. "CanAllocate",
unalloc = passiveRow.Ascendancy.Name .. "Frame" .. typeFrame .. "Normal",
}
end
ascendancyGroups = ascendancyGroups or {}
ascendancyGroups[passiveRow.Ascendancy.Name] = ascendancyGroups[passiveRow.Ascendancy.Name] or { }
ascendancyGroups[passiveRow.Ascendancy.Name].startId = passiveRow.AscendancyStart and passive.id or ascendancyGroups[passiveRow.Ascendancy.Name].startId
ascendancyGroups[passiveRow.Ascendancy.Name][i] = true
end
if passiveRow.FrameArt ~= nil and node["isOnlyImage"] == nil then
local frameArt = passiveRow.FrameArt
if frameArt ~= nil then
local uiNormal = uiImages[string.lower(frameArt.Normal)]
addToSheet(getSheet("group-background"), uiNormal.path, "frame", commonMetadata(nil))
local uiActive = uiImages[string.lower(frameArt.Active)]
addToSheet(getSheet("group-background"), uiActive.path, "frame", commonMetadata(nil))
local uiCanAllocate = uiImages[string.lower(frameArt.CanAllocate)]
addToSheet(getSheet("group-background"), uiCanAllocate.path, "frame", commonMetadata(nil))
node.nodeOverlay = {
alloc = uiActive.path,
path = uiCanAllocate.path,
unalloc = uiNormal.path,
}
end
end
if passiveRow.AscendancyUnlock ~= nil then
node.unlockConstraint = {
ascendancy = passiveRow.AscendancyUnlock.Name,
nodes = {}
}
for id, refNode in ipairs(passiveRow.ConstraintNode) do
node.unlockConstraint.nodes[id] = refNode.PassiveSkillNodeId
end
node.connectionArt = "CharacterPlanned"
end
if passiveRow.Stats ~= nil then
node["stats"] = node["stats"] or {}
local parseStats = {}
local totalStats = 0
local namesStats = ""
for k, stat in ipairs(passiveRow.Stats) do
parseStats[stat.Id] = { min = passiveRow["Stat" .. k], max = passiveRow["Stat" .. k] }
totalStats = totalStats + 1
namesStats = namesStats .. stat.Id .. " | "
end
local out, orders, missing = describeStats(parseStats)
if #out < totalStats then
table.insert(missingStatInfo, "====================================")
table.insert(missingStatInfo,"Stats not found for passive " .. passiveRow.Name .. " " .. passive.id)
table.insert(missingStatInfo,"Stats found: " .. totalStats)
table.insert(missingStatInfo,namesStats)
table.insert(missingStatInfo,"Stats out: " .. #out)
for k, line in ipairs(out) do
table.insert(missingStatInfo,line)
end
table.insert(missingStatInfo,"Missing: ")
for k, _ in pairs(missing) do
if k ~= 1 then
table.insert(missingStatInfo,k)
end
end
table.insert(missingStatInfo,"====================================")
end
for k, line in ipairs(out) do
table.insert(node["stats"], line)
end
end
if passiveRow.MasteryGroup ~= nil then
node["activeEffectImage"] = passiveRow.MasteryGroup.MasteryArt.Effect
local uiEffect = uiImages[string.lower(passiveRow.MasteryGroup.MasteryArt.Effect)]
addToSheet(getSheet("mastery-active-effect"), uiEffect.path, "masteryActiveEffect", commonMetadata(passiveRow.MasteryGroup.MasteryArt.Effect))
end
if passiveRow.Attribute == true then
node["options"] = {}
node["isAttribute"] = true
for attId, value in pairs(base_attributes) do
table.insert(node["options"], {
["id"] = attId,
["name"] = base_attributes[attId].name,
["icon"] = base_attributes[attId].icon,
["stats"] = base_attributes[attId].stats,
})
end
end
if passiveRow.GrantedSkill ~= nil then
node["stats"] = node["stats"] or {}
for _, gemEffect in pairs(passiveRow.GrantedSkill.GemEffects) do
local skillName = passiveRow.GrantedSkill.BaseItemType.Name
table.insert(node["stats"], "Grants Skill: " .. skillName)
local statDescription =string.sub(string.lower(gemEffect.GrantedEffect.ActiveSkill.StatDescription), 1, -5)
local handle = NewFileSearch("ggpk/" .. statDescription ..".csd")
local almostOnce = false
while handle do
almostOnce = true
if not handle:NextFile() then
break
end
end
if not almostOnce then
table.insert(missingStatInfo, "===================================>Missing stat" .. statDescription)
end
end
end
if passiveRow.PassivePointsGranted > 0 then
node["stats"] = node["stats"] or {}
table.insert(node["stats"], "Grants ".. passiveRow.PassivePointsGranted .." Passive Skill Point")
end
if passiveRow.WeaponPointsGranted > 0 then
node["stats"] = node["stats"] or {}
table.insert(node["stats"], passiveRow.WeaponPointsGranted .." Passive Skill Points become Weapon Set Skill Points")
end
local bResult = dat("blightcraftingresults"):GetRow("PassiveSkillsKey", passiveRow)
if bResult ~= nil then
node["recipe"] = {}
local bCraftRecipe = dat("blightcraftingrecipes"):GetRow("BlightCraftingResultsKey", bResult)
if bCraftRecipe ~= nil then
for _, item in ipairs(bCraftRecipe.Recipe) do
table.insert(node["recipe"], item.NameShort)
addToSheet(getSheet("oils"), item.Oil.ItemVisualIdentityKey.DDSFile, "oil", commonMetadata(item.NameShort))
end
end
end
local switchNode = dat("classpassiveskilloverrides"):GetRow("OriginalNode", passiveRow)
if switchNode ~= nil then
node["isSwitchable"] = true
local nodeInfo = {
["id"] = switchNode.SwitchedNode.PassiveSkillNodeId,
["name"] = switchNode.SwitchedNode.Name,
["icon"] = switchNode.SwitchedNode.Icon,
["stats"] = {},
}
addToSheet(getSheet("skills"), switchNode.SwitchedNode.Icon, "normalActive", commonMetadata(nil))
addToSheet(getSheet("skills-disabled"), switchNode.SwitchedNode.Icon, "normalInactive", commonMetadata(nil))
if switchNode.SwitchedNode.Stats ~= nil then
local parseStats = {}
for k, stat in ipairs(switchNode.SwitchedNode.Stats) do
parseStats[stat.Id] = { min = switchNode.SwitchedNode["Stat" .. k], max = switchNode.SwitchedNode["Stat" .. k] }
end
local out, orders = describeStats(parseStats)
for k, line in ipairs(out) do
table.insert(nodeInfo["stats"], line)
end
end
node["options"] = {
[switchNode.Character.Name] = nodeInfo
}
end
if passiveRow.Ascendancy and ascedancyReplacements[passiveRow.Ascendancy.Name] then
node["isSwitchable"] = true
local ascendancyRow = dat("ascendancy"):GetRow("Name", ascedancyReplacements[passiveRow.Ascendancy.Name])
local nodeInfo = {
["ascendancyName"] = ascedancyReplacements[passiveRow.Ascendancy.Name]
}
local switchNode = dat("ascendancypassiveskilloverrides"):GetRow("OriginalNode", passiveRow)
if switchNode then
nodeInfo.id = switchNode.SwitchedNode.PassiveSkillNodeId
nodeInfo.name = switchNode.SwitchedNode.Name
nodeInfo.icon = switchNode.SwitchedNode.Icon
nodeInfo.stats = {}
addToSheet(getSheet("skills"), switchNode.SwitchedNode.Icon, "normalActive", commonMetadata(nil))
addToSheet(getSheet("skills-disabled"), switchNode.SwitchedNode.Icon, "normalInactive", commonMetadata(nil))
if switchNode.SwitchedNode.Stats ~= nil then
local parseStats = {}
for k, stat in ipairs(switchNode.SwitchedNode.Stats) do
parseStats[stat.Id] = { min = switchNode.SwitchedNode["Stat" .. k], max = switchNode.SwitchedNode["Stat" .. k] }
end
local out, orders = describeStats(parseStats)
for k, line in ipairs(out) do
table.insert(nodeInfo["stats"], line)
end
end
end
if passiveRow.JewelSocket and ascendancyRow.UIArt.JewelFrame then
local uioverride = ascendancyRow.UIArt.JewelFrame
local uiSocketNormal = uiImages[string.lower(uioverride.Normal)]
addToSheet(getSheet("group-background"), uiSocketNormal.path, "frame", commonMetadata(nil))
local uiSocketActive = uiImages[string.lower(uioverride.Active)]
addToSheet(getSheet("group-background"), uiSocketActive.path, "frame", commonMetadata(nil))
local uiSocketCanAllocate = uiImages[string.lower(uioverride.CanAllocate)]
addToSheet(getSheet("group-background"), uiSocketCanAllocate.path, "frame", commonMetadata(nil))
nodeInfo.nodeOverlay = {
alloc = uiSocketActive.path,
path = uiSocketCanAllocate.path,
unalloc = uiSocketNormal.path,
}
elseif node["isOnlyImage"] == nil then
local typeFrame = nodeInfo["isNotable"] and "Large" or "Small"
nodeInfo.nodeOverlay = {
alloc = ascendancyRow.Name .. "Frame" .. typeFrame .. "Allocated",
path = ascendancyRow.Name .. "Frame" .. typeFrame .. "CanAllocate",
unalloc = ascendancyRow.Name .. "Frame" .. typeFrame .. "Normal",
}
end
node["options"] = {
[ascedancyReplacements[passiveRow.Ascendancy.Name]] = nodeInfo
}
end
if #passiveRow.ClassStart > 0 then
node["classesStart"] = {}
for _, classStart in ipairs(passiveRow.ClassStart) do
if classStart.Name:find(ignoreFilter) == nil then
table.insert(node["classesStart"], classStart.Name)
end
end
end
if passiveRow.MultipleChoice then
node["isMultipleChoice"] = true
end
if passiveRow.MultipleChoiceOption then
node["isMultipleChoiceOption"] = true
end
if passiveRow["FreeAllocate"] == true then
node["isFreeAllocate"] = true
end
if passiveRow["ApplyToArmour?"] == true then
node["applyToArmour"] = true
end
end
for k, connection in ipairs(passive.connections) do
table.insert(node.connections, {
id = connection.id,
orbit = connection.radius,
})
end
orbits[passive.radius + 1] = true
orbitsConstants[passive.radius + 1] = math.max(orbitsConstants[passive.radius + 1] or 1, passive.position)
tree.nodes[passive.id] = node
table.insert(treeGroup.nodes, passive.id)
:: exitNode ::
end
for orbit, _ in pairs(orbits) do
table.insert(treeGroup.orbits, orbit - 1)
end
if #treeGroup.nodes > 0 then
tree.groups[i] = treeGroup
if not groupIsAscendancy then
tree.min_x = math.min(tree.min_x, group.x)
tree.min_y = math.min(tree.min_y, group.y)
tree.max_x = math.max(tree.max_x, group.x)
tree.max_y = math.max(tree.max_y, group.y)
end
else
end
end
MakeDir(basePath .. version)
if #missingStatInfo > 0 then
local file = io.open(basePath .. version .. "/missingStats.txt", "w")
for _, line in ipairs(missingStatInfo) do
file:write(line .. "\n")
end
file:close()
end
printf("Generating jewel slots...")
local jewelSlots = dat("passivejewelslots")
for jewelSlot in jewelSlots:Rows() do
table.insert(tree.jewelSlots, jewelSlot.Passive.PassiveSkillNodeId)
end
for i, orbit in ipairs(orbitsConstants) do
orbit = i == 1 and orbit or math.ceil(orbit / 12) * 12
tree.constants.skillsPerOrbit[i] = orbit
end
for orbit, skillsInOrbit in ipairs(tree.constants.skillsPerOrbit) do
tree.constants.orbitAnglesByOrbit[orbit] = CalcOrbitAngles(skillsInOrbit)
end
local widthTree, heightTree = tree.max_x - tree.min_x, tree.max_y - tree.min_y
local radiusTree = (math.max(widthTree, heightTree) + 700) / 2
local arcAngle = { [0] = 0, [1] = 0, [2] = 12, [3] = 24, [4] = 36, [5] = 48, [6] = 60, [7] = 72, [8] = 84, [9] = 96 }
for i, classId in ipairs(psg.passives) do
local nodeStart = tree.nodes[classId]
local group = tree.groups[nodeStart.group]
local angleToCenter = math.atan2(group.y, group.x)
local hardCoded = radiusTree + 2800
local total = 0
local classes = {}
for _, class in ipairs(tree.classes) do
for _, nodeClasses in ipairs(nodeStart.classesStart) do
if nodeClasses == class.name then
total = total + #class.ascendancies
table.insert(classes, class)
end
end
end
local startAngle = angleToCenter - math.rad(arcAngle[total] / 2)
local angleStep = math.rad(arcAngle[total] / (total - 1)) or 0
local j = 1
for _, class in ipairs(classes) do
for _, ascendancy in ipairs(class.ascendancies) do
local angle = startAngle + (j - 1) * angleStep
local cX = hardCoded * math.cos(angle)
local cY = hardCoded * math.sin(angle)
local info = ascendancyGroups[ascendancy.id]
if info == nil then
printf("Ascendancy group " .. ascendancy.id .. " not found")
goto continuepositioning
end
local ascendancyNode = tree.nodes[info.startId]
if ascendancyNode == nil then
printf("Ascendancy node " .. ascendancy.id .. " not found")
goto continuepositioning
end
ascendancy.background.x = cX
ascendancy.background.y = cY
local groupAscendancy = tree.groups[ascendancyNode.group]
local innerRadious = dat("ascendancy"):GetRow("Id", ascendancy.internalId).distanceTree
local newInnerX = cX + math.cos(angleToCenter) * innerRadious
local newInnerY = cY + math.sin(angleToCenter) * innerRadious
local nodeAngle = tree.constants.orbitAnglesByOrbit[ascendancyNode.orbit + 1][ascendancyNode.orbitIndex + 1]
local orbitRadius = tree.constants.orbitRadii[ascendancyNode.orbit + 1]
local newX = newInnerX - math.sin(nodeAngle) * orbitRadius
local newY = newInnerY + math.cos(nodeAngle) * orbitRadius
local offsetX = newX - groupAscendancy.x
local offsetY = newY - groupAscendancy.y
for groupId, value in pairs(info) do
if type(value) == "boolean" then
local group = tree.groups[groupId]
group.x = group.x + offsetX
group.y = group.y + offsetY
tree.min_x = math.min(tree.min_x, group.x - hardCoded / 2)
tree.min_y = math.min(tree.min_y, group.y - hardCoded / 2)
tree.max_x = math.max(tree.max_x, group.x + hardCoded / 2)
tree.max_y = math.max(tree.max_y, group.y + hardCoded / 2)
end
end
j = j + 1
:: continuepositioning ::
end
end
end
printf("Fixing replace ascendancies position...")
for from, to in pairs(ascedancyReplacements) do
local fromAscendancy
local toAscendancy
for _, class in ipairs(tree.classes) do
for _, ascendancy in ipairs(class.ascendancies) do
if ascendancy.name == from then
fromAscendancy = ascendancy
end
if ascendancy.name == to then
toAscendancy = ascendancy
end
end
end
if fromAscendancy == nil then
printf("From ascendancy " .. from .. " not found")
goto continuereplace
end
if toAscendancy == nil then
printf("To ascendancy " .. to .. " not found")
goto continuereplace
end
fromAscendancy.replaceBy = to
toAscendancy.background.x = fromAscendancy.background.x
toAscendancy.background.y = fromAscendancy.background.y
:: continuereplace ::
end
extractSheetFiles(sheets, use4kIfPossible, linesDds)
for i, sheet in ipairs(sheets) do
calculateDDSPack(sheet, main.ggpk.oozPath, basePath .. version .. "/", use4kIfPossible)
for file, fileInfo in pairs(sheet.ddsCoords) do
tree.ddsCoords[file] = fileInfo
end
end
printf("Convert DDS line files to PNG...")
nvtt.ExportDDSToPng(main.ggpk.oozPath, basePath .. version .. "/", "lines", linesDds, true)
for _, lines in ipairs(linesFiles) do
lines.file = lines.file:gsub(".dds", ".png")
lines.mask = lines.mask:gsub(".dds", ".png")
end
gimpBatch.extract_lines_from_image("lines_extract", linesFiles, main.ggpk.oozPath, basePath .. version .. "/", GetScriptPath() .. "/Tree/GimpBatch/extract_lines.scm", generateAssets)
for _, lines in ipairs(linesFiles) do
local curveOrbitFile = 9
for i = 0, lines.total - 1 do
local name
local middle
if i == 0 then
name = lines.first .. lines.postfix
middle = 0
elseif i == 3 then
curveOrbitFile = curveOrbitFile - 1
middle = curveOrbitFile
curveOrbitFile = curveOrbitFile - 1
name = lines.prefix .. i .. lines.postfix
elseif i == 7 then
middle = 7
name = lines.prefix .. i .. lines.postfix
else
name = lines.prefix .. i .. lines.postfix
middle = curveOrbitFile
curveOrbitFile = curveOrbitFile - 1
end
tree.assets[name] = {
lines.basename .. middle .. lines.extension
}
end
end
printf("Generating lua tree file")
local out, err = io.open(fileTree, "w")
if out == nil then
printf("Error opening file " .. fileTree)
printf(err)
return
end
out:write('return ')
writeLuaTable(out, tree, 1)
out:close()
local fileTreeJson = fileTree:gsub(".lua", ".json")
printf("Generating JSON tree file")
local out, err = io.open(fileTreeJson, "w")
if out == nil then
printf("Error opening file " .. fileTreeJson)
printf(err)
return
end
out:write(json.encode(tree))
out:close()
printf("Skill tree generated")
:: final ::