commit
0757a58d16
7 changed files with 645 additions and 0 deletions
@ -0,0 +1,9 @@ |
|||||||
|
root = true |
||||||
|
|
||||||
|
[*] |
||||||
|
end_of_line = lf |
||||||
|
indent_style = space |
||||||
|
indent_size = 4 |
||||||
|
|
||||||
|
insert_final_newline = true |
||||||
|
trim_trailing_whitespace = true |
@ -0,0 +1,106 @@ |
|||||||
|
# Contraptions Concept |
||||||
|
|
||||||
|
## Progression |
||||||
|
|
||||||
|
- Assemble wooden contraption inside a rectangle of frames |
||||||
|
- Rotate a contraption |
||||||
|
- Take a contraption to water |
||||||
|
- Use a shovel to propel a contraption on water |
||||||
|
- Craft a wooden wheel |
||||||
|
- Attach wheels to a contraption |
||||||
|
- Construct a wooden rail (gravel + staff?) |
||||||
|
- Push a contraption or contraption on a rail |
||||||
|
- Attach a handle to a contraption |
||||||
|
- Pull a contraption using a handle |
||||||
|
- Assemble a hinged contraption |
||||||
|
- Rotate a hinged contraption using a handle |
||||||
|
- Assemble a contraption inside a cuboid of lode frames |
||||||
|
- Craft a lode wheel |
||||||
|
- Construct a lode rail (gravel + lode rod?) |
||||||
|
- Build a self-propelled contraption |
||||||
|
- Ride a contraption going full speed |
||||||
|
|
||||||
|
## Materials |
||||||
|
|
||||||
|
### Contraption Tiers |
||||||
|
|
||||||
|
- Wooden: Plank, Log |
||||||
|
- ??? |
||||||
|
|
||||||
|
### Ground |
||||||
|
|
||||||
|
Different types of ground apply different amount of friction onto a contraption, slowing it down. These are just some rough guessed values to play around with. |
||||||
|
|
||||||
|
- dirty: 2 |
||||||
|
- plank: 1 |
||||||
|
- cobble: 1 |
||||||
|
- smooth: 0.5 |
||||||
|
- Tarstone: 0.3 |
||||||
|
- Water: 0.4 |
||||||
|
- Wooden Rail: 0.5 |
||||||
|
- Lode Rail: 0.2 |
||||||
|
|
||||||
|
## Details |
||||||
|
|
||||||
|
### Assemble wooden contraption inside a rectangle of frames |
||||||
|
|
||||||
|
``` |
||||||
|
++++ |
||||||
|
+PP+ + = Wooden Frame |
||||||
|
+PP+ P = Plank |
||||||
|
+PP+ (Pummel frame with hammer to complete.) |
||||||
|
++++ |
||||||
|
``` |
||||||
|
|
||||||
|
A contraption is a connected set of nodes that can be pushed by players to move it. Multiple players actively pushing reduces the cooldown between each push. Larger contraptions are more difficult to push. |
||||||
|
|
||||||
|
contraptions are affected by gravity. |
||||||
|
|
||||||
|
contraptions will drag with them any "pushable" nodes (such as items) directly above them. If a contraption is pushed underneath an overhang and items can't follow, they will fall off. |
||||||
|
|
||||||
|
### Turn a contraption |
||||||
|
|
||||||
|
??? |
||||||
|
|
||||||
|
Plaforms can turn gradually, their "forward" direction changing to an angle not perfectly aligned with the grid. |
||||||
|
|
||||||
|
``` |
||||||
|
LLLL LLL |
||||||
|
RRRR RRRL LL |
||||||
|
R RRLL |
||||||
|
RR |
||||||
|
L |
||||||
|
RL |
||||||
|
RL |
||||||
|
RL |
||||||
|
R |
||||||
|
|
||||||
|
RL |
||||||
|
RL |
||||||
|
RL |
||||||
|
RL |
||||||
|
|
||||||
|
RL |
||||||
|
RL |
||||||
|
RL |
||||||
|
RL |
||||||
|
|
||||||
|
RL |
||||||
|
RL |
||||||
|
RL |
||||||
|
RL |
||||||
|
``` |
||||||
|
|
||||||
|
### Craft a wheel |
||||||
|
|
||||||
|
??? |
||||||
|
|
||||||
|
### Attach wheels to a contraption to create a cart |
||||||
|
|
||||||
|
``` |
||||||
|
WPPW P = Wooden contraption |
||||||
|
PP W = Wooden Wheel |
||||||
|
WPPW (Pummel wheel into contraption to complete.) |
||||||
|
``` |
||||||
|
|
||||||
|
A wheeled contraption is able to build up momentum, loosing it due to passive speed loss and friction. Different surfaces have different amounts of friction. |
@ -0,0 +1,20 @@ |
|||||||
|
# NodeCore Contraptions Mod |
||||||
|
|
||||||
|
This mod for [NodeCore], a game written for the open source [Minetest] engine, |
||||||
|
adds so-called "contraptions" to the game, which are node-based objects that |
||||||
|
can be moved around the world as a connected structure. |
||||||
|
|
||||||
|
Contraptions can be platforms, rafts, carts, boats, doors, minecarts, vehicles |
||||||
|
and even trains. Or, at least, that's the plan. This mod is still heavily in |
||||||
|
development. Items and players riding on top or inside contraption will be |
||||||
|
pushed along with them. |
||||||
|
|
||||||
|
These contraptions are inspired by the *Minecraft* mod [Create], which allows |
||||||
|
creation of similar contraptions, however this implementation will achieve |
||||||
|
this effect by using purely nodes in their usual grid. Rotation will be done |
||||||
|
in a similar fashion as the vehicles in [Cataclysm: Dark Days Ahead]. |
||||||
|
|
||||||
|
[NodeCore]: https://content.minetest.net/packages/Warr1024/nodecore/ |
||||||
|
[Minetest]: https://minetest.net/ |
||||||
|
[Create]: https://modrinth.com/mod/create |
||||||
|
[Cataclysm: Dark Days Ahead]: https://cataclysmdda.org/ |
@ -0,0 +1,336 @@ |
|||||||
|
local math, pairs, ipairs, setmetatable, table_insert, print |
||||||
|
= math, pairs, ipairs, setmetatable, table.insert, print |
||||||
|
|
||||||
|
local minetest, vector, nodecore |
||||||
|
= minetest, vector, nodecore |
||||||
|
|
||||||
|
local mod_name = minetest.get_current_modname() |
||||||
|
local mod_storage = minetest.get_mod_storage() |
||||||
|
|
||||||
|
-- This is the object that is returned by this script. |
||||||
|
local contraption = {} |
||||||
|
|
||||||
|
-- Some constants. Should probably be removed. |
||||||
|
local AIR = { name = "air" } |
||||||
|
local PLANK = { name = "nc_woodwork:plank" } |
||||||
|
local CONTRAPTION_WOOD = { name = "nc_contraptions:contraption_wood" } |
||||||
|
|
||||||
|
-- Counts up to infinity as contraptions get created. |
||||||
|
-- Each contraption gets assigned a unique id for its lifetime. |
||||||
|
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: Unload and save contraptions when they're in unloaded chunks. |
||||||
|
-- TODO: Use meta field `infotext` for displaying debug info about a contraption? |
||||||
|
|
||||||
|
local function error(...) |
||||||
|
local str = "[" .. mod_name .. "] " |
||||||
|
for _, v in ipairs({...}) do str = str + tostring(v) end |
||||||
|
print(str) |
||||||
|
end |
||||||
|
|
||||||
|
-------------------- |
||||||
|
-- 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") |
||||||
|
|
||||||
|
if version == 0 then |
||||||
|
-- Assume that no mod data has been saved yet. |
||||||
|
return |
||||||
|
elseif version ~= 1 then |
||||||
|
error("Unknown mod storage version " .. version) |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
id_counter = mod_storage:get_int("id_counter") |
||||||
|
|
||||||
|
local contraptions_str = mod_storage:get("contraptions") |
||||||
|
local contraptions = minetest.deserialize(contraptions_str) |
||||||
|
|
||||||
|
for _, id in ipairs(contraptions) do |
||||||
|
local str = mod_storage:get("contraption_" .. id) |
||||||
|
contraption.load_from_string(id, str) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function contraption.on_shutdown() |
||||||
|
-- TODO: Only modify mod storage where contraptions have changed / been destroyed. |
||||||
|
-- For now we're just going to wipe the storage and re-create it. |
||||||
|
mod_storage:from_table(nil) |
||||||
|
|
||||||
|
mod_storage:set_int("version", 1) |
||||||
|
mod_storage:set_int("id_counter", id_counter) |
||||||
|
|
||||||
|
local contraptions = {} |
||||||
|
|
||||||
|
for id, contrap in pairs(active_contraptions) do |
||||||
|
local str = contrap:save_to_string() |
||||||
|
mod_storage:set_string("contraption_" .. id, str) |
||||||
|
|
||||||
|
table_insert(contraptions, id) |
||||||
|
end |
||||||
|
|
||||||
|
mod_storage:set_string("contraptions", minetest.serialize(contraptions)); |
||||||
|
end |
||||||
|
|
||||||
|
---------------------- |
||||||
|
-- PUBLIC FUNCTIONS -- |
||||||
|
---------------------- |
||||||
|
|
||||||
|
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 |
||||||
|
end |
||||||
|
end |
||||||
|
return nil |
||||||
|
end |
||||||
|
|
||||||
|
------------------------------------- |
||||||
|
-- CONTRAPTION TYPE IMPLEMENTATION -- |
||||||
|
------------------------------------- |
||||||
|
|
||||||
|
local metatable = { __index = contraption } |
||||||
|
contraption.metatable = metatable |
||||||
|
|
||||||
|
function contraption.create(region) |
||||||
|
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 |
||||||
|
local pos_hash = minetest.hash_node_position(offset) |
||||||
|
nodes[pos_hash] = CONTRAPTION_WOOD |
||||||
|
num_nodes = num_nodes + 1 |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
-- Less than 4 nodes don't make a contraption. |
||||||
|
if num_nodes < 4 then return nil end |
||||||
|
|
||||||
|
-- TODO: Shrink region to the minimum required cuboid. |
||||||
|
-- In the above code we could just use pos as table key. |
||||||
|
|
||||||
|
-- TODO: Flood fill algorithm to make sure all nodes are connected. |
||||||
|
|
||||||
|
id_counter = id_counter + 1 |
||||||
|
local result = setmetatable({ |
||||||
|
id = id_counter, |
||||||
|
region = region, |
||||||
|
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 = minetest.get_position_from_hash(pos_hash) |
||||||
|
local abs_pos = vector.add(region.min, rel_pos) |
||||||
|
minetest.set_node(abs_pos, node) |
||||||
|
end |
||||||
|
|
||||||
|
active_contraptions[result.id] = result |
||||||
|
return result |
||||||
|
end |
||||||
|
|
||||||
|
function contraption.load_from_string(id, str) |
||||||
|
local obj = minetest.deserialize(str) |
||||||
|
|
||||||
|
if obj.version ~= 1 then |
||||||
|
error("Unknown contraption version " .. obj.version) |
||||||
|
return nil |
||||||
|
end |
||||||
|
|
||||||
|
local result = setmetatable({ |
||||||
|
id = id, |
||||||
|
|
||||||
|
region = obj.region, |
||||||
|
nodes = obj.nodes, |
||||||
|
num_nodes = obj.num_nodes, |
||||||
|
|
||||||
|
motion = obj.motion and vector.copy(obj.motion ) or nil, |
||||||
|
partial = obj.partial and vector.copy(obj.partial) or nil, |
||||||
|
}, metatable) |
||||||
|
|
||||||
|
active_contraptions[id] = result |
||||||
|
return obj |
||||||
|
end |
||||||
|
|
||||||
|
function contraption:save_to_string() |
||||||
|
local to_save = { |
||||||
|
version = 1, |
||||||
|
|
||||||
|
region = self.region, |
||||||
|
nodes = self.nodes, |
||||||
|
num_nodes = self.num_nodes, |
||||||
|
|
||||||
|
motion = self.motion, |
||||||
|
partial = self.partial, |
||||||
|
} |
||||||
|
return minetest.serialize(to_save) |
||||||
|
end |
||||||
|
|
||||||
|
function contraption:destroy() |
||||||
|
active_contraptions[self.id] = nil |
||||||
|
|
||||||
|
for pos_hash, node in pairs(self.nodes) do |
||||||
|
local rel_pos = minetest.get_position_from_hash(pos_hash) |
||||||
|
local abs_pos = vector.add(self.region.min, rel_pos) |
||||||
|
local def = minetest.registered_nodes[node.name] |
||||||
|
minetest.set_node(abs_pos, def.drop_in_place) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function contraption:push(pusher, dir) |
||||||
|
-- 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 = vector.add(self.motion, dir) |
||||||
|
end |
||||||
|
|
||||||
|
function contraption:update(delta_time) |
||||||
|
if not self.motion then return end |
||||||
|
|
||||||
|
-- Partial motion that accumulates over time, |
||||||
|
-- since we can only move in increments of 1. |
||||||
|
self.partial = self.partial or vector.zero() |
||||||
|
self.partial = self.partial + self.motion * 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 |
||||||
|
end |
||||||
|
end |
||||||
|
-- .. and move it 1 step into that direction. |
||||||
|
if which ~= 0 then |
||||||
|
local step = vector.zero() |
||||||
|
step[which] = math.sign(self.partial[which]) |
||||||
|
if self:move(step) then |
||||||
|
self.partial = self.partial - step |
||||||
|
else |
||||||
|
-- Reset motion into the direction that we bumped into. |
||||||
|
-- Allows for "sliding" along walls instead of stopping outright. |
||||||
|
self.motion[which] = 0 |
||||||
|
self.partial[which] = 0 |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
-- Apply friction. |
||||||
|
local FRICTION = 0.25 |
||||||
|
self.motion = self.motion * (1 - FRICTION * delta_time) |
||||||
|
|
||||||
|
-- If motion is small enough, unset it for performance. |
||||||
|
if self.motion:length() < 0.1 then |
||||||
|
self.motion = nil |
||||||
|
self.partial = nil |
||||||
|
end |
||||||
|
|
||||||
|
if self.recent_pusher then |
||||||
|
local has_pushers = false |
||||||
|
for pusher, delay in pairs(self.recent_pusher) do |
||||||
|
if delay > 0 then |
||||||
|
self.recent_pusher[pusher] = math.max(0, delay - delta_time) |
||||||
|
has_pushers = true |
||||||
|
end |
||||||
|
end |
||||||
|
-- If there are no active pushers, delete the `recent_pushers` table. |
||||||
|
if not has_pushers then self.recent_pusher = nil end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function contraption:move(offset) |
||||||
|
local to_set = {} -- Nodes that need to be modified. |
||||||
|
local to_clear = {} -- Nodes to be cleared (to AIR). |
||||||
|
|
||||||
|
-- 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 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) |
||||||
|
|
||||||
|
local new_abs_pos = vector.add(old_abs_pos, offset) |
||||||
|
local new_rel_pos = vector.add(old_rel_pos, offset) |
||||||
|
local new_rel_pos_hash = minetest.hash_node_position(new_rel_pos) |
||||||
|
|
||||||
|
-- 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] |
||||||
|
if self_overlap then |
||||||
|
if not nodecore.match(node, self_overlap) then |
||||||
|
to_set[new_rel_pos_hash] = node |
||||||
|
end |
||||||
|
else |
||||||
|
local new_node = minetest.get_node(new_abs_pos) |
||||||
|
if nodecore.buildable_to(new_node) then |
||||||
|
to_set[new_rel_pos_hash] = node |
||||||
|
else |
||||||
|
-- We bumped into a wall or something. |
||||||
|
return false |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
-- Set nodes that need to be changed. |
||||||
|
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) |
||||||
|
end |
||||||
|
|
||||||
|
-- Clear nodes that need to be cleared (to AIR). |
||||||
|
for pos_hash, node in pairs(to_clear) 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) |
||||||
|
end |
||||||
|
|
||||||
|
-- Move the contraption in the world. |
||||||
|
self.region.min = self.region.min + offset |
||||||
|
self.region.max = self.region.max + offset |
||||||
|
return true |
||||||
|
end |
||||||
|
|
||||||
|
return contraption |
@ -0,0 +1,68 @@ |
|||||||
|
local minetest, vector, nodecore, include |
||||||
|
= minetest, vector, nodecore, include |
||||||
|
|
||||||
|
local search = include("search") |
||||||
|
local contraption = include("contraption") |
||||||
|
|
||||||
|
local mod_name = minetest.get_current_modname() |
||||||
|
|
||||||
|
local i_stick = "nc_tree:stick" |
||||||
|
local i_staff = "nc_tree:staff" |
||||||
|
local i_frame = "nc_woodwork:frame" |
||||||
|
local i_form = "nc_woodwork:form" |
||||||
|
local i_plank = "nc_woodwork:plank" |
||||||
|
|
||||||
|
local t_plank = "nc_woodwork_plank.png" |
||||||
|
local t_frame_log = "(nc_tree_tree_side.png^[mask:nc_api_storebox_frame.png^[opacity:127)" |
||||||
|
|
||||||
|
-- Load and save contraptions. |
||||||
|
contraption.on_startup() |
||||||
|
minetest.register_on_shutdown(contraption.on_shutdown) |
||||||
|
|
||||||
|
-- `on_global_step` updates and moves around active contraptions. |
||||||
|
minetest.register_globalstep(contraption.on_global_step) |
||||||
|
|
||||||
|
-- Punching a contraption applies some force to it. |
||||||
|
-- Multiple players can push a contraption more effectively. |
||||||
|
minetest.register_on_punchnode(function(pos, node, puncher, pointed_thing) |
||||||
|
local contrap = contraption.find(pos) |
||||||
|
if not contrap then return end |
||||||
|
|
||||||
|
if (not pointed_thing.above) or (not pointed_thing.under) then return end |
||||||
|
local dir = vector.subtract(pointed_thing.under, pointed_thing.above) |
||||||
|
contrap:push(puncher, dir) |
||||||
|
end) |
||||||
|
|
||||||
|
-- TODO: Provide a method to register additional types of contraption blocks. |
||||||
|
minetest.register_node(mod_name .. ":contraption_wood", { |
||||||
|
description = "Wooden Contraption", |
||||||
|
tiles = { t_plank .. "^" .. t_frame_log }, |
||||||
|
sounds = nodecore.sounds("nc_tree_woody"), |
||||||
|
groups = { |
||||||
|
choppy = 1, |
||||||
|
flammable = 2, |
||||||
|
fire_fuel = 5, |
||||||
|
}, |
||||||
|
on_dig = function(pos, node, digger) |
||||||
|
local contrap = contraption.find(pos) |
||||||
|
if contrap then contrap:destroy() |
||||||
|
else minetest.node_dig(pos, node, digger) end |
||||||
|
end, |
||||||
|
drop_in_place = "nc_woodwork:plank", |
||||||
|
}) |
||||||
|
|
||||||
|
-- TODO: Take priority over or hook into frame-to-form recipe to do our thing instead. |
||||||
|
nodecore.register_craft({ |
||||||
|
label = "assemble wooden contraption", |
||||||
|
action = "pummel", |
||||||
|
toolgroups = { scratchy = 3 }, |
||||||
|
indexkeys = { i_frame }, |
||||||
|
nodes = { { match = i_frame } }, |
||||||
|
check = function(pos, data) |
||||||
|
data.found_square = search.find_rectangle(pos, i_frame) |
||||||
|
return not not data.found_square |
||||||
|
end, |
||||||
|
after = function(pos, data) |
||||||
|
contraption.create(data.found_square) |
||||||
|
end, |
||||||
|
}) |
@ -0,0 +1,3 @@ |
|||||||
|
name = nc_contraptions |
||||||
|
description = Adds movable contraptions to NodeCore. |
||||||
|
depends = nc_api_all |
@ -0,0 +1,103 @@ |
|||||||
|
local util = {} |
||||||
|
|
||||||
|
local AXES = { |
||||||
|
x = vector.new(1, 0, 0), |
||||||
|
y = vector.new(0, 1, 0), |
||||||
|
z = vector.new(0, 0, 1), |
||||||
|
} |
||||||
|
util.AXES = AXES; |
||||||
|
|
||||||
|
-- Gets an array of vectors relative to `pos` of neighbors that have the specified `match`. |
||||||
|
local function find_neighbors_with_name(pos, match) |
||||||
|
local result = {} |
||||||
|
for _, v in pairs(AXES) do |
||||||
|
if minetest.get_node(pos - v).name == match then |
||||||
|
table.insert(result, -v) |
||||||
|
end |
||||||
|
end |
||||||
|
for _, v in pairs(AXES) do |
||||||
|
if minetest.get_node(pos + v).name == match then |
||||||
|
table.insert(result, v) |
||||||
|
end |
||||||
|
end |
||||||
|
return result |
||||||
|
end |
||||||
|
util.find_neighbors_with_name = find_neighbors_with_name; |
||||||
|
|
||||||
|
-- Moves `pos` towards `dir` while node is still `match`. |
||||||
|
-- returns (vector, array) or nil |
||||||
|
local function move_until_corner(pos, match, dir) |
||||||
|
repeat pos = pos + dir |
||||||
|
until minetest.get_node(pos + dir).name ~= match |
||||||
|
local neighbors = find_neighbors_with_name(pos, match) |
||||||
|
if #neighbors == 2 |
||||||
|
then return pos, neighbors |
||||||
|
else return nil |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
-- Moves `pos` towards `dir`, checking that each node has exactly two neighbors |
||||||
|
-- that of type `match`, until a corner turning towards `expected_corner_dir` is hit. |
||||||
|
-- returns vector or nil |
||||||
|
local function move_until_corner_with_checks(pos, match, dir, expected_corner_dir) |
||||||
|
while true do |
||||||
|
pos = pos + dir |
||||||
|
|
||||||
|
local neighbors = find_neighbors_with_name(pos, match) |
||||||
|
if #neighbors ~= 2 then return nil end |
||||||
|
|
||||||
|
-- Get the "next direction" the piece is going. |
||||||
|
local next_dir = neighbors[1] |
||||||
|
if next_dir == -dir then next_dir = neighbors[2] end |
||||||
|
|
||||||
|
if next_dir ~= dir then |
||||||
|
if next_dir == expected_corner_dir |
||||||
|
then return pos |
||||||
|
else return nil |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
-- Finds a rectangle structure made from the specified node `match`. |
||||||
|
-- returns { min = vector, max = vector } or nil |
||||||
|
local function find_rectangle(pos, match) |
||||||
|
local neighbors = find_neighbors_with_name(pos, match) |
||||||
|
if #neighbors ~= 2 then return nil end |
||||||
|
|
||||||
|
-- If we found a line piece (neighbors are opposite) then move in the |
||||||
|
-- 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. |
||||||
|
pos, neighbors = move_until_corner(pos, match, neighbors[1]) |
||||||
|
if not pos then return nil end |
||||||
|
end |
||||||
|
|
||||||
|
-- We're now guaranteed to be at a corner piece. |
||||||
|
-- Move to the corner piece with the smallest xyz coordinate. |
||||||
|
while neighbors[1].x + neighbors[1].y + neighbors[1].z < 1 do |
||||||
|
pos, neighbors = move_until_corner(pos, match, neighbors[1]) |
||||||
|
if not pos then return nil end |
||||||
|
end |
||||||
|
|
||||||
|
-- Find the neighboring corners of the starting corner. |
||||||
|
local first_corner = move_until_corner_with_checks(pos, match, neighbors[1], neighbors[2]) |
||||||
|
if not first_corner then return nil end |
||||||
|
local second_corner = move_until_corner_with_checks(pos, match, neighbors[2], neighbors[1]) |
||||||
|
if not second_corner then return nil end |
||||||
|
|
||||||
|
-- Continue towards the final corner from the new corners. |
||||||
|
first_corner = move_until_corner_with_checks(first_corner, match, neighbors[2], -neighbors[1]) |
||||||
|
if not first_corner then return nil end |
||||||
|
second_corner = move_until_corner_with_checks(second_corner, match, neighbors[1], -neighbors[2]) |
||||||
|
if not second_corner then return nil end |
||||||
|
|
||||||
|
-- Ensure that they have met up at the same location. |
||||||
|
if first_corner ~= second_corner then return nil end |
||||||
|
|
||||||
|
return { min = pos, max = second_corner } |
||||||
|
end |
||||||
|
util.find_rectangle = find_rectangle; |
||||||
|
|
||||||
|
return util |
Loading…
Reference in new issue