Move items with contraption

main
copygirl 1 year ago
parent f4e9ac9af6
commit 6b0326af1f
  1. 122
      contraption.lua

@ -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).

Loading…
Cancel
Save