|
|
|
@ -4,11 +4,8 @@ local math, pairs, ipairs, setmetatable, table_insert |
|
|
|
|
local minetest, vector, nodecore |
|
|
|
|
= minetest, vector, nodecore |
|
|
|
|
|
|
|
|
|
local utility = include("utility") |
|
|
|
|
local error = utility.error |
|
|
|
|
|
|
|
|
|
local mod_name = minetest.get_current_modname() |
|
|
|
|
local mod_storage = minetest.get_mod_storage() |
|
|
|
|
include("vector_extensions") |
|
|
|
|
local region = include("region") |
|
|
|
|
|
|
|
|
|
-- This is the object that is returned by this script. |
|
|
|
|
local contraption = {} |
|
|
|
@ -25,6 +22,10 @@ local id_counter = 0 |
|
|
|
|
-- Table containing the currently active contraptions keyed by their id. |
|
|
|
|
local active_contraptions = {} |
|
|
|
|
|
|
|
|
|
-- Inactive contraptions get stored in mod storage. |
|
|
|
|
-- Currently only being used to save contraptions on shutdown. |
|
|
|
|
local mod_storage = minetest.get_mod_storage() |
|
|
|
|
|
|
|
|
|
-- TODO: Friction depending on ground. |
|
|
|
|
-- TODO: Gravity. |
|
|
|
|
-- TODO: Water physics! |
|
|
|
@ -46,12 +47,6 @@ local active_contraptions = {} |
|
|
|
|
-- EVENT HANDLING -- |
|
|
|
|
-------------------- |
|
|
|
|
|
|
|
|
|
function contraption.on_global_step(delta_time) |
|
|
|
|
for _, contrap in pairs(active_contraptions) do |
|
|
|
|
contrap:update(delta_time) |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
function contraption.on_startup() |
|
|
|
|
local version = mod_storage:get_int("version") |
|
|
|
|
|
|
|
|
@ -94,6 +89,12 @@ function contraption.on_shutdown() |
|
|
|
|
mod_storage:set_string("contraptions", minetest.serialize(contraptions)); |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
function contraption.on_global_step(delta_time) |
|
|
|
|
for _, contrap in pairs(active_contraptions) do |
|
|
|
|
contrap:update(delta_time) |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
---------------------- |
|
|
|
|
-- PUBLIC FUNCTIONS -- |
|
|
|
|
---------------------- |
|
|
|
@ -101,13 +102,9 @@ end |
|
|
|
|
function contraption.find(pos) |
|
|
|
|
-- First check if the node at `pos` is a contraption block. |
|
|
|
|
for _, c in pairs(active_contraptions) do |
|
|
|
|
if pos.x >= c.region.min.x and pos.x <= c.region.max.x |
|
|
|
|
and pos.y >= c.region.min.y and pos.y <= c.region.max.y |
|
|
|
|
and pos.z >= c.region.min.z and pos.z <= c.region.max.z |
|
|
|
|
then |
|
|
|
|
local rel_pos = pos - c.region.min |
|
|
|
|
local rel_pos_hash = minetest.hash_node_position(rel_pos) |
|
|
|
|
if c.nodes[rel_pos_hash] then return c end |
|
|
|
|
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 |
|
|
|
|
end |
|
|
|
|
return nil |
|
|
|
@ -127,20 +124,17 @@ end |
|
|
|
|
local metatable = { __index = contraption } |
|
|
|
|
contraption.metatable = metatable |
|
|
|
|
|
|
|
|
|
function contraption.create(region) |
|
|
|
|
function contraption.create(r) |
|
|
|
|
r = region.copy(r) |
|
|
|
|
|
|
|
|
|
local nodes = {} |
|
|
|
|
local num_nodes = 0 |
|
|
|
|
for x = region.min.x, region.max.x do |
|
|
|
|
for y = region.min.y, region.max.y do |
|
|
|
|
for z = region.min.z, region.max.z do |
|
|
|
|
local pos = vector.new(x, y, z) |
|
|
|
|
local node = minetest.get_node(pos) |
|
|
|
|
if nodecore.match(node, PLANK) then |
|
|
|
|
local offset = pos - region.min |
|
|
|
|
nodes[offset:hash()] = CONTRAPTION_WOOD |
|
|
|
|
num_nodes = num_nodes + 1 |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
for pos in r:iter_node_positions() do |
|
|
|
|
local node = minetest.get_node(pos) |
|
|
|
|
if nodecore.match(node, PLANK) then |
|
|
|
|
local offset = r:to_relative(pos) |
|
|
|
|
nodes[offset:hash()] = CONTRAPTION_WOOD |
|
|
|
|
num_nodes = num_nodes + 1 |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
@ -155,15 +149,15 @@ function contraption.create(region) |
|
|
|
|
id_counter = id_counter + 1 |
|
|
|
|
local result = setmetatable({ |
|
|
|
|
id = id_counter, |
|
|
|
|
region = region, |
|
|
|
|
region = r, |
|
|
|
|
nodes = nodes, |
|
|
|
|
num_nodes = num_nodes, |
|
|
|
|
}, metatable) |
|
|
|
|
|
|
|
|
|
-- Change nodes from base nodes to their contraption version. |
|
|
|
|
for pos_hash, node in pairs(nodes) do |
|
|
|
|
local rel_pos = vector.convert(pos_hash) |
|
|
|
|
local abs_pos = region.min + rel_pos |
|
|
|
|
local rel_pos = vector.from_hash(pos_hash) |
|
|
|
|
local abs_pos = r:to_absolute(rel_pos) |
|
|
|
|
minetest.set_node(abs_pos, node) |
|
|
|
|
end |
|
|
|
|
|
|
|
|
@ -175,7 +169,7 @@ end |
|
|
|
|
function contraption:on_initialize() |
|
|
|
|
local above_nodes = {} |
|
|
|
|
for orig_pos_hash, node in pairs(self.nodes) do |
|
|
|
|
local orig_pos = vector.convert(orig_pos_hash) |
|
|
|
|
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. |
|
|
|
@ -199,15 +193,12 @@ function contraption.load_from_string(id, str) |
|
|
|
|
local result = setmetatable({ |
|
|
|
|
id = id, |
|
|
|
|
|
|
|
|
|
region = { |
|
|
|
|
min = vector.convert(obj.region.min), |
|
|
|
|
max = vector.convert(obj.region.max) |
|
|
|
|
}, |
|
|
|
|
nodes = obj.nodes, |
|
|
|
|
region = region.copy(obj.region), |
|
|
|
|
nodes = obj.nodes, |
|
|
|
|
num_nodes = obj.num_nodes, |
|
|
|
|
|
|
|
|
|
motion = obj.motion and vector.convert(obj.motion ) or nil, |
|
|
|
|
partial = obj.partial and vector.convert(obj.partial) or nil, |
|
|
|
|
motion = obj.motion and vector.copy(obj.motion ) or nil, |
|
|
|
|
partial = obj.partial and vector.copy(obj.partial) or nil, |
|
|
|
|
}, metatable) |
|
|
|
|
|
|
|
|
|
result:on_initialize() |
|
|
|
@ -233,8 +224,8 @@ function contraption:destroy() |
|
|
|
|
active_contraptions[self.id] = nil |
|
|
|
|
|
|
|
|
|
for pos_hash, node in pairs(self.nodes) do |
|
|
|
|
local rel_pos = vector.convert(pos_hash) |
|
|
|
|
local abs_pos = self.region.min + rel_pos |
|
|
|
|
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) |
|
|
|
|
end |
|
|
|
@ -263,35 +254,46 @@ function contraption:update(delta_time) |
|
|
|
|
|
|
|
|
|
-- If partial motion has increased to more than 1 on |
|
|
|
|
-- one axis, find out which axis has the largest motion .. |
|
|
|
|
local max = 0 |
|
|
|
|
local which = 0 |
|
|
|
|
for i = 1, 3 do |
|
|
|
|
local abs = math.abs(self.partial[i]) |
|
|
|
|
if abs >= 1 and abs > max then |
|
|
|
|
max = abs |
|
|
|
|
which = i |
|
|
|
|
do |
|
|
|
|
local max = 0 |
|
|
|
|
for i = 1, 3 do |
|
|
|
|
local abs = math.abs(self.partial[i]) |
|
|
|
|
if abs >= 1 and abs > max then |
|
|
|
|
max = abs |
|
|
|
|
which = i |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
-- .. and move it 1 step into that direction. |
|
|
|
|
if which ~= 0 then |
|
|
|
|
local objects_to_move = {} |
|
|
|
|
local pos1 = self.region.min:offset(-1, -1, -1) |
|
|
|
|
local pos2 = self.region.max:offset( 1, 2, 1) |
|
|
|
|
for _, obj in ipairs(minetest.get_objects_in_area(pos1, pos2)) do |
|
|
|
|
if obj:is_player() then |
|
|
|
|
local adjusted_pos = vector.offset(obj:get_pos(), 0, -0.5, 0) |
|
|
|
|
local rel_pos = adjusted_pos:round() - self.region.min |
|
|
|
|
if self.nodes[rel_pos:hash()] then |
|
|
|
|
table_insert(objects_to_move, obj) |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
local step = vector.zero() |
|
|
|
|
step[which] = math.sign(self.partial[which]) |
|
|
|
|
if self:move(step) then |
|
|
|
|
local moved_nodes = self:move(step) |
|
|
|
|
|
|
|
|
|
if moved_nodes then |
|
|
|
|
-- Reduce partial motion by the amount moved. |
|
|
|
|
self.partial = self.partial - step |
|
|
|
|
for _, obj in ipairs(objects_to_move) do obj:set_pos(vector.add(obj:get_pos(), step)) end |
|
|
|
|
|
|
|
|
|
-- Push players around. |
|
|
|
|
local r = self.region:expand(2):extend_in_place(step) |
|
|
|
|
for _, player in ipairs(minetest.get_objects_in_area(r.min, r.max)) do |
|
|
|
|
if player:is_player() then |
|
|
|
|
local extends = region.from_object(player) |
|
|
|
|
extends:expand_in_place(0.5) |
|
|
|
|
extends:extend_in_place(0, -0.5, 0) |
|
|
|
|
extends:extend_in_place(-step) |
|
|
|
|
|
|
|
|
|
for pos in extends:iter_node_positions() do |
|
|
|
|
if moved_nodes[pos:hash()] then |
|
|
|
|
-- TODO: Add velocity instead of teleporting. |
|
|
|
|
player:set_pos(vector.add(player:get_pos(), step)) |
|
|
|
|
break |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
else |
|
|
|
|
-- Reset motion into the direction that we bumped into. |
|
|
|
|
-- Allows for "sliding" along walls instead of stopping outright. |
|
|
|
@ -324,6 +326,11 @@ function contraption:update(delta_time) |
|
|
|
|
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. |
|
|
|
|
local moved_nodes = {} |
|
|
|
|
-- TODO: Just have a function that returns the nodes-to-moved (if possible) and a separate one to actually do the moving? |
|
|
|
|
|
|
|
|
|
-- `to_clear` and `to_push` use a relative position hash |
|
|
|
|
-- as their key, since entries will need to be removed. |
|
|
|
|
local to_clear = {} -- Table of nodes to be cleared (to `AIR`). |
|
|
|
@ -336,16 +343,20 @@ function contraption:move(offset) |
|
|
|
|
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 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() } |
|
|
|
|
to_push[rel_pos:hash()] = { |
|
|
|
|
node = node, |
|
|
|
|
meta = minetest.get_meta(abs_pos):to_table() |
|
|
|
|
} |
|
|
|
|
end |
|
|
|
|
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.convert(old_rel_pos_hash) |
|
|
|
|
local old_abs_pos = self.region.min + old_rel_pos |
|
|
|
|
local old_rel_pos = vector.from_hash(old_rel_pos_hash) |
|
|
|
|
local old_abs_pos = self.region:to_absolute(old_rel_pos) |
|
|
|
|
|
|
|
|
|
local new_abs_pos = old_abs_pos + offset |
|
|
|
|
local new_rel_pos = old_rel_pos + offset |
|
|
|
@ -356,49 +367,59 @@ function contraption:move(offset) |
|
|
|
|
|
|
|
|
|
local self_overlap = self.nodes[new_rel_pos_hash] |
|
|
|
|
if self_overlap then |
|
|
|
|
moved_nodes[old_abs_pos:hash()] = { node = node } |
|
|
|
|
if not nodecore.match(node, self_overlap) then |
|
|
|
|
table.insert(to_set, { pos = new_abs_pos, node = node }) |
|
|
|
|
table_insert(to_set, { pos = new_abs_pos, node = node }) |
|
|
|
|
end |
|
|
|
|
else |
|
|
|
|
local new_node = minetest.get_node(new_abs_pos) |
|
|
|
|
-- 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() } |
|
|
|
|
table.insert(to_set, { pos = new_abs_pos, node = node }) |
|
|
|
|
moved_nodes[old_abs_pos:hash()] = new_node |
|
|
|
|
table_insert(to_set, { pos = new_abs_pos, node = node }) |
|
|
|
|
|
|
|
|
|
while true do |
|
|
|
|
old_abs_pos = new_abs_pos |
|
|
|
|
old_rel_pos = new_rel_pos |
|
|
|
|
new_abs_pos = new_abs_pos + offset |
|
|
|
|
new_rel_pos = new_rel_pos + offset |
|
|
|
|
new_rel_pos_hash = new_rel_pos:hash() |
|
|
|
|
|
|
|
|
|
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. |
|
|
|
|
table.insert(to_set, nodecore.underride({ pos = new_abs_pos }, new_node)) |
|
|
|
|
moved_nodes[old_abs_pos:hash()] = new_node |
|
|
|
|
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 |
|
|
|
|
-- The next node is part of the contraption. |
|
|
|
|
-- If it can move, this pushable node can move. |
|
|
|
|
table.insert(to_set, nodecore.underride({ pos = new_abs_pos }, new_node)) |
|
|
|
|
moved_nodes[old_abs_pos:hash()] = new_node |
|
|
|
|
table_insert(to_set, nodecore.underride({ pos = new_abs_pos }, new_node)) |
|
|
|
|
-- Do not clear this node by the contraption moving. |
|
|
|
|
to_clear[new_rel_pos_hash] = nil |
|
|
|
|
break |
|
|
|
|
elseif nodecore.buildable_to(new_abs_pos) then |
|
|
|
|
-- The next node is replaceable, so we can replace it. |
|
|
|
|
table.insert(to_set, nodecore.underride({ pos = new_abs_pos }, new_node)) |
|
|
|
|
moved_nodes[old_abs_pos:hash()] = new_node |
|
|
|
|
table_insert(to_set, nodecore.underride({ pos = new_abs_pos }, 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 |
|
|
|
|
return nil |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
elseif nodecore.buildable_to(new_node) then |
|
|
|
|
table.insert(to_set, { pos = new_abs_pos, node = node }) |
|
|
|
|
moved_nodes[old_abs_pos:hash()] = { node = node } |
|
|
|
|
table_insert(to_set, { pos = new_abs_pos, node = node }) |
|
|
|
|
else |
|
|
|
|
-- We bumped into a wall or something. |
|
|
|
|
return false |
|
|
|
|
return nil |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
@ -406,14 +427,14 @@ function contraption:move(offset) |
|
|
|
|
-- 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 = vector.convert(pos_hash) |
|
|
|
|
local rel_pos = vector.from_hash(pos_hash) |
|
|
|
|
local forward_rel_pos = rel_pos + offset |
|
|
|
|
|
|
|
|
|
-- If there's a node to push ahead of this one, skip. |
|
|
|
|
-- The node ahead will take care of pulling the ones behind along with it. |
|
|
|
|
if to_push[forward_rel_pos:hash()] then goto continue end |
|
|
|
|
|
|
|
|
|
local forward_abs_pos = self.region.min + forward_rel_pos |
|
|
|
|
local forward_abs_pos = self.region:to_absolute(forward_rel_pos) |
|
|
|
|
|
|
|
|
|
if self.nodes[forward_rel_pos] then |
|
|
|
|
-- Do not clear this node by the contraption moving. |
|
|
|
@ -424,7 +445,8 @@ function contraption:move(offset) |
|
|
|
|
if not nodecore.buildable_to(forward_abs_pos) then goto continue end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
table.insert(to_set, nodecore.underride({ pos = forward_abs_pos }, node)) |
|
|
|
|
moved_nodes[self.region:to_absolute(rel_pos):hash()] = node |
|
|
|
|
table_insert(to_set, nodecore.underride({ pos = forward_abs_pos }, node)) |
|
|
|
|
|
|
|
|
|
while true do |
|
|
|
|
local backward_rel_pos = rel_pos - offset |
|
|
|
@ -432,8 +454,9 @@ function contraption:move(offset) |
|
|
|
|
node = to_push[backward_rel_pos_hash] |
|
|
|
|
if not node then break end |
|
|
|
|
|
|
|
|
|
local abs_pos = self.region.min + rel_pos |
|
|
|
|
table.insert(to_set, nodecore.underride({ pos = abs_pos }, node)) |
|
|
|
|
local abs_pos = self.region:to_absolute(rel_pos) |
|
|
|
|
moved_nodes[self.region:to_absolute(backward_rel_pos):hash()] = node |
|
|
|
|
table_insert(to_set, nodecore.underride({ pos = abs_pos }, node)) |
|
|
|
|
rel_pos = backward_rel_pos |
|
|
|
|
pos_hash = backward_rel_pos_hash |
|
|
|
|
end |
|
|
|
@ -450,15 +473,15 @@ function contraption:move(offset) |
|
|
|
|
|
|
|
|
|
-- Clear nodes that need to be cleared (to AIR). |
|
|
|
|
for pos_hash, node in pairs(to_clear) do |
|
|
|
|
local rel_pos = vector.convert(pos_hash) |
|
|
|
|
local abs_pos = self.region.min + rel_pos |
|
|
|
|
local rel_pos = vector.from_hash(pos_hash) |
|
|
|
|
local abs_pos = self.region:to_absolute(rel_pos) |
|
|
|
|
minetest.set_node(abs_pos, node) |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
-- Move the contraption in the world. |
|
|
|
|
self.region.min = self.region.min + offset |
|
|
|
|
self.region.max = self.region.max + offset |
|
|
|
|
return true |
|
|
|
|
return moved_nodes |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
return contraption |
|
|
|
|