2000 lines
66 KiB
Lua
2000 lines
66 KiB
Lua
-- 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 robots 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 = "password"; -- IMPORTANT: change it before running mod, 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 related data
|
|
--[[
|
|
[name] = { sandbox= .., bytecode = ..., ram = ..., obj = robot object, spawnpos= ..., authlevel = ...}
|
|
robot object = object of entity, used to manipulate movements and more
|
|
--]]
|
|
basic_robot.ids = {}; -- stores maxid for each player
|
|
--[name] = {id = .., maxid = .. }, current id for robot controller, 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, texture, 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 = texture or "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("<robot ".. name .. "> " .. 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,"<robot ".. name .. "> " .. 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 pos<i1 then
|
|
return false
|
|
end
|
|
while i1 do
|
|
i2=string.find(script,"\"",i1+1);
|
|
if i2 then
|
|
par = 1 - par;
|
|
else
|
|
return false
|
|
end
|
|
if par == 0 then
|
|
if i1<pos and pos<i2 then
|
|
return true
|
|
end
|
|
end
|
|
i1=i2;
|
|
end
|
|
return false;
|
|
end
|
|
|
|
-- COMPILATION
|
|
|
|
preprocess_code = function(script)
|
|
--[[ idea: in each local a = function (args) ... end insert counter like:
|
|
local a = function (args) counter() ... end
|
|
when counter exceeds limit exit with error
|
|
--]]
|
|
|
|
script="_ccounter = 0; " .. script;
|
|
|
|
local i1 -- process script to insert call counter in every function
|
|
local _increase_ccounter = " if _ccounter > " .. basic_robot.call_limit ..
|
|
" then error(\"Execution count \".. _ccounter .. \" exceeded ".. basic_robot.call_limit .. "\") end _ccounter = _ccounter + 1; "
|
|
|
|
|
|
local i1=0; local i2 = 0;
|
|
local found = true;
|
|
|
|
while (found) do -- PROCESS SCRIPT AND INSERT COUNTER AT PROBLEMATIC SPOTS
|
|
|
|
found = false;
|
|
i2 = nil;
|
|
|
|
-- i1 = where its looking
|
|
|
|
i2=string.find (script, "while ", i1) -- fix while OK
|
|
if i2 then
|
|
if not is_inside_string(i2,script) then
|
|
local i21 = i2;
|
|
i2=string.find(script, "do", i2);
|
|
if i2 then
|
|
script = script.sub(script,1, i2+1) .. _increase_ccounter .. script.sub(script, i2+2);
|
|
i1=i21+6; -- after while
|
|
found = true;
|
|
end
|
|
end
|
|
end
|
|
|
|
i2=string.find (script, "function", i1) -- fix functions
|
|
if i2 then
|
|
--minetest.chat_send_all("func0")
|
|
if not is_inside_string(i2,script) then
|
|
i2=string.find(script, ")", i2);
|
|
if i2 then
|
|
script = script.sub(script,1, i2) .. _increase_ccounter .. script.sub(script, i2+1);
|
|
i1=i2+string.len(_increase_ccounter);
|
|
found = true;
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
i2=string.find (script, "for ", i1) -- fix for OK
|
|
if i2 then
|
|
if not is_inside_string(i2,script) then
|
|
i2=string.find(script, "do", i2);
|
|
if i2 then
|
|
script = script.sub(script,1, i2+1) .. _increase_ccounter .. script.sub(script, i2+2);
|
|
i1=i2+string.len(_increase_ccounter);
|
|
found = true;
|
|
end
|
|
end
|
|
end
|
|
|
|
i2=string.find (script, "goto ", i1) -- fix goto OK
|
|
if i2 then
|
|
if not is_inside_string(i2,script) then
|
|
script = script.sub(script,1, i2-1) .. _increase_ccounter .. script.sub(script, i2);
|
|
i1=i2+string.len(_increase_ccounter)+5; -- insert + skip goto
|
|
found = true;
|
|
end
|
|
end
|
|
--minetest.chat_send_all("code rem " .. string.sub(script,i1))
|
|
|
|
end
|
|
|
|
return script
|
|
end
|
|
|
|
|
|
local function CompileCode ( script )
|
|
|
|
--minetest.chat_send_all(script)
|
|
--if true then return nil, "" end
|
|
|
|
local ScriptFunc, CompileError = loadstring( script )
|
|
if CompileError then
|
|
return nil, CompileError
|
|
end
|
|
return ScriptFunc, nil
|
|
end
|
|
|
|
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;
|
|
if basic_robot.data[name].authlevel<3 then -- not admin
|
|
err = check_code(script);
|
|
script = preprocess_code(script);
|
|
end
|
|
if err then return err end
|
|
|
|
local bytecode, err = CompileCode ( script );
|
|
if err then return err end
|
|
basic_robot.data[name].bytecode = bytecode;
|
|
return nil
|
|
end
|
|
|
|
basic_robot.commands.setCode=setCode; -- so we can use it
|
|
|
|
local function runSandbox( name)
|
|
|
|
local data = basic_robot.data[name]
|
|
local ScriptFunc = data.bytecode;
|
|
if not ScriptFunc then
|
|
return "Bytecode missing."
|
|
end
|
|
|
|
data.ccounter = 0;
|
|
data.operations = basic_robot.maxoperations;
|
|
|
|
setfenv( ScriptFunc, data.sandbox )
|
|
|
|
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 = basic_robot.entry_count;
|
|
if privs.robot then maxid = basic_robot.advanced_count 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 code = minetest.formspec_escape(meta:get_string("code"));
|
|
local form;
|
|
|
|
local id = meta:get_int("id");
|
|
|
|
if mode ~= 1 then -- when placed
|
|
|
|
form =
|
|
"size[9.5,8]" .. -- width, height
|
|
"textarea[1.25,-0.25;8.75,9.8;code;;".. code.."]"..
|
|
"button[-0.25,7.5;1.25,1;EDIT;EDIT]"..
|
|
"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 -- when robot clicked
|
|
form =
|
|
"size[9.5,8]" .. -- width, height
|
|
"textarea[1.25,-0.25;8.75,9.8;code;;".. code.."]"..
|
|
"button_exit[-0.25,-0.25;1.25,1;OK;SAVE]"..
|
|
"button[-0.25, 1.75;1.25,1;despawn;STOP]"..
|
|
"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
|
|
meta:set_string("formspec",form)
|
|
|
|
end
|
|
|
|
basic_robot.editor = {};
|
|
editor_get_lines = function(text,name)
|
|
local data = basic_robot.editor[name];
|
|
if not data then
|
|
basic_robot.editor[name] = {};
|
|
basic_robot.editor[name].lines = {};
|
|
basic_robot.editor[name].selection = 1;
|
|
data = basic_robot.editor[name];
|
|
else
|
|
data.lines = {};
|
|
end
|
|
|
|
local lines = data.lines;
|
|
for line in string.gmatch(text,"[^\r\n]+") do lines[#lines+1] = line end
|
|
end
|
|
|
|
|
|
code_edit_form = function(pos,name)
|
|
local lines = basic_robot.editor[name].lines;
|
|
local input = minetest.formspec_escape(basic_robot.editor[name].input or "");
|
|
local selection = basic_robot.editor[name].selection or 1;
|
|
|
|
local list = "";
|
|
for _,line in pairs(lines) do list = list .. minetest.formspec_escape(line) .. "," end
|
|
local form = "size[12,9.25]" .. "textlist[0,0;12,8;listname;" .. list .. ";"..selection..";false]" ..
|
|
"button[10,8;2,1;INSERT;INSERT LINE]" ..
|
|
"button[10,8.75;2,1;DELETE;DELETE LINE]" ..
|
|
"button_exit[2,8.75;2,1;SAVE;SAVE CODE]" ..
|
|
"button[0,8.75;2,1;UPDATE;UPDATE LINE]"..
|
|
"textarea[0.25,8;10,1;input;;".. input .. "]"
|
|
return form
|
|
end
|
|
|
|
|
|
|
|
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
|
|
|
|
-- check if admin robot
|
|
basic_robot.data[name].authlevel = self.authlevel or 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 ( name )
|
|
end
|
|
|
|
minetest.register_entity("basic_robot:robot",{
|
|
operations = basic_robot.maxoperations,
|
|
owner = "",
|
|
name = "",
|
|
hp_max = 100,
|
|
itemstring = "robot",
|
|
code = "",
|
|
timer = 0,
|
|
timestep = 1, -- run every 1 second
|
|
spawnpos = nil,
|
|
--visual="mesh",
|
|
--mesh = "char.obj", --this is good: aligned and rotated in blender - but how to move nametag up? now is stuck in head
|
|
--textures={"character.png"},
|
|
|
|
visual="cube",
|
|
textures={"arrow.png","basic_machine_side.png","face.png","basic_machine_side.png","basic_machine_side.png","basic_machine_side.png"},
|
|
|
|
visual_size={x=1,y=1},
|
|
running = 0, -- does it run code or is it idle?
|
|
collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5},
|
|
physical=true,
|
|
|
|
on_activate = function(self, staticdata)
|
|
|
|
-- reactivate robot
|
|
if staticdata~="" then
|
|
|
|
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.owner = data.owner;
|
|
self.authlevel = data.authlevel;
|
|
|
|
self.spawnpos = {x=data.spawnpos.x,y=data.spawnpos.y,z=data.spawnpos.z};
|
|
init_robot(self.object);
|
|
self.running = 1;
|
|
|
|
|
|
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")
|
|
self.object:remove();
|
|
end
|
|
|
|
return
|
|
end
|
|
|
|
-- lost robots
|
|
|
|
--if not self.spawnpos then self.object:remove() return end
|
|
|
|
end,
|
|
|
|
get_staticdata = function(self)
|
|
return self.name;
|
|
end,
|
|
|
|
on_punch = function (self, puncher, time_from_last_punch, tool_capabilities, dir)
|
|
|
|
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.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, -6<v<6, if v = 0 then stop\n"..
|
|
" turn.left(), turn.right(), turn.angle(45)\n"..
|
|
" dig.direction()\n"..
|
|
" place.direction(\"default:dirt\", optional orientation param)\n"..
|
|
" read_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,index) looks at node and returns false/true, direction can be self,\n"..
|
|
" if index>0 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, texture, is_entity) 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") |