From 8c0e10c287f844967adfa72b65b8047224d1baa8 Mon Sep 17 00:00:00 2001 From: rnd Date: Fri, 1 May 2015 18:40:54 +0200 Subject: [PATCH] working: mover, keypad --- depends.txt | 1 + init.lua | 5 + mesecon_doors.lua | 173 +++++++++++++ mover.lua | 559 +++++++++++++++++++++++++++++++++++++++++ sounds/transporter.ogg | Bin 0 -> 7830 bytes textures/detector.png | Bin 0 -> 2107 bytes textures/keypad.png | Bin 0 -> 3470 bytes 7 files changed, 738 insertions(+) create mode 100644 depends.txt create mode 100644 init.lua create mode 100644 mesecon_doors.lua create mode 100644 mover.lua create mode 100644 sounds/transporter.ogg create mode 100644 textures/detector.png create mode 100644 textures/keypad.png diff --git a/depends.txt b/depends.txt new file mode 100644 index 0000000..331d858 --- /dev/null +++ b/depends.txt @@ -0,0 +1 @@ +default \ No newline at end of file diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..4e29407 --- /dev/null +++ b/init.lua @@ -0,0 +1,5 @@ +dofile(minetest.get_modpath("basic_machines").."/mover.lua") + +minetest.after(1, function() -- if you want keypad to open doors + dofile(minetest.get_modpath("basic_machines").."/mesecon_doors.lua") +end) diff --git a/mesecon_doors.lua b/mesecon_doors.lua new file mode 100644 index 0000000..78e4be4 --- /dev/null +++ b/mesecon_doors.lua @@ -0,0 +1,173 @@ +doors = {} + +-- Registers a door - REDEFINITION ONLY | DOORS MOD MUST HAVE BEEN LOADED BEFORE +-- name: The name of the door +-- def: a table with the folowing fields: +-- description +-- inventory_image +-- groups +-- tiles_bottom: the tiles of the bottom part of the door {front, side} +-- tiles_top: the tiles of the bottom part of the door {front, side} +-- If the following fields are not defined the default values are used +-- node_box_bottom +-- node_box_top +-- selection_box_bottom +-- selection_box_top +-- only_placer_can_open: if true only the player who placed the door can +-- open it + +function doors:register_door(name, def) + def.groups.not_in_creative_inventory = 1 + + local box = {{-0.5, -0.5, -0.5, 0.5, 0.5, -0.5+1.5/16}} + + if not def.node_box_bottom then + def.node_box_bottom = box + end + if not def.node_box_top then + def.node_box_top = box + end + if not def.selection_box_bottom then + def.selection_box_bottom= box + end + if not def.selection_box_top then + def.selection_box_top = box + end + + local tt = def.tiles_top + local tb = def.tiles_bottom + + local function after_dig_node(pos, name) + if minetest.get_node(pos).name == name then + minetest.remove_node(pos) + end + end + + local function on_rightclick(pos, dir, check_name, replace, replace_dir, params) + pos.y = pos.y+dir + if not minetest.get_node(pos).name == check_name then + return + end + local p2 = minetest.get_node(pos).param2 + p2 = params[p2+1] + + local meta = minetest.get_meta(pos):to_table() + minetest.set_node(pos, {name=replace_dir, param2=p2}) + minetest.get_meta(pos):from_table(meta) + + pos.y = pos.y-dir + meta = minetest.get_meta(pos):to_table() + minetest.set_node(pos, {name=replace, param2=p2}) + minetest.get_meta(pos):from_table(meta) + end + + local function on_mesecons_signal_open (pos, node) + on_rightclick(pos, 1, name.."_t_1", name.."_b_2", name.."_t_2", {1,2,3,0}) + minetest.after(5, function() -- rnd: auto close after 5 seconds + on_rightclick(pos, 1, name.."_t_2", name.."_b_1", name.."_t_1", {3,0,1,2}) + end + ) + end + + local function on_mesecons_signal_close (pos, node) + on_rightclick(pos, 1, name.."_t_2", name.."_b_1", name.."_t_1", {3,0,1,2}) + end + + local function check_player_priv(pos, player) + if not def.only_placer_can_open then + return true + end + local meta = minetest.get_meta(pos) + local pn = player:get_player_name() + return meta:get_string("doors_owner") == pn + end + + minetest.register_node(":"..name.."_b_1", { + tiles = {tb[2], tb[2], tb[2], tb[2], tb[1], tb[1].."^[transformfx"}, + paramtype = "light", + paramtype2 = "facedir", + drop = name, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = def.node_box_bottom + }, + selection_box = { + type = "fixed", + fixed = def.selection_box_bottom + }, + groups = def.groups, + + after_dig_node = function(pos, oldnode, oldmetadata, digger) + pos.y = pos.y+1 + after_dig_node(pos, name.."_t_1") + end, + + on_rightclick = function(pos, node, puncher) + if check_player_priv(pos, puncher) then + on_rightclick(pos, 1, name.."_t_1", name.."_b_2", name.."_t_2", {1,2,3,0}) + end + end, + + mesecons = {effector = { + action_on = on_mesecons_signal_open + }}, + + can_dig = check_player_priv, + }) + + minetest.register_node(":"..name.."_b_2", { + tiles = {tb[2], tb[2], tb[2], tb[2], tb[1].."^[transformfx", tb[1]}, + paramtype = "light", + paramtype2 = "facedir", + drop = name, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = def.node_box_bottom + }, + selection_box = { + type = "fixed", + fixed = def.selection_box_bottom + }, + groups = def.groups, + + after_dig_node = function(pos, oldnode, oldmetadata, digger) + pos.y = pos.y+1 + after_dig_node(pos, name.."_t_2") + end, + + on_rightclick = function(pos, node, puncher) + if check_player_priv(pos, puncher) then + on_rightclick(pos, 1, name.."_t_2", name.."_b_1", name.."_t_1", {3,0,1,2}) + end + end, + + mesecons = {effector = { + action_off = on_mesecons_signal_close + }}, + + can_dig = check_player_priv, + }) +end + +doors:register_door("doors:door_wood", { + description = "Wooden Door", + inventory_image = "door_wood.png", + groups = {snappy=1,choppy=2,oddly_breakable_by_hand=2,flammable=2,door=1}, + tiles_bottom = {"door_wood_b.png", "door_brown.png"}, + tiles_top = {"door_wood_a.png", "door_brown.png"}, + sounds = default.node_sound_wood_defaults(), +}) + +doors:register_door("doors:door_steel", { + description = "Steel Door", + inventory_image = "door_steel.png", + groups = {snappy=1,bendy=2,cracky=1,melty=2,level=2,door=1}, + tiles_bottom = {"door_steel_b.png", "door_grey.png"}, + tiles_top = {"door_steel_a.png", "door_grey.png"}, + only_placer_can_open = true, + sounds = default.node_sound_stone_defaults(), +}) + +print("[basic machines] loaded") \ No newline at end of file diff --git a/mover.lua b/mover.lua new file mode 100644 index 0000000..79b1c03 --- /dev/null +++ b/mover.lua @@ -0,0 +1,559 @@ +------------------------------------------------------------------------------------------------------------------------------------ +-- BASIC MACHINES MOD by rnd +-- mod with basic simple automatization for minetest. No background processing, no abm's or other lag causing background processing. +------------------------------------------------------------------------------------------------------------------------------------ + +-- MOVER: universal moving machine, requires coal in nearby chest to operate +-- can take item from chest and place it in chest or as a node outside at ranges -5,+5 +-- it can be used for filtering by setting "filter". if set to "object" it will teleport all objects at start location. +-- if set to "drop" it will drop node at target location, if set to "dig" it will dig out nodes and return appropriate drops. + +-- input is: where to take and where to put +-- to operate mese power is needed + +-- KEYPAD: can serve as a button to activate machines ( partially mesecons compatible ). Can be password protected. Can serve as a +-- replacement for mesecons blinky plant, limited to max 100 operations. +-- As a simple example it can be used to open doors, which close automatically after 5 seconds. + + +MOVER_FUEL_STORAGE_CAPACITY = 5; -- how many operations from one coal lump +minetest.register_node("basic_machines:mover", { + description = "Mover", + tiles = {"default_furnace_top.png"}, + groups = {oddly_breakable_by_hand=2,mesecon_effector_on = 1}, + sounds = default.node_sound_wood_defaults(), + after_place_node = function(pos, placer) + local meta = minetest.env:get_meta(pos) + meta:set_string("infotext", "Mover block. Right click to set it up. Or set positions by punching it.") + meta:set_string("owner", placer:get_player_name()); meta:set_int("public",0); + meta:set_int("x0",0);meta:set_int("y0",-1);meta:set_int("z0",0); -- source1 + meta:set_int("x1",0);meta:set_int("y1",-1);meta:set_int("z1",0); -- source2: defines cube + meta:set_int("pc",0); meta:set_int("dim",1);-- current cube position and dimensions + meta:set_int("x2",0);meta:set_int("y2",1);meta:set_int("z2",0); + meta:set_float("fuel",0) + meta:set_string("prefer", ""); + end, + + mesecons = {effector = { + action_on = function (pos, node) + local meta = minetest.get_meta(pos); + local fuel = meta:get_float("fuel"); + + --minetest.chat_send_all("mover mesecons: runnning with pos " .. pos.x .. " " .. pos.y .. " " .. pos.z) + + local x0,y0,z0,x1,y1,z1; + x0=meta:get_int("x0");y0=meta:get_int("y0");z0=meta:get_int("z0"); + local pos1 = {x=x0+pos.x,y=y0+pos.y,z=z0+pos.z}; -- where to take from + local pos2 = {x=meta:get_int("x2")+pos.x,y=meta:get_int("y2")+pos.y,z=meta:get_int("z2")+pos.z}; -- where to put + + local pc = meta:get_int("pc"); local dim = meta:get_int("dim"); pc = (pc+1) % dim;meta:set_int("pc",pc) -- cycle position + x1=meta:get_int("x1")-x0+1;y1=meta:get_int("y1")-y0+1;z1=meta:get_int("z1")-z0+1; -- get dimensions + + --pc = z*a*b+x*b+y, from x,y,z to pc + -- set current input position + pos1.y = y0 + (pc % y1); pc = (pc - (pc % y1))/y1; + pos1.x = x0 + (pc % x1); pc = (pc - (pc % x1))/x1; + pos1.z = z0 + pc; + pos1.x = pos.x+pos1.x;pos1.y = pos.y+pos1.y;pos1.z = pos.z+pos1.z; + + if fuel<=0 then -- needs fuel to operate, find nearby open chest with fuel within radius 1 + local r = 1; + + local positions = minetest.find_nodes_in_area( --find furnace with fuel + {x=pos.x-r, y=pos.y-r, z=pos.z-r}, + {x=pos.x+r, y=pos.y+r, z=pos.z+r}, + "default:chest") + local fpos = nil; + for _, p in ipairs(positions) do + -- dont take coal from source or target location + if (p.x ~= pos1.x or p.y~=pos1.y or p.z ~= pos1.z) and (p.x ~= pos2.x or p.y~=pos2.y or p.z ~= pos2.z) then + fpos = p; + end + end + + if not fpos then return end -- no chest found + local cmeta = minetest.get_meta(fpos); + local inv = cmeta:get_inventory(); + local fuels = {["default:coal_lump"]=1,["default:cactus"]=1,["default:coalblock"]=10}; + local found_fuel = nil;local stack; + for i,v in pairs(fuels) do + stack = ItemStack({name=i}) + if inv:contains_item("main", stack) then found_fuel = v break end + end + -- check for this fuel + if found_fuel~=nil then + --minetest.chat_send_all(" refueled ") + inv:remove_item("main", stack) + meta:set_float("fuel", fuel+MOVER_FUEL_STORAGE_CAPACITY*found_fuel); + fuel = fuel+MOVER_FUEL_STORAGE_CAPACITY*found_fuel; + meta:set_string("infotext", "Mover block. Fuel "..MOVER_FUEL_STORAGE_CAPACITY); + else meta:set_string("infotext", "Mover block. Out of fuel.");return + end + --check fuel + if fuel == 0 then return end + end + + local owner = meta:get_string("owner"); + + -- check protections + if minetest.is_protected(pos1, owner) or minetest.is_protected(pos2, owner) then + meta:set_float("fuel", -1); + meta:set_string("infotext", "Mover block. Protection fail. Deactivated.") + return end + + local prefer = meta:get_string("prefer"); local mode = meta:get_string("mode"); + + if mode == "reverse" then -- reverse pos1, pos2 + local post = {x=pos1.x,y=pos1.y,z=pos1.z}; + pos1 = {x=pos2.x,y=pos2.y,z=pos2.z}; + pos2 = {x=post.x,y=post.y,z=post.z}; + end + local node1 = minetest.get_node(pos1);local node2 = minetest.get_node(pos2); + + if mode == "object" then -- teleport objects, for free + for _,obj in pairs(minetest.get_objects_inside_radius(pos1, 2)) do + obj:moveto(pos2, false) + end + minetest.sound_play("transporter", {pos=pos2,gain=1.0,max_hear_distance = 32,}) + --meta:set_float("fuel", fuel - 1); + return + end + + local dig=false; if mode == "dig" then dig = true; end -- digs at target location + local drop = false; if mode == "drop" then drop = true; end -- drops node instead of placing it + + -- decide what to do if source or target are chests + local source_chest=false; if string.find(node1.name,"default:chest") then source_chest=true end + if node1.name == "air" then return end -- nothing to move + + local target_chest = false + if node2.name == "default:chest" or node2.name == "default:chest_locked" then + target_chest = true + end + if not target_chest and minetest.get_node(pos2).name ~= "air" then return end -- do nothing if target nonempty and not chest + + -- filtering + if prefer~="" then -- prefered node set + if prefer~=node1.name and not source_chest then return end -- only take prefered node or from chests + if source_chest then -- take stuff from chest + --minetest.chat_send_all(" source chest detected") + local cmeta = minetest.get_meta(pos1); + local inv = cmeta:get_inventory(); + local stack = ItemStack({name=prefer}) + if inv:contains_item("main", stack) then + inv:remove_item("main", stack); + else return + end + end + node1 = {}; node1.name = prefer; + end + + if source_chest and prefer == "" then return end -- doesnt know what to take out of chest + --minetest.chat_send_all(" moving ") + + -- if target chest put in chest + if target_chest then + local cmeta = minetest.get_meta(pos2); + local inv = cmeta:get_inventory(); + + -- dig tree or cactus + local count = 0; + if dig then + -- check for cactus or tree + local dig_up = false + local dig_up_table = {"default:cactus","default:tree","default:jungletree","default:papyrus"}; + for i,v in pairs(dig_up_table) do + if node1.name==v then dig_up = true break end + end + + + if dig_up == true then -- dig up to height 10, break sooner if needed + for i=0,10 do + local pos3 = {x=pos1.x,y=pos1.y+i,z=pos1.z}; + local dname= minetest.get_node(pos3).name; + if dname ~=node1.name then break end + minetest.set_node(pos3,{name="air"}); count = count+1; + end + end + + -- read what to drop, if none just keep original node + local table = minetest.registered_items[node1.name]; + if table~=nil then + if table.drop~= nil and table.drop~="" then + node1={}; node1.name = table.drop; + end + end + + end + + local stack = ItemStack(node1.name) + if count>0 then stack = ItemStack({name=node1.name, count=count}) end + + if inv:room_for_item("main", stack) then + inv:add_item("main", stack); + end + end + + minetest.sound_play("transporter", {pos=pos2,gain=1.0,max_hear_distance = 32,}) + fuel = fuel -1; meta:set_float("fuel", fuel); -- burn fuel + meta:set_string("infotext", "Mover block. Fuel "..fuel); + + + if not target_chest then + if not drop then minetest.set_node(pos2, {name = node1.name}); end + if drop then + local stack = ItemStack(node1.name);minetest.add_item(pos2,stack) -- drops it + end + end + if not source_chest then + if dig then minetest.dig_node(pos1);nodeupdate(pos1) end + minetest.set_node(pos1, {name = "air"}); + end + end + } + }, + on_rightclick = function(pos, node, player, itemstack, pointed_thing) + local meta = minetest.get_meta(pos); + if meta:get_string("owner")~=player:get_player_name() then return end -- only owner can set up mover + + local x0,y0,z0,x1,y1,z1,x2,y2,z2,prefer,mode; + x0=meta:get_int("x0");y0=meta:get_int("y0");z0=meta:get_int("z0"); + x1=meta:get_int("x1");y1=meta:get_int("y1");z1=meta:get_int("z1"); + x2=meta:get_int("x2");y2=meta:get_int("y2");z2=meta:get_int("z2"); + prefer = meta:get_string("prefer");mode = meta:get_string("mode"); + local form = + "size[3,5]" .. -- width, height + "field[0.25,0.5;1,1;x0;source1;"..x0.."] field[1.25,0.5;1,1;y0;;"..y0.."] field[2.25,0.5;1,1;z0;;"..z0.."]".. + "field[0.25,1.5;1,1;x1;source2;"..x1.."] field[1.25,1.5;1,1;y1;;"..y1.."] field[2.25,1.5;1,1;z1;;"..z1.."]".. + "field[0.25,2.5;1,1;x2;Target;"..x2.."] field[1.25,2.5;1,1;y2;;"..y2.."] field[2.25,2.5;1,1;z2;;"..z2.."]".. + "button[2,4.25.;1,1;OK;OK] field[0.25,3.5;3,1;prefer;filter only block;"..prefer.."]".. + "field[0.25,4.5;2,1;mode;mode;"..mode.."]"; + minetest.show_formspec(player:get_player_name(), "basic_machines:mover_"..minetest.pos_to_string(pos), form) + end +}) + +-- KEYPAD + +minetest.register_node("basic_machines:keypad", { + description = "Keypad", + tiles = {"keypad.png"}, + groups = {oddly_breakable_by_hand=2}, + sounds = default.node_sound_wood_defaults(), + after_place_node = function(pos, placer) + local meta = minetest.env:get_meta(pos) + meta:set_string("infotext", "Keypad. Right click to set it up. Or punch it while holding sneak.") + meta:set_string("owner", placer:get_player_name()); meta:set_int("public",1); + meta:set_int("x0",0);meta:set_int("y0",1);meta:set_int("z0",0); -- target + meta:set_string("pass", ""); + meta:set_int("iter",1); + end, + + mesecons = {effector = { + action_on = function (pos, node) + local meta = minetest.get_meta(pos); + -- not yet defined ... ??? + end + } + }, + on_rightclick = function(pos, node, player, itemstack, pointed_thing) + local meta = minetest.get_meta(pos); + if meta:get_string("owner")~= player:get_player_name() then return end -- only owner can setup keypad + local x0,y0,z0,pass,iter; + x0=meta:get_int("x0");y0=meta:get_int("y0");z0=meta:get_int("z0");iter=meta:get_int("iter") or 1; + + pass = meta:get_string("pass"); + local form = + "size[3,2.75]" .. -- width, height + "field[0.25,0.5;1,1;x0;target;"..x0.."] field[1.25,0.5;1,1;y0;;"..y0.."] field[2.25,0.5;1,1;z0;;"..z0.."]".. + "button[0.,2.25;1,1;OK;OK] field[0.25,1.5;3,1;pass;Password: ;"..pass.."]" .. "field[1.25,2.5;2,1;iter;Repeat;".. iter .."]"; + minetest.show_formspec(player:get_player_name(), "basic_machines:keypad_"..minetest.pos_to_string(pos), form) + end +}) + +local function use_keypad(pos,name) + + if minetest.is_protected(pos, name) then meta:set_string("infotext", "Protection fail. reset."); meta:set_int("count",0) end + local meta = minetest.get_meta(pos); + local count = meta:get_int("count") or 0; + + if count<=0 then return end; count = count - 1; meta:set_int("count",count); + minetest.after(5, function() use_keypad(pos,name) end ) -- repeat operation as many times as set with "iter" + + local x0,y0,z0; + x0=meta:get_int("x0");y0=meta:get_int("y0");z0=meta:get_int("z0"); + x0=pos.x+x0;y0=pos.y+y0;z0=pos.z+z0; + --minetest.chat_send_all("KEYPAD USED. TARGET ".. x0 .. " " .. y0 .. " " .. z0); + local tpos = {x=x0,y=y0,z=z0}; + + local node = minetest.get_node(tpos);if not node.name then return end -- error + + + local table = minetest.registered_nodes[node.name]; + if not table then return end -- error + if not table.mesecons then return end -- error + if not table.mesecons.effector then return end -- error + local effector=table.mesecons.effector; + if not effector.action_on then return end + + effector.action_on(tpos,node); -- run + --minetest.chat_send_all("MESECONS RUN") + +end + + +local function check_keypad(pos,name) + local meta = minetest.get_meta(pos); + local pass = meta:get_string("pass"); + if pass == "" then meta:set_int("count",meta:get_int("iter")); use_keypad(pos,name) return end + pass = "" + local form = + "size[3,1]" .. -- width, height + "button[0.,0.5;1,1;OK;OK] field[0.25,0.25;3,1;pass;Enter Password: ;".."".."]"; + minetest.show_formspec(name, "basic_machines:check_keypad_"..minetest.pos_to_string(pos), form) + +end + +-- DETECTOR + +minetest.register_node("basic_machines:detector", { -- TO DO + description = "Detector", + tiles = {"detector.png"}, + groups = {oddly_breakable_by_hand=2}, + sounds = default.node_sound_wood_defaults(), + after_place_node = function(pos, placer) + local meta = minetest.env:get_meta(pos) + meta:set_string("infotext", "Detector. Right click/punch to set it up.") + meta:set_string("owner", placer:get_player_name()); meta:set_int("public",0); + meta:set_int("x1",0);meta:set_int("y1",-1);meta:set_int("z0",0); -- source: read + meta:set_int("x2",0);meta:set_int("y2",-1);meta:set_int("z2",0); -- target: activate + end, + + mesecons = {effector = { + action_on = function (pos, node) + local meta = minetest.get_meta(pos); + -- not yet defined ... ??? + end + } + }, + on_rightclick = function(pos, node, player, itemstack, pointed_thing) + local meta = minetest.get_meta(pos); + if meta:get_string("owner")~= player:get_player_name() then return end -- only owner can setup keypad + local x0,y0,z0,pass,iter; + x0=meta:get_int("x0");y0=meta:get_int("y0");z0=meta:get_int("z0");iter=meta:get_int("iter") or 1; + + pass = meta:get_string("pass"); + local form = + "size[3,2.75]" .. -- width, height + "field[0.25,0.5;1,1;x0;target;"..x0.."] field[1.25,0.5;1,1;y0;;"..y0.."] field[2.25,0.5;1,1;z0;;"..z0.."]".. + "button[0.,2.25;1,1;OK;OK] field[0.25,1.5;3,1;pass;Password: ;"..pass.."]" .. "field[1.25,2.5;2,1;iter;Repeat;".. iter .."]"; + minetest.show_formspec(player:get_player_name(), "basic_machines:keypad_"..minetest.pos_to_string(pos), form) + end +}) + + + + + + +local punchset = {}; +punchset.known_nodes = {["basic_machines:mover"]=true,["basic_machines:keypad"]=true}; + +-- handles set up punches +minetest.register_on_punchnode(function(pos, node, puncher, pointed_thing) + local name = puncher:get_player_name(); if name==nil then return end + if punchset[name]== nil then -- set up punchstate + punchset[name] = {} + punchset[name].node = "" + punchset[name].pos1 = {x=0,y=0,z=0};punchset[name].pos2 = {x=0,y=0,z=0};punchset[name].pos = {x=0,y=0,z=0}; + punchset[name].state = 0; -- 0 ready for punch, 1 ready for start position, 2 ready for end position + return + end + + -- check for known node names in case of first punch + if punchset[name].state == 0 and not punchset.known_nodes[node.name] then return end + -- from now on only punches with mover/keypad/... or setup punches + + if punchset.known_nodes[node.name] then -- check if owner of machine is punching or if machine public + local meta = minetest.get_meta(pos); + if not (meta:get_int("public") == 1) then + if meta:get_string("owner")~= name then return end + end + end + + if node.name == "basic_machines:mover" then -- mover init code + if punchset[name].state == 0 then + minetest.chat_send_player(name, "mover setup: Now punch starting and end position to set up mover.") + punchset[name].node = node.name;punchset[name].pos = {x=pos.x,y=pos.y,z=pos.z}; + punchset[name].state = 1 + return + end + end + + if punchset[name].node == "basic_machines:mover" then -- mover code + if punchset[name].state == 1 then + if math.abs(punchset[name].pos.x - pos.x)>5 or math.abs(punchset[name].pos.y - pos.y)>5 or math.abs(punchset[name].pos.z - pos.z)>5 then + minetest.chat_send_player(name, "mover setup: Punch closer to mover. reseting.") + punchset[name].state = 0; return + end + punchset[name].pos1 = {x=pos.x,y=pos.y,z=pos.z};punchset[name].state = 2; + minetest.chat_send_player(name, "mover setup: Start position for mover set. Punch again to set end position.") + return + end + + if punchset[name].state == 2 then + if punchset[name].node~="basic_machines:mover" then punchset[name].state = 0 return end + if math.abs(punchset[name].pos.x - pos.x)>5 or math.abs(punchset[name].pos.y - pos.y)>5 or math.abs(punchset[name].pos.z - pos.z)>5 then + minetest.chat_send_player(name, "mover setup: Punch closer to mover. reseting.") + punchset[name].state = 0; return + end + punchset[name].pos2 = {x=pos.x,y=pos.y,z=pos.z}; punchset[name].state = 0; + minetest.chat_send_player(name, "End position for mover set.") + local x = punchset[name].pos1.x-punchset[name].pos.x; + local y = punchset[name].pos1.y-punchset[name].pos.y; + local z = punchset[name].pos1.z-punchset[name].pos.z; + local meta = minetest.get_meta(punchset[name].pos); + meta:set_int("x0",x);meta:set_int("y0",y);meta:set_int("z0",z); + meta:set_int("x1",x);meta:set_int("y1",y);meta:set_int("z1",z); + x = punchset[name].pos2.x-punchset[name].pos.x; + y = punchset[name].pos2.y-punchset[name].pos.y; + z = punchset[name].pos2.z-punchset[name].pos.z; + meta:set_int("x2",x);meta:set_int("y2",y);meta:set_int("z2",z); + meta:set_int("pc",0); meta:set_int("dim",1); + return + end + end + + if node.name == "basic_machines:keypad" then -- keypad init/usage code + if not puncher:get_player_control().sneak then + check_keypad(pos,name)-- not setup, just standard operation + return + end + local meta = minetest.get_meta(pos); + if meta:get_string("owner")~= name then minetest.chat_send_player(name, "Only owner can set up keypad.") return end + + if punchset[name].state == 0 then + minetest.chat_send_player(name, "keypad setup: Now punch the target block.") + punchset[name].node = node.name;punchset[name].pos = {x=pos.x,y=pos.y,z=pos.z}; + punchset[name].state = 1 + return + end + end + + if punchset[name].node=="basic_machines:keypad" then -- keypad setup code + if punchset[name].state == 1 then + local meta = minetest.get_meta(punchset[name].pos); + local x = pos.x-punchset[name].pos.x; + local y = pos.y-punchset[name].pos.y; + local z = pos.z-punchset[name].pos.z; + + if math.abs(x)>5 or math.abs(y)>5 or math.abs(z)>5 then + minetest.chat_send_player(name, "keypad setup: Punch closer to keypad. reseting.") + punchset[name].state = 0; return + end + + meta:set_int("x0",x);meta:set_int("y0",y);meta:set_int("z0",z); + punchset[name].state = 0 + minetest.chat_send_player(name, "keypad setup: Keypad target set with coordinates " .. x .. " " .. y .. " " .. z) + return + end + end + +end) + + +-- handles forms processing for all machines +minetest.register_on_player_receive_fields(function(player,formname,fields) + + local fname = "basic_machines:mover_" + if string.sub(formname,0,string.len(fname)) == fname then + local pos_s = string.sub(formname,string.len(fname)+1); local pos = minetest.string_to_pos(pos_s) + local name = player:get_player_name(); if name==nil then return end + local meta = minetest.get_meta(pos) + if name ~= meta:get_string("owner") or not fields then return end -- only owner can interact + --minetest.chat_send_all("formname " .. formname .. " fields " .. dump(fields)) + + if fields.OK == "OK" then + local x0,y0,z0,x1,y1,z1,x2,y2,z2; + x0=tonumber(fields.x0) or 0;y0=tonumber(fields.y0) or -1;z0=tonumber(fields.z0) or 0 + x1=tonumber(fields.x1) or 0;y1=tonumber(fields.y1) or -1;z1=tonumber(fields.z1) or 0 + x2=tonumber(fields.x2) or 0;y2=tonumber(fields.y2) or 1;z2=tonumber(fields.z2) or 0; + if math.abs(x1)>5 or math.abs(y1)>5 or math.abs(z1)>5 or math.abs(x2)>5 or math.abs(y2)>5 or math.abs(z2)>5 then + minetest.chat_send_player(name,"all coordinates must be between -5 and 5"); return + end + if x1 ".. x1 ..","..y1..","..z1.. " and target coord ".. x2 ..","..y2..",".. z2 .. ". Put chest with coal next to it and start with mese signal."); + if meta:get_float("fuel")<0 then meta:set_float("fuel",0) end -- reset block + end + return + end + + fname = "basic_machines:keypad_" + if string.sub(formname,0,string.len(fname)) == fname then + local pos_s = string.sub(formname,string.len(fname)+1); local pos = minetest.string_to_pos(pos_s) + local name = player:get_player_name(); if name==nil then return end + local meta = minetest.get_meta(pos) + if name ~= meta:get_string("owner") or not fields then return end -- only owner can interact + + if fields.OK == "OK" then + local x0,y0,z0,pass; + x0=tonumber(fields.x0) or 0;y0=tonumber(fields.y0) or 1;z0=tonumber(fields.z0) or 0 + pass = fields.pass or ""; + if math.abs(x0)>5 or math.abs(y0)>5 or math.abs(z0)>5 then + minetest.chat_send_player(name,"all coordinates must be between -5 and 5"); return + end + meta:set_int("x0",x0);meta:set_int("y0",y0);meta:set_int("z0",z0);meta:set_string("pass",pass); + meta:set_int("iter",math.min(tonumber(fields.iter) or 1,100)); + meta:set_string("infotext", "Punch keypad to use it."); + if pass~="" then meta:set_string("infotext",meta:get_string("infotext").. ". Password protected."); end + end + return + end + + fname = "basic_machines:check_keypad_" + if string.sub(formname,0,string.len(fname)) == fname then + local pos_s = string.sub(formname,string.len(fname)+1); local pos = minetest.string_to_pos(pos_s) + local name = player:get_player_name(); if name==nil then return end + local meta = minetest.get_meta(pos) + + if fields.OK == "OK" then + local pass; + pass = fields.pass or ""; + if pass~=meta:get_string("pass") then + minetest.chat_send_player(name,"ACCESS DENIED. WRONG PASSWORD.") + return + end + minetest.chat_send_player(name,"ACCESS GRANTED.") + meta:set_int("count",meta:get_int("iter")); use_keypad(pos,name) + return + end + end + +end) + +-- CRAFTS + +minetest.register_craft({ + output = "basic_machines:mover", + recipe = { + {"default:diamond", "default:diamond", "default:diamond"}, + {"default:mese_crystal", "default:mese_crystal","default:mese_crystal"}, + {"default:stone", "default:wood", "default:stone"} + } +}) + +minetest.register_craft({ + output = "basic_machines:keypad", + recipe = { + {"default:stick"}, + {"default:wood"}, + } +}) \ No newline at end of file diff --git a/sounds/transporter.ogg b/sounds/transporter.ogg new file mode 100644 index 0000000000000000000000000000000000000000..886a5fa76c9979ba2e55b67ddc723e03b0fd6f77 GIT binary patch literal 7830 zcmch6c{tSF+xQvV48kPKU@$}(MMjjN#Mno~AhKskNMUGEX>64t#8lEG%7~ayCQF2} zWQkJNBq2-6lAc06de7+le81QGyMEVuy??#uI&%TPif-) zgN6v9BnJcB-h)Sd{lnE99L(@IH*GDg9RvbF%LAw8bCBxqhdXu@ckq~FaxRJx;La#6oow*}*C zQRPP`wZ^~+qNrb%#+yzSbeO)dT6CfHc%*E2o#W%ynoUB{%=Fhyf z$!kqA7yVlAC}C2HdY_904zYip;dJ~YK^g!#xoY%LHBq$e10nDn0JKqoRQn zD-{ux$N_$?tWj{D<@r2II?E;HP+<0^sM~5;x7CXt1hzfc+WugsT61l=B>;Bz5>25J z$hCET0HD0p=y&u|#$D1`2eOgz=Zkp&4FEnkZ=FS%gw29V*VkQLi~n!^vn>t)5Ub{g z;r&MiC?#1LlbaNAcOfYfcD;!_snev86*q!UQ1$J0C^kkZotfMHWBByk9~GbhI4Gfa zF{VWYr4;WYvDq{%9|wCMRb1LmO{t*X#i2=+%+u{vaK)M^AdmI9cr*#lfek*7L9xW= zXZq0bEm&#?p#>MWPJ1dR!&bjFFWpwZP32AsB^b|19$&jPbt7gl5ct znvH8>fDEGrWc<<~f$>suukn0dwGm45uxQa!(N^AO7tBhUk54qLmjj4^K zQ;$4Hq3z4~7YNdkCe)djK=E9dpFHY<&??)2SE(sRtG+_*GCLTTS1wX61r0#x35 zU<%_*Gp-r!`<$>zHBOw+qT)wUo@6p?$(vYt`&~Qo;xoxfm#Mbo&8is`SK&hcbaJq2 zK2#|W7XG%zLt6NNJpdO8miRmg3Yl;>(}av~Vfb3&KQhB@i7lBCX!S`Gt(`(_B_K!qp-)-wWzAeg_@-M<2jLh zlhLb>XR~^PY}h@6R)HFMy*_~&&AmP^@XgHacy_{7pDl$={XXrwoJ=Z2CE?qJZ_W-b zu4-Y%X|a2luIwUO~LlUvYH7>pEyQgar7ZsfiQEBaH$u=NYUG0t1WdG$!)g<)} zB^}n7d=n(5-keSCZ0H|~L$g0JgFf$W>OVb8#Jr`>vwKI}UYI>+#=Wds%0I1T-ZL22 z!9iW8qV=bgBj#IAGJ-VOAKhJNcK6Djc|k$Ks-~%&q-_9r*CPO~K3?1I@N+uA*#esJ zb0P~9PsXEi;~6A1yi>B(^USz(!e$nO;)+t`K&@T(rD(V0#f!VDtCowC0}C15u4*jK zRo7%ZN6G|Wm_Tve!YaS&dXK=_@7tKk=pf?Z3NoI3h2mO-%}6Jx6~?8J@%SbRB?;sB$2xGKM%8m6Wv& zG#Row0qzk@n8iPLFTYvL-qnVDgShvsaj4b8=lJi(-*$%0A@3Yn}FPl2eQ!}4$)SWZV( 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 literal 0 HcmV?d00001 diff --git a/textures/detector.png b/textures/detector.png new file mode 100644 index 0000000000000000000000000000000000000000..aca41f6be3c44bd0cc5d9c64a84b7d1f4af1440f GIT binary patch literal 2107 zcmV-B2*me^P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf2hd4GK~z{ry_Q>y zoOKz-f8Y6=k*>3l|Gdt&RzJt%~%+{TDp+pm&NTB~v ze~0f>wCO}=jJ&K^rt%#q-y18~J%~dWJ2J77G_fLNiBw~~$DVA+gU1ZUQ*~;sh#&vv zHRj%)XMC)J^+u6YKVLgwNMMkUE##Ud^0tag|IJ>E8QS_4nc6&Fx2d}YqE0^o3VXH3 zOd;lb#}abRI*m@qFOI*)@n>TE^gd|J0Ki*`k>fx6m}UECsQSBbvs&Z+{DB& zl=CD-tejh+Jg-H}mJ_~xZ-B=VonFGP|M(_<`b&dC%EzuJn_6qary>KJHSa>6er81#(dnYji2k+&Ct*`T+#tkf3L6Sm3v*b z1B}{=TFg?vsCtjGD9E z_FbMiUE|n;%d%F2FOL@E2!W$ZFs1_h&aHFg{T_y+ijP7988>Cj^ISY_=-3WXY-3BF zmzI{8C>QZ{ztiq8J-r*lk~}Lq5^AkMkCDcf-e*|ueUH#gjO8Wtv9G9lT1}6 zGRG7I;hRVbwljD<=3lr?#bB&C9+R5qfrq%Z?=GC+F zRFzh9Y_#M07>U4bOVWJProOVy+PSyzq6YH|T~hK$=_Qd-%W0DI=Z{98EnBf)B2bzJ z4$B{fU4ltN{lBMR((DABTkg_zr-`!rNN&1hZWAK}K3&bwsdYK=QiJHLps zKl=F+!FjRI%VNKZ^Xlo$n2bq_)q*%VEj{l$*gt5L3?BG|&)J0y{(Po|SMafHpLRA+ zvQOV^!w7+ct0w1O4C$?_(gNwhdX>-J9B|*~^PGNVh3C#RIkQ+|Ey+uGsv@C+N>Aul z{a>~@GV5~ZVUHuby8QCl*LnC~lI+qn*{DurWoYlxzWu4T4TkyfLLzM$TwIK3 zO)8h=@e5g;u`+?iHpRt(p-pUtYcv9zcqEeWbV)SPtXho+?y)#}xX16Fc#Big^F;gX zu0%{skK?6nLY)y7Rh09MkXS`=^L69gd(#vzzkQJ(D3w;_5G}7nq&@Kzwaah?fw9x8 z)J`g|ojRYqp-yx0yjrcx7w?|rV~3?^yJlIgTU@O56akY=#${K@XYaVD_JqH_a+aU` z?IQV!DfZ9q{OOUvZBh}3jbC&><|G*H+w&A=%CT0Y_IaI`=&K~&huNz?OUrc z6BV0-`?KtsPLa>Mw3WZPjUI2Twz#m-p%;gDg`Cu{{^UC5sdW+^$rAPVz5_ize#GPYLm6f& zX?#tbSOyl>s+_*C!3&Ea|LjV>OH)kmuF%jtsdhCn-vc{FU<McJJTVs)4Hz=)b&I2f^vrS9#@0 zXhkYc&36q9g-Ej?-6B@2#g1~E*E3I+{W@NzNq3u(K5`POvQF34Akq47O0GiSO8EVg z@qC+lMT5$>bYRtyt8LKB8It7TOP4U}?M`?X0{xHPFR!Z`c`sbSd4S7}??hdMJPsiXgB}>002ovPDHLkV1lxs7#siq literal 0 HcmV?d00001 diff --git a/textures/keypad.png b/textures/keypad.png new file mode 100644 index 0000000000000000000000000000000000000000..527249e486c0a3023c3ec7f130c5cfbe6e300a0a GIT binary patch literal 3470 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 w%tD+o%?auM%XmDvA>oD)hwo0HyjY0*7fF+