1162 lines
34 KiB
Lua

--[[
mech - mechanisms for Inside The Box
]]--
mech = {}
-- Logic
-- Hashes a vector as a 6-byte string
local function hash_vector(v)
local x = v.x + 32768
local y = v.y + 32768
local z = v.z + 32768
return string.char(math.floor(x / 256)) .. string.char(x % 256) ..
string.char(math.floor(y / 256)) .. string.char(y % 256) ..
string.char(math.floor(z / 256)) .. string.char(z % 256)
end
local function dehash_vector(s)
return {
x = 256 * string.byte(s, 1) + string.byte(s, 2) - 32768,
y = 256 * string.byte(s, 3) + string.byte(s, 4) - 32768,
z = 256 * string.byte(s, 5) + string.byte(s, 6) - 32768,
}
end
local defer_tbl = {}
minetest.register_globalstep(function(dtime)
local t = table.copy(defer_tbl)
defer_tbl = {}
for pos, func in pairs(t) do
func(minetest.string_to_pos(pos))
end
end)
local function defer(pos, func)
local p = minetest.pos_to_string(pos)
if not defer_tbl[p] then
defer_tbl[p] = func
end
end
function mech.trigger(pos)
local meta = minetest.get_meta(pos)
local offsets = minetest.deserialize(meta:get_string("offsets")) or {}
for v, _ in pairs(offsets) do
local np = vector.add(pos, dehash_vector(v))
local node = minetest.get_node(np)
if node and minetest.registered_nodes[node.name] and
minetest.registered_nodes[node.name].on_trigger then
defer(np, minetest.registered_nodes[node.name].on_trigger)
elseif node and node.name ~= "air" and node.name ~= "nodes:placeholder" then
-- mech breaking
local def = minetest.registered_nodes[node.name]
local sounds = def.sounds or {}
if sounds.dug then
minetest.sound_play(sounds.dug, {pos = np})
end
minetest.remove_node(np)
minetest.check_for_falling(np)
-- throw some particles around
if not def.tiles then
return
end
local texture = def.tiles and def.tiles[1] and def.tiles[1].name or
def.tiles[1] or def.tiles or "dirt.png"
if type(texture) ~= "string" then
return
end
minetest.add_particlespawner({
amount = 16,
time = 0.05,
minpos = vector.add(np, -0.5),
maxpos = vector.add(np, 0.5),
minvel = {x = -0.4, y = -0.4, z = -0.4},
maxvel = {x = -0.4, y = -0.4, z = -0.4},
minacc = {x = 0, y = -10, z = 0},
maxacc = {x = 0, y = -10, z = 0},
minexptime = 0.3,
maxexptime = 0.7,
minsize = 1.0,
maxsize = 2.4,
collisiondetection = true,
texture = texture .. "^[sheet:4x4:" .. math.random(4) .. "," .. math.random(4)
})
end
end
end
function mech.untrigger(pos)
local meta = minetest.get_meta(pos)
local offsets = minetest.deserialize(meta:get_string("offsets")) or {}
for v, _ in pairs(offsets) do
local np = vector.add(pos, dehash_vector(v))
local node = minetest.get_node(np)
if node and minetest.registered_nodes[node.name] and
minetest.registered_nodes[node.name].on_untrigger then
defer(np, minetest.registered_nodes[node.name].on_untrigger)
end
end
end
function mech.link(pos1, pos2)
local meta1 = minetest.get_meta(pos1)
local off1 = minetest.deserialize(meta1:get_string("offsets")) or {}
off1[hash_vector(vector.subtract(pos2, pos1))] = true
local meta2 = minetest.get_meta(pos2)
local off2 = minetest.deserialize(meta2:get_string("roffsets")) or {}
off2[hash_vector(vector.subtract(pos1, pos2))] = true
local function c(t)
local n = 0
for _, _ in pairs(t) do
n = n + 1
end
return n
end
if c(off1) > 64 or c(off2) > 64 then
return false
end
meta1:set_string("offsets", minetest.serialize(off1))
meta1:mark_as_private("offsets")
meta2:set_string("roffsets", minetest.serialize(off2))
meta1:mark_as_private("roffsets")
return true
end
local function unlink(pos, meta)
local offsets = minetest.deserialize(meta.fields.offsets or "") or {}
for v, _ in pairs(offsets) do
local np = vector.add(pos, dehash_vector(v))
local meta2 = minetest.get_meta(np)
local roff = minetest.deserialize(meta2:get_string("roffsets")) or {}
roff[hash_vector(vector.subtract(pos, np))] = nil
meta2:set_string("roffsets", minetest.serialize(roff))
meta2:mark_as_private("roffsets")
end
local roffsets = minetest.deserialize(meta.fields.roffsets or "") or {}
for v, _ in pairs(roffsets) do
local np = vector.add(pos, dehash_vector(v))
local meta2 = minetest.get_meta(np)
local off = minetest.deserialize(meta2:get_string("offsets")) or {}
off[hash_vector(vector.subtract(pos, np))] = nil
meta2:set_string("offsets", minetest.serialize(off))
meta2:mark_as_private("offsets")
end
end
function mech.after_dig(pos, oldnode, oldmetadata, digger)
unlink(pos, oldmetadata)
end
local function mech_connect(itemstack, placer, pointed_thing, rightclick)
if not pointed_thing or not pointed_thing.under then
return
end
if not placer then
return
end
local name = placer:get_player_name()
if not boxes.players_editing_boxes[name] and not minetest.check_player_privs(name, "server") then
return
end
local box = boxes.players_editing_boxes[name]
if not box then
box = {
minp = { x = -32768, y = -32768, z = -32768 },
maxp = { x = 32768, y = 32768, z = 32768 },
}
end
local pos = pointed_thing.under
if pos.x <= box.minp.x or pos.x >= box.maxp.x or
pos.y <= box.minp.y or pos.y >= box.maxp.y or
pos.z <= box.minp.z or pos.z >= box.maxp.z
then
return
end
local tmeta = itemstack:get_metadata()
if rightclick then
if tmeta == "" then
minetest.chat_send_player(placer:get_player_name(),
"Left-click a node first.")
return itemstack
end
local pos1 = dehash_vector(tmeta)
local pos2 = pointed_thing.under
if placer:get_player_control().sneak then
-- special version of unlink()
local m1 = minetest.get_meta(pos1)
local t1 = m1:to_table()
local offsets = minetest.deserialize(t1.fields.offsets or "") or {}
offsets[hash_vector(vector.subtract(pos2, pos1))] = nil
m1:set_string("offsets", minetest.serialize(offsets))
m1:mark_as_private("offsets")
local m2 = minetest.get_meta(pos2)
local t2 = m2:to_table()
local roffsets = minetest.deserialize(t2.fields.roffsets or "") or {}
roffsets[hash_vector(vector.subtract(pos1, pos2))] = nil
m2:set_string("roffsets", minetest.serialize(roffsets))
m2:mark_as_private("roffsets")
minetest.chat_send_player(placer:get_player_name(), "Connection removed from \"" ..
minetest.get_node(pos2).name .. "\" at " ..
minetest.pos_to_string(pos2))
minetest.sound_play("button_untrigger", {pos = pointed_thing.under})
else
if mech.link(pos1, pos2) then
minetest.chat_send_player(placer:get_player_name(), "Connection completed with \"" ..
minetest.get_node(pos2).name .. "\" at " ..
minetest.pos_to_string(pos2))
minetest.sound_play("button_untrigger", {pos = pointed_thing.under})
else
minetest.chat_send_player(placer:get_player_name(), "Connection failed. Too many connections.")
end
end
else -- left click
itemstack:set_metadata(hash_vector(pointed_thing.under))
minetest.chat_send_player(placer:get_player_name(), "Connection started with \"" ..
minetest.get_node(pointed_thing.under).name .. "\" at " ..
minetest.pos_to_string(pointed_thing.under))
minetest.sound_play("button_trigger", {pos = pointed_thing.under})
local meta = itemstack:get_meta()
meta:set_string("description", "Connector tool\n" ..
"Right click create a connection from \"" .. minetest.get_node(pointed_thing.under).name ..
"\" at " .. minetest.pos_to_string(pointed_thing.under) ..
"\nShift right click to remove the connection from \"" ..
minetest.get_node(pointed_thing.under).name ..
"\" at " .. minetest.pos_to_string(pointed_thing.under))
end
return itemstack
end
minetest.register_tool("mech:connector", {
description = "Connector tool\nLeft click to start a link\nRight click to complete a link",
inventory_image = "connector_tool.png",
on_use = function(itemstack, placer, pointed_thing)
return mech_connect(itemstack, placer, pointed_thing, false)
end,
on_place = function(itemstack, placer, pointed_thing)
return mech_connect(itemstack, placer, pointed_thing, true)
end,
})
frame.register("mech:connector")
local function do_player_place(pos, node, placer, itemstack, pointed_thing)
local name = placer:get_player_name()
-- check placer is playing a box, otherwise it's invalid anyway
local box = boxes.players_in_boxes[name]
if not box then
return
end
-- double check box coords
if boxes.find_box(pos).box_id ~= box.box_id then
return
end
if not itemstack then
return
end
local item = itemstack:get_name()
if not item then
return
end
local def = minetest.registered_nodes[item]
if not def or not def.groups or not def.groups.node and
not def.groups.shovel and not def.groups.pickaxe and
not def.groups.axe then
return
end
-- check for placeholder in target location
local tnode = minetest.get_node(pointed_thing.above)
if tnode.name ~= "nodes:placeholder" then
return itemstack
end
local meta = minetest.get_meta(pointed_thing.above)
local placeable = meta:get_string("placeable")
if placeable == "" then
return itemstack
end
local t = minetest.parse_json(placeable)
if not t[item] then
return itemstack
end
minetest.set_node(pointed_thing.above, {name = item})
local sounds = def.sounds or {}
if sounds.place then
minetest.sound_play(sounds.place, {pos = pointed_thing.above})
end
itemstack:take_item()
return itemstack
end
-- secrets (keys?)
-- collection points
-- boxes:
-- - fake
-- - real with key
-- - real with tools
-- event creators:
-- buttons
minetest.register_node("mech:button", {
description = "Button",
drawtype = "mesh",
mesh = "button_up.obj",
tiles = {"button_switch.png"},
paramtype = "light",
paramtype2 = "facedir",
walkable = false,
groups = {node = 1, unbreakable = 1, trigger = 1},
sounds = sounds.metal,
collision_box = {
type = "fixed",
fixed = {{-5/16, -1/4, 1/4, 5/16, 1/4, 1/2}},
},
selection_box = {
type = "fixed",
fixed = {{-5/16, -1/4, 1/4, 5/16, 1/4, 1/2}},
},
after_dig_node = mech.after_dig,
on_punch = function(pos, node, puncher, pointed_thing)
mech.trigger(pos)
node.name = "mech:button_down"
minetest.swap_node(pos, node)
minetest.get_node_timer(pos):start(1)
minetest.sound_play("button_trigger", {pos = pos})
end,
on_rightclick = function(pos, node, puncher, itemstack, pointed_thing)
mech.trigger(pos)
node.name = "mech:button_down"
minetest.swap_node(pos, node)
minetest.get_node_timer(pos):start(1)
minetest.sound_play("button_trigger", {pos = pos})
return itemstack
end,
})
minetest.register_node("mech:button_down", {
description = "Button (pressed)",
drawtype = "mesh",
mesh = "button_down.obj",
tiles = {"button_switch.png"},
paramtype = "light",
paramtype2 = "facedir",
walkable = false,
groups = {node = 1, unbreakable = 1, mech = 1, trigger = 1},
sounds = sounds.metal,
collision_box = {
type = "fixed",
fixed = {{-5/16, -1/4, 1/4, 5/16, 1/4, 1/2}},
},
selection_box = {
type = "fixed",
fixed = {{-5/16, -1/4, 1/4, 5/16, 1/4, 1/2}},
},
after_dig_node = mech.after_dig,
on_timer = function(pos)
mech.untrigger(pos)
local node = minetest.get_node(pos)
node.name = "mech:button"
minetest.swap_node(pos, node)
minetest.sound_play("button_untrigger", {pos = pos})
end,
})
-- switches
minetest.register_node("mech:switch", {
description = "Switch (off)",
drawtype = "mesh",
mesh = "switch.obj",
tiles = {"button_switch.png"},
paramtype = "light",
paramtype2 = "facedir",
walkable = false,
groups = {node = 1, unbreakable = 1, trigger = 1},
sounds = sounds.metal,
collision_box = {
type = "fixed",
fixed = {{-1/4, -5/16, 1/4, 1/4, 5/16, 1/2}},
},
selection_box = {
type = "fixed",
fixed = {{-1/4, -5/16, 1/4, 1/4, 5/16, 1/2}},
},
after_dig_node = mech.after_dig,
on_punch = function(pos, node, puncher, pointed_thing)
mech.trigger(pos)
node.name = "mech:switch_on"
minetest.swap_node(pos, node)
minetest.sound_play("button_trigger", {pos = pos})
end,
on_rightclick = function(pos, node, puncher, itemstack, pointed_thing)
mech.trigger(pos)
node.name = "mech:switch_on"
minetest.swap_node(pos, node)
minetest.sound_play("button_trigger", {pos = pos})
return itemstack
end,
})
minetest.register_node("mech:switch_on", {
description = "Switch (on)",
drawtype = "mesh",
mesh = "switch_on.obj",
tiles = {"button_switch.png"},
paramtype = "light",
paramtype2 = "facedir",
walkable = false,
groups = {node = 1, unbreakable = 1, mech = 1, trigger = 1},
sounds = sounds.metal,
collision_box = {
type = "fixed",
fixed = {{-1/4, -5/16, 1/4, 1/4, 5/16, 1/2}},
},
selection_box = {
type = "fixed",
fixed = {{-1/4, -5/16, 1/4, 1/4, 5/16, 1/2}},
},
after_dig_node = mech.after_dig,
on_punch = function(pos, node, puncher, pointed_thing)
mech.untrigger(pos)
node.name = "mech:switch"
minetest.swap_node(pos, node)
minetest.sound_play("button_untrigger", {pos = pos})
end,
on_rightclick = function(pos, node, puncher, itemstack, pointed_thing)
mech.untrigger(pos)
node.name = "mech:switch"
minetest.swap_node(pos, node)
minetest.sound_play("button_untrigger", {pos = pos})
return itemstack
end,
})
-- pressure plates
minetest.register_node("mech:pressure_plate", {
description = "Pressure plate",
drawtype = "nodebox",
tiles = {"blocks_tiles.png^[sheet:8x8:3,2"},
node_box = {
type = "fixed",
fixed = {{-7/16, -1/2, -7/16, 7/16, -7/16, 7/16}},
},
paramtype = "light",
groups = {node = 1, unbreakable = 1, trigger = 1},
sounds = sounds.metal,
walkable = false,
after_dig_node = mech.after_dig,
on_walk_over = function(pos, node, player)
--
-- somewhat complex case becayse of the on_walk_over
-- combination with triggers will put the plate back
-- again right after removal happens, and so the plate
-- never gets removed if you self-remove the plate
--
local remove
local meta = minetest.get_meta(pos)
local offsets = minetest.deserialize(meta:get_string("offsets")) or {}
local h = hash_vector({x = 0, y = 0, z = 0})
for v, _ in pairs(offsets) do
if v == h then
remove = true
end
end
mech.trigger(pos)
if remove then
return
end
node.name = "mech:pressure_plate_down"
minetest.swap_node(pos, node)
minetest.get_node_timer(pos):start(0.5)
minetest.sound_play("button_trigger", {pos = pos})
end,
})
minetest.register_node("mech:pressure_plate_down", {
description = "Pressure plate (down)",
drawtype = "nodebox",
tiles = {"blocks_tiles.png^[sheet:8x8:3,2"},
node_box = {
type = "fixed",
fixed = {{-7/16, -1/2, -7/16, 7/16, -1/2 + 0.001, 7/16}},
},
paramtype = "light",
groups = {node = 1, unbreakable = 1, mech = 1, trigger = 1},
sounds = sounds.metal,
after_dig_node = mech.after_dig,
walkable = false,
on_walk_over = function(pos, node, player)
minetest.get_node_timer(pos):start(0.5)
end,
on_timer = function(pos)
mech.untrigger(pos)
local node = minetest.get_node(pos)
node.name = "mech:pressure_plate"
minetest.swap_node(pos, node)
minetest.sound_play("button_untrigger", {pos = pos})
end,
})
-- piston
local pistondir = {
[0] = {x = 0, y = 1, z = 0},
[1] = {x = 0, y = 0, z = 1},
[2] = {x = 0, y = 0, z = -1},
[3] = {x = 1, y = 0, z = 0},
[4] = {x = -1, y = 0, z = 0},
[5] = {x = 0, y = -1, z = 0},
}
local function piston_on_trigger(pos)
local node = minetest.get_node(pos)
local dir = pistondir[math.floor(node.param2 / 4)]
-- determine what the first buildable_to node is ahead of the piston
local _p = vector.add(pos, dir)
local stack = 32
while true do
local _n = minetest.get_node(_p)
if minetest.registered_nodes[_n.name].unpushable then
return
end
if minetest.registered_nodes[_n.name].buildable_to then
break
end
stack = stack - 1
if stack == 0 then
-- can't push a single node
return
end
-- try next block
_p = vector.add(_p, dir)
end
-- now swap all the nodes outward
while stack < 32 do
local _pp = vector.subtract(_p, dir)
local _nn = minetest.get_node(_pp)
minetest.swap_node(_p, _nn)
minetest.check_for_falling(_pp)
stack = stack + 1
_p = _pp
end
-- set the piston head
if node.name == "mech:piston_base" then
node.name = "mech:piston_top"
else
node.name = "mech:piston_top_sticky"
end
minetest.swap_node(_p, node)
-- last, piston base
node.name = "mech:piston_base_extended"
minetest.swap_node(pos, node)
minetest.sound_play("piston_trigger", {pos = pos})
-- make nodes fall if needed
minetest.check_for_falling(_p)
end
minetest.register_node("mech:piston_base", {
description = "Piston",
paramtype2 = "facedir",
tiles = {"piston_top_normal.png", "piston_bottom.png", "piston_side.png"},
groups = {node = 1, unbreakable = 1},
sounds = sounds.wood,
after_dig_node = mech.after_dig,
on_trigger = piston_on_trigger,
})
minetest.register_node("mech:piston_base_sticky", {
description = "Piston (sticky)",
paramtype2 = "facedir",
tiles = {"piston_top_sticky.png", "piston_bottom.png", "piston_side.png"},
groups = {node = 1, unbreakable = 1},
sounds = sounds.wood,
after_dig_node = mech.after_dig,
on_trigger = piston_on_trigger,
})
minetest.register_node("mech:piston_base_extended", {
description = "Piston",
paramtype = "light",
paramtype2 = "facedir",
drawtype = "nodebox",
tiles = {"piston_inner.png", "piston_bottom.png", "[combine:16x16:0,0=piston_side.png:0,-12=piston_side.png"},
node_box = {
type = "fixed",
fixed = {
{-1/2, -1/2, -1/2, 1/2, 1/4, 1/2}, -- base
{-1/8, 1/4, -1/8, 1/8, 1/2, 1/8}, -- rod
}
},
groups = {node = 1, unbreakable = 1, mech = 1},
sounds = sounds.wood,
after_dig_node = mech.after_dig,
on_trigger = function(pos) end,
on_untrigger = function(pos)
local node = minetest.get_node(pos)
local dir = pistondir[math.floor(node.param2 / 4)]
local npos = vector.add(pos, dir)
local nnode = minetest.get_node(npos)
local nnpos = vector.add(npos, dir)
if nnode.name == "mech:piston_top_sticky" then
local nnnode = minetest.get_node(nnpos)
if not minetest.registered_nodes[nnnode.name].unpushable then
minetest.remove_node(nnpos)
minetest.swap_node(npos, nnnode)
else
minetest.remove_node(npos)
end
node.name = "mech:piston_base_sticky"
elseif nnode.name == "mech:piston_top" then
minetest.remove_node(npos)
node.name = "mech:piston_base"
else
-- wall exploit otherwise
return
end
minetest.swap_node(pos, node)
minetest.sound_play("piston_untrigger", {pos = pos})
-- make nodes fall if needed
minetest.check_for_falling(npos)
minetest.check_for_falling(nnpos)
end,
})
minetest.register_node("mech:piston_top", {
description = "Piston Top",
paramtype = "light",
paramtype2 = "facedir",
drawtype = "nodebox",
tiles = {"piston_top_normal.png", "piston_inner.png", "piston_side.png"},
node_box = {
type = "fixed",
fixed = {
{-1/2, 1/4, -1/2, 1/2, 1/2, 1/2}, -- head
{-1/8, -1/2, -1/8, 1/8, 1/4, 1/8}, -- rod
}
},
groups = {node = 1, unbreakable = 1, piston_top = 1},
unpushable = 1,
sounds = sounds.wood,
on_trigger = function(pos) end,
})
minetest.register_node("mech:piston_top_sticky", {
description = "Piston Top (sticky)",
paramtype = "light",
paramtype2 = "facedir",
drawtype = "nodebox",
tiles = {"piston_top_sticky.png", "piston_inner.png", "piston_side.png"},
groups = {node = 1, unbreakable = 1, piston_top = 1},
unpushable = 1,
sounds = sounds.wood,
node_box = {
type = "fixed",
fixed = {
{-1/2, 1/4, -1/2, 1/2, 1/2, 1/2}, -- head
{-1/8, -1/2, -1/8, 1/8, 1/4, 1/8}, -- rod
}
},
on_trigger = function(pos) end,
})
minetest.register_node("mech:delayer", {
description = "Delayer",
tiles = {"delayer.png"},
place_param2 = 1,
groups = {node = 1, unbreakable = 1, trigger = 1},
sounds = sounds.metal,
after_dig_node = mech.after_dig,
on_trigger = function(pos)
local node = minetest.get_node(pos)
minetest.after(node.param2, mech.trigger, pos)
end,
on_untrigger = function(pos)
local node = minetest.get_node(pos)
minetest.after(node.param2, mech.untrigger, pos)
end,
on_punch = function(pos, node, puncher, pointed_thing)
local name = puncher:get_player_name()
if not puncher or not boxes.players_editing_boxes[name] then
return
end
node.param2 = (node.param2 % 16) + 1
minetest.chat_send_player(name, "Delay = " .. node.param2)
minetest.swap_node(pos, node)
end,
on_rightclick = function(pos, node, puncher, itemstack, pointed_thing)
local name = puncher:get_player_name()
if not puncher then
return itemstack
elseif not boxes.players_editing_boxes[name] then
return do_player_place(pos, node, puncher, itemstack, pointed_thing)
end
node.param2 = (node.param2 - 2) % 16 + 1
minetest.chat_send_player(name, "Delay = " .. node.param2)
minetest.swap_node(pos, node)
end,
})
minetest.register_node("mech:extender", {
description = "Extender",
tiles = {"extender.png"},
place_param2 = 1,
groups = {node = 1, unbreakable = 1, trigger = 1},
sounds = sounds.metal,
after_dig_node = mech.after_dig,
on_trigger = mech.trigger,
on_untrigger = function(pos)
local node = minetest.get_node(pos)
minetest.after(node.param2, mech.untrigger, pos)
end,
on_punch = function(pos, node, puncher, pointed_thing)
local name = puncher:get_player_name()
if not puncher or not boxes.players_editing_boxes[name] then
return
end
node.param2 = (node.param2 % 16) + 1
minetest.chat_send_player(name, "Delay = " .. node.param2)
minetest.swap_node(pos, node)
end,
on_rightclick = function(pos, node, puncher, itemstack, pointed_thing)
local name = puncher:get_player_name()
if not puncher then
return itemstack
elseif not boxes.players_editing_boxes[name] then
return do_player_place(pos, node, puncher, itemstack, pointed_thing)
end
node.param2 = (node.param2 - 2) % 16 + 1
minetest.chat_send_player(name, "Delay = " .. node.param2)
minetest.swap_node(pos, node)
end,
})
minetest.register_node("mech:inverter", {
description = "Inverter",
tiles = {"inverter.png"},
place_param2 = 1,
groups = {node = 1, unbreakable = 1, trigger = 1},
sounds = sounds.metal,
after_dig_node = mech.after_dig,
on_trigger = mech.untrigger,
on_untrigger = mech.trigger,
})
minetest.register_node("mech:filter", {
description = "Filter",
tiles = {"filter.png"},
place_param2 = 1,
groups = {node = 1, unbreakable = 1, trigger = 1},
sounds = sounds.metal,
after_dig_node = mech.after_dig,
on_trigger = mech.trigger,
})
minetest.register_node("mech:adder", {
description = "Adder",
tiles = {"adder.png"},
place_param2 = 32,
groups = {node = 1, unbreakable = 1, trigger = 1},
sounds = sounds.metal,
after_dig_node = mech.after_dig,
on_trigger = function(pos)
local node = minetest.get_node(pos)
local count = (node.param2 % 16)
local need = math.floor(node.param2 / 16)
if count < 15 then
count = count + 1
end
if count == need then
mech.trigger(pos)
end
node.param2 = need * 16 + count
minetest.swap_node(pos, node)
end,
on_untrigger = function(pos)
local node = minetest.get_node(pos)
local count = (node.param2 % 16)
local need = math.floor(node.param2 / 16)
if count > 0 then
count = count - 1
end
if count == (need - 1) then
mech.untrigger(pos)
end
node.param2 = need * 16 + count
minetest.swap_node(pos, node)
end,
on_punch = function(pos, node, puncher, pointed_thing)
local name = puncher:get_player_name()
if not puncher or not boxes.players_editing_boxes[name] then
return
end
node.param2 = (node.param2 + 16) % 256
minetest.chat_send_player(name, "Count = " .. math.floor(node.param2 / 16))
minetest.swap_node(pos, node)
end,
on_rightclick = function(pos, node, puncher, itemstack, pointed_thing)
local name = puncher:get_player_name()
if not puncher then
return itemstack
elseif not boxes.players_editing_boxes[name] then
return do_player_place(pos, node, puncher, itemstack, pointed_thing)
end
node.param2 = (node.param2 - 16) % 256
minetest.chat_send_player(name, "Count = " .. math.floor(node.param2 / 16))
minetest.swap_node(pos, node)
end,
})
local facedir_top = {
[0] = {x = 0, y = 1, z = 0},
[1] = {x = 0, y = 0, z = 1},
[2] = {x = 0, y = 0, z = -1},
[3] = {x = 1, y = 0, z = 0},
[4] = {x = -1, y = 0, z = 0},
[5] = {x = 0, y = -1, z = 0},
}
local function is_detected(name, meta)
local n = meta:get_string("nodes")
if n ~= "" then
local nodes = minetest.deserialize(n)
if nodes[name] then
return true
end
-- don't let it detect air/liquidflowing!
return false
end
if name == "air" or name == "nodes:placeholder" then
return false
end
local g = minetest.registered_nodes[name].groups
if g.torch ~= nil or g.piston_top ~= nil then
return false
end
return true
end
minetest.register_node("mech:node_detector", {
description = "Node detector\nPunch nodes while wielding this to limit detection to punched nodes",
tiles = {"detector_top.png", "detector.png"},
groups = {node = 1, unbreakable = 1, trigger = 1},
sounds = sounds.metal,
paramtype2 = "facedir",
on_use = function(itemstack, user, pointed_thing)
if not pointed_thing.under then
return itemstack
end
local pos = pointed_thing.under
local node = minetest.get_node(pos)
local meta = itemstack:get_meta()
local def = minetest.registered_nodes[node.name]
if def.groups.not_in_creative_inventory and not def.groups.frame_with_content or
def.groups.trigger or def.groups.mech or
def.groups.door
then
return itemstack
end
local nodes = minetest.deserialize(meta:get_string("nodes")) or {}
nodes[node.name] = 1
meta:set_string("nodes", minetest.serialize(nodes))
local s = ""
for k, _ in pairs(nodes) do
if s == "" then
s = k
else
s = s .. ", " .. k
end
end
minetest.chat_send_player(user:get_player_name(), "This detector will detect: " .. s)
meta:set_string("description", "Detector node\nDetects: " .. s)
return itemstack
end,
on_place = function(itemstack, placer, pointed_thing)
local meta = itemstack:get_meta()
local pos = pointed_thing.above
minetest.set_node(pos, {name = "mech:node_detector"})
local nmeta = minetest.get_meta(pos)
local nodes = meta:get_string("nodes")
if nodes ~= "" then
nmeta:set_string("nodes", nodes)
nmeta:mark_as_private("nodes")
end
return itemstack
end,
on_punch = function(pos, node, puncher, pointed_thing)
local name = puncher:get_player_name()
if not puncher or not boxes.players_editing_boxes[name] then
return
end
local meta = minetest.get_meta(pos)
local dist = (meta:get_int("distance") + 1) % 16
minetest.chat_send_player(name, "Distance = " .. dist)
meta:set_int("distance", dist)
end,
on_rightclick = function(pos, node, puncher, itemstack, pointed_thing)
local name = puncher:get_player_name()
if not puncher then
return itemstack
elseif not boxes.players_editing_boxes[name] then
return do_player_place(pos, node, puncher, itemstack, pointed_thing)
end
local meta = minetest.get_meta(pos)
local dist = (meta:get_int("distance") - 1) % 16
minetest.chat_send_player(name, "Distance = " .. dist)
meta:set_int("distance", dist)
end,
after_dig_node = mech.after_dig,
after_box_construct = function(pos)
local meta = minetest.get_meta(pos)
meta:set_int("detected", 0)
minetest.get_node_timer(pos):start(0.5)
end,
on_construct = function(pos)
local meta = minetest.get_meta(pos)
meta:set_int("detected", 0)
minetest.get_node_timer(pos):start(0.5)
end,
on_timer = function(pos)
local node = minetest.get_node(pos)
local dir = facedir_top[math.floor(node.param2 / 4)]
local meta = minetest.get_meta(pos)
local dist = meta:get_int("distance") or 0
if dist > 0 then
dir = vector.multiply(dir, dist + 1)
end
local p2 = vector.add(pos, dir)
local bb = boxes.find_box(pos)
if bb and bb ~= boxes.find_box(p2) then
return true
end
local n2 = minetest.get_node(p2)
local state = meta:get_int("detected")
if state == 0 and is_detected(n2.name, meta) == true then
mech.trigger(pos)
meta:set_int("detected", 1)
minetest.sound_play("button_trigger", {pos = pos})
elseif state == 1 and is_detected(n2.name, meta) == false then
mech.untrigger(pos)
meta:set_int("detected", 0)
minetest.sound_play("button_untrigger", {pos = pos})
end
return true
end,
on_trigger = function() end,
on_untrigger = function() end,
})
minetest.register_node("mech:node_creator", {
description = "Node creator\nIf you don't fill it before placing, it will make air",
tiles = {"creator_top.png", "creator.png"},
groups = {node = 1, unbreakable = 1, trigger = 1},
sounds = sounds.metal,
paramtype2 = "facedir",
liquids_pointable = true,
on_use = function(itemstack, user, pointed_thing)
if not pointed_thing.under then
return itemstack
end
local pos = pointed_thing.under
local node = minetest.get_node(pos)
if node.name == "boxes:pedestal" then
return itemstack
end
local meta = itemstack:get_meta()
local def = minetest.registered_nodes[node.name]
if def.groups.not_in_creative_inventory or def.groups.door then
return itemstack
end
meta:set_string("node", minetest.serialize(node))
meta:set_string("meta", minetest.serialize(minetest.get_meta(pos):to_table()))
minetest.chat_send_player(user:get_player_name(), "The creator node will place: " .. node.name)
meta:set_string("description", "Creator node\nCreates: " .. node.name)
return itemstack
end,
on_place = function(itemstack, placer, pointed_thing)
local meta = itemstack:get_meta()
local nodestr = meta:get_string("node")
local metastr = meta:get_string("meta")
if not nodestr or nodestr == "" then
nodestr = minetest.serialize({name = "air"})
end
if not metastr or metastr == "" then
metastr = minetest.serialize({fields = {}, inventory = {}})
end
local pos = pointed_thing.above
minetest.set_node(pos, {name = "mech:node_creator"})
local nmeta = minetest.get_meta(pos)
nmeta:set_string("node", nodestr)
nmeta:mark_as_private("node")
nmeta:set_string("meta", metastr)
nmeta:mark_as_private("meta")
return itemstack
end,
on_punch = function(pos, node, puncher, pointed_thing)
local name = puncher:get_player_name()
if not puncher or not boxes.players_editing_boxes[name] then
return
end
local meta = minetest.get_meta(pos)
local dist = (meta:get_int("distance") + 1) % 16
minetest.chat_send_player(name, "Distance = " .. dist)
meta:set_int("distance", dist)
end,
on_rightclick = function(pos, node, puncher, itemstack, pointed_thing)
local name = puncher:get_player_name()
if not puncher then
return itemstack
elseif not boxes.players_editing_boxes[name] then
return do_player_place(pos, node, puncher, itemstack, pointed_thing)
end
local meta = minetest.get_meta(pos)
local dist = (meta:get_int("distance") - 1) % 16
minetest.chat_send_player(name, "Distance = " .. dist)
meta:set_int("distance", dist)
end,
after_dig_node = mech.after_dig,
on_trigger = function(pos)
local meta = minetest.get_meta(pos)
local node = minetest.get_node(pos)
local dist = meta:get_int("distance") or 0
local dir = facedir_top[math.floor(node.param2 / 4)]
if dist > 0 then
dir = vector.multiply(dir, dist + 1)
end
local p2 = vector.add(pos, dir)
local bb = boxes.find_box(pos)
if bb and bb ~= boxes.find_box(p2) then
return
end
-- if erasing signs, we have to remove entities
local d2 = minetest.registered_nodes[minetest.get_node(p2).name]
if d2.groups and d2.groups.sign then
d2.on_destruct(p2)
end
local n2 = minetest.deserialize(meta:get_string("node"))
if not n2 or not n2.name then
-- We were deferred, but the node was removed and
-- therefore meta was too.
return
end
minetest.swap_node(p2, n2)
local m2 = minetest.deserialize(meta:get_string("meta"))
local meta2 = minetest.get_meta(p2)
meta2:from_table(m2)
--FIXME: this omits mark_as_private!
local def = minetest.registered_nodes[n2.name]
if def.after_box_construct then
local pc = table.copy(p2)
def.after_box_construct(pc)
end
local sounds = def.sounds or {}
if sounds.place then
minetest.sound_play(sounds.place, {pos = p2})
end
minetest.check_for_falling(p2)
return true
end,
on_untrigger = function() end,
})
-- event reactors:
-- doors
-- trapdoors
-- disappearing nodes
-- appearing nodes
-- moved from 'boxes' mod here
minetest.register_node(":boxes:pedestal", {
description = "Nexus Pedestal",
tiles = {"blocks_tiles.png^[sheet:8x8:2,0"},
drawtype = "nodebox",
paramtype = "light",
light_source = 4,
node_box = {
type = "connected",
fixed = {{-1/2, -1/2, -1/2, 1/2, 1/2, 1/2}},
connect_top = {
{-1/2, 1/2, -1/2, -1/4, 3/4, -1/4},
{1/4, 1/2, -1/2, 1/2, 3/4, -1/4},
{-1/2, 1/2, 1/4, -1/4, 3/4, 1/2},
{1/4, 1/2, 1/4, 1/2, 3/4, 1/2},
},
},
connects_to = {"boxes:nexus"},
groups = {node = 1, mech = 1, trigger = 1},
sounds = sounds.stone,
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
if itemstack:get_name() ~= "boxes:nexus" then
return itemstack
end
local above = {x = pos.x, y = pos.y + 1, z = pos.z}
local above_name = minetest.get_node(above).name
if not minetest.registered_nodes[above_name] or not minetest.registered_nodes[above_name].buildable_to then
return itemstack
end
itemstack:take_item()
minetest.set_node(above, {name = "boxes:nexus", param2 = math.random(24) - 1})
minetest.sound_play("nexus_place", {pos = pos})
boxes.increase_items(clicker)
mech.trigger(pos)
return itemstack
end,
after_place_node = function(pos, placer, itemstack, pointed_thing)
if not placer then
return
end
local name = placer:get_player_name()
local box = boxes.players_editing_boxes[name]
if box then
box.num_items = box.num_items + 1
minetest.log("action", "box = " .. box.box_id .. ", num_items = " .. box.num_items)
end
end,
after_dig_node = function(pos, oldnode, oldmeta, digger)
if not digger then
return
end
local name = digger:get_player_name()
local box = boxes.players_editing_boxes[name]
if box then
box.num_items = math.max(0, box.num_items - 1)
minetest.log("action", "box = " .. box.box_id .. ", num_items = " .. box.num_items)
end
end,
on_trigger = function() end,
})