Compare commits

...

2 Commits

  1. 1
      CONCEPT.md
  2. 376
      contraption.lua
  3. 2
      init.lua
  4. 8
      region.lua
  5. 2
      search.lua

@ -3,6 +3,7 @@
## Progression
- Assemble wooden contraption inside a rectangle of frames
- Move items with contraption
- Rotate a contraption
- Take a contraption to water
- Use a shovel to propel a contraption on water

@ -102,10 +102,7 @@ end
function contraption.find(pos)
-- First check if the node at `pos` is a contraption block.
for _, c in pairs(active_contraptions) do
if c.region:contains(pos) then
local rel_pos = c.region:to_relative(pos)
if c.nodes[rel_pos:hash()] then return c end
end
if c:get_node_data(pos) then return c end
end
return nil
end
@ -127,13 +124,13 @@ contraption.metatable = metatable
function contraption.create(r)
r = region.copy(r)
local nodes = {}
local num_nodes = 0
for pos in r:iter_node_positions() do
local node = minetest.get_node(pos)
local node_data = {}
for abs_pos in r:iter_node_positions() do
local node = minetest.get_node(abs_pos)
if nodecore.match(node, PLANK) then
local offset = r:to_relative(pos)
nodes[offset:hash()] = CONTRAPTION_WOOD
local rel_pos = r:to_relative(abs_pos)
node_data[rel_pos:hash()] = { rel_pos = rel_pos, node = CONTRAPTION_WOOD }
num_nodes = num_nodes + 1
end
end
@ -146,37 +143,33 @@ function contraption.create(r)
-- TODO: Flood fill algorithm to make sure all nodes are connected.
-- Change nodes from base nodes to their contraption version.
for _, data in pairs(node_data) do
minetest.set_node(data.pos, data.node)
end
id_counter = id_counter + 1
local result = setmetatable({
id = id_counter,
region = r,
nodes = nodes,
id = id_counter,
region = r,
num_nodes = num_nodes,
node_data = node_data,
}, metatable)
-- Change nodes from base nodes to their contraption version.
for pos_hash, node in pairs(nodes) do
local rel_pos = vector.from_hash(pos_hash)
local abs_pos = r:to_absolute(rel_pos)
minetest.set_node(abs_pos, node)
end
result:on_initialize()
active_contraptions[result.id] = result
result:on_initialize()
return result
end
function contraption:on_initialize()
-- Calculate the nodes above the contraption that it
-- could potentially pull along with it as it moves.
local above_nodes = {}
for orig_pos_hash, node in pairs(self.nodes) do
local orig_pos = vector.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 = orig_pos:offset(0, 1, 0)
if not self.nodes[above_pos:hash()] then
for pos_hash, data in pairs(self.node_data) do
local above_pos = data.pos:offset(0, 1, 0)
if not self:get_node_data(above_pos) then
-- Non-contraption block above this one. Move items here along.
table_insert(above_nodes, above_pos)
above_nodes[pos_hash] = { pos = above_pos }
end
end
self.above_nodes = above_nodes
@ -190,11 +183,21 @@ function contraption.load_from_string(id, str)
return nil
end
-- TODO: Remove this temporary backward-compatibility.
obj.node_data = obj.node_data or obj.nodes
-- Recover "unnecessary" data.
obj.region = region.copy(obj.region)
for pos_hash, node in pairs(obj.node_data) do
local rel_pos = vector.from_hash(pos_hash)
obj.node_data[pos_hash] = { rel_pos = rel_pos, node = node }
end
local result = setmetatable({
id = id,
region = region.copy(obj.region),
nodes = obj.nodes,
region = obj.region,
node_data = obj.node_data,
num_nodes = obj.num_nodes,
motion = obj.motion and vector.copy(obj.motion ) or nil,
@ -207,12 +210,16 @@ function contraption.load_from_string(id, str)
end
function contraption:save_to_string()
local node_data = {} -- Clear out unnecessary data.
for pos_hash, data in pairs(self.node_data) do
node_data[pos_hash] = data.node end
local to_save = {
version = 1,
region = self.region,
nodes = self.nodes,
num_nodes = self.num_nodes,
node_data = node_data,
motion = self.motion,
partial = self.partial,
@ -223,25 +230,32 @@ end
function contraption:destroy()
active_contraptions[self.id] = nil
for pos_hash, node in pairs(self.nodes) do
local rel_pos = vector.from_hash(pos_hash)
local abs_pos = self.region:to_absolute(rel_pos)
local def = minetest.registered_nodes[node.name]
minetest.set_node(abs_pos, def.drop_in_place)
for _, data in pairs(self.node_data) do
local def = minetest.registered_nodes[data.node.name]
minetest.set_node(data.pos, def.drop_in_place)
end
end
function contraption:get_node_data(pos, is_relative)
if not is_relative then pos = self.region:to_relative(pos) end
return self.node_data[pos:hash()]
end
function contraption:push(pusher, dir)
local can_move = not not self:simulate_move(dir)
local has_moved = not not self:move(dir)
minetest.chat_send_all("can_move=" .. tostring(can_move) .. " :: has_moved=" .. tostring(has_moved))
-- See if the same player has already pushed this contraption recently.
-- If so, do not apply the full amount of motion from the push.
local pusher_name = pusher.get_player_name and pusher:get_player_name() or ""
self.recent_pusher = self.recent_pusher or {}
local delay = self.recent_pusher[pusher_name]
if delay then dir = dir * (1 - math.min(1, delay)) end
self.recent_pusher[pusher_name] = 1.0 -- seconds
self.motion = self.motion or vector.zero()
self.motion = self.motion + dir
-- local pusher_name = pusher.get_player_name and pusher:get_player_name() or ""
-- self.recent_pusher = self.recent_pusher or {}
-- local delay = self.recent_pusher[pusher_name]
-- if delay then dir = dir * (1 - math.min(1, delay)) end
-- self.recent_pusher[pusher_name] = 1.0 -- seconds
-- self.motion = self.motion or vector.zero()
-- self.motion = self.motion + dir
end
function contraption:update(delta_time)
@ -325,6 +339,252 @@ function contraption:update(delta_time)
end
end
function contraption:simulate_move(offset)
local success = true
local affected = {
-- [pos_hash] = {
-- type = "fixed" -- Part of the contraption, must move.
-- or "dragged" -- Dragged on top, may or may not move.
-- or "pushed" -- Pushed in front, must move.
-- or "replace" -- Replaced by another node.
-- or "blocks" -- Blocks movement of the contraption.
-- rel_pos = vector
-- abs_pos = vector
-- node = node
-- meta = true or nil
-- target = <affected>
-- success = bool or nil when not calculated yet
-- }
}
for pos_hash, data in pairs(self.node_data) do
local abs_pos = self.region:to_absolute(data.rel_pos);
affected[pos_hash] = {
type = "fixed",
rel_pos = data.rel_pos,
abs_pos = abs_pos,
node = data.node,
}
end
for pos_hash, data in pairs(self.above_nodes) do
local abs_pos = self.region:to_absolute(data.rel_pos);
local node = minetest.get_node(data.abs_pos)
if contraption.is_pushable(node) then
affected[pos_hash] = {
type = "dragged",
rel_pos = data.rel_pos,
abs_pos = abs_pos,
node = node,
meta = true,
}
end
end
-- Collect additional relevant nodes.
local newly_affected = {}
local function check_target(data)
local offset_rel_pos = data.rel_pos + offset
local offset_abs_pos = data.abs_pos + offset
local offset_pos_hash = offset_rel_pos:hash()
local target = affected[offset_pos_hash]
if target then
-- If the node ahead is "fixed" (part of the contraption),
-- then it has to be able to move for the whole to move.
-- We can consider this node to be able to move forward.
data.target = target
if target.type == "fixed" then
data.success = true
end
else
target = { rel_pos = offset_rel_pos, abs_pos = offset_abs_pos,
node = minetest.get_node(offset_abs_pos) }
newly_affected[offset_pos_hash] = target
data.target = target
if data.type == "fixed" and contraption.is_pushable(target.node) then
target.type = "pushed"
target.meta = true
check_target(target)
-- Target node can be replaced, so do so.
elseif nodecore.buildable_to(target.node) then
target.type = "replace"
data.success = true
-- Target node blocks movement.
else
target.type = "blocks"
data.success = false
success = false
end
end
end
for _, data in pairs(affected) do check_target(data) end
for pos_hash, data in newly_affected do affected[pos_hash] = data
for _, data in pairs(affected) do
local offset_rel_pos = data.rel_pos + offset
local offset_abs_pos = data.abs.pos + offset
local offset_pos_hash = offset_rel_pos:hash()
data.target = affected[offset_pos_hash]
if data.target then
if data.target.type == "fixed" then
data.success = true
else
-- TODO: Need to check of the node in front can move.
-- Probably gonna add a set of nodes to check.
end
else
data.target = { rel_pos = offset_rel_pos, abs_pos = offset_abs_pos,
node = minetest.get_node(offset_abs_pos) }
newly_affected[offset_pos_hash] = data.target
if data.type == "fixed" then
if contraption.is_pushable(data.target.node) then
data.target.type = "pushed"
offset_rel_pos = offset_rel_pos + offset
offset_abs_pos = offset_abs_pos + offset
offset_pos_hash = offset_rel_pos:hash()
data.target.target = affected[offset_pos_hash]
if data.target.target then
if data.target.type == "fixed" then
data.success = true
end
else
data.target.target = { rel_pos = offset_rel_pos, abs_pos = offset_abs_pos,
node = minetest.get_node(offset_abs_pos) }
newly_affected[offset_pos_hash] = data.target.target
if nodecore.buildable_to(data.target.node) then
data.target.type = "replace"
data.success = true
else
data.target.type = "blocks"
data.success = false
success = false
end
end
end
end
if data.success == nil then
end
end
end
local function try_move(pos_hash, rel_pos, data, can_push)
local offset_rel_pos = rel_pos + offset
local offset_abs_pos = data.pos + offset
local offset_pos_hash = offset_rel_pos:hash()
local offset_data
local result_entry = result[offset_pos_hash]
if result_entry then
-- `result` already has an entry at the offset position.
-- This means that node has already been checked and can move.
offset_data = result_entry.from
else
offset_data = { pos = offset_abs_pos }
local contraption_node = self.nodes[offset_pos_hash]
if contraption_node then
-- There's a contraption at the offset position.
-- If the whole can move, this node will be able to as well.
offset_data.node = contraption_node
else
local pushable_data = to_push[offset_pos_hash]
if pushable_data then
-- Node is pushable and was going to be pushed anyway.
-- TODO: Get rid of this nonsense.
-- Node has already been processed by another pushable node.
if pushable_data.can_move ~= nil then return pushable_data.can_move end
offset_data = pushable_data
local success = try_move(offset_pos_hash, offset_rel_pos, offset_data, false)
if to_push_iterating then pushable_data.can_move = success
else to_push[offset_pos_hash] = nil end
if not success then return false end
else
offset_data.node = minetest.get_node(offset_abs_pos)
if can_push and contraption.is_pushable(offset_data.node) then
-- There's a pushable node in the way, so try to push it.
-- Only one up to one node can be pushed this way.
offset_data.meta = true -- Grab meta data for the pushed node later.
if not try_move(offset_pos_hash, offset_rel_pos, offset_data, false) then return false end
elseif nodecore.buildable_to(offset_data.node) then
-- Offset node can be replaced by this one.
else
-- Bumped into something.
return false
end
end
end
end
-- Successfully moved, add to result.
result[pos_hash] = { from = data, to = offset_data }
return true
end
-- Attempt to move nodes that are part of the contraption.
-- If any of them can't be moved, the contraption can't move.
for pos_hash, node in pairs(self.nodes) do
local rel_pos = vector.from_hash(pos_hash)
local abs_pos = self.region:to_absolute(rel_pos)
local data = { pos = abs_pos, node = node }
if not try_move(pos_hash, rel_pos, data, true) then return nil end
end
-- `to_push` contains "lose" nodes that are not directly
-- moved by the contraption, and may or may not be pushed.
to_push_iterating = true
for pos_hash, pushable_data in pairs(to_push) do
if result[pos_hash] then
local rel_pos = vector.from_hash(pos_hash)
local data = { pos = pushable_data.abs_pos, node = pushable_data.node, meta = true }
pushable_data.can_move = try_move(pos_hash, rel_pos, data, false)
end
end
-- Fill in the meta for each entry where requested.
-- It's not needed until we know the contraption can move.
for _, data in pairs(result) do
if data.meta == true then
data.meta = minetest.get_meta(data.pos):to_table()
end
end
return result
end
function contraption.update_world(moved_nodes)
local to_set = {} -- Nodes to set because they have changed.
for pos_hash, data in pairs(moved_nodes) do
to_set[pos_hash] = { node = AIR }
end
for _, data in pairs(moved_nodes) do
end
end
function contraption:move(offset)
-- Hash table of absolute positions before moved to nodes that have been moved by the
-- contraption, either because they're part of it, or have been pushed as a result.
@ -340,23 +600,13 @@ function contraption:move(offset)
-- 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:to_absolute(rel_pos)
local node = minetest.get_node(abs_pos)
if contraption.is_pushable(node) then
to_push[rel_pos:hash()] = {
node = node,
meta = minetest.get_meta(abs_pos):to_table()
}
end
end
for pos_hash in pairs(self.node_data) do to_clear[pos_hash] = AIR end
-- TODO: Redo this loop to work similar to the `to_push` loop below.
for old_rel_pos_hash, node in pairs(self.nodes) do
local old_rel_pos = vector.from_hash(old_rel_pos_hash)
local old_abs_pos = self.region:to_absolute(old_rel_pos)
for _, data in pairs(self.node_data) do
local node = data.node
local old_abs_pos = data.pos
local old_rel_pos = self.region:to_relative(old_abs_pos)
local new_abs_pos = old_abs_pos + offset
local new_rel_pos = old_rel_pos + offset
@ -365,10 +615,10 @@ function contraption:move(offset)
-- Node is being moved to this position, no need to clear here.
to_clear[new_rel_pos_hash] = nil
local self_overlap = self.nodes[new_rel_pos_hash]
local self_overlap = self.node_data[new_rel_pos_hash]
if self_overlap then
moved_nodes[old_abs_pos:hash()] = { node = node }
if not nodecore.match(node, self_overlap) then
if not nodecore.match(node, self_overlap.node) then
table_insert(to_set, { pos = new_abs_pos, node = node })
end
else
@ -394,7 +644,7 @@ function contraption:move(offset)
table_insert(to_set, nodecore.underride({ pos = new_abs_pos }, new_node))
to_push[new_rel_pos_hash] = nil
new_node = pushable
elseif self.nodes[new_rel_pos_hash] then
elseif self.node_data[new_rel_pos_hash] then
-- The next node is part of the contraption.
-- If it can move, this pushable node can move.
moved_nodes[old_abs_pos:hash()] = new_node
@ -436,7 +686,7 @@ function contraption:move(offset)
local forward_abs_pos = self.region:to_absolute(forward_rel_pos)
if self.nodes[forward_rel_pos] then
if self.node_data[forward_rel_pos] then
-- Do not clear this node by the contraption moving.
to_clear[forward_rel_pos] = nil
else

@ -4,7 +4,7 @@ local minetest, vector, nodecore, include
local search = include("search")
local contraption = include("contraption")
local mod_name = minetest.get_current_modname()
local mod_name = minetest.get_current_modname()
local i_stick = "nc_tree:stick"
local i_staff = "nc_tree:staff"

@ -1,5 +1,5 @@
local math, setmetatable, vector
= math, setmetatable, vector
local math, getmetatable, setmetatable, vector
= math, getmetatable, setmetatable, vector
local math_min, math_max, math_floor, math_ceil, math_round
= math.min, math.max, math.floor, math.ceil, math.round
@ -134,6 +134,10 @@ function region:iter_node_positions()
end
end
function region:check()
return getmetatable(self) == metatable
end
function region.equals(a, b)
return vector.equals(a.min, b.min)
and vector.equals(a.max, b.max)

@ -69,7 +69,7 @@ local function find_rectangle(pos, match)
-- direction of the smallest xyz coordinate.
if neighbors[1] == -neighbors[2] then
-- NOTE: Due to how `get_neighbors_with_name` is written, the first element
-- is guaranteed to contain the vector pointing towards negative.
-- is guaranteed to contain the vector pointing towards negative.
pos, neighbors = move_until_corner(pos, match, neighbors[1])
if not pos then return nil end
end

Loading…
Cancel
Save