diff --git a/commands.lua b/commands.lua index 52df3db..21dc26a 100644 --- a/commands.lua +++ b/commands.lua @@ -90,10 +90,10 @@ basic_robot.commands.dig = function(name,dir) local obj = basic_robot.data[name].obj; local pos = pos_in_dir(obj, dir) local luaent = obj:get_luaentity(); - if minetest.is_protected(pos,luaent.owner ) then return false end + if minetest.is_protected(pos,luaent.owner) then return false end local nodename = minetest.get_node(pos).name; - if nodename == "air" then return false end + if nodename == "air" or nodename=="ignore" then return false end local spos = obj:get_luaentity().spawnpos; local inv = minetest.get_meta(spos):get_inventory(); @@ -177,6 +177,22 @@ basic_robot.commands.take_item = function(name,item, inventory,dir) return contains end +basic_robot.commands.check_inventory = function(name,itemname, inventory,dir) + local obj = basic_robot.data[name].obj; + local tpos; + if dir~=0 then + tpos = pos_in_dir(obj, dir); -- position of target block in front + else + tpos = obj:get_luaentity().spawnpos; + end + + local tinv = minetest.get_meta(tpos):get_inventory(); + local stack = ItemStack(itemname); + if not inventory then inventory = "main"; end + + return tinv:contains_item(inventory, stack); +end + basic_robot.no_teleport_table = { ["itemframes:item"] = true, @@ -187,7 +203,7 @@ basic_robot.no_teleport_table = { basic_robot.commands.pickup = function(r,name) if r>8 then return false end - + local pos = basic_robot.data[name].obj:getpos(); local spos = basic_robot.data[name].spawnpos; -- position of spawner block local meta = minetest.get_meta(spos); @@ -195,8 +211,8 @@ basic_robot.commands.pickup = function(r,name) for _,obj in pairs(minetest.get_objects_inside_radius({x=pos.x,y=pos.y,z=pos.z}, r)) do local lua_entity = obj:get_luaentity() - if not obj:is_player() and lua_entity and lua_entity.itemstring ~= "" then - local detected_obj = lua_entity.name or "" + if not obj:is_player() and lua_entity and lua_entity.itemstring then + local detected_obj = lua_entity.itemstring or "" if not basic_robot.no_teleport_table[detected_obj] then -- object on no teleport list -- put item in chest local stack = ItemStack(lua_entity.itemstring) @@ -245,11 +261,14 @@ basic_robot.commands.place = function(name,nodename, dir) inv:remove_item("main", ItemStack(nodename)); --DS - local sounds = minetest.registered_nodes[nodename].sounds - if sounds then - local sound = sounds.place - if sound then - minetest.sound_play(sound,{object=obj, max_hear_distance = 10}) + local registered_node = minetest.registered_nodes[nodename]; + if registered_node then + local sounds = registered_node.sounds + if sounds then + local sound = sounds.place + if sound then + minetest.sound_play(sound,{object=obj, max_hear_distance = 10}) + end end end @@ -283,6 +302,29 @@ basic_robot.commands.attack = function(name, target) -- attack range 4, damage 5 end +basic_robot.commands.grab = function(name,target) + + local reach = 4; + + local tplayer = minetest.get_player_by_name(target); + if not tplayer then return false end + local obj = basic_robot.data[name].obj; + local pos = obj:getpos(); + local tpos = tplayer:getpos(); + + if math.abs(pos.x-tpos.x)> reach or math.abs(pos.y-tpos.y)> reach or math.abs(pos.z-tpos.z)> reach then + return false + end + + if tplayer:get_attach() then + tplayer:set_detach() + else + tplayer:set_attach(obj, "", {x=0,y=5,z=0}, {x=0,y=0,z=0}) + end + + return true + +end basic_robot.commands.read_book = function (itemstack) -- itemstack should contain book local data = minetest.deserialize(itemstack:get_metadata()) @@ -293,14 +335,15 @@ basic_robot.commands.read_book = function (itemstack) -- itemstack should contai end end -basic_robot.commands.write_book = function(name,text) -- returns itemstack containing book +basic_robot.commands.write_book = function(name,title,text) -- returns itemstack containing book local lpp = 14; local new_stack = ItemStack("default:book_written") local data = {} - data.title = "program book" - data.text = text + if title == "" or not title then title = "program book "..minetest.get_gametime() end + data.title = title + data.text = text or "" data.text_len = #data.text data.page = 1 data.page_max = math.ceil((#data.text:gsub("[^\n]", "") + 1) / lpp) diff --git a/init.lua b/init.lua index e6bc077..3009c55 100644 --- a/init.lua +++ b/init.lua @@ -2,32 +2,37 @@ basic_robot = {}; -basic_robot.call_limit = 32; -- how many execution calls per script execution allowed -basic_robot.bad_inventory_blocks = { +---- SETTINGS ------ +basic_robot.call_limit = 32; -- how many execution calls per script run allowed + +basic_robot.bad_inventory_blocks = { -- disallow taking from these nodes inventories ["craft_guide:sign_wall"] = true, } -basic_robot.maxdig = 1; -- how many digs allowed per execution, 0 = unlimited +basic_robot.maxdig = 1; -- how many digs allowed per run, 0 = unlimited +---------------------- -basic_robot.version = "11/26a"; - - -basic_robot.data = {}; -basic_robot.data.listening = {}; -- which robots listen to chat +basic_robot.version = "12/21a"; +basic_robot.data = {}; -- stores all robot data --[[ -[name] = {sandbox= .., bytecode = ..., ram = ..., obj = robot object,spawnpos=...} +[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 all players +--[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") + + + -- SANDBOX for running lua code isolated and safely function getSandboxEnv (name) + local commands = basic_robot.commands; local env = { @@ -85,6 +90,15 @@ function getSandboxEnv (name) up = function(item, inventory) commands.take_item(name,item, inventory,5) end, }, + check_inventory = { + left = function(itemname, inventory) return commands.check_inventory(name,itemname, inventory,1) end, + right = function(itemname, inventory) return commands.check_inventory(name,itemname, inventory,2) end, + forward = function(itemname, inventory) return commands.check_inventory(name,itemname, inventory,3) end, + backward = function(itemname, inventory) return commands.check_inventory(name,itemname, inventory,4) end, + down = function(itemname, inventory) return commands.check_inventory(name,itemname, inventory,5) end, + up = function(itemname, inventory) return commands.check_inventory(name,itemname, inventory,6) end, + self = function(itemname, inventory) return commands.check_inventory(name,itemname, inventory,0) end, + }, pickup = function(r) -- pick up items around robot commands.pickup(r, name); @@ -206,6 +220,8 @@ function getSandboxEnv (name) 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, + read_node = { -- returns node name left = function() return commands.read_node(name,1) end, right = function() return commands.read_node(name,2) end, @@ -232,7 +248,8 @@ function getSandboxEnv (name) basic_robot.data[name].quiet_mode=true end else - minetest.chat_send_player(name," " .. text) + + minetest.chat_send_player(basic_robot.data[name].owner," " .. text) end end, @@ -240,8 +257,11 @@ function getSandboxEnv (name) book = { read = function(i) if i<=0 or i > 32 then return nil end - local inv = minetest.get_meta(basic_robot.data[name].spawnpos):get_inventory(); - local itemstack = inv:get_stack("library", i); + 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 @@ -249,10 +269,10 @@ function getSandboxEnv (name) end end, - write = function(i,text) + 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(name,text); + local stack = basic_robot.commands.write_book(name,title,text); if stack then inv:set_stack("library", i, stack) end end }, @@ -291,6 +311,7 @@ function getSandboxEnv (name) 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, @@ -343,10 +364,20 @@ function getSandboxEnv (name) basic_robot.data[name].ccounter = _ccounter + 1; end, }; - env._G = env; + + --special sandbox for admin + local isadmin=basic_robot.data[name].isadmin + + if not isadmin then + env._G = env; + else + env._G=_G; + end + return env end +-- code checker local function check_code(code) @@ -390,6 +421,7 @@ local function is_inside_string(pos,script) return false; end +-- COMPILATION local function CompileCode ( script ) @@ -417,7 +449,7 @@ local function CompileCode ( script ) --minetest.chat_send_all("while0"); if not is_inside_string(i2,script) then local i21 = i2; - i2=string.find(script, "do ", i2); + i2=string.find(script, "do", i2); if i2 then script = script.sub(script,1, i2+1) .. insert_code .. script.sub(script, i2+2); i1=i21+6; -- after while @@ -475,13 +507,16 @@ local function CompileCode ( script ) return ScriptFunc, nil end -local function initSandbox ( name ) +local function initSandbox (name) basic_robot.data[name].sandbox = getSandboxEnv (name); end local function setCode( name, script ) -- to run script: 1. initSandbox 2. setCode 3. runSandbox + local err; - err = check_code(script); + if not basic_robot.data[name].isadmin then + err = check_code(script); + end if err then return err end local bytecode, err = CompileCode ( script ); @@ -494,48 +529,59 @@ basic_robot.commands.setCode=setCode; -- so we can use it local function runSandbox( name) - local ScriptFunc = basic_robot.data[name].bytecode; + local data = basic_robot.data[name] + local ScriptFunc = data.bytecode; if not ScriptFunc then return "Bytecode missing." end - basic_robot.data[name].ccounter = 0; - basic_robot.data[name].digcount = 1; + data.ccounter = 0; + data.digcount = 1; - setfenv( ScriptFunc, basic_robot.data[name].sandbox ) + setfenv( ScriptFunc, data.sandbox ) - local Result, RuntimeError = pcall( ScriptFunc ) - if RuntimeError then - return RuntimeError - end + local Result, RuntimeError = pcall( ScriptFunc ) + if RuntimeError then + return RuntimeError + end return nil end -- note: to see memory used by lua in kbytes: collectgarbage("count") +local function setupid(owner) + local privs = minetest.get_player_privs(owner); if not privs then return end + local maxid = 2; + if privs.robot then maxid = 16 end -- max id's per user + basic_robot.ids[owner] = {id = 1, maxid = maxid}; --active id for remove control +end + local robot_spawner_update_form = function (pos, mode) if not pos then return end local meta = minetest.get_meta(pos); if not meta then return end - local x0,y0,z0; - x0=meta:get_int("x0");y0=meta:get_int("y0");z0=meta:get_int("z0"); -- direction of velocity local code = minetest.formspec_escape(meta:get_string("code")); local form; - if mode ~= 1 then + local id = meta:get_int("id"); + + if mode ~= 1 then -- when placed + form = "size[9.5,6]" .. -- width, height "textarea[1.25,-0.25;8.75,7.6;code;;".. code.."]".. "button_exit[-0.25,-0.25;1.25,1;OK;SAVE]".. "button_exit[-0.25, 0.75;1.25,1;spawn;START]".. "button[-0.25, 1.75;1.25,1;despawn;STOP]".. + "field[0.25,3.;1.,1;id;id;"..id.."]".. "button[-0.25, 3.6;1.25,1;inventory;storage]".. "button[-0.25, 4.6;1.25,1;library;library]".. "button[-0.25, 5.6;1.25,1;help;help]"; - else + + else -- when robot clicked form = "size[9.5,6]" .. -- width, height "textarea[1.25,-0.25;8.75,7.6;code;;".. code.."]".. @@ -544,6 +590,7 @@ local robot_spawner_update_form = function (pos, mode) "button[-0.25, 3.6;1.25,1;inventory;storage]".. "button[-0.25, 4.6;1.25,1;library;library]".. "button[-0.25, 5.6;1.25,1;help;help]"; + end if mode ==1 then return form end @@ -551,23 +598,31 @@ local robot_spawner_update_form = function (pos, mode) end -local function init_robot(self) +local function init_robot(obj) + local self = obj:get_luaentity(); + local name = self.name; -- robot name + basic_robot.data[name].obj = obj; --register object + --init settings + basic_robot.data.listening[name] = nil -- dont listen at beginning + basic_robot.data[name].quiet_mode = false; -- can chat globally - basic_robot.data[self.owner].obj = self.object; -- BUG: some problems with functions using object later?? - basic_robot.data.listening[self.owner] = nil -- dont listen at beginning - basic_robot.data[self.owner].quiet_mode = false; + -- check if admin robot + local privs = minetest.get_player_privs(self.owner); + if privs then basic_robot.data[name].isadmin = privs.privs end - self.object:set_properties({infotext = "robot " .. self.owner}); - self.object:set_properties({nametag = "[" .. self.owner.."]",nametag_color = "LawnGreen"}); - self.object:set_armor_groups({fleshy=0}) + --robot appearance,armor... + obj:set_properties({infotext = "robot " .. name}); + obj:set_properties({nametag = "[" .. name.."]",nametag_color = "LawnGreen"}); + obj:set_armor_groups({fleshy=0}) - initSandbox ( self.owner ) + initSandbox ( name ) end minetest.register_entity("basic_robot:robot",{ energy = 1, owner = "", + name = "", hp_max = 10, code = "", timer = 0, @@ -586,24 +641,27 @@ minetest.register_entity("basic_robot:robot",{ physical=true, on_activate = function(self, staticdata) - -- reactivate robot if staticdata~="" then - self.owner = staticdata; -- remember its owner - if not basic_robot.data[self.owner] then - minetest.chat_send_player(self.owner, "#ROBOT INIT: error. spawn robot again.") + self.name = staticdata; -- remember its name + + local data = basic_robot.data[self.name]; + + if not data then + --minetest.chat_send_all("#ROBOT INIT: error. spawn robot again.") self.object:remove(); return; end - self.spawnpos = {x=basic_robot.data[self.owner].spawnpos.x,y=basic_robot.data[self.owner].spawnpos.y,z=basic_robot.data[self.owner].spawnpos.z}; - init_robot(self); + self.owner = data.owner; + self.spawnpos = {x=data.spawnpos.x,y=data.spawnpos.y,z=data.spawnpos.z}; + init_robot(self.object); self.running = 1; - local pos = basic_robot.data[self.owner].spawnpos; - local meta = minetest.get_meta(pos); + + local meta = minetest.get_meta(data.spawnpos); if meta then self.code = meta:get_string("code") end -- remember code if not self.code or self.code == "" then minetest.chat_send_player(self.owner, "#ROBOT INIT: no code found") @@ -613,38 +671,15 @@ minetest.register_entity("basic_robot:robot",{ return end - -- init robot TODO: rewrite for less buggy - minetest.after(0, -- so that stuff with spawner is initialized before - function() - - if not self.spawnpos then self.object:remove() return end - - if not basic_robot.data[self.owner] then - basic_robot.data[self.owner] = {}; - end - - basic_robot.data[self.owner].spawnpos = {x=self.spawnpos.x,y=self.spawnpos.y,z=self.spawnpos.z}; - init_robot(self); -- set properties, init sandbox - - local err = setCode( self.owner, 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.owner].obj = nil; - return - end - - self.running = 1 - - end - ) + -- lost robots + --minetest.chat_send_all("D R " .. self.owner) + + --if not self.spawnpos then self.object:remove() return end end, get_staticdata = function(self) - return self.owner; + return self.name; end, on_punch = function (self, puncher, time_from_last_punch, tool_capabilities, dir) @@ -652,32 +687,33 @@ minetest.register_entity("basic_robot:robot",{ end, on_step = function(self, dtime) + self.timer=self.timer+dtime if self.timer>self.timestep and self.running == 1 then self.timer = 0; - local err = runSandbox(self.owner); + local err = runSandbox(self.name); if err then minetest.chat_send_player(self.owner,"#ROBOT ERROR : " .. err) self.running = 0; -- stop execution if string.find(err,"stack overflow") then -- remove stupid player privs and spawner, ban player ip - local owner = self.owner; - local pos = basic_robot.data[owner].spawnpos; + local name = self.name; + local pos = basic_robot.data[name].spawnpos; minetest.set_node(pos, {name = "air"}); - local privs = core.get_player_privs(owner);privs.interact = false; + local privs = core.get_player_privs(self.owner);privs.interact = false; - core.set_player_privs(owner, privs); minetest.auth_reload() - minetest.ban_player(owner) + core.set_player_privs(self.owner, privs); minetest.auth_reload() + minetest.ban_player(self.owner) end - local owner = self.owner; - local pos = basic_robot.data[owner].spawnpos; + local name = self.name; + local pos = basic_robot.data[name].spawnpos; - if not basic_robot.data[owner] then return end - if basic_robot.data[owner].obj then - basic_robot.data[owner].obj = nil; + 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(); @@ -692,15 +728,18 @@ minetest.register_entity("basic_robot:robot",{ 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.owner, form); + minetest.show_formspec(clicker:get_player_name(), "robot_worker_" .. self.name, form); -- yyy end, }) + local spawn_robot = function(pos,node,ttl) 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 @@ -725,36 +764,104 @@ local spawn_robot = function(pos,node,ttl) -- 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 robot already exists do nothing - if basic_robot.data[owner] and basic_robot.data[owner].obj then - minetest.chat_send_player(owner,"#ROBOT: robot already active, removing") - basic_robot.data[owner].obj:remove(); - basic_robot.data[owner].obj = nil; + 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 = meta:get_string("owner"); + + luaent.owner = owner; + luaent.name = name; luaent.code = meta:get_string("code"); luaent.spawnpos = {x=pos.x,y=pos.y-1,z=pos.z}; - -- note: + + + local data = basic_robot.data[name]; + if not data then + basic_robot.data[name] = {}; + data = basic_robot.data[name]; + 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"); + 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 - +--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.reset then - local meta = minetest.get_meta(pos); - meta:set_string("code",""); - robot_spawner_update_form(pos); - return - end - if fields.OK then local meta = minetest.get_meta(pos); @@ -763,6 +870,14 @@ local on_receive_robot_form = function(pos, formname, fields, sender) local code = fields.code or ""; meta:set_string("code", code) 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<1 or id>basic_robot.ids[owner].maxid then return end + meta:set_int("id",id) -- set active id for spawner + end robot_spawner_update_form(pos); return @@ -770,33 +885,35 @@ local on_receive_robot_form = function(pos, formname, fields, sender) if fields.help then - local text = "BASIC LUA SYNTAX\n \nif x==1 then do_something else do_something_else end".. - "\nfor i = 1, 5 do something end \nwhile i<6 do something; i=i+1; end\n".. + local text = "BASIC LUA SYNTAX\n \nif x==1 then A else B end".. + "\nfor i = 1, 5 do something end \nwhile i<6 do A; i=i+1; end\n".. "\narrays: 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\nmove.direction(), where direction is forward, backward, left,right, up, down)\n".. + " **MOVEMENT,DIGGING, PLACING, INVENTORY TAKE/INSERT\nmove.direction(), where direction is forward, backward, left,right, up, down)\n".. "forward_down direction only works with dig, place and read_node\n".. "turn.left(), turn.right(), turn.angle(45)\n".. - "dig.direction()\n place.direction(\"default:dirt\")\nread_node.direction() tells you names of nodes\n".. + "dig.direction()\nplace.direction(\"default:dirt\")\nread_node.direction() tells you names of nodes\n".. "insert.direction(item, inventory) inserts item from robot inventory to target inventory\n".. + "check_inventory.direction(itemname, inventory) looks at node and returns false/true, direction can be self\n".. "pickup(r) picks up all items around robot in radius r<8\n".. "take.direction(item, inventory) takes item from target inventory into robot inventory\n".. "read_text.direction(stringname) reads text of signs, chests and other blocks, optional stringname for other meta\n".. - "**BOOKS/CODE\nbook.read(i) returns contents of book at i-th position in library \nbook.write(i,text) writes book at i-th position\n".. + " **BOOKS/CODE\nbook.read(i) returns contents of book at i-th position in library \nbook.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) is true if node can be found at radius 3 around robot, otherwise false\n".. "**PLAYERS\n".. "find_player(3) finds player and returns his name in radius 3 around robot, if not returns false\n".. "attack(target) attempts to attack target player if nearby \n".. + "grab(target) attempt to grab target player if nearby \n".. "player.getpos(name) return position of player, player.connected() returns list of players\n".. - "**ROBOT\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 = read_mail() reads mail, if any\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.spam(0/1) (dis)enable message repeat to all\n".. "self.remove() removes robot\n".. @@ -828,11 +945,14 @@ local on_receive_robot_form = function(pos, formname, fields, sender) local meta = minetest.get_meta(pos); local owner = meta:get_string("owner"); + local id = meta:get_int("id"); + local name = owner..id; + - if not basic_robot.data[owner] then return end - if basic_robot.data[owner].obj then - basic_robot.data[owner].obj:remove(); - basic_robot.data[owner].obj = nil; + 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 @@ -842,29 +962,75 @@ local on_receive_robot_form = function(pos, formname, fields, sender) local form = "size[8,8]" .. -- width, height "list["..list_name..";main;0.,0;8,4;]".. - "list[current_player;main;0,4.25;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 form = - "size[8,8]" .. -- width, height - "list["..list_name..";library;0.,0;8,4;]".. + 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 = minetest.deserialize(itemstack:get_metadata()) + 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_inventory", form); + minetest.show_formspec(sender:get_player_name(), "robot_library_"..minetest.pos_to_string(pos), form); end end --- handle form when rightclicking robot entity +-- 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); - local sender = minetest.get_player_by_name(name); + 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; @@ -873,11 +1039,11 @@ minetest.register_on_player_receive_fields( 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 + -- if not sender then on_receive_robot_form(pos,formname, fields, player) - else - on_receive_robot_form(pos,formname, fields, sender) - end + -- else + -- on_receive_robot_form(pos,formname, fields, sender) + -- end return end @@ -885,20 +1051,24 @@ minetest.register_on_player_receive_fields( local robot_formname = "robot_control_"; if string.find(formname,robot_formname) then - local name = string.sub(formname, string.len(robot_formname)+1); - local sender = minetest.get_player_by_name(name); if not sender then return end + local name = string.sub(formname, string.len(robot_formname)+1); -- robot name if fields.OK and fields.code then - local item = sender:get_wielded_item(); --set_wielded_item(item) + local item = player:get_wielded_item(); --set_wielded_item(item) item:set_metadata(fields.code); - sender:set_wielded_item(item); + 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); - local sender = minetest.get_player_by_name(name); if not sender then return end + local name = string.sub(formname, string.len(robot_formname)+1); -- robot name + local commands = basic_robot.commands; if fields.turnleft then @@ -927,6 +1097,36 @@ minetest.register_on_player_receive_fields( 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 text = basic_robot.commands.read_book(itemstack) or ""; + local form = "size [8,8] textarea[0.,0;8.75,9.75;book;BOOK CONTENTS;" .. minetest.formspec_escape(text) .. "]" + minetest.show_formspec(player:get_player_name(), "robot_book", 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 + + end ) @@ -935,8 +1135,9 @@ minetest.register_on_chat_message( function(name, message) local listeners = basic_robot.data.listening; for pname,_ in pairs(listeners) do - basic_robot.data[pname].listen_msg = message; - basic_robot.data[pname].listen_speaker = name; + local data = basic_robot.data[pname]; + data.listen_msg = message; + data.listen_speaker = name; end end ) @@ -957,16 +1158,21 @@ minetest.register_node("basic_robot:spawner", { local privs = minetest.get_player_privs(placer:get_player_name()); if privs.privs then meta:set_int("admin",1) end meta:set_string("code",""); + meta:set_int("id",1); -- initial robot id + 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",32); + inv:set_size("library",16); --4*4 end, mesecons = {effector = { - action_on = spawn_robot + action_on = spawn_robot, + action_off = despawn_robot } }, @@ -1030,44 +1236,53 @@ minetest.register_craftitem("basic_robot:control", { on_secondary_use = function(itemstack, user, pointed_thing) - local name = user:get_player_name(); + 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]" .. -- width, height - "textarea[1.25,-0.25;8.75,3;code;;".. code.."]".. - "button_exit[-0.25,-0.25;1.25,1;OK;SAVE]"; - minetest.show_formspec(name, "robot_control_" .. name, 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 name = user:get_player_name(); + local owner = user:get_player_name(); + 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 - if basic_robot.data[name] and basic_robot.data[name].sandbox then + local data = basic_robot.data[name]; + + if data and data.sandbox then else minetest.chat_send_player(name, "#remote control: your robot must be running"); return end - local t0 = basic_robot.data[name].remoteuse or 0; -- prevent too fast remote use + local t0 = data.remoteuse or 0; -- prevent too fast remote use local t1 = minetest.get_gametime(); if t1-t0<1 then return end - basic_robot.data[name].remoteuse = t1; + data.remoteuse = t1; script = itemstack:get_metadata(); if script == "" then --display control form - minetest.show_formspec(name, "robot_manual_control_" .. name, get_manual_control_form(name)); + minetest.show_formspec(owner, "robot_manual_control_" .. name, get_manual_control_form(name)); return end local ScriptFunc, CompileError = loadstring( script ) if CompileError then - minetest.chat_send_player(name, "#remote control: compile error " .. CompileError ) + minetest.chat_send_player(owner, "#remote control: compile error " .. CompileError ) return end @@ -1075,7 +1290,7 @@ minetest.register_craftitem("basic_robot:control", { local Result, RuntimeError = pcall( ScriptFunc ); if RuntimeError then - minetest.chat_send_player(name, "#remote control: run error " .. RuntimeError ) + minetest.chat_send_player(owner, "#remote control: run error " .. RuntimeError ) return end end, @@ -1092,10 +1307,10 @@ minetest.register_entity( 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_diamond_block.png"}, + textures = {"heart.png"}, is_visible = true, oldvel = {x=0,y=0,z=0}, - owner = "", + name = "", -- name of originating robot --on_activate = function(self, staticdata) -- self.object:remove() @@ -1108,8 +1323,9 @@ minetest.register_entity( 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 - if basic_robot.data[self.owner] then - basic_robot.data[self.owner].fire_pos = self.object:getpos(); + local data = basic_robot.data[self.name]; + if data then + data.fire_pos = self.object:getpos(); end self.object:remove() return @@ -1134,4 +1350,7 @@ minetest.register_craft({ {"default:mese_crystal", "default:mese_crystal","default:mese_crystal"}, {"default:stone", "default:steel_ingot", "default:stone"} } -}) \ No newline at end of file +}) + + +minetest.register_privilege("robot", "increased number of allowed active robots") \ No newline at end of file diff --git a/textures/control.png b/textures/control.png new file mode 100644 index 0000000..73528e9 Binary files /dev/null and b/textures/control.png differ