Module:Sandbox/SkillTree: Difference between revisions
From Melvor Idle
(def not chatgpt) |
No edit summary |
||
(8 intermediate revisions by the same user not shown) | |||
Line 42: | Line 42: | ||
local args = frame.args ~= nil and frame.args or frame | local args = frame.args ~= nil and frame.args or frame | ||
local skillName = args[1] | local skillName = args[1] | ||
if not skillName then | if not skillName then | ||
return "Invalid skillName" | return "Invalid skillName" | ||
end | end | ||
local skillNodes = p.getSkillTreeNodesFromSkillName(skillName) | local skillNodes = p.getSkillTreeNodesFromSkillName(skillName) | ||
if not skillNodes or #skillNodes == 0 then | if not skillNodes or #skillNodes == 0 then | ||
return "No skill tree found for: " .. skillName | return "No skill tree found for: " .. skillName | ||
end | |||
-- Helper function to calculate the level of each node | |||
local function calculateNodeLevels(nodes) | |||
local levels = {} | |||
local function findLevel(node, currentLevel) | |||
if not levels[node.name] then | |||
levels[node.name] = currentLevel | |||
else | |||
levels[node.name] = math.max(levels[node.name], currentLevel) | |||
end | |||
-- Recursively set level for child nodes | |||
if node.children then | |||
for _, child in ipairs(node.children) do | |||
findLevel(child, currentLevel + 1) | |||
end | |||
end | |||
end | |||
-- Start with nodes that have no parents (root nodes) | |||
for _, node in ipairs(nodes) do | |||
if not node.parents or #node.parents == 0 then | |||
findLevel(node, 0) -- Root nodes start at level 0 | |||
end | |||
end | |||
return levels | |||
end | |||
-- Calculate levels for each node | |||
local nodeLevels = calculateNodeLevels(skillNodes) | |||
-- Calculate maxLevel based on the calculated levels | |||
local maxLevel = 0 | |||
for _, level in pairs(nodeLevels) do | |||
if level > maxLevel then | |||
maxLevel = level | |||
end | |||
end | end | ||
-- Constants for layout | -- Constants for layout | ||
local | local containerWidth = 1000 -- Adjust container width as needed | ||
local baseY = 50 -- Start Y position (top of the screen) | local baseY = 50 -- Start Y position (top of the screen) | ||
local verticalSpacing = 200 -- Distance between levels (Y-axis) | local verticalSpacing = 200 -- Distance between levels (Y-axis) | ||
local horizontalSpacing = 300 -- Distance between nodes at the same level (X-axis) | local horizontalSpacing = 300 -- Distance between nodes at the same level (X-axis) | ||
-- | -- Count nodes per level for horizontal positioning | ||
local nodesPerLevel = {} | local nodesPerLevel = {} | ||
for | for nodeName, level in pairs(nodeLevels) do | ||
if not nodesPerLevel[level] then | if not nodesPerLevel[level] then | ||
nodesPerLevel[level] = 0 | nodesPerLevel[level] = 0 | ||
Line 80: | Line 110: | ||
local html = mw.html.create('div'):addClass('skill-tree-container'):css({ | local html = mw.html.create('div'):addClass('skill-tree-container'):css({ | ||
['position'] = 'relative', | ['position'] = 'relative', | ||
['width'] = ' | ['width'] = containerWidth .. 'px', | ||
['height'] = (maxLevel + 1) * verticalSpacing .. 'px', -- Adjust height dynamically | ['height'] = (maxLevel + 1) * verticalSpacing .. 'px', -- Adjust height dynamically | ||
['margin'] = 'auto' | ['margin'] = 'auto', | ||
['border'] = '1px solid red' -- Debug border for the entire tree | |||
}) | }) | ||
Line 88: | Line 119: | ||
local svg = html:tag('svg'):attr('height', '100%'):attr('width', '100%') | local svg = html:tag('svg'):attr('height', '100%'):attr('width', '100%') | ||
:attr('preserveAspectRatio', 'none') | :attr('preserveAspectRatio', 'none') | ||
:attr('viewBox', '0 0 | :attr('viewBox', '0 0 ' .. containerWidth .. ' ' .. (maxLevel + 1) * verticalSpacing) | ||
-- | -- Track positions for each node | ||
local nodePositions = {} | local nodePositions = {} | ||
-- | -- Keep track of how many nodes we've placed on each level | ||
for | local nodeIndexPerLevel = {} | ||
local level = node.level | |||
local indexInLevel = nodesPerLevel[level] | -- Position each node dynamically | ||
for i, node in ipairs(skillNodes) do | |||
local level = nodeLevels[node.name] | |||
nodeIndexPerLevel[level] = (nodeIndexPerLevel[level] or 0) + 1 | |||
local indexInLevel = nodeIndexPerLevel[level] | |||
-- Center the nodes in each level | |||
local baseX = (containerWidth / 2) - ((nodesPerLevel[level] - 1) / 2) * horizontalSpacing | |||
-- Calculate X and Y positions for this node | -- Calculate X and Y positions for this node | ||
local xPos = baseX + (indexInLevel - | local xPos = baseX + (indexInLevel - 1) * horizontalSpacing | ||
local yPos = baseY + level * verticalSpacing | local yPos = baseY + level * verticalSpacing | ||
-- Store | -- Store node position for connections | ||
nodePositions[node.name] = { x = xPos, y = yPos } | nodePositions[node.name] = { x = xPos, y = yPos } | ||
Line 108: | Line 146: | ||
local nodeDiv = html:tag('div'):css({ | local nodeDiv = html:tag('div'):css({ | ||
['position'] = 'absolute', | ['position'] = 'absolute', | ||
['width'] = ' | ['width'] = '150px', | ||
['height'] = '100px', | ['height'] = '100px', | ||
['top'] = yPos .. 'px', | ['top'] = yPos .. 'px', | ||
Line 122: | Line 160: | ||
['font-weight'] = '700', | ['font-weight'] = '700', | ||
}):done() | }):done() | ||
-- Append nodeDiv to HTML | -- Append nodeDiv to HTML | ||
Line 159: | Line 172: | ||
local childPos = nodePositions[node.name] | local childPos = nodePositions[node.name] | ||
if parentPos and childPos then | if parentPos and childPos then | ||
local path = string.format("M%d,%d L%d,%d", parentPos.x + | local path = string.format("M%d,%d L%d,%d", parentPos.x + 75, parentPos.y + 100, childPos.x + 75, childPos.y) | ||
svg:tag('path') | svg:tag('path') | ||
:attr('d', path) | :attr('d', path) | ||
Line 178: | Line 191: | ||
return tostring(html) | return tostring(html) | ||
end | end | ||
return p | return p |
Latest revision as of 12:49, 5 September 2024
Documentation for this module may be created at Module:Sandbox/SkillTree/doc
local p = {}
local GameData = require('Module:GameData')
function p.getSkillTreeNodes(checkFunc)
local nodes = {}
for skillName, skillData in pairs(GameData.skillData) do
local skillTrees = skillData.skillTrees
if skillTrees then
for _, skillTree in ipairs(skillTrees) do
for _, node in ipairs(GameData.getEntities(skillTree.nodes, checkFunc)) do
local nodeCopy = {}
for k, v in pairs(node) do
nodeCopy[k] = v
end
nodeCopy.skillName = skillName
table.insert(nodes, nodeCopy)
end
end
end
end
return nodes
end
function p.getSkillTreeNodesFromSkillName(skillName)
mw.log(GameData.skillData)
local skillData = GameData.skillData[skillName]
if not skillData then return nil end
local nodes = {}
for _, node in ipairs(skillData.skillTrees[1].nodes) do
table.insert(nodes, node)
end
return nodes
end
function p.generateSkillTree(frame)
local args = frame.args ~= nil and frame.args or frame
local skillName = args[1]
if not skillName then
return "Invalid skillName"
end
local skillNodes = p.getSkillTreeNodesFromSkillName(skillName)
if not skillNodes or #skillNodes == 0 then
return "No skill tree found for: " .. skillName
end
-- Helper function to calculate the level of each node
local function calculateNodeLevels(nodes)
local levels = {}
local function findLevel(node, currentLevel)
if not levels[node.name] then
levels[node.name] = currentLevel
else
levels[node.name] = math.max(levels[node.name], currentLevel)
end
-- Recursively set level for child nodes
if node.children then
for _, child in ipairs(node.children) do
findLevel(child, currentLevel + 1)
end
end
end
-- Start with nodes that have no parents (root nodes)
for _, node in ipairs(nodes) do
if not node.parents or #node.parents == 0 then
findLevel(node, 0) -- Root nodes start at level 0
end
end
return levels
end
-- Calculate levels for each node
local nodeLevels = calculateNodeLevels(skillNodes)
-- Calculate maxLevel based on the calculated levels
local maxLevel = 0
for _, level in pairs(nodeLevels) do
if level > maxLevel then
maxLevel = level
end
end
-- Constants for layout
local containerWidth = 1000 -- Adjust container width as needed
local baseY = 50 -- Start Y position (top of the screen)
local verticalSpacing = 200 -- Distance between levels (Y-axis)
local horizontalSpacing = 300 -- Distance between nodes at the same level (X-axis)
-- Count nodes per level for horizontal positioning
local nodesPerLevel = {}
for nodeName, level in pairs(nodeLevels) do
if not nodesPerLevel[level] then
nodesPerLevel[level] = 0
end
nodesPerLevel[level] = nodesPerLevel[level] + 1
end
-- Container for the skill tree
local html = mw.html.create('div'):addClass('skill-tree-container'):css({
['position'] = 'relative',
['width'] = containerWidth .. 'px',
['height'] = (maxLevel + 1) * verticalSpacing .. 'px', -- Adjust height dynamically
['margin'] = 'auto',
['border'] = '1px solid red' -- Debug border for the entire tree
})
-- SVG container for the node connections
local svg = html:tag('svg'):attr('height', '100%'):attr('width', '100%')
:attr('preserveAspectRatio', 'none')
:attr('viewBox', '0 0 ' .. containerWidth .. ' ' .. (maxLevel + 1) * verticalSpacing)
-- Track positions for each node
local nodePositions = {}
-- Keep track of how many nodes we've placed on each level
local nodeIndexPerLevel = {}
-- Position each node dynamically
for i, node in ipairs(skillNodes) do
local level = nodeLevels[node.name]
nodeIndexPerLevel[level] = (nodeIndexPerLevel[level] or 0) + 1
local indexInLevel = nodeIndexPerLevel[level]
-- Center the nodes in each level
local baseX = (containerWidth / 2) - ((nodesPerLevel[level] - 1) / 2) * horizontalSpacing
-- Calculate X and Y positions for this node
local xPos = baseX + (indexInLevel - 1) * horizontalSpacing
local yPos = baseY + level * verticalSpacing
-- Store node position for connections
nodePositions[node.name] = { x = xPos, y = yPos }
-- Node container
local nodeDiv = html:tag('div'):css({
['position'] = 'absolute',
['width'] = '150px',
['height'] = '100px',
['top'] = yPos .. 'px',
['left'] = xPos .. 'px',
['background-color'] = '#f5f5f5',
['border'] = '2px solid black',
['text-align'] = 'center'
})
-- Node title (e.g., skill name)
nodeDiv:tag('span'):wikitext(node.name):css({
['font-size'] = '14px',
['font-weight'] = '700',
}):done()
-- Append nodeDiv to HTML
html:done()
end
-- Draw connections between nodes
for _, node in ipairs(skillNodes) do
if node.parents then
for _, parent in ipairs(node.parents) do
local parentPos = nodePositions[parent.name]
local childPos = nodePositions[node.name]
if parentPos and childPos then
local path = string.format("M%d,%d L%d,%d", parentPos.x + 75, parentPos.y + 100, childPos.x + 75, childPos.y)
svg:tag('path')
:attr('d', path)
:css({
['stroke'] = 'yellow', -- Connection color (could be dynamic)
['stroke-width'] = '3',
['fill'] = 'none'
})
:done()
end
end
end
end
-- Close the SVG tag
svg:done()
return tostring(html)
end
return p