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