--[[ 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, })