2024-06-23 13:13:16 +00:00
|
|
|
local Tactree = {}
|
2024-06-24 18:02:39 +00:00
|
|
|
local Nodes = {}
|
2024-06-23 13:13:16 +00:00
|
|
|
|
|
|
|
local function NodeStart(node, args)
|
2024-06-30 22:23:36 +00:00
|
|
|
-- don't start again if started
|
2024-07-12 12:19:42 +00:00
|
|
|
if node._started then return end
|
2024-06-23 13:13:16 +00:00
|
|
|
|
2024-07-12 12:19:42 +00:00
|
|
|
node.data = node.parent and node.parent.data or args or node.data
|
2024-06-24 18:03:59 +00:00
|
|
|
node.data[node] = node.data[node] or {}
|
2024-06-23 13:13:16 +00:00
|
|
|
node:_start()
|
2024-07-12 12:19:42 +00:00
|
|
|
|
|
|
|
node._started = true
|
2024-06-23 13:13:16 +00:00
|
|
|
end
|
|
|
|
|
2024-07-12 12:19:42 +00:00
|
|
|
local function NodeUpdate(node, ...)
|
|
|
|
-- automatically start if not started
|
|
|
|
if not node._started then NodeStart(node) end
|
2024-06-23 13:13:16 +00:00
|
|
|
|
2024-07-12 12:19:42 +00:00
|
|
|
local result = node:_tick(...)
|
2024-06-23 13:13:16 +00:00
|
|
|
|
|
|
|
if type(result) == 'boolean' then
|
|
|
|
node:_finish()
|
|
|
|
|
2024-07-12 12:19:42 +00:00
|
|
|
node._started = false
|
2024-06-23 13:13:16 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
return result
|
|
|
|
end
|
|
|
|
|
2024-06-24 03:53:32 +00:00
|
|
|
local function NodePrivateData(node)
|
|
|
|
return node.data[node]
|
|
|
|
end
|
|
|
|
|
2024-06-23 13:13:16 +00:00
|
|
|
local function default_impl(node) end
|
2024-06-24 03:53:32 +00:00
|
|
|
function Tactree.Leaf(name)
|
2024-06-23 13:13:16 +00:00
|
|
|
return function(t)
|
2024-06-24 18:02:39 +00:00
|
|
|
if Nodes[name] then
|
2024-06-23 13:13:16 +00:00
|
|
|
error('node with name \'' .. name .. '\' already exists.', 2)
|
|
|
|
else
|
2024-06-24 18:02:39 +00:00
|
|
|
Nodes[name] = {}
|
2024-06-23 13:13:16 +00:00
|
|
|
end
|
|
|
|
|
2024-06-24 18:02:39 +00:00
|
|
|
local node_type = Nodes[name]
|
2024-06-23 13:13:16 +00:00
|
|
|
|
|
|
|
local tick = t.tick or t[1]
|
|
|
|
if type(tick) ~= 'function' then error('no tick function supplied', 2) end
|
|
|
|
|
|
|
|
node_type._tick = tick
|
|
|
|
node_type._start = type(t.start) == 'function' and t.start or default_impl
|
|
|
|
node_type._finish = type(t.finish) == 'function' and t.finish or default_impl
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-06-24 03:53:32 +00:00
|
|
|
function Tactree.Composite(name)
|
|
|
|
return function(parent_type_name)
|
|
|
|
return function(children)
|
2024-06-24 18:02:39 +00:00
|
|
|
if Nodes[name] then
|
2024-06-24 03:53:32 +00:00
|
|
|
error('node with name \'' .. name .. '\' already exists.', 2)
|
|
|
|
else
|
2024-06-24 18:02:39 +00:00
|
|
|
if not Nodes[parent_type_name] then error('no such node \'' .. name .. '\'', 2) end
|
|
|
|
Nodes[name] = setmetatable({}, { __index = Nodes[parent_type_name] })
|
2024-06-24 03:53:32 +00:00
|
|
|
end
|
|
|
|
|
2024-06-24 18:02:39 +00:00
|
|
|
local node_type = Nodes[name]
|
2024-06-24 03:53:32 +00:00
|
|
|
|
|
|
|
node_type.children = {}
|
|
|
|
|
|
|
|
for _, child in ipairs(children) do
|
|
|
|
if type(child) ~= 'string' then error('invalid child node', 2) end
|
|
|
|
|
|
|
|
table.insert(node_type.children, child)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-07-12 12:19:42 +00:00
|
|
|
local function tree_by_name(name, depth)
|
|
|
|
depth = depth or 2
|
|
|
|
|
|
|
|
if not Nodes[name] then error('no such node \'' .. name .. '\'', depth) end
|
2024-06-23 13:13:16 +00:00
|
|
|
|
2024-06-24 03:53:32 +00:00
|
|
|
local node = {}
|
2024-06-23 13:13:16 +00:00
|
|
|
node.children = {}
|
|
|
|
node.start = NodeStart
|
|
|
|
node.update = NodeUpdate
|
2024-06-24 03:53:32 +00:00
|
|
|
node.private = NodePrivateData
|
2024-06-23 13:13:16 +00:00
|
|
|
|
2024-06-24 18:02:39 +00:00
|
|
|
-- this will crash if there is a recursive structure
|
|
|
|
-- find a way to prevent this?
|
|
|
|
if Nodes[name].children then
|
|
|
|
for _, child in ipairs(Nodes[name].children) do
|
2024-07-12 12:19:42 +00:00
|
|
|
local cn = tree_by_name(child, depth + 1)
|
2024-06-24 18:02:39 +00:00
|
|
|
cn.parent = node
|
|
|
|
table.insert(node.children, cn)
|
2024-06-23 13:13:16 +00:00
|
|
|
end
|
|
|
|
end
|
2024-06-24 03:53:32 +00:00
|
|
|
|
2024-06-30 22:23:36 +00:00
|
|
|
return setmetatable(node, { __index = Nodes[name], __tostring = function() return name end })
|
2024-06-23 13:13:16 +00:00
|
|
|
end
|
|
|
|
|
2024-07-12 12:19:42 +00:00
|
|
|
function Tactree.Tree(name)
|
|
|
|
if not Nodes[name] then error('no such node \'' .. name .. '\'', 2) end
|
2024-06-24 18:02:39 +00:00
|
|
|
|
2024-07-12 12:19:42 +00:00
|
|
|
return function(children)
|
|
|
|
local node = {}
|
|
|
|
node.children = {}
|
|
|
|
node.start = NodeStart
|
|
|
|
node.update = NodeUpdate
|
|
|
|
node.private = NodePrivateData
|
|
|
|
|
|
|
|
-- add pre-defined children
|
|
|
|
if Nodes[name].children then
|
|
|
|
for _, child in ipairs(Nodes[name].children) do
|
|
|
|
local cn = tree_by_name(child, 3)
|
|
|
|
cn.parent = node
|
|
|
|
table.insert(node.children, cn)
|
2024-06-24 18:02:39 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-07-12 12:19:42 +00:00
|
|
|
-- add extra children from call
|
|
|
|
for _, child in ipairs(children) do
|
|
|
|
local cn
|
|
|
|
if type(child) == 'string' then
|
|
|
|
cn = tree_by_name(child, 3)
|
|
|
|
elseif type(child) == 'table' then
|
|
|
|
cn = child
|
2024-06-27 19:31:21 +00:00
|
|
|
else
|
2024-07-12 12:19:42 +00:00
|
|
|
error('invalid child type: \'' .. type(child) '\'', 3)
|
2024-06-27 19:31:21 +00:00
|
|
|
end
|
|
|
|
|
2024-07-12 12:19:42 +00:00
|
|
|
cn.parent = node
|
|
|
|
table.insert(node.children, cn)
|
|
|
|
end
|
2024-06-30 22:23:36 +00:00
|
|
|
|
2024-07-12 12:19:42 +00:00
|
|
|
return setmetatable(node, { __index = Nodes[name], __tostring = function() return name end })
|
2024-06-30 22:23:36 +00:00
|
|
|
end
|
2024-07-12 12:19:42 +00:00
|
|
|
end
|
2024-06-30 22:23:36 +00:00
|
|
|
|
2024-07-12 12:19:42 +00:00
|
|
|
Tactree.Leaf 'Sequence'
|
2024-06-24 18:02:39 +00:00
|
|
|
{
|
|
|
|
start = function(node)
|
2024-07-12 12:19:42 +00:00
|
|
|
node:private().current_child = 1
|
2024-06-24 18:02:39 +00:00
|
|
|
end,
|
2024-07-12 12:19:42 +00:00
|
|
|
tick = function(node, ...)
|
|
|
|
local result
|
|
|
|
repeat
|
|
|
|
result = node.children[node:private().current_child]:update(...)
|
|
|
|
if result then
|
|
|
|
node:private().current_child = node:private().current_child + 1
|
2024-06-27 19:31:21 +00:00
|
|
|
end
|
2024-07-12 12:19:42 +00:00
|
|
|
until (not result) or (node:private().current_child > #node.children)
|
|
|
|
|
|
|
|
return result
|
2024-06-27 19:31:21 +00:00
|
|
|
end
|
|
|
|
}
|
|
|
|
|
2024-07-12 12:19:42 +00:00
|
|
|
Tactree.Leaf 'Selector'
|
2024-06-27 19:31:21 +00:00
|
|
|
{
|
|
|
|
start = function(node)
|
2024-07-12 12:19:42 +00:00
|
|
|
node:private().current_child = 1
|
2024-06-27 19:31:21 +00:00
|
|
|
end,
|
2024-07-12 12:19:42 +00:00
|
|
|
tick = function(node, ...)
|
|
|
|
local result
|
|
|
|
repeat
|
|
|
|
result = node.children[node:private().current_child]:update(...)
|
2024-06-27 19:31:21 +00:00
|
|
|
if result then
|
2024-07-12 12:19:42 +00:00
|
|
|
node:private().current_child = node:private().current_child + 1
|
2024-06-27 19:31:21 +00:00
|
|
|
end
|
2024-07-12 12:19:42 +00:00
|
|
|
until (result ~= false) or (node:private().current_child > #node.children)
|
|
|
|
|
|
|
|
return result
|
2024-06-24 18:02:39 +00:00
|
|
|
end
|
|
|
|
}
|
|
|
|
|
2024-07-12 12:19:42 +00:00
|
|
|
Tactree.Leaf 'Inverter'
|
2024-06-27 19:31:21 +00:00
|
|
|
{
|
|
|
|
start = function(node)
|
|
|
|
node.children[1]:start()
|
|
|
|
end,
|
2024-07-12 12:19:42 +00:00
|
|
|
tick = function(node, ...)
|
|
|
|
local result = node.children[1]:update(...)
|
2024-06-27 19:31:21 +00:00
|
|
|
|
2024-07-12 12:19:42 +00:00
|
|
|
if type(result) == 'boolean' then return not result end
|
2024-06-27 19:31:21 +00:00
|
|
|
end
|
|
|
|
}
|
|
|
|
|
2024-06-23 13:13:16 +00:00
|
|
|
--[[
|
2024-06-24 03:53:32 +00:00
|
|
|
Example usage
|
|
|
|
-------------
|
|
|
|
|
|
|
|
Tactree.Leaf 'Find nearest object' { function (node) ... end }
|
|
|
|
Tactree.Leaf 'Pick up object' { function (node) ... end}
|
|
|
|
Tactree.Leaf 'Move' { function (node) return node.data.creature:move(node.data.tx, node.data.ty, dt) end }
|
|
|
|
Tactree.Composite 'Pick up nearest object' 'Sequence' { 'Find nearest object', 'Move', 'Pick up object' }
|
2024-06-23 13:13:16 +00:00
|
|
|
|
2024-06-24 03:53:32 +00:00
|
|
|
local tr = Tactree.Tree 'Pick up nearest object'
|
2024-06-23 13:13:16 +00:00
|
|
|
|
2024-06-24 03:53:32 +00:00
|
|
|
tr:start{ ... } ( or, if you already have a table, tr:start(existing_table) )
|
2024-06-23 13:13:16 +00:00
|
|
|
|
|
|
|
t:update()
|
|
|
|
]]--
|
|
|
|
|
|
|
|
return Tactree
|