-- basic_robot by rnd, 2016 basic_robot = {}; ---- SETTINGS ------ basic_robot.call_limit = 48; -- how many execution calls per script run allowed basic_robot.entry_count = 2 -- how many robot ordinary player can have basic_robot.advanced_count = 16 -- how many robots player with robot privs can have basic_robot.radius = 32; -- divide whole world into blocks of this size - used for managing events like keyboard punches basic_robot.password = "robot passw0rd"; -- IMPORTANT: change it before running mod, server private password used for authentifications basic_robot.bad_inventory_blocks = { -- disallow taking from these nodes inventories to prevent player abuses ["craft_guide:sign_wall"] = true, } basic_robot.maxoperations = 2; -- how many operations (dig, generate energy,..) available per run, 0 = unlimited basic_robot.dig_require_energy = true; -- does robot require energy to dig? ---------------------- basic_robot.http_api = minetest.request_http_api(); basic_robot.version = "2017/12/18a"; basic_robot.data = {}; -- stores all robot data --[[ [name] = { sandbox= .., bytecode = ..., ram = ..., obj = robot object,spawnpos=...} robot object = object of entity, used to manipulate movements and more --]] basic_robot.ids = {}; -- stores maxid for each player --[name] = {id = .., maxid = .. }, current id, how many robot ids player can use basic_robot.data.listening = {}; -- which robots listen to chat dofile(minetest.get_modpath("basic_robot").."/commands.lua") local check_code, preprocess_code,is_inside_string; -- SANDBOX for running lua code isolated and safely function getSandboxEnv (name) local authlevel = basic_robot.data[name].authlevel or 0; local commands = basic_robot.commands; local directions = {left = 1, right = 2, forward = 3, backward = 4, up = 5, down = 6, left_down = 7, right_down = 8, forward_down = 9, backward_down = 10, left_up = 11, right_up = 12, forward_up = 13, backward_up = 14 } local env = { pcall=pcall, robot_version = function() return basic_robot.version end, boost = function(v) if math.abs(v)>2 then v = 0 end; local obj = basic_robot.data[name].obj; if v == 0 then local pos = obj:getpos(); pos.x = math.floor(pos.x+0.5);pos.y = math.floor(pos.y+0.5); pos.z = math.floor(pos.z+0.5); obj:setpos(pos); obj:set_velocity({x=0,y=0,z=0}); return end local yaw = obj:get_yaw(); obj:set_velocity({x=v*math.cos(yaw),y=0,z=v*math.sin(yaw)}); end, turn = { left = function() commands.turn(name,math.pi/2) end, right = function() commands.turn(name,-math.pi/2) end, angle = function(angle) commands.turn(name,angle*math.pi/180) end, }, pickup = function(r) -- pick up items around robot return commands.pickup(r, name); end, craft = function(item, idx,mode) return commands.craft(item, mode, idx, name) end, self = { pos = function() return basic_robot.data[name].obj:getpos() end, spawnpos = function() local pos = basic_robot.data[name].spawnpos; return {x=pos.x,y=pos.y,z=pos.z} end, name = function() return name end, viewdir = function() local yaw = basic_robot.data[name].obj:getyaw(); return {x=math.cos(yaw), y = 0, z=math.sin(yaw)} end, set_properties = function(properties) if not properties then return end; local obj = basic_robot.data[name].obj; obj:set_properties(properties); end, set_animation = function(anim_start,anim_end,anim_speed,anim_stand_start) local obj = basic_robot.data[name].obj; obj:set_animation({x=anim_start,y=anim_end}, anim_speed, anim_stand_start) end, listen = function (mode) if mode == 1 then basic_robot.data.listening[name] = true else basic_robot.data.listening[name] = nil end end, listen_msg = function() local msg = basic_robot.data[name].listen_msg; local speaker = basic_robot.data[name].listen_speaker; basic_robot.data[name].listen_msg = nil; basic_robot.data[name].listen_speaker = nil; return speaker,msg end, read_mail = function() local mail = basic_robot.data[name].listen_mail; local sender = basic_robot.data[name].listen_sender; basic_robot.data[name].listen_mail = nil; basic_robot.data[name].listen_sender = nil; return sender,mail end, send_mail = function(target,mail) if not basic_robot.data[target] then return false end basic_robot.data[target].listen_mail = mail; basic_robot.data[target].listen_sender = name; end, remove = function() error("abort") basic_robot.data[name].obj:remove(); basic_robot.data[name].obj=nil; end, reset = function() local pos = basic_robot.data[name].spawnpos; local obj = basic_robot.data[name].obj; obj:setpos({x=pos.x,y=pos.y+1,z=pos.z}); obj:setyaw(0); end, set_libpos = function(pos) local pos = basic_robot.data[name].spawnpos; local meta = minetest.get_meta(pos); meta:set_string("libpos",pos.x .. " " .. pos.y .. " " .. pos.z) end, spam = function (mode) -- allow more than one msg per "say" if mode == 1 then basic_robot.data[name].allow_spam = true else basic_robot.data[name].allow_spam = nil end end, fire = function(speed, pitch,gravity, is_entity) -- experimental: fires an projectile local obj = basic_robot.data[name].obj; local pos = obj:getpos(); local yaw = obj:getyaw(); pitch = pitch*math.pi/180 local velocity = {x=speed*math.cos(yaw)*math.cos(pitch), y=speed*math.sin(pitch),z=speed*math.sin(yaw)*math.cos(pitch)}; -- fire particle if not is_entity then minetest.add_particle( { pos = pos, expirationtime = 10, velocity = {x=speed*math.cos(yaw)*math.cos(pitch), y=speed*math.sin(pitch),z=speed*math.sin(yaw)*math.cos(pitch)}, size = 5, texture = "default_apple.png", acceleration = {x=0,y=-gravity,z=0}, collisiondetection = true, collision_removal = true, } ); return end local obj = minetest.add_entity(pos, "basic_robot:projectile"); if not obj then return end obj:setvelocity(velocity); obj:setacceleration({x=0,y=-gravity,z=0}); local luaent = obj:get_luaentity(); luaent.name = name; luaent.spawnpos = pos; end, fire_pos = function() local fire_pos = basic_robot.data[name].fire_pos; basic_robot.data[name].fire_pos = nil; return fire_pos end, label = function(text) local obj = basic_robot.data[name].obj; obj:set_properties({nametag = text}); -- "[" .. name .. "] " .. end, display_text = function(text,linesize,size) local obj = basic_robot.data[name].obj; return commands.display_text(obj,text,linesize,size) end, sound = function(sample,volume, pos) if pos then return minetest.sound_play( sample, { pos = pos, gain = volume or 1, max_hear_distance = 32, -- default, uses an euclidean metric }) end local obj = basic_robot.data[name].obj; return minetest.sound_play( sample, { object = obj, gain = volume or 1, max_hear_distance = 32, -- default, uses an euclidean metric }) end, sound_stop = function(handle) minetest.sound_stop(handle) end, }, machine = {-- adds technic like functionality to robots: power generation, smelting, grinding, compressing energy = function() return basic_robot.data[name].menergy or 0 end, generate_power = function(input,amount) return commands.machine.generate_power(name,input, amount) end, smelt = function(input,amount) return commands.machine.smelt(name,input, amount) end, grind = function(input) return commands.machine.grind(name,input) end, compress = function(input) return commands.machine.compress(name,input) end, transfer_power = function(amount,target) return commands.machine.transfer_power(name,amount,target) end, }, crypto = {-- basic cryptography - encryption, scramble, mod hash encrypt = commands.crypto.encrypt, decrypt = commands.crypto.decrypt, scramble = commands.crypto.scramble, basic_hash = commands.crypto.basic_hash, }; keyboard = { get = function() return commands.keyboard.get(name) end, set = function(pos,type) return commands.keyboard.set(basic_robot.data[name],pos,type) end, read = function(pos) return minetest.get_node(pos).name end, }, find_nodes = function(nodename,r) if r>8 then return false end local q = minetest.find_node_near(basic_robot.data[name].obj:getpos(), r, nodename); if q==nil then return false end local p = basic_robot.data[name].obj:getpos() return math.sqrt((p.x-q.x)^2+(p.y-q.y)^2+(p.z-q.z)^2) end, -- in radius around position find_player = function(r,pos) pos = pos or basic_robot.data[name].obj:getpos(); if r>10 then return false end local objects = minetest.get_objects_inside_radius(pos, r); local plist = {}; for _,obj in pairs(objects) do if obj:is_player() then plist[#plist+1]=obj:get_player_name(); end end if not plist[1] then return nil end return plist end, -- in radius around position player = { getpos = function(name) local player = minetest.get_player_by_name(name); if player then return player:getpos() else return nil end end, connected = function() local players = minetest.get_connected_players(); local plist = {} for _,player in pairs(players) do plist[#plist+1]=player:get_player_name() end if not plist[1] then return nil else return plist end end }, attack = function(target) return basic_robot.commands.attack(name,target) end, -- attack player if nearby grab = function(target) return basic_robot.commands.grab(name,target) end, say = function(text, owneronly) if not basic_robot.data[name].quiet_mode and not owneronly then minetest.chat_send_all(" " .. text) if not basic_robot.data[name].allow_spam then basic_robot.data[name].quiet_mode=true end else minetest.chat_send_player(basic_robot.data[name].owner," " .. text) end end, book = { read = function(i) if i<=0 or i > 32 then return nil end local pos = basic_robot.data[name].spawnpos; local meta = minetest.get_meta(pos); local libposstring = meta:get_string("libpos"); local words = {}; for word in string.gmatch(libposstring,"%S+") do words[#words+1]=word end local libpos = {x=tonumber(words[1] or pos.x),y=tonumber(words[2] or pos.y),z=tonumber(words[3] or pos.z)}; local inv = minetest.get_meta(libpos):get_inventory();local itemstack = inv:get_stack("library", i); if itemstack then return commands.read_book(itemstack); else return nil end end, write = function(i,title,text) if i<=0 or i > 32 then return nil end local inv = minetest.get_meta(basic_robot.data[name].spawnpos):get_inventory(); local stack = basic_robot.commands.write_book(basic_robot.data[name].owner,title,text); if stack then inv:set_stack("library", i, stack) end end }, code = { set = function(text) -- replace bytecode in sandbox with this local err = commands.setCode( name, text ); -- compile code if err then minetest.chat_send_player(name,"#ROBOT CODE COMPILATION ERROR : " .. err) local obj = basic_robot.data[name].obj; obj:remove(); basic_robot.data[name].obj = nil; return end end, run = function(script) if basic_robot.data[name].authlevel < 3 then local err = check_code(script); script = preprocess_code(script); if err then minetest.chat_send_player(name,"#ROBOT CODE CHECK ERROR : " .. err) return end end local ScriptFunc, CompileError = loadstring( script ) if CompileError then minetest.chat_send_player(name, "#code.run: compile error " .. CompileError ) return false end setfenv( ScriptFunc, basic_robot.data[name].sandbox ) local Result, RuntimeError = pcall( ScriptFunc ); if RuntimeError then minetest.chat_send_player(name, "#code.run: run error " .. RuntimeError ) return false end return true end }, rom = basic_robot.data[name].rom, string = { byte = string.byte, char = string.char, find = string.find, format = string.format, gsub = string.gsub, gmatch = string.gmatch, len = string.len, lower = string.lower, upper = string.upper, rep = string.rep, reverse = string.reverse, sub = string.sub, }, math = { abs = math.abs, acos = math.acos, asin = math.asin, atan = math.atan, atan2 = math.atan2, ceil = math.ceil, cos = math.cos, cosh = math.cosh, deg = math.deg, exp = math.exp, floor = math.floor, fmod = math.fmod, frexp = math.frexp, huge = math.huge, ldexp = math.ldexp, log = math.log, log10 = math.log10, max = math.max, min = math.min, modf = math.modf, pi = math.pi, pow = math.pow, rad = math.rad, random = math.random, sin = math.sin, sinh = math.sinh, sqrt = math.sqrt, tan = math.tan, tanh = math.tanh, }, table = { concat = table.concat, insert = table.insert, maxn = table.maxn, remove = table.remove, sort = table.sort, }, os = { clock = os.clock, difftime = os.difftime, time = os.time, date = os.date, }, colorize = core.colorize, serialize = minetest.serialize, deserialize = minetest.deserialize, tonumber = tonumber, pairs = pairs, ipairs = ipairs, error = error, type=type, --_ccounter = basic_robot.data[name].ccounter, -- counts how many executions of critical spots in script increase_ccounter = function() local _ccounter = basic_robot.data[name].ccounter; if _ccounter > basic_robot.call_limit then error("Execution limit " .. basic_robot.call_limit .. " exceeded"); end basic_robot.data[name].ccounter = _ccounter + 1; end, }; -- ROBOT FUNCTIONS: move,dig, place,insert,take,check_inventory,activate,read_node,read_text,write_text env.move = {}; -- changes position of robot for dir, dir_id in pairs(directions) do env.move[dir] = function() return commands.move(name,dir_id) end end env.dig = {}; for dir, dir_id in pairs(directions) do env.dig[dir] = function() return commands.dig(name,dir_id) end end env.place = {}; for dir, dir_id in pairs(directions) do env.place[dir] = function(nodename, param2) return commands.place(name,nodename, param2, dir_id) end end env.insert = {}; -- insert item from robot inventory into another inventory for dir, dir_id in pairs(directions) do env.insert[dir] = function(item, inventory) return commands.insert_item(name,item, inventory,dir_id) end end env.take = {}; -- takes item from inventory and puts it in robot inventory for dir, dir_id in pairs(directions) do env.take[dir] = function(item, inventory) return commands.take_item(name,item, inventory,dir_id) end end env.check_inventory = {}; for dir, dir_id in pairs(directions) do env.check_inventory[dir] = function(itemname, inventory,i) return commands.check_inventory(name,itemname, inventory,i,dir_id) end end env.check_inventory.self = function(itemname, inventory,i) return commands.check_inventory(name,itemname, inventory,i,0) end; env.activate = {}; for dir, dir_id in pairs(directions) do env.activate[dir] = function(mode) return commands.activate(name,mode, dir_id) end end env.read_node = {}; for dir, dir_id in pairs(directions) do env.read_node[dir] = function() return commands.read_node(name,dir_id) end end env.read_text = {} -- returns text for dir, dir_id in pairs(directions) do env.read_text[dir] = function(stringname,mode) return commands.read_text(name,mode,dir_id,stringname) end end env.write_text = {} -- returns text for dir, dir_id in pairs(directions) do env.write_text[dir] = function(text) return commands.write_text(name, dir_id,text) end end if authlevel>=1 then -- robot privs env.self.read_form = function() local fields = basic_robot.data[name].read_form; local sender = basic_robot.data[name].form_sender; basic_robot.data[name].read_form = nil; basic_robot.data[name].form_sender = nil; return sender,fields end env.self.show_form = function(playername, form) commands.show_form(name, playername, form) end end -- set up sandbox for puzzle if authlevel>=2 then -- puzzle privs basic_robot.data[name].puzzle = {}; local data = basic_robot.data[name]; local pdata = data.puzzle; pdata.triggerdata = {}; pdata.gamedata = {}; pdata.block_ids = {} pdata.triggers = {}; env.puzzle = { -- puzzle functionality set_node = function(pos,node) commands.puzzle.set_node(data,pos,node) end, get_node = function(pos) return minetest.get_node(pos) end, activate = function(mode,pos) commands.puzzle.activate(data,mode,pos) end, get_meta = function(pos) return commands.puzzle.get_meta(data,pos) end, get_gametime = function() return minetest.get_gametime() end, get_node_inv = function(pos) return commands.puzzle.get_node_inv(data,pos) end, get_player = function(pname) return commands.puzzle.get_player(data,pname) end, chat_send_player = function(pname, text) minetest.chat_send_player(pname or "", text) end, get_player_inv = function(pname) return commands.puzzle.get_player_inv(data,pname) end, set_triggers = function(triggers) commands.puzzle.set_triggers(pdata,triggers) end, -- FIX THIS! check_triggers = function(pname) local player = minetest.get_player_by_name(pname); if not player then return end commands.puzzle.checkpos(pdata,player:getpos(),pname) end, add_particle = function(def) minetest.add_particle(def) end, count_objects = function(pos,radius) return #minetest.get_objects_inside_radius(pos, math.min(radius,5)) end, pdata = pdata, ItemStack = ItemStack, } end --special sandbox for admin if authlevel<3 then -- is admin? env._G = env; else env.minetest = minetest; env._G=_G; debug = debug; end return env end -- code checker check_code = function(code) --"while ", "for ", "do ","goto ", local bad_code = {"repeat", "until", "_ccounter", "_G", "while%(", "while{", "pcall","\\\""} for _, v in pairs(bad_code) do if string.find(code, v) then return v .. " is not allowed!"; end end end is_inside_string = function(pos,script) local i1=string.find (script, "\"", 1); if not i1 then return false end local i2=0; local par = 1; if posself.timestep and self.running == 1 then self.timer = 0; local err = runSandbox(self.name); if err and type(err) == "string" then local i = string.find(err,":"); if i then err = string.sub(err,i+1) end if string.sub(err,-5)~="abort" then minetest.chat_send_player(self.owner,"#ROBOT ERROR : " .. err) end self.running = 0; -- stop execution if string.find(err,"stack overflow") then -- remove stupid player privs and spawner, ban player ip local name = self.name; local pos = basic_robot.data[name].spawnpos; minetest.set_node(pos, {name = "air"}); local privs = core.get_player_privs(self.owner);privs.interact = false; core.set_player_privs(self.owner, privs); minetest.auth_reload() minetest.ban_player(self.owner) end local name = self.name; local pos = basic_robot.data[name].spawnpos; if not basic_robot.data[name] then return end if basic_robot.data[name].obj then basic_robot.data[name].obj = nil; end self.object:remove(); end return end return end, on_rightclick = function(self, clicker) local text = minetest.formspec_escape(self.code); local form = robot_spawner_update_form(self.spawnpos,1); minetest.show_formspec(clicker:get_player_name(), "robot_worker_" .. self.name, form); end, }) local spawn_robot = function(pos,node,ttl) if type(ttl) ~= "number" then ttl = 0 end if ttl<0 then return end local meta = minetest.get_meta(pos); --temperature based spam activate protect local t0 = meta:get_int("t"); local t1 = minetest.get_gametime(); local T = meta:get_int("T"); -- temperature if t0>t1-2 then -- activated before natural time T=T+1; else if T>0 then T=T-1 if t1-t0>5 then T = 0 end end end meta:set_int("T",T); meta:set_int("t",t1); -- update last activation time if T > 2 then -- overheat minetest.sound_play("default_cool_lava",{pos = pos, max_hear_distance = 16, gain = 0.25}) meta:set_string("infotext","overheat: temperature ".. T) return end -- spawn robot on top pos.y=pos.y+1; local owner = meta:get_string("owner") local id = meta:get_int("id"); local name = owner..id; if id <= 0 then -- just compile code and run it, no robot spawn local codechange = false; if meta:get_int("codechange") == 1 then meta:set_int("codechange",0); codechange = true; end -- compile code & run it local err; local data = basic_robot.data[name]; if codechange or (not data) then basic_robot.data[name] = {}; data = basic_robot.data[name]; meta:set_string("infotext",minetest.get_gametime().. " code changed ") data.owner = owner; data.authlevel = meta:get_int("authlevel") local sec_hash = minetest.get_password_hash("",data.authlevel.. owner .. basic_robot.password) if meta:get_string("sec_hash")~= sec_hash then minetest.chat_send_all("#ROBOT: " .. name .. " is using fake auth level. dig and place again.") return end if not data.obj then --create virtual robot that reports position and other properties local obj = {}; function obj:getpos() return {x=pos.x,y=pos.y,z=pos.z} end function obj:getyaw() return 0 end function obj:get_luaentity() local luaent = {}; luaent.owner = owner luaent.spawnpos = {x=pos.x,y=pos.y-1,z=pos.z}; return luaent end function obj:remove() end data.obj = obj; end end if not data.bytecode then local script = meta:get_string("code"); if data.authlevel<3 then -- not admin err = check_code(script); script = preprocess_code(script); end if err then meta:set_string("infotext","#CODE CHECK ERROR : " .. err); return end local bytecode, err = loadstring( script ) if err then meta:set_string("infotext","#COMPILE ERROR : " .. err) return end data.bytecode = bytecode; end --sandbox init if not data.sandbox then data.sandbox = getSandboxEnv (name) end -- actual code run process data.ccounter = 0;data.operations = basic_robot.maxoperations; setfenv(data.bytecode, data.sandbox ) local Result, err = pcall( data.bytecode ) if err then meta:set_string("infotext","#RUN ERROR : " .. err) return end return end -- if robot already exists do nothing if basic_robot.data[name] and basic_robot.data[name].obj then minetest.chat_send_player(owner,"#ROBOT: ".. name .. " already active, removing ") basic_robot.data[name].obj:remove(); basic_robot.data[name].obj = nil; end local objects = minetest.get_objects_inside_radius(pos, 0.9); for _,obj in pairs(objects) do if not obj:is_player() then obj:remove() end end local obj = minetest.add_entity(pos,"basic_robot:robot"); local luaent = obj:get_luaentity(); luaent.owner = owner; luaent.name = name; luaent.code = meta:get_string("code"); luaent.spawnpos = {x=pos.x,y=pos.y-1,z=pos.z}; luaent.authlevel = meta:get_int("authlevel") local sec_hash = minetest.get_password_hash("",luaent.authlevel.. owner .. basic_robot.password) if meta:get_string("sec_hash")~= sec_hash then minetest.chat_send_all("#ROBOT: " .. name .. " is using fake auth level. dig and place again.") obj:remove(); return end local data = basic_robot.data[name]; if data == nil then basic_robot.data[name] = {}; data = basic_robot.data[name]; data.rom = {}; end data.owner = owner; data.spawnpos = {x=pos.x,y=pos.y-1,z=pos.z}; init_robot(obj); -- set properties, init sandbox local self = obj:get_luaentity(); local err = setCode( self.name, self.code ); -- compile code if err then minetest.chat_send_player(self.owner,"#ROBOT CODE COMPILATION ERROR : " .. err) self.running = 0; -- stop execution self.object:remove(); basic_robot.data[self.name].obj = nil; return end self.running = 1 end local despawn_robot = function(pos) local meta = minetest.get_meta(pos); --temperature based spam activate protect local t0 = meta:get_int("t"); local t1 = minetest.get_gametime(); local T = meta:get_int("T"); -- temperature if t0>t1-2 then -- activated before natural time T=T+1; else if T>0 then T=T-1 if t1-t0>5 then T = 0 end end end meta:set_int("T",T); meta:set_int("t",t1); -- update last activation time if T > 2 then -- overheat minetest.sound_play("default_cool_lava",{pos = pos, max_hear_distance = 16, gain = 0.25}) meta:set_string("infotext","overheat: temperature ".. T) return end -- spawn position on top pos.y=pos.y+1; local owner = meta:get_string("owner") local id = meta:get_int("id"); if id <= 0 then meta:set_int("codechange",1) return end local name = owner..id; -- if robot already exists remove it if basic_robot.data[name] and basic_robot.data[name].obj then minetest.chat_send_player(owner,"#ROBOT: ".. name .. " removed") basic_robot.data[name].obj:remove(); basic_robot.data[name].obj = nil; end local objects = minetest.get_objects_inside_radius(pos, 0.9); for _,obj in pairs(objects) do if not obj:is_player() then obj:remove() end end end -- GUI -- robogui GUI START ================================================== robogui = {}; -- a simple table of entries: [guiName] = {getForm = ... , show = ... , response = ... , guidata = ...} robogui.register = function(def) robogui[def.guiName] = {getForm = def.getForm, show = def.show, response = def.response, guidata = def.guidata or {}} end minetest.register_on_player_receive_fields( function(player, formname, fields) local gui = robogui[formname]; if gui then gui.response(player,formname,fields) end end ) -- robogui GUI END ==================================================== --- DEMO of simple form registration, all in one place, clean and tidy -- adapted for use with basic_robot -- if not basic_gui then -- basic_gui = _G.basic_gui; minetest = _G.minetest; -- basic_gui.register({ -- guiName = "mainWindow", -- formname -- getForm = function(form_id, update) -- actual form design -- local gui = basic_gui["mainWindow"]; -- local formdata = gui.guidata[form_id] -- if not formdata then -- init -- gui.guidata[form_id] = {}; formdata = gui.guidata[form_id] -- formdata.sel_tab = 1; -- formdata.text = "default"; -- formdata.form = ""; -- end -- if not update then return formdata.form end -- local sel_tab = formdata.sel_tab; -- local text = formdata.text; -- formdata.form = "size[8,9]".. -- "label[0,0;basic_gui_DEMO, form_id " .. form_id .. ", tab " .. sel_tab .. "]".. -- "button[0,1;2,1;gui_button;CLICK ME]".. -- "textarea[0.25,2;2,1;gui_textarea;text;" .. text .. "]".. -- "tabheader[0,0;tabs;tab1,table demo,tab3;".. sel_tab .. ";true;true]".. -- "list[current_player;main;0,5;8,4;]"; -- if sel_tab == 2 then -- formdata.form = "size[12,6.5;true]" .. -- "tablecolumns[color;tree;text,width=32;text]" .. -- "tableoptions[background=#00000000;border=false]" .. -- "field[0.3,0.1;10.2,1;search_string;;" .. minetest.formspec_escape(text) .. "]" .. -- "field_close_on_enter[search_string;false]" .. -- "button[10.2,-0.2;2,1;search;" .. "Search" .. "]" .. -- "table[0,0.8;12,4.5;list_settings;".. -- "#FFFF00,1,TEST A,,".. -- "#FFFF00,2,TEST A,,".. -- ",3,test a,value A,".. -- "#FFFF00,1,TEST B,,".. -- ",2,test b,value B," -- end -- formdata.info = "This information comes with the form post"; -- extra data set -- end, -- show = function(player_name,update) -- this is used to show form to user -- local formname = "mainWindow"; -- local form_id = player_name; -- each player has his own window! -- local gui = basic_gui[formname]; -- local formdata = gui.guidata[form_id]; -- all form data for this id gets stored here -- if update then gui.getForm(form_id,true); formdata = gui.guidata[form_id]; end -- minetest.show_formspec(player_name, "mainWindow", formdata.form) -- end, -- response = function(player,formname, fields) -- this handles response -- local player_name = player:get_player_name(); -- local form_id = player_name; -- local gui = basic_gui[formname]; -- local formdata = gui.guidata[form_id]; --gui.guidata[form_id]; -- if not formdata then say("err") return end --error! -- if fields.gui_textarea then -- formdata.text = fields.gui_textarea or "" -- end -- if fields.tabs then -- formdata.sel_tab = tonumber(fields.tabs) or 1; -- gui.show(player_name,true) -- update and show form -- else -- local form = "size[5,5]" .. -- "label[0,0;you interacted with demo form, fields : " .. -- _G.minetest.formspec_escape(_G.dump(fields)) .. "]".. -- "label[0,4;" .. formdata.info .. "]" -- _G.minetest.show_formspec(player_name,"basic_response", form); -- end -- end, -- }) -- end --process forms from spawner local on_receive_robot_form = function(pos, formname, fields, sender) local name = sender:get_player_name(); if minetest.is_protected(pos,name) then return end if fields.OK then local meta = minetest.get_meta(pos); if fields.code then local code = fields.code or ""; if meta:get_int("admin") == 1 then local privs = minetest.get_player_privs(name); -- only admin can edit admin robot code if not privs.privs then return end end meta:set_string("code", code); meta:set_int("codechange",1) end if fields.id then local id = math.floor(tonumber(fields.id) or 1); local owner = meta:get_string("owner") if not basic_robot.ids[owner] then setupid(owner) end if id<-1000 or id>basic_robot.ids[owner].maxid then local privs = minetest.get_player_privs(name); if not privs.privs then return end end meta:set_int("id",id) -- set active id for spawner meta:set_string("name", owner..id) end robot_spawner_update_form(pos); return end if fields.EDIT then local meta = minetest.get_meta(pos);if not meta then return end if meta:get_int("admin") == 1 then local privs = minetest.get_player_privs(name); -- only admin can edit admin robot code if not privs.privs then return end end local code = meta:get_string("code"); editor_get_lines(code,name); local form = code_edit_form(pos,name); minetest.after(0, -- why it ignores this form sometimes? old form interfering? function() minetest.show_formspec(name, "robot_editor_:"..minetest.pos_to_string(pos), form); end ) return end if fields.help then ----- INGAME HELP ------ local text = "BASIC LUA SYNTAX\n \nif x==1 then A else B end".. "\n for i = 1, 5 do something end \nwhile i<6 do A; i=i+1; end\n".. "\n arrays: myTable1 = {1,2,3}, myTable2 = {[\"entry1\"]=5, [\"entry2\"]=1}\n".. " access table entries with myTable1[1] or myTable2.entry1 or myTable2[\"entry1\"]\n \n".. "ROBOT COMMANDS\n \n".. "**MOVEMENT,DIGGING, PLACING, INVENTORY TAKE/INSERT\n move.direction(), where direction is forward, backward, left,right, up, down)\n".. " left_down, ..., backward_down, left_up, ..., backward_up\n".. " boost(v) sets robot velocity, -60 it returns itemname. if itemname == \"\" it checks if inventory empty\n".. " activate.direction(mode) activates target block\n".. " pickup(r) picks up all items around robot in radius r<8 and returns list or nil\n".. " craft(item,idx,mode) crafts item if required materials are present in inventory. mode = 1 returns recipe, optional recipe idx\n".. " take.direction(item, inventory) takes item from target inventory into robot inventory\n".. " read_text.direction(stringname,mode) reads text of signs, chests and other blocks, optional stringname for other meta,\n mode 1 read number\n".. " write_text.direction(text,mode) writes text to target block as infotext\n".. "**BOOKS/CODE\n title,text=book.read(i) returns title,contents of book at i-th position in library \n book.write(i,title,text) writes book at i-th position at spawner library\n".. " code.run(text) compiles and runs the code in sandbox\n".. " code.set(text) replaces current bytecode of robot\n".. " find_nodes(\"default:dirt\",3) returns distance to node in radius 3 around robot, or false if none\n".. "**PLAYERS\n".. " find_player(3,pos) finds players in radius 3 around robot(position) and returns list, if none returns nil\n".. " attack(target) attempts to attack target player if nearby \n".. " grab(target) attempt to grab target player if nearby and returns true if succesful \n".. " player.getpos(name) return position of player, player.connected() returns list of players\n".. "**ROBOT\n".. " say(\"hello\") will speak\n".. " self.listen(0/1) (de)attaches chat listener to robot\n".. " speaker, msg = self.listen_msg() retrieves last chat message if robot listens\n".. " self.send_mail(target,mail) sends mail to target robot\n".. " sender,mail = self.read_mail() reads mail, if any\n" .. " self.pos() returns table {x=pos.x,y=pos.y,z=pos.z}\n".. " self.name() returns robot name\n".. " self.set_properties({textures=.., visual=..,visual_size=.., , ) sets visual appearance\n".. " set_animation(anim_start,anim_end,anim_speed,anim_stand_start) set mesh animation \n".. " self.spam(0/1) (dis)enable message repeat to all\n".. " self.remove() stops program and removes robot object\n".. " self.reset() resets robot position\n".. " self.spawnpos() returns position of spawner block\n".. " self.viewdir() returns vector of view for robot\n".. " self.fire(speed, pitch,gravity) fires a projectile from robot\n".. " self.fire_pos() returns last hit position\n".. " self.label(text) changes robot label\n".. " self.display_text(text,linesize,size) displays text instead of robot face, if no size return tex\n".. " self.sound(sample,volume, opt. pos) plays sound named 'sample' at robot location (opt. pos)\n".. " rom is aditional table that can store persistent data, like rom.x=1\n".. "**KEYBOARD : place spawner at coordinates (20i,40j+1,20k) to monitor events\n".. " keyboard.get() returns table {x=..,y=..,z=..,puncher = .. , type = .. } for keyboard event\n".. " keyboard.set(pos,type) set key at pos of type 0=air,1-6,7-15,16-271, limited to range 10 around\n".. " keyboard.read(pos) return node name at pos\n".. "**TECHNIC FUNCTIONALITY: namespace 'machine'. most functions return true or nil, error\n" .. " energy() displays available energy\n".. " generate_power(fuel, amount) = energy, attempt to generate power from fuel material,\n" .. " if amount>0 try generate amount of power using builtin generator - this requires\n" .. " 40 gold/mese/diamonblock upgrades for each 1 amount\n".. " smelt(input,amount) = progress/true. works as a furnace, if amount>0 try to\n" .. " use power to smelt - requires 10 upgrades for each 1 amount, energy cost is:\n".. " 1/40*(1+amount)\n".. " grind(input) - grinds input material, requires upgrades for harder material\n".. " compress(input) - requires upgrades - energy intensive process\n" .. " transfer_power(amount,target_robot_name)\n".. "**CRYPTOGRAPHY: namespace 'crypto'\n".. " encrypt(input,password) returns encrypted text, password is any string \n".. " decrypt(input,password) attempts to decrypt encrypted text\n".. " scramble(input,randomseed,sgn) (de)permutes text randomly according to sgn = -1,1\n".. " basic_hash(input,n) returns simple mod hash from string input within range 0...n-1\n".. "**PUZZLE: namespace 'puzzle' - need puzzle priv\n".. " set_triggers({trigger1, trigger2,...}) sets and initializes spatial triggers\n".. " check_triggers(pname) check if player is close to any trigger and run that trigger\n".. " set_node(pos,node) - set any node, limited to current protector mapblock & get_node(pos)\n".. " get_player(pname) return player objRef in current mapblock\n".. " chat_send_player(pname, text) \n".. " get_node_inv(pos) / get_player_inv(pname) - return inventories of nodes/players in current mapblock\n".. " get_meta(pos) - return meta of target position\n".. " get_gametime() - return current gametime\n".. " ItemStack(itemname) returns ItemRef to be used with inventory\n".. " count_objects(pos,radius)\n".. " pdata contains puzzle data like .triggers and .gamedata\n".. " add_particle(def)\n" text = minetest.formspec_escape(text); --local form = "size [8,7] textarea[0,0;8.5,8.5;help;HELP;".. text.."]" --textlist[X,Y;W,H;name;listelem 1,listelem 2,...,listelem n] local list = ""; for word in string.gmatch(text, "(.-)\r?\n+") do list = list .. word .. ", " end local form = "size [10,8] textlist[-0.25,-0.25;10.25,8.5;help;" .. list .. "]" minetest.show_formspec(sender:get_player_name(), "robot_help", form); return end if fields.spawn then spawn_robot(pos,0,0); return end if fields.despawn then local meta = minetest.get_meta(pos); local owner = meta:get_string("owner"); local id = meta:get_int("id"); local name = owner..id; if id<=0 then meta:set_int("codechange",1) end if not basic_robot.data[name] then return end if basic_robot.data[name].obj then basic_robot.data[name].obj:remove(); basic_robot.data[name].obj = nil; end return end if fields.inventory then local list_name = "nodemeta:"..pos.x..','..pos.y..','..pos.z ; local form = "size[8,8]" .. -- width, height "list["..list_name..";main;0.,0;8,4;]".. "list[current_player;main;0,4.25;8,4;]".. "listring[" .. list_name .. ";main]".. "listring[current_player;main]"; minetest.show_formspec(sender:get_player_name(), "robot_inventory", form); end if fields.library then local list_name = "nodemeta:"..pos.x..','..pos.y..','..pos.z ; local list = ""; local meta = minetest.get_meta(pos); local owner = meta:get_string("owner"); local id = meta:get_int("id"); local name = owner..id; local libposstring = meta:get_string("libpos"); local words = {}; for word in string.gmatch(libposstring,"%S+") do words[#words+1]=word end local libpos = {x=tonumber(words[1] or pos.x),y=tonumber(words[2] or pos.y),z=tonumber(words[3] or pos.z)}; local libform = ""; if libpos.x and libpos.y and libpos.z and not minetest.is_protected(libpos,owner) then libform = "list["..list_name..";library;4.25,0;4,4;]"; else libform = "label[4.25,0;Library position is protected]"; end local libnodename = minetest.get_node(libpos).name; if libnodename~="basic_robot:spawner" then if libnodename == "ignore" then libform = "label[4.25,0;library target area is not loaded]" else libform = "label[4.25,0;there is no spawner at library coordinates]" end else local inv = minetest.get_meta(libpos):get_inventory(); local text = ""; for i=1,16 do local itemstack = inv:get_stack("library", i); local data = itemstack:get_meta():to_table().fields -- 0.4.16 --local data = minetest.deserialize(itemstack:get_metadata()) -- pre 0.4.16 if data then text = string.sub(data.title or "",1,32); else text = ""; end text = i..". " .. minetest.formspec_escape(text); list = list .. text .. ","; end end --for word in string.gmatch(text, "(.-)\r?\n+") do list = list .. word .. ", " end -- matches lines local form = "size [8,8] textlist[0,0;4.,3.;books;" .. list .. "]".. "field[0.25,3.5;3.25,1;libpos;Position of spawner used as library;"..libposstring.."]".. "button_exit[3.25,3.2;1.,1;OK;SAVE]".. libform.. "list[current_player;main;0,4.25;8,4;]"; minetest.show_formspec(sender:get_player_name(), "robot_library_"..minetest.pos_to_string(pos), form); end end -- handle form: when rightclicking robot entity, remote controller minetest.register_on_player_receive_fields( function(player, formname, fields) local robot_formname = "robot_worker_"; if string.find(formname,robot_formname) then local name = string.sub(formname, string.len(robot_formname)+1); -- robot name local sender = player:get_player_name(); --minetest.get_player_by_name(name); if basic_robot.data[name] and basic_robot.data[name].spawnpos then local pos = basic_robot.data[name].spawnpos; local privs = minetest.get_player_privs(player:get_player_name()); local is_protected = minetest.is_protected(pos, player:get_player_name()); if is_protected and not privs.privs then return 0 end -- if not sender then on_receive_robot_form(pos,formname, fields, player) -- else -- on_receive_robot_form(pos,formname, fields, sender) -- end return end end local robot_formname = "robot_control_"; if string.find(formname,robot_formname) then local name = string.sub(formname, string.len(robot_formname)+1); -- robot name if fields.OK and fields.code then local item = player:get_wielded_item(); --set_wielded_item(item) item:set_metadata(fields.code); player:set_wielded_item(item); if fields.id then local id = tonumber(fields.id) or 1; local owner = player:get_player_name(); basic_robot.ids[owner].id = id -- set active id end end return end local robot_formname = "robot_manual_control_"; if string.find(formname,robot_formname) then local name = string.sub(formname, string.len(robot_formname)+1); -- robot name local commands = basic_robot.commands; if fields.turnleft then pcall(function () commands.turn(name,math.pi/2) end) elseif fields.turnright then pcall(function () commands.turn(name,-math.pi/2) end) elseif fields.forward then pcall(function () commands.move(name,3) end) elseif fields.back then pcall(function () commands.move(name,4) end) elseif fields.left then pcall(function () commands.move(name,1) end) elseif fields.right then pcall(function () commands.move(name,2) end) elseif fields.dig then pcall(function () basic_robot.data[name].operations = basic_robot.maxoperations; commands.dig(name,3) end) elseif fields.up then pcall(function () commands.move(name,5) end) elseif fields.down then pcall(function () commands.move(name,6) end) elseif fields.digdown then pcall(function () basic_robot.data[name].operations = basic_robot.maxoperations; commands.dig(name,6) end) elseif fields.digup then pcall(function () basic_robot.data[name].operations = basic_robot.maxoperations; commands.dig(name,5) end) end return end local robot_formname = "robot_library_"; if string.find(formname,robot_formname) then local spos = minetest.string_to_pos(string.sub(formname, string.len(robot_formname)+1)); if fields.books then if string.sub(fields.books,1,3)=="DCL" then local sel = tonumber(string.sub(fields.books,5)) or 1; local meta = minetest.get_meta(spos); local libposstring = meta:get_string("libpos"); local words = {}; for word in string.gmatch(libposstring,"%S+") do words[#words+1]=word end local libpos = {x=tonumber(words[1] or spos.x),y=tonumber(words[2] or spos.y),z=tonumber(words[3] or spos.z)}; local inv = minetest.get_meta(libpos):get_inventory();local itemstack = inv:get_stack("library", sel); if itemstack then local title,text = basic_robot.commands.read_book(itemstack); title = title or ""; text = text or ""; local dtitle = minetest.formspec_escape(title); local form = "size [8,8] textarea[0.,0.;8.75,8.5;book; TITLE : " .. minetest.formspec_escape(title) .. ";" .. minetest.formspec_escape(text) .. "] button_exit[-0.25,7.5;1.25,1;OK;SAVE] ".. "button_exit[1.,7.5;2.75,1;LOAD;USE AS PROGRAM] field[4,8;4.5,0.5;title;title;"..dtitle.."]"; minetest.show_formspec(player:get_player_name(), "robot_book_".. sel.. ":".. minetest.pos_to_string(libpos), form); end end end if fields.OK and fields.libpos then local sender = player:get_player_name(); --minetest.get_player_by_name(name); local meta = minetest.get_meta(spos); meta:set_string("libpos", fields.libpos); end return end local robot_formname = "robot_editor_"; -- editor gui TODO if string.find(formname,robot_formname) then local name = player:get_player_name(); local p = string.find(formname,":"); local pos = minetest.string_to_pos(string.sub(formname, p+1)); if fields.listname then local list = fields.listname; if string.sub(list,1,3) == "CHG" then local selection = tonumber(string.sub(list,5)) or 1 basic_robot.editor[name].selection = selection; local lines = basic_robot.editor[name].lines; basic_robot.editor[name].input = lines[selection] or ""; minetest.show_formspec(name, "robot_editor_:"..minetest.pos_to_string(pos), code_edit_form(pos,name)); end elseif fields.UPDATE then local lines = basic_robot.editor[name].lines or {}; local selection = basic_robot.editor[name].selection or 1; fields.input = fields.input or ""; fields.input = string.gsub(fields.input, '\\([%[%]\\,;])', '%1') -- dumb minetest POSTS escaped stuff... lines[selection] = fields.input basic_robot.editor[name].input = lines[selection]; minetest.show_formspec(name, "robot_editor_:"..minetest.pos_to_string(pos), code_edit_form(pos,name)); elseif fields.DELETE then local selection = basic_robot.editor[name].selection or 1; table.remove(basic_robot.editor[name].lines,selection); minetest.show_formspec(name, "robot_editor_:"..minetest.pos_to_string(pos), code_edit_form(pos,name)); elseif fields.INSERT then local selection = basic_robot.editor[name].selection or 1; table.insert(basic_robot.editor[name].lines,selection,"") minetest.show_formspec(name, "robot_editor_:"..minetest.pos_to_string(pos), code_edit_form(pos,name)); elseif fields.SAVE then local selection = basic_robot.editor[name].selection or 1; local lines = basic_robot.editor[name].lines or {}; if fields.input and fields.input~="" then fields.input = string.gsub(fields.input, '\\([%[%]\\,;])', '%1') -- dumb minetest POSTS escaped stuff... lines[selection]= fields.input end local meta = minetest.get_meta(pos); if not lines then return end local code = table.concat(lines,"\n"); meta:set_string("code",code); basic_robot.editor[name].lines = {}; robot_spawner_update_form(pos,0); end return end local robot_formname = "robot_book_"; -- book editing gui if string.find(formname,robot_formname) then local p = string.find(formname,":"); local sel = tonumber(string.sub(formname, string.len(robot_formname)+1,p-1)) or 1; local libpos = minetest.string_to_pos(string.sub(formname, p+1)); if minetest.is_protected(libpos, player:get_player_name()) then return end if fields.OK and fields.book then local meta = minetest.get_meta(libpos); local inv = minetest.get_meta(libpos):get_inventory();local itemstack = inv:get_stack("library", sel); if itemstack then local data = itemstack:get_meta():to_table().fields -- 0.4.16, old minetest.deserialize(itemstack:get_metadata()) if not data then data = {} end local text = fields.book or ""; data.text = text or "" data.title = fields.title or "" data.text_len = #data.text data.page = 1 data.owner = data.owner or "" local lpp = 14 data.page_max = math.ceil((#text:gsub("[^\n]", "") + 1) / lpp) --local data_str = minetest.serialize(data) local new_stack = ItemStack("default:book_written") new_stack:get_meta():from_table({fields = data}) -- 0.4.16 --new_stack:set_metadata(data_str); inv:set_stack("library",sel, new_stack); end end if fields.LOAD then local meta = minetest.get_meta(libpos); --minetest.chat_send_all(libpos.x .. " " .. libpos.y .. " " .. libpos.z) --minetest.chat_send_all(fields.book or "") local inv = minetest.get_meta(libpos):get_inventory();local itemstack = inv:get_stack("library", sel); if itemstack then local data = itemstack:get_meta():to_table().fields -- 0.4.16, old minetest.deserialize(itemstack:get_metadata()) or {}; meta:set_string("code", data.text or "") robot_spawner_update_form(libpos); minetest.chat_send_player(player:get_player_name(),"#robot: program loaded from book") end end return end end ) -- handle chats minetest.register_on_chat_message( function(name, message) local listeners = basic_robot.data.listening; for pname,_ in pairs(listeners) do local data = basic_robot.data[pname]; data.listen_msg = message; data.listen_speaker = name; end return false end ) minetest.register_node("basic_robot:spawner", { description = "Spawns robot", tiles = {"cpu.png"}, groups = {cracky=3, mesecon_effector_on = 1}, drawtype = "allfaces", paramtype = "light", param1=1, walkable = true, alpha = 150, after_place_node = function(pos, placer) local meta = minetest.env:get_meta(pos) local owner = placer:get_player_name(); meta:set_string("owner", owner); local privs = minetest.get_player_privs(placer:get_player_name()); local authlevel = 0; if privs.privs then -- set auth level depending on privs authlevel = 3 elseif privs.puzzle then authlevel = 2 elseif privs.robot then authlevel = 1 else authlevel = 0 end meta:set_int("authlevel",authlevel) local sec_hash = minetest.get_password_hash("",authlevel .. owner .. basic_robot.password) -- 'digitally sign' authlevel using password meta:set_string("sec_hash", sec_hash); meta:set_string("code",""); meta:set_int("id",1); -- initial robot id meta:set_string("name", placer:get_player_name().."1") meta:set_string("infotext", "robot spawner (owned by ".. placer:get_player_name() .. ")") meta:set_string("libpos",pos.x .. " " .. pos.y .. " " .. pos.z) robot_spawner_update_form(pos); local inv = meta:get_inventory(); -- spawner inventory inv:set_size("main",32); inv:set_size("library",16); --4*4 end, mesecons = {effector = { action_on = spawn_robot, action_off = despawn_robot } }, on_receive_fields = on_receive_robot_form, allow_metadata_inventory_put = function(pos, listname, index, stack, player) local meta = minetest.get_meta(pos); local privs = minetest.get_player_privs(player:get_player_name()); if minetest.is_protected(pos, player:get_player_name()) and not privs.privs then return 0 end return stack:get_count(); end, allow_metadata_inventory_take = function(pos, listname, index, stack, player) local meta = minetest.get_meta(pos); local privs = minetest.get_player_privs(player:get_player_name()); if minetest.is_protected(pos, player:get_player_name()) and not privs.privs then return 0 end return stack:get_count(); end, allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) local meta = minetest.get_meta(pos); local privs = minetest.get_player_privs(player:get_player_name()); if minetest.is_protected(pos, player:get_player_name()) and not privs.privs then return 0 end return count end, can_dig = function(pos, player) if minetest.is_protected(pos, player:get_player_name()) then return false end local meta = minetest.get_meta(pos); if not meta:get_inventory():is_empty("main") or not meta:get_inventory():is_empty("library") then return false end return true end }) local get_manual_control_form = function(name) local form = "size[2.5,3]" .. -- width, height "button[-0.25,-0.25;1.,1;turnleft;TLeft]".. "button[0.75,-0.25;1.,1;forward;GO]".. "button[1.75,-0.25;1.,1;turnright;TRight]".. "button[-0.25,0.75;1.,1;left;LEFT]".. "button[0.75,0.75;1.,1;dig;DIG]".. "button[1.75,0.75;1.,1;right;RIGHT]".. "button[-0.25,1.75;1.,1;down;DOWN]".. "button[0.75,1.75;1.,1;back;BACK]".. "button[1.75,1.75;1.,1;up;UP]".. "button[-0.25,2.65;1.,1;digdown;DDown]".. "button[1.75,2.65;1.,1;digup;DUp]"; return form; end -- remote control minetest.register_craftitem("basic_robot:control", { description = "Robot remote control", inventory_image = "control.png", groups = {book = 1, not_in_creative_inventory = 1}, stack_max = 1, on_secondary_use = function(itemstack, user, pointed_thing) local owner = user:get_player_name(); local code = minetest.formspec_escape(itemstack:get_metadata()); local ids = basic_robot.ids[owner]; if not ids then setupid(owner) end local id = basic_robot.ids[owner].id or 1; -- read active id for player local name = owner..id; local form = "size[9.5,1.25]" .. -- width, height "textarea[1.25,-0.25;8.75,2.25;code;;".. code.."]".. "button_exit[-0.25,-0.25;1.25,1;OK;SAVE]".. "field[0.25,1;1.,1;id;id;"..id.."]" minetest.show_formspec(owner, "robot_control_" .. name, form); return end, on_use = function(itemstack, user, pointed_thing) local owner = user:get_player_name(); local script = itemstack:get_metadata(); if script == "@" then -- remote control as a tool - notify robot in current block of pointed position, using keyboard event type 0 local round = math.floor; local r = basic_robot.radius; local ry = 2*r; -- note: this is skyblock adjusted local pos = pointed_thing.under if not pos then return end local ppos = {x=round(pos.x/r+0.5)*r,y=round(pos.y/ry+0.5)*ry+1,z=round(pos.z/r+0.5)*r}; -- just on top of basic_protect:protector! local meta = minetest.get_meta(ppos); local name = meta:get_string("name"); local data = basic_robot.data[name]; if data then data.keyboard = {x=pos.x,y=pos.y,z=pos.z, puncher = owner, type = 0} end return end local ids = basic_robot.ids[owner]; if not ids then setupid(owner) end local id = basic_robot.ids[owner].id or 1; -- read active id local name = owner .. id local data = basic_robot.data[name]; if data and data.sandbox then else minetest.chat_send_player(owner, "#remote control: your robot must be running"); return end local t0 = data.remoteuse or 0; -- prevent too fast remote use local t1 = minetest.get_gametime(); if t1-t0<1 then return end data.remoteuse = t1; if data.authlevel >= 3 then local privs = minetest.get_player_privs(owner); -- only admin can run admin robot if not privs.privs then return end end if script == "" then --display control form minetest.show_formspec(owner, "robot_manual_control_" .. name, get_manual_control_form(name)); return end if data.authlevel<3 then if check_code(script)~=nil then return end end local ScriptFunc, CompileError = loadstring( script ) if CompileError then minetest.chat_send_player(owner, "#remote control: compile error " .. CompileError ) return end setfenv( ScriptFunc, basic_robot.data[name].sandbox ) local Result, RuntimeError = pcall( ScriptFunc ); if RuntimeError then minetest.chat_send_player(owner, "#remote control: run error " .. RuntimeError ) return end end, }) minetest.register_entity( "basic_robot:projectile", { hp_max = 100, physical = true, collide_with_objects = true, weight = 5, collisionbox = {-0.15,-0.15,-0.15, 0.15,0.15,0.15}, visual ="sprite", visual_size = {x=0.5, y=0.5}, textures = {"default_furnace_fire_fg.png"}, is_visible = true, oldvel = {x=0,y=0,z=0}, name = "", -- name of originating robot spawnpos = {}, state = false, --on_activate = function(self, staticdata) -- self.object:remove() --end, --get_staticdata = function(self) -- return nil --end, on_step = function(self, dtime) local vel = self.object:getvelocity(); if (self.oldvel.x~=0 and vel.x==0) or (self.oldvel.y~=0 and vel.y==0) or (self.oldvel.z~=0 and vel.z==0) then local data = basic_robot.data[self.name]; if data then data.fire_pos = self.object:getpos(); end self.object:remove() return elseif vel.x==0 and vel.y==0 and vel.z==0 then self.object:remove() end self.oldvel = vel; if not self.state then self.state = true end end, get_staticdata = function(self) -- this gets called before object put in world and before it hides if not self.state then return nil end local data = basic_robot.data[self.name]; if data then data.fire_pos = self.object:getpos(); end self.object:remove(); return nil end, }) minetest.register_craft({ output = "basic_robot:control", recipe = { {"default:stick"}, {"default:mese_crystal"} } }) minetest.register_craft({ output = "basic_robot:spawner", recipe = { {"default:mese_crystal", "default:mese_crystal","default:mese_crystal"}, {"default:mese_crystal", "default:mese_crystal","default:mese_crystal"}, {"default:stone", "default:steel_ingot", "default:stone"} } }) minetest.register_privilege("robot", "increased number of allowed active robots") minetest.register_privilege("puzzle", "allow player to use puzzle. namespace in robots")