diff --git a/sl_robot/base.lua b/sl_robot/base.lua new file mode 100644 index 0000000..4bef2ab --- /dev/null +++ b/sl_robot/base.lua @@ -0,0 +1,452 @@ +--[[ + + sl_robot + ======== + + Copyright (C) 2018 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + base.lua: + +]]-- + +local FUEL_AMOUNT = 1000000 + +local sHELP = [[SaferLua Robot + + The SaferLua Robot can be programmed in Lua by + means of this Robot Base. + The Robot needs Bio Fluel to operate. + +]] + +local Cache = {} + +local tCommands = {} +local tFunctions = {" Overview", " Data structures"} +local tHelpTexts = {[" Overview"] = sHELP, [" Data structures"] = safer_lua.DataStructHelp} +local sFunctionList = "" +local tFunctionIndex = {} + +minetest.after(2, function() + sFunctionList = table.concat(tFunctions, ",") + for idx,key in ipairs(tFunctions) do + tFunctionIndex[key] = idx + end +end) + +local function output(pos, text) + local meta = minetest.get_meta(pos) + text = meta:get_string("output") .. "\n" .. (text or "") + text = text:sub(-500,-1) + meta:set_string("output", text) +end + +-- +-- API functions for function/action registrations +-- +function sl_robot.register_action(key, attr) + tCommands[key] = attr.cmnd + table.insert(tFunctions, " $"..key) + tHelpTexts[" $"..key] = attr.help +end + +local function merge(dest, keys, values) + for idx,key in ipairs(keys) do + dest.env[key] = values[idx] + end + return dest +end + +sl_robot.register_action("print", { + cmnd = function(self, text1, text2, text3) + local pos = self.meta.pos + text1 = tostring(text1 or "") + text2 = tostring(text2 or "") + text3 = tostring(text3 or "") + output(pos, text1..text2..text3) + end, + help = " $print(text,...)\n".. + " Send a text line to the output window.\n".. + " The function accepts up to 3 text strings\n".. + ' e.g. $print("Hello ", name, " !")' +}) + + +local function allow_metadata_inventory_put(pos, listname, index, stack, player) + if minetest.is_protected(pos, player:get_player_name()) then + return 0 + end + local meta = minetest.get_meta(pos) + if meta:get_int("state") == tubelib.RUNNING then + return 0 + end + local inv = meta:get_inventory() + if listname == "main" then + return stack:get_count() + elseif listname == "fuel" and stack:get_name() == "tubelib_addons1:biofuel" then + return stack:get_count() + end + return 0 +end + +local function allow_metadata_inventory_take(pos, listname, index, stack, player) + if minetest.is_protected(pos, player:get_player_name()) then + return 0 + end + local meta = minetest.get_meta(pos) + if meta:get_int("state") == tubelib.RUNNING then + return 0 + end + return stack:get_count() +end + +local function formspec1(meta) + local running = meta:get_int("state") == tubelib.RUNNING + local cmnd = running and "stop;Stop" or "start;Start" + local init = meta:get_string("init") + local fuel = math.floor((meta:get_int("fuel") * 100.0) / FUEL_AMOUNT) + init = minetest.formspec_escape(init) + return "size[10,8]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "tabheader[0,0;tab;Inv,init,loop,outp,notes,help;1;;true]".. + "label[5.3,0.5;1]label[6.3,0.5;2]label[7.3,0.5;3]label[8.3,0.5;4]".. + "list[context;main;5,1;4,2;]".. + "label[5.3,3;5]label[6.3,3;6]label[7.3,3;7]label[8.3,3;8]".. + "list[context;fuel;1,1.7;1,1;]".. + "item_image[1,1.7;1,1;tubelib_addons1:biofuel]".. + "image[2,1.7;1,1;default_furnace_fire_bg.png^[lowpart:".. + (fuel)..":default_furnace_fire_fg.png]".. + "list[current_player;main;1,4;8,4;]".. + "listring[context;main]".. + "listring[current_player;main]" + +end + +local function formspec2(meta) + local running = meta:get_int("state") == tubelib.RUNNING + local cmnd = running and "stop;Stop" or "start;Start" + local init = meta:get_string("init") + init = minetest.formspec_escape(init) + return "size[10,8]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "tabheader[0,0;tab;Inv,init,loop,outp,notes,help;2;;true]".. + "textarea[0.3,0.2;10,8.3;init;function init();"..init.."]".. + "label[0,7.3;end]".. + "button_exit[4.4,7.5;1.8,1;cancel;Cancel]".. + "button[6.3,7.5;1.8,1;save;Save]".. + "button[8.2,7.5;1.8,1;"..cmnd.."]" +end + +local function formspec3(meta) + local running = meta:get_int("state") == tubelib.RUNNING + local cmnd = running and "stop;Stop" or "start;Start" + local loop = meta:get_string("loop") + loop = minetest.formspec_escape(loop) + return "size[10,8]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "tabheader[0,0;tab;Inv,init,loop,outp,notes,help;3;;true]".. + "textarea[0.3,0.2;10,8.3;loop;function loop(ticks, elapsed);"..loop.."]".. + "label[0,7.3;end]".. + "button_exit[4.4,7.5;1.8,1;cancel;Cancel]".. + "button[6.3,7.5;1.8,1;save;Save]".. + "button[8.2,7.5;1.8,1;"..cmnd.."]" +end + +local function formspec4(meta) + local running = meta:get_int("state") == tubelib.RUNNING + local cmnd = running and "stop;Stop" or "start;Start" + local output = meta:get_string("output") + output = minetest.formspec_escape(output) + return "size[10,8]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "tabheader[0,0;tab;Inv,init,loop,outp,notes,help;4;;true]".. + "textarea[0.3,0.2;10,8.3;help;Output:;"..output.."]".. + "button[4.4,7.5;1.8,1;clear;Clear]".. + "button[6.3,7.5;1.8,1;update;Update]".. + "button[8.2,7.5;1.8,1;"..cmnd.."]" +end + +local function formspec5(meta) + local notes = meta:get_string("notes") + notes = minetest.formspec_escape(notes) + return "size[10,8]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "tabheader[0,0;tab;Inv,init,loop,outp,notes,help;5;;true]".. + "textarea[0.3,0.2;10,8.3;notes;Notepad:;"..notes.."]".. + "button_exit[6.3,7.5;1.8,1;cancel;Cancel]".. + "button[8.2,7.5;1.8,1;save;Save]" +end + +local function formspec6(items, pos, text) + text = minetest.formspec_escape(text) + return "size[10,8]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "tabheader[0,0;tab;Inv,init,loop,outp,notes,help;6;;true]".. + "label[0,-0.2;Functions:]".. + "dropdown[0.3,0.2;10,8.3;functions;"..items..";"..pos.."]".. + "textarea[0.3,1.3;10,8;help;Help:;"..text.."]" +end + +local function error(pos, err) + output(pos, err) + local meta = minetest.get_meta(pos) + local number = meta:get_string("number") + meta:set_string("formspec", formspec4(meta)) + meta:set_string("infotext", "Robot Base "..number..": error") + meta:set_int("state", tubelib.STOPPED) + minetest.get_node_timer(pos):stop() + local robot_pos = minetest.string_to_pos(meta:get_string("robot_pos")) + minetest.sound_play('sl_robot_error', {pos = robot_pos}) + return false +end + +-- check the fuel level and return false if empty +local function check_fuel(pos, meta) + local fuel = meta:get_int("fuel") + if fuel <= 0 then + if tubelib.get_this_item(meta, "fuel", 1) == nil then + return false + end + fuel = FUEL_AMOUNT + end + meta:set_int("fuel", fuel) + return true +end + +local function reset_robot(pos, meta) + print("robot_pos", meta:get_string("robot_pos")) + local robot_pos = minetest.string_to_pos(meta:get_string("robot_pos")) + if robot_pos then + sl_robot.remove_robot(robot_pos) + end + local param2 = (minetest.get_node(pos).param2 + 1) % 4 + robot_pos = sl_robot.new_pos(pos, param2, 1) + local pos_below = {x=robot_pos.x, y=robot_pos.y-1, z=robot_pos.z} + + meta:set_string("robot_pos", minetest.pos_to_string(robot_pos)) + meta:set_int("robot_param2", param2) + sl_robot.place_robot(robot_pos, pos_below, param2, nil) +end + + +local function compile(pos, meta, number) + local init = meta:get_string("init") + local loop = meta:get_string("loop") + local owner = meta:get_string("owner") + local env = table.copy(tCommands) + reset_robot(pos, meta) + env.meta = {pos=pos, owner=owner, number=number, error=error} + local co, code = safer_lua.co_create(pos, init, loop, env, error) + + if co then + Cache[number] = {code=code, co=co} + return true + end + return false +end + +local function start_robot(pos) + local meta = minetest.get_meta(pos) + local number = meta:get_string("number") + + meta:set_string("output", "") + + if not check_fuel(pos, meta) then + local number = meta:get_string("number") + meta:set_string("infotext", "Robot Base "..number..": no fuel") + return false + end + if compile(pos, meta, number) then + meta:set_int("state", tubelib.RUNNING) + meta:set_string("formspec", formspec4(meta)) + minetest.get_node_timer(pos):start(1) + meta:set_string("infotext", "Robot Base "..number..": running") + return true + end + return false +end + +local function stop_robot(pos) + local meta = minetest.get_meta(pos) + local number = meta:get_string("number") + local robot_pos = minetest.string_to_pos(meta:get_string("robot_pos")) + meta:set_int("state", tubelib.STOPPED) + minetest.get_node_timer(pos):stop() + meta:set_string("infotext", "Robot Base "..number..": stopped") + meta:set_string("formspec", formspec3(meta)) + if Cache[number] then + local code = Cache[number].code + local env = getfenv(code) + sl_robot.remove_robot(robot_pos) + end +end + +local function call_loop(pos, meta, elapsed) + local t = minetest.get_us_time() + local number = meta:get_string("number") + if Cache[number] or compile(pos, meta, number) then + local cpu = meta:get_int("cpu") or 0 + local code = Cache[number].code + local co = Cache[number].co + local res = safer_lua.co_resume(pos, co, code, error) + if res then + t = minetest.get_us_time() - t + cpu = math.floor(((cpu * 20) + t) / 21) + meta:set_int("cpu", cpu) + local robot_pos = meta:get_string("robot_pos") + meta:set_string("infotext", "Robot Base "..number..": running ("..cpu.."us) "..robot_pos) + meta:set_int("fuel", meta:get_int("fuel") - t) + end + return res + end + return false +end + +local function on_timer(pos, elapsed) + local meta = minetest.get_meta(pos) + + --so some maintenance every 10 cycles + local ticks = (meta:get_int("ticks") or 0) + 1 + meta:set_int("ticks", ticks) + if (ticks % 100) == 0 then + if not check_fuel(pos, meta) then + local number = meta:get_string("number") + meta:set_string("infotext", "Robot Base "..number..": no fuel") + return false + end + end + return call_loop(pos, meta, elapsed) +end + +local function on_receive_fields(pos, formname, fields, player) + if minetest.is_protected(pos, player:get_player_name()) then + return + end + local meta = minetest.get_meta(pos) + + --print(dump(fields)) + if fields.cancel == nil then + if fields.init then + meta:set_string("init", fields.init) + meta:set_string("formspec", formspec2(meta)) + elseif fields.loop then + meta:set_string("loop", fields.loop) + meta:set_string("formspec", formspec3(meta)) + elseif fields.notes then + meta:set_string("notes", fields.notes) + meta:set_string("formspec", formspec5(meta)) + end + end + + if fields.update then + meta:set_string("formspec", formspec4(meta)) + elseif fields.clear then + meta:set_string("output", "") + meta:set_string("formspec", formspec4(meta)) + elseif fields.tab == "1" then + meta:set_string("formspec", formspec1(meta)) + elseif fields.tab == "2" then + meta:set_string("formspec", formspec2(meta)) + elseif fields.tab == "3" then + meta:set_string("formspec", formspec3(meta)) + elseif fields.tab == "4" then + meta:set_string("formspec", formspec4(meta)) + elseif fields.tab == "5" then + meta:set_string("formspec", formspec5(meta)) + elseif fields.tab == "6" then + meta:set_string("formspec", formspec6(sFunctionList, 1, sHELP)) + elseif fields.start == "Start" then + start_robot(pos) + minetest.log("action", player:get_player_name() .. + " starts the sl_robot at ".. minetest.pos_to_string(pos)) + elseif fields.stop == "Stop" then + stop_robot(pos) + elseif fields.functions then + local key = fields.functions + local text = tHelpTexts[key] or "" + local pos = tFunctionIndex[key] or 1 + meta:set_string("formspec", formspec6(sFunctionList, pos, text)) + end +end + +minetest.register_node("sl_robot:base", { + description = "SaferLua Robot Base", + stack_max = 1, + tiles = { + -- up, down, right, left, back, front + 'sl_robot_base_top.png', + 'sl_robot_base_top.png', + 'sl_robot_base_right.png', + 'sl_robot_base_left.png', + 'sl_robot_base_front.png', + 'sl_robot_base_front.png', + }, + + on_construct = function(pos) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + inv:set_size('main', 8) + inv:set_size('fuel', 1) + end, + + after_place_node = function(pos, placer) + local meta = minetest.get_meta(pos) + local number = tubelib.add_node(pos, "sl_robot:base") + meta:set_string("owner", placer:get_player_name()) + meta:set_string("number", number) + meta:set_int("state", tubelib.STOPPED) + meta:set_string("init", "-- called only once") + meta:set_string("loop", "-- called cyclically") + meta:set_string("notes", "For your notes / snippets") + meta:set_string("formspec", formspec1(meta)) + meta:set_string("infotext", "Robot Base "..number..": stopped") + end, + + on_receive_fields = on_receive_fields, + + on_dig = function(pos, node, puncher, pointed_thing) + if minetest.is_protected(pos, puncher:get_player_name()) then + return + end + local meta = minetest.get_meta(pos) + if meta:get_int("state") == tubelib.RUNNING then + return + end + minetest.node_dig(pos, node, puncher, pointed_thing) + tubelib.remove_node(pos) + end, + + on_timer = on_timer, + allow_metadata_inventory_put = allow_metadata_inventory_put, + allow_metadata_inventory_take = allow_metadata_inventory_take, + + paramtype = "light", + sunlight_propagates = true, + paramtype2 = "facedir", + is_ground_content = false, + groups = {cracky = 1}, + sounds = default.node_sound_metal_defaults(), +}) + + +--minetest.register_craft({ +-- type = "shapeless", +-- output = "sl_robot:robot", +-- recipe = {"smartline:controller"} +--}) + diff --git a/sl_robot/commands.lua b/sl_robot/commands.lua new file mode 100644 index 0000000..f05dce4 --- /dev/null +++ b/sl_robot/commands.lua @@ -0,0 +1,115 @@ +--[[ + + sl_robot + ======== + + Copyright (C) 2018 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + commands.lua: + + Register all robot commands + +]]-- + + +sl_robot.register_action("get_ms_time", { + cmnd = function(self) + return math.floor(minetest.get_us_time() / 1000) + end, + help = "$get_ms_time()\n".. + " returns time with millisecond precision." +}) + +sl_robot.register_action("forward", { + cmnd = function(self, steps) + steps = tonumber(steps or 1) + local idx = 1 + while idx <= steps do + local meta = minetest.get_meta(self.meta.pos) + local robot_pos = minetest.string_to_pos(meta:get_string("robot_pos")) + local robot_param2 = meta:get_int("robot_param2") + local new_pos = sl_robot.move_robot(robot_pos, robot_param2, 1) + if new_pos then -- not blocked? + if new_pos.y == robot_pos.y then -- forward move? + idx = idx + 1 + end + meta:set_string("robot_pos", minetest.pos_to_string(new_pos)) + else -- blocked + -- because of unloaded areas and the LBM replace blocked robots + --minetest.set_node(robot_pos, {name = "sl_robot:robot_dummy", param2 = robot_param2}) + end + coroutine.yield() + end + end, + help = "tbd" +}) + +sl_robot.register_action("left", { + cmnd = function(self) + local meta = minetest.get_meta(self.meta.pos) + local robot_pos = minetest.string_to_pos(meta:get_string("robot_pos")) + local robot_param2 = meta:get_int("robot_param2") + robot_param2 = sl_robot.turn_robot(robot_pos, robot_param2, "L") + meta:set_int("robot_param2", robot_param2) + coroutine.yield() + end, + help = "tbd" +}) + +sl_robot.register_action("right", { + cmnd = function(self) + local meta = minetest.get_meta(self.meta.pos) + local robot_pos = minetest.string_to_pos(meta:get_string("robot_pos")) + local robot_param2 = meta:get_int("robot_param2") + robot_param2 = sl_robot.turn_robot(robot_pos, robot_param2, "R") + meta:set_int("robot_param2", robot_param2) + coroutine.yield() + end, + help = "tbd" +}) + +sl_robot.register_action("up", { + cmnd = function(self) + local meta = minetest.get_meta(self.meta.pos) + local robot_pos = minetest.string_to_pos(meta:get_string("robot_pos")) + local robot_param2 = meta:get_int("robot_param2") + local new_pos + while true do + new_pos = sl_robot.robot_up(robot_pos, robot_param2) + if new_pos then break end + coroutine.yield() + end + meta:set_string("robot_pos", minetest.pos_to_string(new_pos)) + coroutine.yield() + end, + help = "tbd" +}) + +sl_robot.register_action("down", { + cmnd = function(self) + local meta = minetest.get_meta(self.meta.pos) + local robot_pos = minetest.string_to_pos(meta:get_string("robot_pos")) + local robot_param2 = meta:get_int("robot_param2") + while true do + new_pos = sl_robot.robot_down(robot_pos, robot_param2) + if new_pos then break end + coroutine.yield() + end + meta:set_string("robot_pos", minetest.pos_to_string(new_pos)) + coroutine.yield() + end, + help = "tbd" +}) + +sl_robot.register_action("stop", { + cmnd = function(self) + while true do + coroutine.yield() + end + end, + help = "tbd" +}) + diff --git a/sl_robot/depends.txt b/sl_robot/depends.txt new file mode 100644 index 0000000..bf0fc13 --- /dev/null +++ b/sl_robot/depends.txt @@ -0,0 +1,4 @@ +default +tubelib +safer_lua + diff --git a/sl_robot/description.txt b/sl_robot/description.txt new file mode 100644 index 0000000..05f8ab8 --- /dev/null +++ b/sl_robot/description.txt @@ -0,0 +1,2 @@ +SaferLua Robot - a simple robot to be programmed in LUA + diff --git a/sl_robot/init.lua b/sl_robot/init.lua new file mode 100644 index 0000000..2dd146b --- /dev/null +++ b/sl_robot/init.lua @@ -0,0 +1,18 @@ +--[[ + + sl_robot + ======== + + Copyright (C) 2018 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + +]]-- + +sl_robot = {} + +--dofile(minetest.get_modpath("sl_robot") .. "/config.lua") +dofile(minetest.get_modpath("sl_robot") .. "/robot.lua") +dofile(minetest.get_modpath("sl_robot") .. "/base.lua") +dofile(minetest.get_modpath("sl_robot") .. "/commands.lua") diff --git a/sl_robot/mod.conf b/sl_robot/mod.conf new file mode 100644 index 0000000..afaee5f --- /dev/null +++ b/sl_robot/mod.conf @@ -0,0 +1 @@ +name=sl_robot diff --git a/sl_robot/readme.md b/sl_robot/readme.md new file mode 100644 index 0000000..74b7708 --- /dev/null +++ b/sl_robot/readme.md @@ -0,0 +1,19 @@ +SaferLua Robot [sl_robot] +========================= + + +A tubelib compatible robot to be programmed in LUA + +The mod uses SaferLua [safer_lua] as Lua sandbox for safe and secure code execution. + + +### License +Copyright (C) 2018 Joachim Stolberg +Code: Licensed under the GNU LGPL version 2.1 or later. See LICENSE.txt + + +### Dependencies +tubelib, safer_lua + +### History +- 2018-07-06 v0.01 * first draft diff --git a/sl_robot/robot.lua b/sl_robot/robot.lua new file mode 100644 index 0000000..1e5dcc0 --- /dev/null +++ b/sl_robot/robot.lua @@ -0,0 +1,282 @@ +--[[ + + sl_robot + ======== + + Copyright (C) 2018 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + robot.lua: + +]]-- + +local Face2Dir = {[0]= + {x=0, y=0, z=1}, + {x=1, y=0, z=0}, + {x=0, y=0, z=-1}, + {x=-1, y=0, z=0}, + {x=0, y=-1, z=0}, + {x=0, y=1, z=0} +} + +function sl_robot.new_pos(pos, param2, step) + return vector.add(pos, vector.multiply(Face2Dir[param2], step)) +end + +-- use Voxel Manipulator to read the node +local function read_node_with_vm(pos) + local vm = VoxelManip() + local MinEdge, MaxEdge = vm:read_from_map(pos, pos) + local data = vm:get_data() + local area = VoxelArea:new({MinEdge = MinEdge, MaxEdge = MaxEdge}) + return { + name = minetest.get_name_from_content_id(data[area:index(pos.x, pos.y, pos.z)]) + } +end + +-- check is posA == air-like and posB == solid and no player around +local function check_pos(posA, posB) + local nodeA = minetest.get_node_or_nil(posA) or read_node_with_vm(posA) + local nodeB = minetest.get_node_or_nil(posB) or read_node_with_vm(posB) + if not minetest.registered_nodes[nodeA.name].walkable and + minetest.registered_nodes[nodeB.name].walkable then + local objects = minetest.get_objects_inside_radius(posA, 1) + if #objects ~= 0 then + minetest.sound_play('sl_robot_go_away', {pos = posA}) + return false + else + return true + end + end + return false +end + +function sl_robot.place_robot(pos1, pos2, param2, player_name) + if check_pos(pos1, pos2) then + minetest.set_node(pos1, {name = "sl_robot:robot", param2 = param2}) + end +end + +function sl_robot.remove_robot(pos) + local node = minetest.get_node(pos) + if node.name == "sl_robot:robot" or node.name == "sl_robot:robot_dummy" then + minetest.remove_node(pos) + local pos1 = {x=pos.x, y=pos.y-1, z=pos.z} + node = minetest.get_node(pos1) + if node.name == "sl_robot:robot_foot" or node.name == "sl_robot:robot_leg" then + minetest.remove_node(pos1) + pos1 = {x=pos.x, y=pos.y-2, z=pos.z} + node = minetest.get_node(pos1) + if node.name == "sl_robot:robot_foot" then + minetest.remove_node(pos1) + end + end + end +end + +-- Positions to check: +-- 3 +-- [R]1 +-- 4 2 +-- 5 +function sl_robot.move_robot(pos, param2, step) + local pos1 = sl_robot.new_pos(pos, param2, step) + local pos2 = {x=pos1.x, y=pos1.y-1, z=pos1.z} + local pos3 = {x=pos1.x, y=pos1.y+1, z=pos1.z} + local pos4 = {x=pos.x, y=pos.y-1, z=pos.z} + local pos5 = {x=pos.x, y=pos.y-2, z=pos.z} + local new_pos = nil + + if check_pos(pos1, pos2) then -- one step forward + new_pos = pos1 + elseif check_pos(pos3, pos1) then -- one step up + new_pos = {x=pos.x, y=pos.y+1, z=pos.z} + minetest.swap_node(pos, {name="sl_robot:robot_foot"}) + minetest.set_node(new_pos, {name="sl_robot:robot", param2=param2}) + minetest.sound_play('sl_robot_step', {pos = new_pos}) + return new_pos + elseif check_pos(pos1, pos4) then -- one step forward + new_pos = pos1 + elseif check_pos(pos4, pos5) then -- one step down + new_pos = pos4 + else + return nil -- blocked + end + local node4 = minetest.get_node(pos4) + if node4.name == "sl_robot:robot_foot" or node4.name == "sl_robot:robot_leg" then + minetest.remove_node(pos4) + local node5 = minetest.get_node(pos5) + if node5.name == "sl_robot:robot_foot" then + minetest.remove_node(pos5) + end + end + minetest.remove_node(pos) + minetest.set_node(new_pos, {name="sl_robot:robot", param2=param2}) + minetest.sound_play('sl_robot_step', {pos = new_pos}) + return new_pos +end + +function sl_robot.turn_robot(pos, param2, dir) + if dir == "R" then + param2 = (param2 + 1) % 4 + else + param2 = (param2 + 3) % 4 + end + minetest.swap_node(pos, {name="sl_robot:robot", param2=param2}) + minetest.sound_play('sl_robot_step', {pos = pos, gain = 0.6}) + return param2 +end + +-- Positions to check: +-- 1 +-- [R] +-- 2 +function sl_robot.robot_up(pos, param2) + local pos1 = {x=pos.x, y=pos.y+1, z=pos.z} + local pos2 = {x=pos.x, y=pos.y-1, z=pos.z} + if check_pos(pos1, pos2) then + local node = minetest.get_node(pos2) + if node.name == "sl_robot:robot_foot" then + minetest.swap_node(pos, {name="sl_robot:robot_leg"}) + else + minetest.swap_node(pos, {name="sl_robot:robot_foot"}) + end + minetest.set_node(pos1, {name="sl_robot:robot", param2=param2}) + minetest.sound_play('sl_robot_step', {pos = new_pos}) + return pos1 + end + return nil +end + +-- Positions to check: +-- [R] +-- 1 +-- 2 +function sl_robot.robot_down(pos, param2) + if dir == "U" then dir = 1 else dir = -1 end + local new_pos = {x=pos.x, y=pos.y-1, z=pos.z} + local new_pos_node = minetest.get_node(new_pos) + local objects = minetest.get_objects_inside_radius(new_pos, 1) + if #objects == 0 and new_pos_node.name == "sl_robot:robot_foot" or + new_pos_node.name == "sl_robot:robot_leg" then + minetest.remove_node(pos) + minetest.set_node(new_pos, {name="sl_robot:robot", param2=param2}) + minetest.sound_play('sl_robot_step', {pos = new_pos}) + return new_pos + end + pos.y = pos.y+1 + minetest.sound_play('sl_robot_go_away', {pos = pos, gain = 0.6}) + pos.y = pos.y-1 + return nil +end + + +minetest.register_node("sl_robot:robot", { + description = "SaferLua Robot", + -- up, down, right, left, back, front + tiles = { + "sl_robot_robot_top.png", + "sl_robot_robot_bottom.png", + "sl_robot_robot_right.png", + "sl_robot_robot_left.png", + "sl_robot_robot_front.png", + "sl_robot_robot_back.png", + + }, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + { -5/16, 3/16, -5/16, 5/16, 8/16, 5/16}, + { -3/16, 2/16, -3/16, 3/16, 3/16, 3/16}, + { -6/16, -7/16, -6/16, 6/16, 2/16, 6/16}, + { -6/16, -8/16, -3/16, 6/16, -7/16, 3/16}, + }, + }, + paramtype2 = "facedir", + is_ground_content = false, + groups = {crumbly=0, not_in_creative_inventory = 1}, + sounds = default.node_sound_metal_defaults(), +}) + +-- dummy robots are used as marker for stucked robots in unloaded areas +minetest.register_node("sl_robot:robot_dummy", { + description = "SaferLua Robot", + -- up, down, right, left, back, front + tiles = { + "sl_robot_robot_top.png^[opacity:127", + "sl_robot_robot_bottom.png^[opacity:127", + "sl_robot_robot_right.png^[opacity:127", + "sl_robot_robot_left.png^[opacity:127", + "sl_robot_robot_front.png^[opacity:127", + "sl_robot_robot_back.png^[opacity:127", + }, + drawtype = "nodebox", + use_texture_alpha = true, + node_box = { + type = "fixed", + fixed = { + { -5/16, 3/16, -5/16, 5/16, 8/16, 5/16}, + { -3/16, 2/16, -3/16, 3/16, 3/16, 3/16}, + { -6/16, -7/16, -6/16, 6/16, 2/16, 6/16}, + { -6/16, -8/16, -3/16, 6/16, -7/16, 3/16}, + }, + }, + paramtype2 = "facedir", + is_ground_content = false, + walkable = false, + drop = "", + groups = {cracky = 3}, + sounds = default.node_sound_metal_defaults(), +}) + +minetest.register_node("sl_robot:robot_leg", { + description = "SaferLua Robot", + tiles = {"sl_robot_robot.png^[transformR90]"}, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + { -1/8, -4/8, -1/8, 1/8, 4/8, 1/8}, + }, + }, + paramtype2 = "facedir", + is_ground_content = false, + groups = {crumbly=0, not_in_creative_inventory = 1}, + sounds = default.node_sound_metal_defaults(), +}) + +minetest.register_node("sl_robot:robot_foot", { + description = "SaferLua Robot", + tiles = {"sl_robot_robot.png^[transformR90]"}, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + { -1/8, -4/8, -1/8, 1/8, 4/8, 1/8}, + { -2/8, -4/8, -2/8, 2/8, -3/8, 2/8}, + }, + }, + paramtype2 = "facedir", + is_ground_content = false, + groups = {crumbly=0, not_in_creative_inventory = 1}, + sounds = default.node_sound_metal_defaults(), +}) + + +minetest.register_lbm({ + label = "[sl_robot] Remove Robots", + name = "sl_robot:update", + nodenames = {"sl_robot:robot", "sl_robot:robot_leg", "sl_robot:robot_foot"}, + run_at_every_load = true, + action = function(pos, node) + if node.name == "sl_robot:robot" then + minetest.swap_node(pos, {name="sl_robot:robot_dummy", param2 = node.param2}) + else + minetest.remove_node(pos) + end + end +}) + diff --git a/sl_robot/sounds/sl_robot_error.ogg b/sl_robot/sounds/sl_robot_error.ogg new file mode 100644 index 0000000..04fdd61 Binary files /dev/null and b/sl_robot/sounds/sl_robot_error.ogg differ diff --git a/sl_robot/sounds/sl_robot_go_away.ogg b/sl_robot/sounds/sl_robot_go_away.ogg new file mode 100644 index 0000000..2eb02d8 Binary files /dev/null and b/sl_robot/sounds/sl_robot_go_away.ogg differ diff --git a/sl_robot/sounds/sl_robot_step.ogg b/sl_robot/sounds/sl_robot_step.ogg new file mode 100644 index 0000000..0f426e5 Binary files /dev/null and b/sl_robot/sounds/sl_robot_step.ogg differ diff --git a/sl_robot/textures/sl_robot_base.png b/sl_robot/textures/sl_robot_base.png new file mode 100644 index 0000000..685e3cb Binary files /dev/null and b/sl_robot/textures/sl_robot_base.png differ diff --git a/sl_robot/textures/sl_robot_base_front.png b/sl_robot/textures/sl_robot_base_front.png new file mode 100644 index 0000000..13a5d6f Binary files /dev/null and b/sl_robot/textures/sl_robot_base_front.png differ diff --git a/sl_robot/textures/sl_robot_base_left.png b/sl_robot/textures/sl_robot_base_left.png new file mode 100644 index 0000000..91e0d77 Binary files /dev/null and b/sl_robot/textures/sl_robot_base_left.png differ diff --git a/sl_robot/textures/sl_robot_base_lid.png b/sl_robot/textures/sl_robot_base_lid.png new file mode 100644 index 0000000..0cc636c Binary files /dev/null and b/sl_robot/textures/sl_robot_base_lid.png differ diff --git a/sl_robot/textures/sl_robot_base_right.png b/sl_robot/textures/sl_robot_base_right.png new file mode 100644 index 0000000..da2df1e Binary files /dev/null and b/sl_robot/textures/sl_robot_base_right.png differ diff --git a/sl_robot/textures/sl_robot_base_top.png b/sl_robot/textures/sl_robot_base_top.png new file mode 100644 index 0000000..ea734d1 Binary files /dev/null and b/sl_robot/textures/sl_robot_base_top.png differ diff --git a/sl_robot/textures/sl_robot_robot.png b/sl_robot/textures/sl_robot_robot.png new file mode 100644 index 0000000..9e4d69c Binary files /dev/null and b/sl_robot/textures/sl_robot_robot.png differ diff --git a/sl_robot/textures/sl_robot_robot_back.png b/sl_robot/textures/sl_robot_robot_back.png new file mode 100644 index 0000000..97459b3 Binary files /dev/null and b/sl_robot/textures/sl_robot_robot_back.png differ diff --git a/sl_robot/textures/sl_robot_robot_bottom.png b/sl_robot/textures/sl_robot_robot_bottom.png new file mode 100644 index 0000000..5eba932 Binary files /dev/null and b/sl_robot/textures/sl_robot_robot_bottom.png differ diff --git a/sl_robot/textures/sl_robot_robot_front.png b/sl_robot/textures/sl_robot_robot_front.png new file mode 100644 index 0000000..9002d0f Binary files /dev/null and b/sl_robot/textures/sl_robot_robot_front.png differ diff --git a/sl_robot/textures/sl_robot_robot_left.png b/sl_robot/textures/sl_robot_robot_left.png new file mode 100644 index 0000000..46ccd2e Binary files /dev/null and b/sl_robot/textures/sl_robot_robot_left.png differ diff --git a/sl_robot/textures/sl_robot_robot_right.png b/sl_robot/textures/sl_robot_robot_right.png new file mode 100644 index 0000000..4ac7f31 Binary files /dev/null and b/sl_robot/textures/sl_robot_robot_right.png differ diff --git a/sl_robot/textures/sl_robot_robot_top.png b/sl_robot/textures/sl_robot_robot_top.png new file mode 100644 index 0000000..d7ae1f3 Binary files /dev/null and b/sl_robot/textures/sl_robot_robot_top.png differ