books, setcode, can listen to chat

master
rnd1 2016-11-14 17:20:04 +01:00
parent c51bf32abd
commit 082eecf618
2 changed files with 373 additions and 52 deletions

View File

@ -13,16 +13,19 @@ local function pos_in_dir(obj, dir) -- position after we move in specified direc
elseif dir == 3 then
elseif dir == 4 then
yaw = yaw+pi;
elseif dir == 5 then
elseif dir == 5 then -- up
pos.y=pos.y+1
elseif dir == 6 then
elseif dir == 6 then -- down
pos.y=pos.y-1
elseif dir == 7 then -- forward, down
pos.y=pos.y-1
end
if dir<5 then
if dir<5 or dir == 7 then -- left, right, back
pos.x = pos.x+math.cos(yaw)
pos.z = pos.z+math.sin(yaw)
end
return pos
end
@ -30,14 +33,27 @@ basic_robot.commands.move = function(name,dir)
local obj = basic_robot.data[name].obj;
local pos = pos_in_dir(obj, dir)
if minetest.get_node(pos).name ~= "air" then return end
-- can move through walkable nodes
if minetest.registered_nodes[minetest.get_node(pos).name].walkable then return end
-- up; no levitation!
if minetest.get_node({x=pos.x,y=pos.y-1,z=pos.z}).name == "air" and
minetest.get_node({x=pos.x,y=pos.y-2,z=pos.z}).name == "air" then
return
return false
end
obj:moveto(pos, true)
-- sit and stand up for model - doenst work for overwriten obj export
-- if dir == 5 then-- up
-- obj:set_animation({x=0,y=0})
-- elseif dir == 6 then -- down
-- obj:set_animation({x=81,y=160})
-- end
return true
end
basic_robot.commands.turn = function (name, angle)
@ -50,19 +66,91 @@ 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 end
if minetest.is_protected(pos,luaent.owner ) then return false end
local nodename = minetest.get_node(pos).name;
if nodename == "air" then return end
if nodename == "air" then return false end
local spos = obj:get_luaentity().spawnpos;
local inv = minetest.get_meta(spos):get_inventory();
if not inv then return end
inv:add_item("main",ItemStack( nodename ));
--DS
local sounds = minetest.registered_nodes[minetest.get_node(pos).name].sounds
if sounds then
local sound = sounds.dug
if sound then
minetest.sound_play(sound,{object=obj, max_hear_distance = 10})
end
end
minetest.set_node(pos,{name = "air"})
return true
end
basic_robot.commands.insert_item = function(name,item, inventory,dir)
local obj = basic_robot.data[name].obj;
local tpos = pos_in_dir(obj, dir); -- position of target block
local luaent = obj:get_luaentity();
if minetest.is_protected(tpos,luaent.owner ) then return false end
local pos = basic_robot.data[name].spawnpos; -- position of spawner block
local meta = minetest.get_meta(pos);
local tmeta = minetest.get_meta(tpos);
local inv = minetest.get_meta(pos):get_inventory();
local tinv = minetest.get_meta(tpos):get_inventory();
if not inventory then inventory = "main"; end
--if not inv then return end
local stack = ItemStack(item);
if (not inv:contains_item("main", stack) or not tinv:room_for_item(inventory, stack)) and meta:get_int("admin")~=1 then
return false
end
tinv:add_item(inventory,stack);
inv:remove_item("main", stack);
return true
end
basic_robot.commands.take_item = function(name,item, inventory,dir)
local obj = basic_robot.data[name].obj;
local tpos = pos_in_dir(obj, dir); -- position of target block
local luaent = obj:get_luaentity();
if minetest.is_protected(tpos,luaent.owner ) then return false end
local pos = basic_robot.data[name].spawnpos; -- position of spawner block
if basic_robot.bad_inventory_blocks[ minetest.get_node(tpos).name ] then return false end -- dont allow take from
local meta = minetest.get_meta(pos);
local tmeta = minetest.get_meta(tpos);
local inv = minetest.get_meta(pos):get_inventory();
local tinv = minetest.get_meta(tpos):get_inventory();
if not inventory then inventory = "main"; end
--if not inv then return end
local stack = ItemStack(item);
if (not tinv:contains_item(inventory, stack) or not inv:room_for_item("main", stack)) and meta:get_int("admin")~=1 then
return false
end
inv:add_item("main",stack);
tinv:remove_item(inventory, stack);
return true
end
basic_robot.commands.read_node = function(name,dir)
local obj = basic_robot.data[name].obj;
local pos = pos_in_dir(obj, dir)
@ -79,15 +167,74 @@ basic_robot.commands.place = function(name,nodename, 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 end
if minetest.get_node(pos).name~="air" then return end
if minetest.is_protected(pos,luaent.owner ) then return false end
if minetest.get_node(pos).name~="air" then return false end
local spos = obj:get_luaentity().spawnpos;
local meta = minetest.get_meta(spos);
local inv = meta:get_inventory();
if not inv then return end
if not inv then return false end
if not inv:contains_item("main", ItemStack(nodename)) and meta:get_int("admin")~=1 then return end
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})
end
end
minetest.set_node(pos,{name = nodename})
end
return true
end
basic_robot.commands.attack = function(name, target) -- attack range 4, damage 5
local reach = 4;
local damage = 5;
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
tplayer:set_hp(tplayer:get_hp()-damage)
return true
end
basic_robot.commands.read_book = function (itemstack) -- itemstack should contain book
local data = minetest.deserialize(itemstack:get_metadata())
if data then
return data.text;
else
return nil
end
end
basic_robot.commands.write_book = function(name,text) -- returns itemstack containing book
local lpp = 14;
local new_stack = ItemStack("default:book_written")
local data = {}
data.title = "program book"
data.text = text
data.text_len = #data.text
data.page = 1
data.page_max = math.ceil((#data.text:gsub("[^\n]", "") + 1) / lpp)
data.owner = name
local data_str = minetest.serialize(data)
new_stack:set_metadata(data_str);
return new_stack;
end

256
init.lua
View File

@ -3,8 +3,15 @@
basic_robot = {};
basic_robot.call_limit = 32; -- how many execution calls per script execution allowed
basic_robot.bad_inventory_blocks = {
["craft_guide:sign_wall"] = true,
}
basic_robot.data = {};
basic_robot.data.listening = {}; -- which robots listen to chat
--[[
[name] = {sandbox= .., bytecode = ..., ram = ..., obj = robot object,spawnpos=...}
robot object = object of entity, used to manipulate movements and more
@ -37,33 +44,65 @@ function getSandboxEnv (name)
},
dig = {
left = function() commands.dig(name,1) end,
right = function() commands.dig(name,2) end,
forward = function() commands.dig(name,3) end,
backward = function() commands.dig(name,4) end,
down = function() commands.dig(name,6) end,
up = function() commands.dig(name,5) end,
left = function() return commands.dig(name,1) end,
right = function() return commands.dig(name,2) end,
forward = function() return commands.dig(name,3) end,
backward = function() return commands.dig(name,4) end,
down = function() return commands.dig(name,6) end,
up = function() return commands.dig(name,5) end,
forward_down = function() return commands.dig(name,7) end,
},
place = {
left = function(nodename) commands.place(name,nodename, 1) end,
right = function(nodename) commands.place(name,nodename, 2) end,
forward = function(nodename) commands.place(name,nodename, 3) end,
backward = function(nodename) commands.place(name,nodename, 4) end,
down = function(nodename) commands.place(name,nodename, 6) end,
up = function(nodename) commands.place(name,nodename, 5) end,
left = function(nodename) return commands.place(name,nodename, 1) end,
right = function(nodename) return commands.place(name,nodename, 2) end,
forward = function(nodename) return commands.place(name,nodename, 3) end,
backward = function(nodename) return commands.place(name,nodename, 4) end,
down = function(nodename) return commands.place(name,nodename, 6) end,
up = function(nodename) return commands.place(name,nodename, 5) end,
forward_down = function(nodename) return commands.place(name,nodename, 7) end,
},
insert = { -- insert item from inventory into another inventory TODO
forward = function(item, inventory) robot_insert(name,item, inventory,1) end,
backward = function(item, inventory) robot_insert(name,item, inventory,2) end,
down = function(item, inventory) robot_insert(name,item, inventory,3) end,
up = function(item, inventory) robot_insert(name,item, inventory,4) end,
insert = { -- insert item from robot inventory into another inventory
left = function(item, inventory) commands.insert_item(name,item, inventory,1) end,
right = function(item, inventory) commands.insert_item(name,item, inventory,2) end,
forward = function(item, inventory) commands.insert_item(name,item, inventory,3) end,
backward = function(item, inventory) commands.insert_item(name,item, inventory,4) end,
down = function(item, inventory) commands.insert_item(name,item, inventory,6) end,
up = function(item, inventory) commands.insert_item(name,item, inventory,5) end,
},
take = {}, -- take item from inventory TODO
take = { -- takes item from inventory and puts it in robot inventory
left = function(item, inventory) commands.take_item(name,item, inventory,1) end,
right = function(item, inventory) commands.take_item(name,item, inventory,2) end,
forward = function(item, inventory) commands.take_item(name,item, inventory,3) end,
backward = function(item, inventory) commands.take_item(name,item, inventory,4) end,
down = function(item, inventory) commands.take_item(name,item, inventory,6) end,
up = function(item, inventory) commands.take_item(name,item, inventory,5) end,
}, -- take item from inventory TODO
selfpos = function() return basic_robot.data[name].obj:getpos() end,
self = {
pos = function() return basic_robot.data[name].obj:getpos() end,
spawnpos = function() return basic_robot.data[name].spawnpos end,
viewdir = function() local yaw = basic_robot.data[name].obj:getyaw(); return {x=math.cos(yaw), y = 0, z=math.sin(yaw)} end,
listen = function (mode)
if mode == 1 then
basic_robot.data.listening[name] = true
else
basic_robot.data.listening[name] = nil
end
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,
},
find_nodes =
function(nodename,r)
@ -71,6 +110,25 @@ function getSandboxEnv (name)
return (minetest.find_node_near(basic_robot.data[name].obj:getpos(), r, nodename)~=nil)
end, -- in radius around position
find_player =
function(r)
if r>8 then return false end
local objects = minetest.get_objects_inside_radius(basic_robot.data[name].obj:getpos(), r);
for _,obj in pairs(objects) do
if obj:is_player() then return obj:get_player_name() end
end
return false
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,
},
attack = function(target) return basic_robot.commands.attack(name,target) end, -- attack player if nearby
read_node = { -- returns node name
left = function() return commands.read_node(name,1) end,
@ -79,6 +137,7 @@ function getSandboxEnv (name)
backward = function() return commands.read_node(name,4) end,
down = function() return commands.read_node(name,6) end,
up = function() return commands.read_node(name,5) end,
forward_down = function() return commands.read_node(name,7) end,
},
read_text = { -- returns node name
@ -90,10 +149,76 @@ function getSandboxEnv (name)
up = function() return commands.read_text(name,5) end,
},
say = function(text)
minetest.chat_send_all("<robot ".. name .. "> " .. text)
say = function(text)
if not basic_robot.data[name].quiet_mode 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(name,"<robot ".. name .. "> " .. text)
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,
fire = function(speed, pitch,gravity) -- experimental: fires an projectile
local obj = basic_robot.data[name].obj;
local pos = obj:getpos();
local yaw = obj:getyaw();
-- fire particle
minetest.add_particle(
{
pos = pos,
expirationtime = 10,
velocity = {x=speed*math.cos(yaw)*math.cos(pitch), y=speed*math.sin(pitch),z=speed*math.sin(yaw)*math.cos(pitch)},
size = 5,
texture = "default_apple.png",
acceleration = {x=0,y=-gravity,z=0},
collisiondetection = true,
collision_removal = true,
}
);
end,
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);
if itemstack then
return commands.read_book(itemstack);
else
return nil
end
end,
write = function(i,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);
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
},
string = {
byte = string.byte, char = string.char,
@ -133,6 +258,10 @@ function getSandboxEnv (name)
time = os.time,
},
tonumber = tonumber,
pairs = pairs,
ipairs = ipairs,
error = error,
debug = debug,
@ -241,6 +370,7 @@ local function setCode( name, script ) -- to run script: 1. initSandbox 2. setCo
return nil
end
basic_robot.commands.setCode=setCode; -- so we can use it
local function runSandbox( name)
@ -283,22 +413,23 @@ local robot_spawner_update_form = function (pos, mode)
if mode ~= 1 then
form =
"size[8,6]" .. -- width, height
"textarea[0.1,0.75;8.4,6.5;code;code;".. code.."]"..
"button_exit[-0.25,-0.25;1.5,1;spawn;START]"..
"button[1.25,-0.25;1.5,1;despawn;STOP]"..
"button[2.75,-0.25;1.5,1;inventory;inventory]"..
"button[5.25,-0.25;1,1;help;help]"..
"button_exit[6.25,-0.25;1,1;reset;CLEAR]"..
"button_exit[7.25,-0.25;1,1;OK;SAVE]";
"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]"..
"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
form =
"size[8,6]" .. -- width, height
"textarea[0.1,0.75;8.4,6.5;code;code;".. code.."]"..
"button[1.25,-0.25;1.5,1;despawn;STOP]"..
"button[2.75,-0.25;1.5,1;inventory;inventory]"..
"button[5.25,-0.25;1,1;help;help]"..
"button_exit[7.25,-0.25;1,1;OK;save]";
"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[-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
@ -307,7 +438,12 @@ local robot_spawner_update_form = function (pos, mode)
end
local function init_robot(self)
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;
self.object:set_properties({infotext = "robot " .. self.owner});
self.object:set_properties({nametag = "robot " .. self.owner,nametag_color = "LawnGreen"});
self.object:set_armor_groups({fleshy=0})
@ -324,8 +460,9 @@ minetest.register_entity("basic_robot:robot",{
timestep = 1, -- run every 1 second
spawnpos = nil,
--visual="mesh",
--mesh = "character.b3d",
--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"},
@ -517,15 +654,31 @@ local on_receive_robot_form = function(pos, formname, fields, sender)
if fields.help then
local text = "BASIC LUA SYNTAX\n \nif CONDITION then BLOCK1 else BLOCK2 end \nmyTable1 = {1,2,3}, myTable2 = {[\"entry1\"]=5, [\"entry2\"]=1}\n"..
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"..
"\narrays: myTable1 = {1,2,3}, myTable2 = {[\"entry1\"]=5, [\"entry2\"]=1}\n"..
"access table entries with myTable1[1] or myTable2.entry1 or myTable2[\"entry1\"]\n"..
"\nROBOT COMMANDS\n\n"..
"move.direction(), where direction is forward, backward, left,right, up, down\n"..
"**MOVEMENT,DIGGING,INVENTORT 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(), place.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"..
"take.direction(item, inventory) takes item from target inventory into robot inventory\n"..
"read_text.direction() reads text of signs, chests and other blocks\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"..
"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"..
"selfpos() returns table {x=pos.x,y=pos.y,z=pos.z}\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"..
"self.pos() returns table {x=pos.x,y=pos.y,z=pos.z}\n"..
"self.spawnpos() returns position of spawner block\n"..
"self.viewdir() returns vector of view for robot\n"..
"self.listen(mode) (de)attaches chat listener to robot\n"..
"listen_msg() retrieves last chat message if robot listens\n"..
"player.getpos(name) return position of player, player.connected() returns list of players\n"..
"fire = function(speed, pitch,gravity) fires a projectile from robot\n"..
"say(\"hello\") will speak";
@ -564,6 +717,15 @@ local on_receive_robot_form = function(pos, formname, fields, sender)
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;]"..
"list[current_player;main;0,4.25;8,4;]";
minetest.show_formspec(sender:get_player_name(), "robot_inventory", form);
end
end
-- handle form when rightclicking robot entity
@ -587,6 +749,17 @@ minetest.register_on_player_receive_fields(
end
)
-- handle chats
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;
end
end
)
minetest.register_node("basic_robot:spawner", {
description = "Spawns robot",
@ -608,6 +781,7 @@ minetest.register_node("basic_robot:spawner", {
local inv = meta:get_inventory(); -- spawner inventory
inv:set_size("main",32);
inv:set_size("library",32);
end,
mesecons = {effector = {
@ -638,7 +812,7 @@ minetest.register_node("basic_robot:spawner", {
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") then return false end
if not meta:get_inventory():is_empty("main") or not meta:get_inventory():is_empty("library") then return false end
return true
end