From 082eecf618b76a53cb94a40f64b514d26fdba85d Mon Sep 17 00:00:00 2001 From: rnd1 Date: Mon, 14 Nov 2016 17:20:04 +0100 Subject: [PATCH] books, setcode, can listen to chat --- commands.lua | 169 +++++++++++++++++++++++++++++++--- init.lua | 256 ++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 373 insertions(+), 52 deletions(-) diff --git a/commands.lua b/commands.lua index dc15859..4e2f45e 100644 --- a/commands.lua +++ b/commands.lua @@ -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 \ No newline at end of file + 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 + diff --git a/init.lua b/init.lua index 9a5d99a..4595ab2 100644 --- a/init.lua +++ b/init.lua @@ -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(" " .. text) + say = function(text) + if not basic_robot.data[name].quiet_mode then + minetest.chat_send_all(" " .. text) + if not basic_robot.data[name].allow_spam then + basic_robot.data[name].quiet_mode=true + end + else + minetest.chat_send_player(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