1162 lines
34 KiB
Lua
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,
|
|
})
|