commit 216bf4adf320f6b336ee198d94e6dbaa9371ad1a Author: rnd1 Date: Tue Nov 8 10:32:58 2016 +0100 init diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..9b2ee21 --- /dev/null +++ b/README.txt @@ -0,0 +1,21 @@ +BASIC_ROBOT: lightweight robot mod for multiplayer +minetest 0.4.14 +(c) 2016 rnd + +MANUAL: + 1. ingame help: right click spawner and click help button + +--------------------------------------------------------------------- +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +---------------------------------------------------------------------- \ No newline at end of file diff --git a/commands.lua b/commands.lua new file mode 100644 index 0000000..47ba88b --- /dev/null +++ b/commands.lua @@ -0,0 +1,68 @@ +basic_robot.commands = {}; + +local pi = math.pi; + +local function pos_in_dir(obj, dir) -- position after we move in specified direction + local yaw = obj:getyaw(); + local pos = obj:getpos(); + + if dir == 1 then + yaw = yaw + pi/2; + elseif dir == 2 then + yaw = yaw - pi/2; + elseif dir == 3 then + elseif dir == 4 then + yaw = yaw+pi; + elseif dir == 5 then + pos.y=pos.y+1 + elseif dir == 6 then + pos.y=pos.y-1 + end + + if dir<5 then + pos.x = pos.x+math.cos(yaw) + pos.z = pos.z+math.sin(yaw) + end + return pos +end + +basic_robot.commands.move = function(obj,dir) + local pos = pos_in_dir(obj, dir) + + if minetest.get_node(pos).name ~= "air" 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 + end + + obj:moveto(pos, true) +end + + +basic_robot.commands.turn = function (obj, angle) + local yaw = obj:getyaw()+angle; + obj:setyaw(yaw); +end + + +basic_robot.commands.dig = function(obj,dir) + local pos = pos_in_dir(obj, dir) + local luaent = obj:get_luaentity(); + if minetest.is_protected(pos,luaent.owner ) then return end + minetest.set_node(pos,{name = "air"}) +end + +basic_robot.commands.read_node = function(obj,dir) + local pos = pos_in_dir(obj, dir) + return minetest.get_node(pos).name or "" +end + + +basic_robot.commands.place = function(obj,nodename, dir) + 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 + minetest.set_node(pos,{name = nodename}) +end \ No newline at end of file diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..26da0e5 --- /dev/null +++ b/init.lua @@ -0,0 +1,443 @@ +-- basic_robot by rnd, 2016 +basic_robot = {}; + +basic_robot.data = {}; +--[[ +[name] = {sandbox= .., bytecode = ..., ram = ..., obj = robot object} +robot object = object of entity, used to manipulate movements and more +--]] + + + +dofile(minetest.get_modpath("basic_robot").."/commands.lua") + +-- SANDBOX for running lua code isolated and safely + +function getSandboxEnv (name) + local obj = basic_robot.data[name].obj; + local commands = basic_robot.commands; + return + { + pcall=pcall, + ram = basic_robot.data[name].ram, -- "ram" - used to store variables + move = { -- changes position of robot + left = function() commands.move(obj, 1) end, + right = function() commands.move(obj, 2) end, + forward = function() commands.move(obj, 3) end, + backward = function() commands.move(obj, 4) end, + up = function() commands.move(obj,5) end, + down = function() commands.move(obj,6) end, + }, + + turn = { + left = function() commands.turn(obj,math.pi/2) end, + right = function() commands.turn(obj,-math.pi/2) end, + angle = function(angle) commands.turn(obj,angle*math.pi/180) end, + }, + + dig = { + left = function() commands.dig(obj, 1) end, + right = function() commands.dig(obj, 2) end, + forward = function() commands.dig(obj, 3) end, + backward = function() commands.dig(obj, 4) end, + down = function() commands.dig(obj, 6) end, + up = function() commands.dig(obj, 5) end, + }, + + place = { + left = function(nodename) commands.place(obj, nodename, 1) end, + right = function(nodename) commands.place(obj,nodename, 2) end, + forward = function(nodename) commands.place(obj,nodename, 3) end, + backward = function(nodename) commands.place(obj,nodename, 4) end, + down = function(nodename) commands.place(obj,nodename, 6) end, + up = function(nodename) commands.place(obj,nodename, 5) end, + }, + + insert = { -- insert item from inventory into another inventory TODO + forward = function(item, inventory) robot_insert(obj, item, inventory,1) end, + backward = function(item, inventory) robot_insert(obj, item, inventory,2) end, + down = function(item, inventory) robot_insert(obj, item, inventory,3) end, + up = function(item, inventory) robot_insert(obj, item, inventory,4) end, + }, + + take = {}, -- take item from inventory TODO + + selfpos = function() return obj:getpos() end, + + find_nodes = + function(nodename,r) + if r>8 then return false end + return (minetest.find_node_near(obj:getpos(), r, nodename)~=nil) + end, -- in radius around position + + + read_node = { -- returns node name + left = function() return commands.read_node(obj, 1) end, + right = function() return commands.read_node(obj, 2) end, + forward = function() return commands.read_node(obj, 3) end, + backward = function() return commands.read_node(obj, 4) end, + down = function() return commands.read_node(obj, 6) end, + up = function() return commands.read_node(obj, 5) end, + }, + + say = function(text) + minetest.chat_send_all(" " .. text) + end, + + + string = { + byte = string.byte, char = string.char, + find = string.find, + format = string.format, gsub = string.gsub, + 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, + + }, + } +end + + +local function check_code(code) + + local bad_code = {"while ", "for ", "do ", "repeat ", "until ", "goto "} + + for _, v in pairs(bad_code) do + if string.find(code, v) then + return v .. " is not allowed!"; + end + end + +end + +local function CompileCode ( script ) + 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; + err = check_code(script); + 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 + + +local function runSandbox( name) + + local ScriptFunc = basic_robot.data[name].bytecode; + if not ScriptFunc then + return "Bytecode missing." + end + + setfenv( ScriptFunc, basic_robot.data[name].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") +-- /spawnentity basic_robot:robot + +-- TODO.. display form when right click robot +local function update_formspec_robot(self) + +end + +minetest.register_entity("basic_robot:robot",{ + energy = 1, + owner = "", + hp_max = 10, + code = "", + timer = 0, + timestep = 1, + spawnpos = "", + visual="cube", + visual_size={x=1,y=1}, + running = 0, + collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5}, + physical=true, + + textures={"arrow.png","basic_machine_side.png","face.png","basic_machine_side.png","basic_machine_side.png","basic_machine_side.png"}, + + on_activate = function(self, staticdata) + + -- how to make it remember owner when it reactivates ?? staticdata seems to be empty + + if staticdata~="" then -- reactivate robot + + self.owner = staticdata; -- remember its owner + if not basic_robot.data[self.owner] then + self.object:remove(); + minetest.chat_send_player(self.owner, "#ROBOT INIT: error. spawn robot again.") + return; + end + + basic_robot.data[self.owner].obj = self.object; + initSandbox ( self.owner ) + self.running = 1; + + return + end + + -- init robot + minetest.after(0, + function() + if not basic_robot.data[self.owner] then + basic_robot.data[self.owner] = {}; + end + basic_robot.data[self.owner].obj = self.object; + + initSandbox ( self.owner ) + local err = setCode( self.owner, self.code ); + if err then + minetest.chat_send_player(self.owner,"#ROBOT CODE COMPILATION ERROR : " .. err) + self.running = 0; -- stop execution + end + + self.running = 1 + end + ) + + self.object:setyaw(0); + + end, + + get_staticdata = function(self) + return self.owner; + 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 ~= 0 then + self.timer = 0; + local err = runSandbox(self.owner); + if err then + minetest.chat_send_player(self.owner,"#ROBOT ERROR : " .. err) + self.running = 0; -- stop execution + end + + return + end + + return + end, + + on_rightclick = function(self, clicker) + -- TODO + end, +}) + + +local robot_spawner_update_form = function (pos) + + local meta = minetest.get_meta(pos); + local x0,y0,z0; + x0=meta:get_int("x0");y0=meta:get_int("y0");z0=meta:get_int("z0"); -- direction of velocity + local code = minetest.formspec_escape(meta:get_string("code")); + + local form = + "size[8,6]" .. -- width, height + "textarea[0.1,0.75;8.4,6.5;code;code;".. code.."]".. + "button_exit[-0.25,-0.25;2,1;spawn;SPAWN]".. + "button[1.75,-0.25;2,1;despawn;REMOVE]".. + "button[5.25,-0.25;1,1;help;help]".. + "button_exit[6.25,-0.25;1,1;reset;reset]".. + "button_exit[7.25,-0.25;1,1;OK;OK]"; + + meta:set_string("formspec",form); + +end + +local spawn_robot = function(pos,node,ttl) + if ttl<0 then return end + + local meta = minetest.get_meta(pos); + 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") + -- if robot already exists do nothing + if basic_robot.data[owner] and basic_robot.data[owner].obj then + if basic_robot.data[owner].obj:getpos() then + minetest.chat_send_player(owner,"#ROBOT ERROR : robot already active") + return + end + end + + local obj = minetest.add_entity(pos,"basic_robot:robot"); + local luaent = obj:get_luaentity(); + luaent.owner = meta:get_string("owner"); + luaent.code = meta:get_string("code"); + luaent.spawnpos = minetest.pos_to_string({x=pos.x,y=pos.y-1,z=pos.z}); +end + +minetest.register_node("basic_robot:spawner", { + description = "Spawns robot", + tiles = {"cpu.png"}, + groups = {oddly_breakable_by_hand=2,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) + meta:set_string("owner", placer:get_player_name()); + local privs = minetest.get_player_privs(placer:get_player_name()); if privs.privs then meta:set_int("admin",1) end + + meta:set_string("code",""); + robot_spawner_update_form(pos); + + end, + + mesecons = {effector = { + action_on = spawn_robot + } + }, + + on_receive_fields = function(pos, formname, fields, sender) + + local name = sender:get_player_name(); + if minetest.is_protected(pos,name) then return end + + if fields.reset then + local meta = minetest.get_meta(pos); + meta:set_string("code",""); + robot_spawner_update_form(pos); + return + end + + if fields.OK then + local privs = minetest.get_player_privs(sender:get_player_name()); + local meta = minetest.get_meta(pos); + --minetest.chat_send_all("form at " .. dump(pos) .. " fields " .. dump(fields)) + + if fields.code then + local code = fields.code or ""; + meta:set_string("code", code) + end + + robot_spawner_update_form(pos); + return + end + + 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".. + "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".. + "turn.left(), turn.right(), turn.angle(45)\n".. + "dig.direction(), place.direction(\"default:dirt\")\nread_node.direction() tells you names of nodes\n".. + "find_nodes(3,\"default:dirt\") 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".. + "say(\"hello\") will speak"; + + + text = minetest.formspec_escape(text); + + local form = "size [8,7] textarea[0,0;8.5,8.5;help;HELP;".. text.."]" + 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"); + + if not basic_robot.data[owner] then return end + if basic_robot.data[owner].obj then + basic_robot.data[owner].obj:remove(); + end + return + end + + end, + +}) + + +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"} + } +}) \ No newline at end of file diff --git a/textures/arrow.png b/textures/arrow.png new file mode 100644 index 0000000..5bb1116 Binary files /dev/null and b/textures/arrow.png differ diff --git a/textures/basic_machine_side.png b/textures/basic_machine_side.png new file mode 100644 index 0000000..b94a7e7 Binary files /dev/null and b/textures/basic_machine_side.png differ diff --git a/textures/cpu.png b/textures/cpu.png new file mode 100644 index 0000000..d178edb Binary files /dev/null and b/textures/cpu.png differ diff --git a/textures/face.png b/textures/face.png new file mode 100644 index 0000000..7ec5976 Binary files /dev/null and b/textures/face.png differ