|
|
|
@ -22,15 +22,21 @@ local id_counter = 0 |
|
|
|
|
-- Table containing the currently active contraptions keyed by their id. |
|
|
|
|
local active_contraptions = {} |
|
|
|
|
|
|
|
|
|
-- TODO: Push along items. |
|
|
|
|
-- TODO: Push along players. |
|
|
|
|
-- TODO: Friction depending on ground. |
|
|
|
|
-- TODO: Gravity. |
|
|
|
|
-- TODO: Water physics! |
|
|
|
|
-- TODO: Rotation. |
|
|
|
|
-- TODO: Sound. |
|
|
|
|
|
|
|
|
|
-- TODO: Push items in a pyramid. |
|
|
|
|
-- TODO: Damage players colliding with fast-moving contraption. |
|
|
|
|
-- TODO: Damage players when squishing them into a wall. |
|
|
|
|
-- TODO: Allow glueing separate contraptions together? |
|
|
|
|
|
|
|
|
|
-- TODO: Unload and save contraptions when they're in unloaded chunks. |
|
|
|
|
-- TODO: Use meta field `infotext` for displaying debug info about a contraption? |
|
|
|
|
-- TODO: Use voxel manipulator for moving contraptions. |
|
|
|
|
|
|
|
|
|
local function error(...) |
|
|
|
|
local str = "[" .. mod_name .. "] " |
|
|
|
@ -109,6 +115,13 @@ function contraption.find(pos) |
|
|
|
|
return nil |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
-- Returns if the specified node is pushable by a contraption, like items. |
|
|
|
|
function contraption.is_pushable(node) |
|
|
|
|
local def = minetest.registered_nodes[node.name] |
|
|
|
|
if def and def.groups and def.groups.is_stack_only then return true end |
|
|
|
|
return false |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
------------------------------------- |
|
|
|
|
-- CONTRAPTION TYPE IMPLEMENTATION -- |
|
|
|
|
------------------------------------- |
|
|
|
@ -157,10 +170,28 @@ function contraption.create(region) |
|
|
|
|
minetest.set_node(abs_pos, node) |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
result:on_initialize() |
|
|
|
|
active_contraptions[result.id] = result |
|
|
|
|
return result |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
function contraption:on_initialize() |
|
|
|
|
local above_nodes = {} |
|
|
|
|
for orig_pos_hash, node in pairs(self.nodes) do |
|
|
|
|
local orig_pos = minetest.get_position_from_hash(orig_pos_hash) |
|
|
|
|
|
|
|
|
|
-- Calculate the nodes above the contraption that it |
|
|
|
|
-- could potentially pull along with it as it moves. |
|
|
|
|
local above_pos = vector.offset(orig_pos, 0, 1, 0) |
|
|
|
|
local above_pos_hash = minetest.hash_node_position(above_pos) |
|
|
|
|
if not self.nodes[above_pos_hash] then |
|
|
|
|
-- Non-contraption block above this one. Move items here along. |
|
|
|
|
table_insert(above_nodes, above_pos) |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
self.above_nodes = above_nodes |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
function contraption.load_from_string(id, str) |
|
|
|
|
local obj = minetest.deserialize(str) |
|
|
|
|
|
|
|
|
@ -180,6 +211,7 @@ function contraption.load_from_string(id, str) |
|
|
|
|
partial = obj.partial and vector.copy(obj.partial) or nil, |
|
|
|
|
}, metatable) |
|
|
|
|
|
|
|
|
|
result:on_initialize() |
|
|
|
|
active_contraptions[id] = result |
|
|
|
|
return obj |
|
|
|
|
end |
|
|
|
@ -279,13 +311,23 @@ function contraption:update(delta_time) |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
function contraption:move(offset) |
|
|
|
|
local to_set = {} -- Nodes that need to be modified. |
|
|
|
|
local to_clear = {} -- Nodes to be cleared (to AIR). |
|
|
|
|
local to_push = {} -- Nodes that want to be pushed. |
|
|
|
|
local to_set = {} -- Nodes that will be modified. |
|
|
|
|
|
|
|
|
|
-- Assume that every node this contraption occupies needs to be cleared. |
|
|
|
|
-- We'll remove entries from `to_clear` when we know a node is moved there. |
|
|
|
|
for pos_hash in pairs(self.nodes) do to_clear[pos_hash] = AIR end |
|
|
|
|
|
|
|
|
|
for _, rel_pos in ipairs(self.above_nodes) do |
|
|
|
|
local abs_pos = self.region.min + rel_pos |
|
|
|
|
local node = minetest.get_node(abs_pos) |
|
|
|
|
if contraption.is_pushable(node) then |
|
|
|
|
local pos_hash = minetest.hash_node_position(rel_pos) |
|
|
|
|
to_push[pos_hash] = { node = node, meta = minetest.get_meta(abs_pos):to_table() } |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
for old_rel_pos_hash, node in pairs(self.nodes) do |
|
|
|
|
local old_rel_pos = minetest.get_position_from_hash(old_rel_pos_hash) |
|
|
|
|
local old_abs_pos = vector.add(self.region.min, old_rel_pos) |
|
|
|
@ -304,7 +346,39 @@ function contraption:move(offset) |
|
|
|
|
end |
|
|
|
|
else |
|
|
|
|
local new_node = minetest.get_node(new_abs_pos) |
|
|
|
|
if nodecore.buildable_to(new_node) then |
|
|
|
|
-- TODO: Could also just check `to_push` first instead of checking the node. |
|
|
|
|
if contraption.is_pushable(new_node) then |
|
|
|
|
new_node = { node = new_node, meta = minetest.get_meta(new_abs_pos):to_table() } |
|
|
|
|
to_set[new_rel_pos_hash] = node |
|
|
|
|
while true do |
|
|
|
|
new_rel_pos = new_rel_pos + offset |
|
|
|
|
new_rel_pos_hash = minetest.hash_node_position(new_rel_pos) |
|
|
|
|
local pushable = to_push[new_rel_pos_hash] |
|
|
|
|
if pushable then |
|
|
|
|
-- The next node is a pushable node that was going to |
|
|
|
|
-- get pushed by the contraption anyway, so continue. |
|
|
|
|
to_set[new_rel_pos_hash] = new_node |
|
|
|
|
to_push[new_rel_pos_hash] = nil |
|
|
|
|
new_node = pushable |
|
|
|
|
elseif self.nodes[new_rel_pos_hash] then |
|
|
|
|
-- The next node is part of the contraption. |
|
|
|
|
-- If it can move, this pushable node can move. |
|
|
|
|
to_set[new_rel_pos_hash] = new_node |
|
|
|
|
-- Do not clear this node by the contraption moving. |
|
|
|
|
to_clear[new_rel_pos_hash] = nil |
|
|
|
|
break |
|
|
|
|
elseif nodecore.buildable_to(self.region.min + new_rel_pos) then |
|
|
|
|
-- The next node is replaceable, so we can replace it. |
|
|
|
|
to_set[new_rel_pos_hash] = new_node |
|
|
|
|
break |
|
|
|
|
else |
|
|
|
|
-- There's a node in the way, abort! |
|
|
|
|
-- NOTE: Can't push a pushable node with another |
|
|
|
|
-- one if already pushed by the contraption. |
|
|
|
|
return false |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
elseif nodecore.buildable_to(new_node) then |
|
|
|
|
to_set[new_rel_pos_hash] = node |
|
|
|
|
else |
|
|
|
|
-- We bumped into a wall or something. |
|
|
|
@ -313,11 +387,51 @@ function contraption:move(offset) |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
-- The `to_push` table contains "lose" nodes that are not directly |
|
|
|
|
-- pushed by the contraption, and may or may not be pushed. |
|
|
|
|
for pos_hash, node in pairs(to_push) do |
|
|
|
|
local rel_pos = minetest.get_position_from_hash(pos_hash) |
|
|
|
|
local forward_rel_pos = vector.add(rel_pos, offset) |
|
|
|
|
local forward_rel_pos_hash = minetest.hash_node_position(forward_rel_pos) |
|
|
|
|
|
|
|
|
|
-- If there's a node to push ahead of this one, skip. |
|
|
|
|
-- The node ahead will take care of pulling the ones behind it along with it. |
|
|
|
|
if to_push[forward_rel_pos_hash] then goto continue end |
|
|
|
|
|
|
|
|
|
if self.nodes[forward_rel_pos] then |
|
|
|
|
-- Do not clear this node by the contraption moving. |
|
|
|
|
to_clear[forward_rel_pos] = nil |
|
|
|
|
else |
|
|
|
|
-- If the node(s) can't be pushed, then just don't, but continue |
|
|
|
|
-- moving the rest of the contraption and other pushable nodes. |
|
|
|
|
local forward_abs_pos = vector.add(self.region.min, forward_rel_pos) |
|
|
|
|
if not nodecore.buildable_to(forward_abs_pos) then goto continue end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
to_set[forward_rel_pos_hash] = node |
|
|
|
|
|
|
|
|
|
while true do |
|
|
|
|
local backward_rel_pos = vector.add(rel_pos, -offset) |
|
|
|
|
local backward_rel_pos_hash = minetest.hash_node_position(backward_rel_pos) |
|
|
|
|
node = to_push[backward_rel_pos_hash] |
|
|
|
|
if not node then break end |
|
|
|
|
|
|
|
|
|
to_set[pos_hash] = node |
|
|
|
|
rel_pos = backward_rel_pos |
|
|
|
|
pos_hash = backward_rel_pos_hash |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
to_clear[pos_hash] = AIR |
|
|
|
|
::continue:: |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
-- Set nodes that need to be changed. |
|
|
|
|
-- TODO: Can this use a vector key instead of hash, or an array? |
|
|
|
|
for pos_hash, node in pairs(to_set) do |
|
|
|
|
local rel_pos = minetest.get_position_from_hash(pos_hash) |
|
|
|
|
local abs_pos = vector.add(self.region.min, rel_pos) |
|
|
|
|
minetest.set_node(abs_pos, node) |
|
|
|
|
minetest.set_node(abs_pos, node.node or node) |
|
|
|
|
if node.meta then minetest.get_meta(abs_pos):from_table(node.meta) end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
-- Clear nodes that need to be cleared (to AIR). |
|
|
|
|