area_containers-cd2025/container.lua
Jude Melton-Houghton 6ee59aea0e Hide the digiline when the mod isn't installed
I also removed the infotext, which seemed probably unnecessary and
which would hinder the hiding.
2021-07-15 18:12:18 -04:00

422 lines
11 KiB
Lua

local function get_node_force(pos)
local node = minetest.get_node_or_nil(pos)
if node then return node end
-- Try to load the block:
local vm = minetest.get_voxel_manip()
vm:read_from_map(pos, pos)
node = minetest.get_node_or_nil(pos)
assert(node)
return node
end
local container_name_prefix = "area_containers:container_"
local exit_offset = vector.new(0, 2, 1)
local digiline_offset = vector.new(3, 0, 3)
local port_labels = {
px = "+X", nx = "-X",
pz = "+Z", nz = "-Z",
py = "+Y", ny = "-Y",
}
local port_offsets = {
px = vector.new(0, 2, 4), nx = vector.new(0, 2, 6),
pz = vector.new(0, 2, 8), nz = vector.new(0, 2, 10),
py = vector.new(0, 2, 12), ny = vector.new(0, 2, 14),
}
local port_dirs = {
px = vector.new(1, 0, 0), nx = vector.new(-1, 0, 0),
pz = vector.new(0, 0, 1), nz = vector.new(0, 0, -1),
py = vector.new(0, 1, 0), ny = vector.new(0, -1, 0),
}
local port_ids_horiz = {"px", "nx", "pz", "nz"}
local port_name_prefix = "area_containers:port_"
local function get_port_id_from_name(node_name)
return string.sub(node_name,
#port_name_prefix + 1, #port_name_prefix + 2)
end
local inside_offsets = { exit_offset, digiline_offset }
for _, offset in pairs(port_offsets) do
inside_offsets[#inside_offsets + 1] = offset
end
local function set_up_exit(param1, param2, inside_pos)
local pos = vector.add(inside_pos, exit_offset)
minetest.set_node(pos, {
name = "area_containers:exit",
param1 = param1, param2 = param2,
})
local meta = minetest.get_meta(pos)
meta:set_string("infotext", "Exit")
end
local function set_up_digiline(param1, param2, inside_pos)
local pos = vector.add(inside_pos, digiline_offset)
minetest.set_node(pos, {
name = "area_containers:digiline",
param1 = param1, param2 = param2,
})
end
local function set_up_ports(param1, param2, inside_pos)
for id, offset in pairs(port_offsets) do
local pos = vector.add(inside_pos, offset)
minetest.set_node(pos, {
name = port_name_prefix .. id .. "_off",
param1 = param1, param2 = param2,
})
local meta = minetest.get_meta(pos)
meta:set_string("infotext", port_labels[id])
end
end
local function construct_inside(container_pos, param1, param2)
local inside_pos = area_containers.get_related_inside(param1, param2)
local min_pos = inside_pos
local max_pos = vector.add(min_pos, 15)
local vm = minetest.get_voxel_manip()
local min_edge, max_edge = vm:read_from_map(min_pos, max_pos)
local area = VoxelArea:new{
MinEdge = min_edge,
MaxEdge = max_edge,
}
local data = vm:get_data()
local c_air = minetest.get_content_id("air")
local c_wall = minetest.get_content_id("area_containers:wall")
for z = min_pos.z, max_pos.z do
for y = min_pos.y, max_pos.y do
for x = min_pos.x, max_pos.x do
local content_id = c_air
if x == min_pos.x or x == max_pos.x or
y == min_pos.y or y == max_pos.y or
z == min_pos.z or z == max_pos.z then
content_id = c_wall
end
data[area:index(x, y, z)] = content_id
end
end
end
vm:set_data(data)
vm:write_to_map(true)
area_containers.set_related_container(param1, param2, container_pos)
set_up_exit(param1, param2, inside_pos)
set_up_digiline(param1, param2, inside_pos)
set_up_ports(param1, param2, inside_pos)
end
area_containers.container = {}
-- There are 16 combinations of the horizontal sides, each with its own name:
area_containers.all_container_states = {}
local all_container_variants = {
"off", "0001", "0010", "0011", "0100", "0101", "0110", "0111",
"1000", "1001", "1010", "1011", "1100", "1101", "1110", "on",
}
for i, variant in ipairs(all_container_variants) do
area_containers.all_container_states[i] =
container_name_prefix .. variant
end
function area_containers.container.on_construct(pos)
local node = get_node_force(pos)
local param1 = node.param1
local param2 = node.param2
if not area_containers.params_are_null(param1, param2) then
-- The node probably moved.
if area_containers.reclaim_relation(param1, param2) then
area_containers.set_related_container(param1, param2,
pos)
return
end
end
local meta = minetest.get_meta(pos)
meta:set_string("infotext", "Area container")
param1, param2 = area_containers.alloc_relation()
if param1 then
construct_inside(pos, param1, param2)
minetest.swap_node(pos, {
name = node.name,
param1 = param1, param2 = param2,
})
end
end
function area_containers.container.on_destruct(pos)
-- Only free properly allocated containers:
local node = get_node_force(pos)
if not area_containers.params_are_null(node.param1, node.param2) then
area_containers.free_relation(node.param1, node.param2)
end
end
function area_containers.container.on_rightclick(pos, node, clicker)
if clicker and minetest.is_player(clicker) then
local inside_pos = area_containers.get_related_inside(
node.param1, node.param2)
local self_pos = area_containers.get_related_container(
node.param1, node.param2)
-- Make sure the player will be able to get back:
if self_pos and vector.equals(pos, self_pos) then
local dest = vector.add(inside_pos, 1)
clicker:set_pos(dest)
end
end
end
function area_containers.container_is_empty(pos, node)
node = node or get_node_force(pos)
local name_prefix = string.sub(node.name, 1, #container_name_prefix)
if name_prefix ~= container_name_prefix then return true end
local inside_pos = area_containers.get_related_inside(
node.param1, node.param2)
-- These represent the area of the inner chamber (inclusive):
local min_pos = vector.add(inside_pos, 1)
local max_pos = vector.add(inside_pos, 14)
-- Detect nodes left inside.
local vm = minetest.get_voxel_manip()
local min_edge, max_edge = vm:read_from_map(min_pos, max_pos)
local area = VoxelArea:new{
MinEdge = min_edge,
MaxEdge = max_edge,
}
local data = vm:get_data()
local c_air = minetest.get_content_id("air")
for z = min_pos.z, max_pos.z do
for y = min_pos.y, max_pos.y do
for x = min_pos.x, max_pos.x do
if data[area:index(x, y, z)] ~= c_air then
return false
end
end
end
end
-- Detect players inside.
-- (Detecting all objects would probably cause problems.)
local objects_inside = minetest.get_objects_in_area(
vector.subtract(min_pos, 1), vector.add(max_pos, 1))
for _, object in ipairs(objects_inside) do
if minetest.is_player(object) then return false end
end
return true
end
function area_containers.container.can_dig(pos)
return area_containers.container_is_empty(pos)
end
function area_containers.container.on_blast()
-- The simplest way to preserve the inside is just to do nothing.
end
area_containers.container.digiline = {
effector = {},
receptor = {},
}
function area_containers.container.digiline.effector.action(pos, node,
channel, msg)
local inside_pos = area_containers.get_related_inside(
node.param1, node.param2)
local digiline_pos = vector.add(inside_pos, digiline_offset)
digiline:receptor_send(digiline_pos, digiline.rules.default,
channel, msg)
end
area_containers.container.groups = {
tubedevice = 1,
tubedevice_receiver = 1,
}
area_containers.container.tube = {
connect_sides = {
left = 1, right = 1,
back = 1, front = 1,
bottom = 1, top = 1,
},
}
function area_containers.container.tube.can_insert()
return true
end
function area_containers.container.tube.insert_object(pos, node, stack, dir,
owner)
local inside_pos = area_containers.get_related_inside(
node.param1, node.param2)
local port_id = "nx"
if dir.x < 0 then
port_id = "px"
elseif dir.z > 0 then
port_id = "nz"
elseif dir.z < 0 then
port_id = "pz"
elseif dir.y > 0 then
port_id = "ny"
elseif dir.y < 0 then
port_id = "py"
end
local port_pos = vector.add(inside_pos, port_offsets[port_id])
local out_speed = math.max(vector.length(dir), 0.1)
local out_vel = vector.new(out_speed, 0, 0)
pipeworks.tube_inject_item(port_pos, port_pos, out_vel, stack, owner)
return ItemStack() -- All inserted.
end
area_containers.container.mesecons = {conductor = {
states = area_containers.all_container_states,
}}
function area_containers.container.mesecons.conductor.rules(node)
local rules = {
{
{x = 1, y = 1, z = 0},
{x = 1, y = 0, z = 0},
{x = 1, y = -1, z = 0},
},
{
{x = -1, y = 1, z = 0},
{x = -1, y = 0, z = 0},
{x = -1, y = -1, z = 0},
},
{
{x = 0, y = 1, z = 1},
{x = 0, y = 0, z = 1},
{x = 0, y = -1, z = 1},
},
{
{x = 0, y = 1, z = -1},
{x = 0, y = 0, z = -1},
{x = 0, y = -1, z = -1},
},
}
local self_pos = area_containers.get_related_container(
node.param1, node.param2)
if self_pos then
local inside_pos = area_containers.get_related_inside(
node.param1, node.param2)
for i, id in ipairs(port_ids_horiz) do
local port_pos =
vector.add(inside_pos, port_offsets[id])
local offset = vector.subtract(port_pos, self_pos)
local face_rules = rules[i]
face_rules[#face_rules + 1] = offset
end
end
return rules
end
area_containers.exit = {}
function area_containers.exit.on_rightclick(pos, node, clicker)
if clicker and minetest.is_player(clicker) then
local container_pos = area_containers.get_related_container(
node.param1, node.param2)
if container_pos then
local dest = vector.offset(container_pos, 0, 1, 0)
clicker:set_pos(dest)
end
end
end
area_containers.digiline = {
digiline = {
effector = {},
receptor = {},
}
}
function area_containers.digiline.digiline.effector.action(pos, node,
channel, msg)
local container_pos =
area_containers.get_related_container(node.param1, node.param2)
if not container_pos then return end
digiline:receptor_send(container_pos, digiline.rules.default,
channel, msg)
end
area_containers.port = {
groups = {
tubedevice = 1,
tubedevice_receiver = 1,
},
tube = {
connect_sides = {
left = 1, right = 1,
back = 1, front = 1,
bottom = 1, top = 1,
},
},
}
function area_containers.port.tube.can_insert(pos, node)
return area_containers.get_related_container(node.param1, node.param2)
~= nil
end
function area_containers.port.tube.insert_object(pos, node, stack, dir, owner)
local container_pos = area_containers.get_related_container(
node.param1, node.param2)
if not container_pos then return stack end
local id = get_port_id_from_name(node.name)
local out_dir = port_dirs[id]
local out_speed = math.max(vector.length(dir), 0.1)
local out_vel = vector.multiply(out_dir, out_speed)
pipeworks.tube_inject_item(container_pos, container_pos, out_vel, stack,
owner)
return ItemStack() -- All inserted.
end
local function get_port_rules(node)
local rules = {
{x = 1, y = -1, z = 0},
{x = 1, y = 0, z = 0},
{x = 1, y = 1, z = 0},
}
local container_pos = area_containers.get_related_container(
node.param1, node.param2)
if container_pos then
local id = get_port_id_from_name(node.name)
local inside_pos = area_containers.get_related_inside(
node.param1, node.param2)
local self_pos = vector.add(inside_pos, port_offsets[id])
local container_offset =
vector.subtract(container_pos, self_pos)
rules[#rules + 1] = container_offset
end
return rules
end
-- The vertical faces don't get mesecons since it wasn't working with them.
area_containers.all_port_variants = {
py_off = {},
ny_off = {},
}
for _, id in ipairs(port_ids_horiz) do
local on_state = id .. "_on"
local off_state = id .. "_off"
area_containers.all_port_variants[on_state] = {
mesecons = {conductor = {
state = "on",
offstate = port_name_prefix .. off_state,
rules = get_port_rules,
}},
}
area_containers.all_port_variants[off_state] = {
mesecons = {conductor = {
state = "off",
onstate = port_name_prefix .. on_state,
rules = get_port_rules,
}},
}
end