414 lines
10 KiB
Lua
414 lines
10 KiB
Lua
-- CLIENTSIDE basic_robot by rnd, 2017
|
|
|
|
|
|
basic_robot = {};
|
|
basic_robot.version = "08/17/2017a";
|
|
basic_robot.data = {}; -- stores all robot data
|
|
basic_robot.data.rom = {}
|
|
|
|
basic_robot.commands = {};
|
|
timestep = 1; -- how often to run robot
|
|
running = 1; -- is robot running?
|
|
|
|
local mod_storage = minetest.get_mod_storage()
|
|
basic_robot.data.code = mod_storage:get_string("code") or ""; -- load code
|
|
|
|
--dofile(minetest.get_modpath("basic_robot").."/commands.lua")
|
|
|
|
|
|
-- SANDBOX for running lua code isolated and safely
|
|
|
|
function getSandboxEnv ()
|
|
|
|
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,
|
|
|
|
self = {
|
|
pos = function() return minetest.localplayer:get_pos() end,
|
|
name = function() return minetest.localplayer:get_name() end,
|
|
viewdir = function()
|
|
local player = minetest.localplayer;
|
|
local yaw = player:get_last_look_horizontal()
|
|
local pitch = player:get_last_look_vertical();
|
|
return {x=math.cos(yaw)*math.cos(pitch), y=math.sin(pitch), z=math.sin(yaw)*math.cos(pitch)}
|
|
end,
|
|
|
|
listen_msg = function()
|
|
local msg = basic_robot.data.listen_msg
|
|
basic_robot.data.listen_msg = nil
|
|
return msg
|
|
end,
|
|
|
|
sent_msg = function()
|
|
local msg = basic_robot.data.sent_msg
|
|
basic_robot.data.sent_msg = nil
|
|
return msg
|
|
end,
|
|
|
|
read_form = function()
|
|
local formname = basic_robot.data.formname;
|
|
if not formname then return end
|
|
local fields = basic_robot.data.fields;
|
|
basic_robot.data.formname = nil;
|
|
return formname,fields
|
|
end,
|
|
|
|
remove = function()
|
|
error("abort")
|
|
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)
|
|
return minetest.sound_play( sample,
|
|
{
|
|
gain = volume or 1,
|
|
})
|
|
end,
|
|
|
|
sound_stop = function(handle)
|
|
minetest.sound_stop(handle)
|
|
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,
|
|
-- },
|
|
|
|
say = function(text, toserver)
|
|
if toserver then
|
|
minetest.send_chat_message(text)
|
|
else
|
|
minetest.display_chat_message(text)
|
|
end
|
|
end,
|
|
|
|
|
|
code = { -- TODO
|
|
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].isadmin ~= 1 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.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,
|
|
tonumber = tonumber, pairs = pairs,
|
|
ipairs = ipairs, error = error, type=type,
|
|
minetest = minetest,
|
|
_G = _G,
|
|
|
|
};
|
|
return env
|
|
end
|
|
|
|
|
|
local function CompileCode ( script )
|
|
|
|
local ScriptFunc, CompileError = loadstring( script )
|
|
if CompileError then
|
|
return nil, CompileError
|
|
end
|
|
return ScriptFunc, nil
|
|
end
|
|
|
|
local function initSandbox()
|
|
basic_robot.data.sandbox = getSandboxEnv();
|
|
end
|
|
|
|
local function setCode(script) -- to run script: 1. initSandbox 2. setCode 3. runSandbox
|
|
local err;
|
|
local bytecode, err = CompileCode ( script );
|
|
if err then return err end
|
|
basic_robot.data.bytecode = bytecode;
|
|
return nil
|
|
end
|
|
|
|
basic_robot.commands.setCode=setCode; -- so we can use it
|
|
|
|
local function runSandbox( name)
|
|
|
|
local data = basic_robot.data;
|
|
local ScriptFunc = data.bytecode;
|
|
if not ScriptFunc then
|
|
return "Bytecode missing."
|
|
end
|
|
|
|
setfenv( ScriptFunc, data.sandbox )
|
|
|
|
local Result, RuntimeError = pcall( ScriptFunc )
|
|
if RuntimeError then
|
|
return RuntimeError
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
local robot_update_form = function ()
|
|
|
|
|
|
local code = minetest.formspec_escape(basic_robot.data.code) or "";
|
|
local form;
|
|
local id = 1;
|
|
|
|
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;START]"..
|
|
"button[-0.25, 0.75;1.25,1;despawn;STOP]"..
|
|
"button[-0.25, 1.75;1.25,1;help;help]"..
|
|
"button[-0.25, 4.75;1.25,1;save;SAVE]"
|
|
|
|
basic_robot.data.form = form;
|
|
end
|
|
|
|
|
|
local timer = 0;
|
|
minetest.register_globalstep(function(dtime)
|
|
timer=timer+dtime
|
|
if timer>timestep and running == 1 then
|
|
timer = 0;
|
|
local err = runSandbox();
|
|
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.display_chat_message("#ROBOT ERROR : " .. err)
|
|
end
|
|
running = 0; -- stop execution
|
|
end
|
|
return
|
|
end
|
|
|
|
return
|
|
end)
|
|
|
|
|
|
|
|
-- 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_formspec_input(
|
|
--minetest.register_on_player_receive_fields(
|
|
function(formname, fields)
|
|
local gui = robogui[formname];
|
|
if gui then --run gui
|
|
gui.response(formname,fields)
|
|
else -- collect data for robot
|
|
basic_robot.data.formname = formname;
|
|
basic_robot.data.fields = fields;
|
|
end
|
|
end
|
|
)
|
|
-- robogui GUI END ====================================================
|
|
|
|
|
|
|
|
--process forms from spawner
|
|
local on_receive_robot_form = function(formname, fields)
|
|
|
|
if fields.OK then
|
|
|
|
local code = fields.code or "";
|
|
basic_robot.data.code = code;
|
|
robot_update_form();
|
|
|
|
initSandbox();
|
|
|
|
local err = setCode(basic_robot.data.code);
|
|
if err then minetest.display_chat_message("#ROBOT CODE COMPILATION ERROR : " .. err); running = 0 return end
|
|
|
|
running = 1;
|
|
-- minetest.after(0, -- why this doesnt show??
|
|
-- function()
|
|
-- minetest.show_formspec("robot", basic_robot.data.form);
|
|
-- end
|
|
-- )
|
|
return
|
|
end
|
|
|
|
if fields.save then
|
|
local code = fields.code or "";
|
|
basic_robot.data.code = code;
|
|
robot_update_form();
|
|
mod_storage:set_string("code", basic_robot.data.code)
|
|
minetest.display_chat_message("#ROBOT: code saved in mod storage.")
|
|
return
|
|
end
|
|
|
|
if fields.help then
|
|
|
|
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"
|
|
|
|
text = minetest.formspec_escape(text);
|
|
|
|
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("robot_help", form);
|
|
|
|
return
|
|
end
|
|
|
|
if fields.despawn then
|
|
running = 0
|
|
return
|
|
end
|
|
|
|
end
|
|
|
|
robogui.register(
|
|
{
|
|
guiName = "robot",
|
|
response = on_receive_robot_form,
|
|
}
|
|
)
|
|
|
|
|
|
-- handle chats
|
|
|
|
minetest.register_on_receiving_chat_message(
|
|
--minetest.register_on_chat_message(
|
|
function(message)
|
|
local data = basic_robot.data;
|
|
|
|
if string.sub(message,1,1) == "!" then
|
|
data.listen_msg = string.sub(message,2);
|
|
return true
|
|
else
|
|
data.listen_msg = message;
|
|
return false
|
|
end
|
|
end
|
|
)
|
|
|
|
|
|
minetest.register_on_sending_chat_message(
|
|
function(message)
|
|
if string.sub(message,1,1) == "," then
|
|
basic_robot.data.sent_msg = string.sub(message,2)
|
|
return true
|
|
end
|
|
end
|
|
)
|
|
|
|
|
|
minetest.register_chatcommand("bot", {
|
|
description = "display robot gui, 0/1 pause/resume bot",
|
|
func = function(param)
|
|
if param == "0" then
|
|
minetest.display_chat_message("#ROBOT: paused.")
|
|
running = 0; return
|
|
elseif param == "1" then
|
|
initSandbox();
|
|
local err = setCode(basic_robot.data.code);
|
|
if err then minetest.display_chat_message("#ROBOT CODE COMPILATION ERROR : " .. err); running = 0 return end
|
|
running = 1;
|
|
minetest.display_chat_message("#ROBOT: started.")
|
|
return
|
|
end
|
|
robot_update_form(); local form = basic_robot.data.form;
|
|
minetest.show_formspec("robot", form)
|
|
end
|
|
})
|