From 6b0326af1fa28360ab6e1cec4935daea92b66a50 Mon Sep 17 00:00:00 2001 From: copygirl Date: Fri, 6 Oct 2023 23:21:59 +0200 Subject: [PATCH] Move items with contraption --- contraption.lua | 122 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 118 insertions(+), 4 deletions(-) diff --git a/contraption.lua b/contraption.lua index f3a72be..60f60bc 100755 --- a/contraption.lua +++ b/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).