better spawning

several robots per user
off signal to spawner from mesecons/basic_machines removes robot
master
rnd1 2016-12-21 12:11:57 +01:00
parent 779f0ab40c
commit ee7473dfac
3 changed files with 427 additions and 165 deletions

View File

@ -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)

523
init.lua
View File

@ -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,"<robot ".. name .. "> " .. text)
minetest.chat_send_player(basic_robot.data[name].owner,"<robot ".. name .. "> " .. 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"}
}
})
})
minetest.register_privilege("robot", "increased number of allowed active robots")

BIN
textures/control.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB