diff --git a/init.lua b/init.lua index 96f0d86..fb1b2ac 100644 --- a/init.lua +++ b/init.lua @@ -227,6 +227,7 @@ else -- dofile(MP.."/lua_controller/controller.lua") -- dofile(MP.."/lua_controller/commands.lua") -- dofile(MP.."/lua_controller/server.lua") +-- dofile(MP.."/lua_controller/sensorchest.lua") -- Items dofile(MP.."/items/barrel.lua") diff --git a/lua_controller/commands.lua b/lua_controller/commands.lua new file mode 100644 index 0000000..315c4f2 --- /dev/null +++ b/lua_controller/commands.lua @@ -0,0 +1,235 @@ +--[[ + + sl_controller + ============= + + Copyright (C) 2018 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + commands.lua: + + Register all basic controller commands + +]]-- + +-- store protection data locally +local LocalRef = {} +local function not_protected(owner, numbers) + LocalRef[owner] = LocalRef[owner] or {} + if LocalRef[owner][numbers] == nil then + LocalRef[owner][numbers] = techage.check_numbers(numbers, owner) + end + return LocalRef[owner][numbers] +end + +techage.lua_ctlr.register_function("get_input", { + cmnd = function(self, num) + num = tostring(num or "") + return techage.lua_ctlr.get_input(self.meta.number, num) + end, + help = ' $get_input(num) --> "on", "off", or nil\n'.. + ' Read local input value from device with number "num".\n'.. + ' example: inp = $get_input("1234")\n'.. + " The device has to be connected with the controller." +}) + +techage.lua_ctlr.register_function("get_status", { + cmnd = function(self, num) + num = tostring(num or "") + return techage.send_single(self.meta.number, num, "state", nil) + end, + help = " $get_status(num) ,\n".. + " Read status string from a remote device.\n".. + ' example: sts = $get_status("1234")' +}) + +techage.lua_ctlr.register_function("get_player_action", { + cmnd = function(self, num) + num = tostring(num or "") + return unpack(techage.send_single(self.meta.number, num, "player_action", nil) or {"","",""}) + end, + help = " $get_player_action(num) ,\n".. + " Read player action status from a Sensor Chest.\n".. + ' example: player, action, item = $get_player_action("1234")' +}) + +--techage.lua_ctlr.register_function("get_counter", { +-- cmnd = function(self, num) +-- num = tostring(num or "") +-- return techage.send_single(self.meta.number, num, "counter", nil) +-- end, +-- help = " $get_counter(num)\n".. +-- " Read number of pushed items from a\n".. +-- " Pusher/Distributor node.\n".. +-- " The Pusher returns a single value (number)\n".. +-- " The Distributor returns a list with 4 values\n".. +-- " like: {red=1, green=0, blue=8, yellow=0}\n".. +-- ' example: cnt = $get_counter("1234")\n' +--}) + +--techage.lua_ctlr.register_function("clear_counter", { +-- cmnd = function(self, num) +-- num = tostring(num or "") +-- return techage.send_single(self.meta.number, num, "clear_counter", nil) +-- end, +-- help = " $clear_counter(num)\n".. +-- " Set counter(s) from Pusher/Distributor to zero.\n".. +-- ' example: $clear_counter("1234")' +--}) + +techage.lua_ctlr.register_function("get_fuel_value", { + cmnd = function(self, num) + num = tostring(num or "") + return techage.send_single(self.meta.number, num, "fuel", nil) + end, + help = " $get_fuel_value(num)\n".. + " Read fuel value from fuel consuming blocks.\n".. + ' example: val = $get_fuel_value("1234")' +}) + +--techage.lua_ctlr.register_function("get_num_items", { +-- cmnd = function(self, num, idx) +-- num = tostring(num or "") +-- idx = tonumber(idx) +-- return techage.send_single(self.meta.number, num, "num_items", idx) +-- end, +-- help = " $get_num_items(num)\n".. +-- " Read number of stored items in one\n".. +-- " storage (1..8) from a Warehouse Box.\n".. +-- ' example: cnt = $get_num_items("1234", 4)\n' +--}) + +techage.lua_ctlr.register_function("time_as_str", { + cmnd = function(self) + local t = minetest.get_timeofday() + local h = math.floor(t*24) % 24 + local m = math.floor(t*1440) % 60 + return string.format("%02d:%02d", h, m) + end, + help = " $time_as_str() --> e.g. '18:45'\n".. + " Read time of day as string (24h).\n".. + ' example: time = $time_as_str()' +}) + +techage.lua_ctlr.register_function("time_as_num", { + cmnd = function(self, num) + local t = minetest.get_timeofday() + local h = math.floor(t*24) % 24 + local m = math.floor(t*1440) % 60 + return h * 100 + m + end, + help = " $time_as_num() --> e.g.: 1845\n".. + " Read time of day as number (24h).\n".. + ' example: time = $time_as_num()' +}) + +techage.lua_ctlr.register_function("playerdetector", { + cmnd = function(self, num) + num = tostring(num or "") + if not_protected(self.meta.owner, num) then + return techage.send_single(self.meta.number, num, "name", nil) + end + end, + help = ' $playerdetector(num) --> e.g. "Joe"\n'.. + ' "" is returned if no player is nearby.\n'.. + ' example: name = $playerdetector("1234")' +}) + +techage.lua_ctlr.register_action("send_cmnd", { + cmnd = function(self, num, text) + num = tostring(num or "") + text = tostring(text or "") + if not_protected(self.meta.owner, num) then + techage.send_single(self.meta.number, num, text, nil) + end + end, + help = " $send_cmnd(num, text)\n".. + ' Send a command to the device with number "num".\n'.. + ' example: $send_cmnd("1234", "on")' +}) + +techage.lua_ctlr.register_action("set_filter", { + cmnd = function(self, num, slot, val) + num = tostring(num or "") + slot = tostring(slot or "red") + val = tostring(val or "on") + if not_protected(self.meta.owner, num) then + techage.send_single(self.meta.number, num, "filter", {slot=slot, val=val}) + end + end, + help = " $set_filter(num, slot, val)\n".. + ' Turn on/off a Distributor filter slot.\n'.. + ' example: $set_filter("1234", "red", "off")' +}) + + +techage.lua_ctlr.register_action("display", { + cmnd = function(self, num, row, text1, text2, text3) + num = tostring(num or "") + text1 = tostring(text1 or "") + text2 = tostring(text2 or "") + text3 = tostring(text3 or "") + if not_protected(self.meta.owner, num) then + techage.send_single(self.meta.number, num, "set", {row = row, str = text1..text2..text3}) + end + end, + help = " $display(num, row, text,...)\n".. + ' Send a text line to the display with number "num".\n'.. + " 'row' is a value from 1..5\n".. + " The function accepts up to 3 text parameters\n".. + ' example: $display("0123", 1, "Hello ", name, " !")' +}) + +techage.lua_ctlr.register_action("clear_screen", { + cmnd = function(self, num) + num = tostring(num or "") + if not_protected(self.meta.owner, num) then + techage.send_single(self.meta.number, num, "clear", nil) + end + end, + help = " $clear_screen(num)\n".. + ' Clear the screen of the display\n'.. + ' with number "num".\n'.. + ' example: $clear_screen("1234")' +}) + +techage.lua_ctlr.register_action("chat", { + cmnd = function(self, text1, text2, text3) + text1 = tostring(text1 or "") + text2 = tostring(text2 or "") + text3 = tostring(text3 or "") + minetest.chat_send_player(self.meta.owner, "[TA4 Lua Controller] "..text1..text2..text3) + end, + help = " $chat(text,...)\n".. + " Send yourself a chat message.\n".. + " The function accepts up to 3 text parameters\n".. + ' example: $chat("Hello ", name)' +}) + +techage.lua_ctlr.register_action("door", { + cmnd = function(self, pos, text) + pos = tostring(pos or "") + text = tostring(text or "") + pos = minetest.string_to_pos("("..pos..")") + if pos then + local door = doors.get(pos) + if door then + local player = { + get_player_name = function() return self.meta.owner end, + is_player = function() return true end, + } + if text == "open" then + door:open(player) + elseif text == "close" then + door:close(player) + end + end + end + end, + help = " $door(pos, text)\n".. + ' Open/Close a door at position "pos"\n'.. + ' example: $door("123,7,-1200", "close")\n'.. + " Hint: Use the Techage Programmer to\ndetermine the door position." +}) diff --git a/lua_controller/controller.lua b/lua_controller/controller.lua new file mode 100644 index 0000000..369e793 --- /dev/null +++ b/lua_controller/controller.lua @@ -0,0 +1,617 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019-2020 Joachim Stolberg + + GPL v3 + See LICENSE.txt for more information + + Lua Controller + +]]-- + +-- for lazy programmers +local S = function(pos) if pos then return minetest.pos_to_string(pos) end end +local M = minetest.get_meta + +local sHELP = [[TA4 Lua Controller + + This controller is used to control and monitor + TechAge machines. + This controller can be programmed in Lua. + + See on GitHub for more help: goo.gl/Et8D6n + + The controller only runs, if a battery is + placed nearby. + +]] + +techage.lua_ctlr = {} + +local BATTERY_CAPA = 5000000 + +local Cache = {} + +local STATE_STOPPED = 0 +local STATE_RUNNING = 1 + +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 techage.lua_ctlr.register_function(key, attr) + tCommands[key] = attr.cmnd + table.insert(tFunctions, " $"..key) + tHelpTexts[" $"..key] = attr.help +end + +function techage.lua_ctlr.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 + +techage.lua_ctlr.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, " !")' +}) + +techage.lua_ctlr.register_action("loopcycle", { + cmnd = function(self, cycletime) + cycletime = math.floor(tonumber(cycletime) or 0) + local meta = minetest.get_meta(self.meta.pos) + meta:set_int("cycletime", cycletime) + meta:set_int("cyclecount", 0) + end, + help = "$loopcycle(seconds)\n".. + " This function allows to change the\n".. + " call frequency of the loop() function.\n".. + " value is in seconds, 0 = disable\n".. + ' e.g. $loopcycle(10)' +}) + +techage.lua_ctlr.register_action("events", { + cmnd = function(self, event) + self.meta.events = event or false + end, + help = "$events(true/false)\n".. + " Enable/disable event handling.\n".. + ' e.g. $events(true) -- enable events' +}) + +techage.lua_ctlr.register_function("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." +}) + +techage.lua_ctlr.register_function("position", { + cmnd = function(self, number) + local info = techage.get_node_info(number) + if info then + return S(info.pos) + end + return "(-,-,-)" + end, + help = "$position(number)\n".. + " returns the position '(x,y,z)' of the device\n with given number." +}) + +techage.lua_ctlr.register_action("battery", { + cmnd = function(self) + local meta = minetest.get_meta(self.meta.pos) + local batpos = minetest.string_to_pos(meta:get_string("battery")) + local batmeta = minetest.get_meta(batpos) + local val = (BATTERY_CAPA - math.min(batmeta:get_int("content") or 0, BATTERY_CAPA)) + return 100 - math.floor((val * 100.0 / BATTERY_CAPA)) + end, + help = " $battery()\n".. + " Get charge level of battery connected to Controller.\n".. + " Function returns percent number (0-100) where 100 means full.\n".. + " example: battery_percent = $battery()" +}) + + +local function formspec0(meta) + local state = meta:get_int("state") == techage.RUNNING + local init = meta:get_string("init") + init = minetest.formspec_escape(init) + return "size[4,3]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "label[0,0;No Battery?]".. + "button[1,2;1.8,1;start;Start]" +end + +local function formspec1(meta) + local state = meta:get_int("state") == techage.RUNNING + local cmnd = state 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;init,func,loop,outp,notes,help;1;;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 formspec2(meta) + local state = meta:get_int("state") == techage.RUNNING + local cmnd = state and "stop;Stop" or "start;Start" + local func = meta:get_string("func") + func = minetest.formspec_escape(func) + return "size[10,8]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "tabheader[0,0;tab;init,func,loop,outp,notes,help;2;;true]".. + "textarea[0.3,0.2;10,8.3;func;functions:;"..func.."]".. + "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 state = meta:get_int("state") == techage.RUNNING + local cmnd = state 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;init,func,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 state = meta:get_int("state") == techage.RUNNING + local cmnd = state and "stop;Stop" or "start;Start" + local output = meta:get_string("output") + output = minetest.formspec_escape(output) + output = output:gsub("\n", ",") + return "size[10,8]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "tabheader[0,0;tab;init,func,loop,outp,notes,help;4;;true]".. + "table[0.2,0.2;9.5,7;output;"..output..";200]".. + "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;init,func,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;init,func,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 patch_error_string(err, line_offs) + local tbl = {} + for s in err:gmatch("[^\r\n]+") do + if s:find("loop:(%d+):") then + local prefix, line, err = s:match("(.-)loop:(%d+):(.+)") + if prefix and line and err then + if tonumber(line) < line_offs then + table.insert(tbl, prefix.."func:"..line..":"..err) + else + line = tonumber(line) - line_offs + table.insert(tbl, prefix.."loop:"..line..":"..err) + end + end + else + table.insert(tbl, s) + end + end + return table.concat(tbl, "\n") +end + +local function error(pos, err) + local meta = minetest.get_meta(pos) + local func = meta:get_string("func") + local _,line_offs = string.gsub(func, "\n", "\n") + line_offs = line_offs + 1 + err = patch_error_string(err, line_offs) + output(pos, err) + local number = meta:get_string("number") + meta:set_string("infotext", "Controller "..number..": error") + meta:set_int("state", techage.STOPPED) + meta:set_int("running", STATE_STOPPED) + meta:set_string("formspec", formspec4(meta)) + minetest.get_node_timer(pos):stop() + return false +end + +local function compile(pos, meta, number) + local init = meta:get_string("init") + local func = meta:get_string("func") + local loop = meta:get_string("loop") + local owner = meta:get_string("owner") + local env = table.copy(tCommands) + env.meta = {pos=pos, owner=owner, number=number, error=error} + local code = safer_lua.init(pos, init, func.."\n"..loop, env, error) + + if code then + Cache[number] = {code=code, inputs={}, events=env.meta.events} + Cache[number].inputs.term = nil -- terminal inputs + Cache[number].inputs.msg = {} -- message queue + return true + end + return false +end + +local function battery(pos) + local battery_pos = minetest.find_node_near(pos, 1, {"techage:ta4_battery"}) + if battery_pos then + local meta = minetest.get_meta(pos) + meta:set_string("battery", minetest.pos_to_string(battery_pos)) + return true + end + return false +end + +local function start_controller(pos) + local meta = minetest.get_meta(pos) + local number = meta:get_string("number") + if not battery(pos) then + meta:set_string("formspec", formspec0(meta)) + return false + end + + meta:set_string("output", "") + meta:set_int("cycletime", 1) + meta:set_int("cyclecount", 0) + meta:set_int("cpu", 0) + + if compile(pos, meta, number) then + meta:set_int("state", techage.RUNNING) + meta:set_int("running", STATE_RUNNING) + minetest.get_node_timer(pos):start(1) + meta:set_string("formspec", formspec4(meta)) + meta:set_string("infotext", "Controller "..number..": running") + return true + end + return false +end + +local function stop_controller(pos) + local meta = minetest.get_meta(pos) + local number = meta:get_string("number") + meta:set_int("state", techage.STOPPED) + meta:set_int("running", STATE_STOPPED) + minetest.get_node_timer(pos):stop() + meta:set_string("infotext", "Controller "..number..": stopped") + meta:set_string("formspec", formspec3(meta)) +end + +local function no_battery(pos) + local meta = minetest.get_meta(pos) + local number = meta:get_string("number") + meta:set_int("state", techage.STOPPED) + meta:set_int("running", STATE_STOPPED) + minetest.get_node_timer(pos):stop() + meta:set_string("infotext", "Controller "..number..": No battery") + meta:set_string("formspec", formspec0(meta)) +end + +local function update_battery(meta, cpu) + local pos = minetest.string_to_pos(meta:get_string("battery")) + if pos then + meta = minetest.get_meta(pos) + local content = meta:get_int("content") - cpu + if content <= 0 then + meta:set_int("content", 0) + return false + end + meta:set_int("content", content) + return true + 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 res = safer_lua.run_loop(pos, elapsed, code, error) + if res then + t = minetest.get_us_time() - t + cpu = math.floor(((cpu * 20) + t) / 21) + meta:set_int("cpu", cpu) + meta:set_string("infotext", "Controller "..number..": running ("..cpu.."us)") + if not update_battery(meta, cpu) then + no_battery(pos) + return false + end + end + -- further messages available? + if next(Cache[number].inputs["msg"]) then + minetest.after(1, call_loop, pos, meta, -1) + end + return res + end + return false +end + +local function on_timer(pos, elapsed) + local meta = minetest.get_meta(pos) + -- considering cycle frequency + local cycletime = meta:get_int("cycletime") or 1 + local cyclecount = (meta:get_int("cyclecount") or 0) + 1 + if cycletime == 0 or cyclecount < cycletime then + meta:set_int("cyclecount", cyclecount) + return true + end + meta:set_int("cyclecount", 0) + + 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", formspec1(meta)) + elseif fields.func then + meta:set_string("func", fields.func) + 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_controller(pos) + minetest.log("action", player:get_player_name() .. + " starts the sl_controller at ".. minetest.pos_to_string(pos)) + elseif fields.stop == "Stop" then + stop_controller(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("techage:ta4_lua_controller", { + description = "TA4 Lua Controller", + inventory_image = "techage_lua_controller_inventory.png", + wield_image = "techage_lua_controller_inventory.png", + stack_max = 1, + tiles = { + -- up, down, right, left, back, front + "techage_smartline.png", + "techage_smartline.png", + "techage_smartline.png", + "techage_smartline.png", + "techage_smartline.png", + "techage_smartline.png^techage_lua_controller.png", + }, + + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + { -6/32, -6/32, 14/32, 6/32, 6/32, 16/32}, + }, + }, + + after_place_node = function(pos, placer) + local meta = minetest.get_meta(pos) + local number = techage.add_node(pos, "techage:ta4_lua_controller") + meta:set_string("owner", placer:get_player_name()) + meta:set_string("number", number) + meta:set_int("state", techage.STOPPED) + meta:set_int("running", STATE_STOPPED) + meta:set_string("init", "-- called only once") + meta:set_string("func", "-- for your functions") + meta:set_string("loop", "-- called every second") + meta:set_string("notes", "For your notes / snippets") + meta:mark_as_private("init") + meta:mark_as_private("func") + meta:mark_as_private("loop") + meta:mark_as_private("notes") + meta:set_string("formspec", formspec1(meta)) + meta:set_string("infotext", "Controller "..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 + + minetest.node_dig(pos, node, puncher, pointed_thing) + techage.remove_node(pos) + end, + + on_timer = on_timer, + + paramtype = "light", + sunlight_propagates = true, + paramtype2 = "facedir", + groups = {choppy=1, cracky=1, crumbly=1}, + is_ground_content = false, + sounds = default.node_sound_stone_defaults(), +}) + + +minetest.register_craft({ + output = "techage:ta4_lua_controller", + recipe = { + {"basic_materials:plastic_sheet", "dye:blue", "basic_materials:plastic_sheet"}, + {"", "default:copper_ingot", ""}, + {"techage:ta4_ramchip", "techage:ta4_wlanchip", "techage:ta4_ramchip"}, + }, +}) + +-- write inputs from remote nodes +local function set_input(pos, number, input, val) + if input and M(pos):get_int("state") == techage.RUNNING then + if (Cache[number] or compile(pos, M(pos), number)) and Cache[number].inputs then + if input == "msg" then + if #Cache[number].inputs["msg"] < 10 then + table.insert(Cache[number].inputs["msg"], val) + end + else + Cache[number].inputs[input] = val + end + if Cache[number].events then -- events enabled? + local t = minetest.get_us_time() + if not Cache[number].last_event or Cache[number].last_event < t then + local meta = minetest.get_meta(pos) + minetest.after(0.01, call_loop, pos, meta, -1) + Cache[number].last_event = t + 100000 -- add 100 ms + end + end + end + end +end + +-- used by the command "input" +function techage.lua_ctlr.get_input(number, input) + if input then + if Cache[number] and Cache[number].inputs then + return Cache[number].inputs[input] or "off" + end + end + return "off" +end + +-- used for Terminal commands +function techage.lua_ctlr.get_command(number) + if Cache[number] and Cache[number].inputs then + local cmnd = Cache[number].inputs["term"] + Cache[number].inputs["term"] = nil + return cmnd + end +end + +-- used for queued messages +function techage.lua_ctlr.get_msg(number) + if Cache[number] and Cache[number].inputs then + return table.remove(Cache[number].inputs["msg"], 1) + end +end + +techage.register_node({"techage:ta4_lua_controller"}, { + on_recv_message = function(pos, topic, payload) + local meta = minetest.get_meta(pos) + local number = meta:get_string("number") + + if topic == "on" then + set_input(pos, number, payload, topic) + elseif topic == "off" then + set_input(pos, number, payload, topic) + elseif topic == "term" then + set_input(pos, number, "term", payload) + elseif topic == "msg" then + set_input(pos, number, "msg", payload) + elseif topic == "state" then + local running = meta:get_int("running") or STATE_STOPPED + return techage.statestring(running) + else + return "unsupported" + end + end, +}) diff --git a/lua_controller/sensorchest.lua b/lua_controller/sensorchest.lua new file mode 100644 index 0000000..a9b07ad --- /dev/null +++ b/lua_controller/sensorchest.lua @@ -0,0 +1,193 @@ +--[[ + + TechAge + ======= + + Copyright (C) 2019-2020 Joachim Stolberg + + GPL v3 + See LICENSE.txt for more information + + TA4 Sensor Chest + +]]-- + +-- for lazy programmers +local M = minetest.get_meta +local S = techage.S + +local PlayerActions = {} +local InventoryState = {} + + +local function store_action(pos, player, action, stack) + local meta = minetest.get_meta(pos) + local name = player and player:get_player_name() or "" + local number = meta:get_string("node_number") + local item = stack:get_name().." "..stack:get_count() + PlayerActions[number] = {name, action, item} +end + +local function send_off_command(pos) + local meta = minetest.get_meta(pos) + local numbers = meta:get_string("numbers") or "" + if numbers ~= "" then + local own_num = meta:get_string("node_number") + techage.send_multi(own_num, numbers, "off") + end +end + + +local function send_command(pos) + local meta = minetest.get_meta(pos) + local numbers = meta:get_string("numbers") or "" + if numbers ~= "" then + local own_num = meta:get_string("node_number") + techage.send_multi(own_num, numbers, "on") + minetest.after(1, send_off_command, pos) + end +end + +local function allow_metadata_inventory_put(pos, listname, index, stack, player) + if minetest.is_protected(pos, player:get_player_name()) then + return 0 + end + store_action(pos, player, "put", stack) + send_command(pos) + return stack:get_count() +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 + store_action(pos, player, "take", stack) + send_command(pos) + return stack:get_count() +end + +local function can_dig(pos, player) + if minetest.is_protected(pos, player:get_player_name()) then + return false + end + local inv = minetest.get_meta(pos):get_inventory() + return inv:is_empty("main") +end + +local function after_dig_node(pos, oldnode, oldmetadata, digger) + techage.remove_node(pos) +end + +local function formspec(pos) + local text = M(pos):get_string("text") + return "size[8,6]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "list[context;main;0,0;2,2;]".. + "button[2,0;1,1;f1;F1]".. + "button[2,1;1,1;ok;OK]".. + "label[3,0;"..text.."]".. + "list[current_player;main;0,2.3;8,4;]".. + "listring[context;main]".. + "listring[current_player;main]" +end + +minetest.register_node("techage:ta4_sensor_chest", { + description = S("TA4 Sensor Chest"), + tiles = { + -- up, down, right, left, back, front + "techage_filling_ta4.png^techage_frame_ta4_top.png", + "techage_filling_ta4.png^techage_frame_ta4.png", + "techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_chest_back_ta4.png^techage_appl_sensor.png", + "techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_chest_back_ta4.png^techage_appl_sensor.png", + "techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_chest_back_ta4.png^techage_appl_sensor.png", + "techage_filling_ta4.png^techage_frame_ta4.png^techage_appl_chest_front_ta4.png^techage_appl_sensor.png", + }, + + on_construct = function(pos) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + inv:set_size('main', 4) + end, + + after_place_node = function(pos, placer) + local meta = minetest.get_meta(pos) + local number = techage.add_node(pos, "techage:ta4_sensor_chest") + meta:set_string("node_number", number) + meta:set_string("owner", placer:get_player_name()) + meta:set_string("text", "Text to be changed\nby command.") + meta:set_string("formspec", formspec(pos)) + meta:set_string("infotext", S("TA4 Sensor Chest").." "..number) + end, + + on_receive_fields = function(pos, formname, fields, player) + local meta = M(pos) + local nvm = techage.get_nvm(pos) + if fields.f1 then + store_action(pos, player, "f1") + send_command(pos) + end + if fields.ok then + store_action(pos, player, "ok") + send_command(pos) + end + meta:set_string("formspec", formspec(pos, meta)) + end, + + techage_set_numbers = function(pos, numbers, player_name) + return techage.logic.set_numbers(pos, numbers, player_name, S("TA4 Sensor Chest")) + end, + + can_dig = can_dig, + after_dig_node = after_dig_node, + allow_metadata_inventory_put = allow_metadata_inventory_put, + allow_metadata_inventory_take = allow_metadata_inventory_take, + + paramtype2 = "facedir", + groups = {choppy=2, cracky=2, crumbly=2}, + is_ground_content = false, + sounds = default.node_sound_wood_defaults(), +}) + +techage.register_node({"ta4_sensor_chest"}, { + on_pull_item = function(pos, in_dir, num) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + return techage.get_items(inv, "main", num) + end, + on_push_item = function(pos, in_dir, stack) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + return techage.put_items(inv, "main", stack) + end, + on_unpull_item = function(pos, in_dir, stack) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + return techage.put_items(inv, "main", stack) + end, + + on_recv_message = function(pos, src, topic, payload) + if topic == "state" then + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + return techage.get_inv_state(inv, "main") + elseif topic == "player_action" then + local meta = minetest.get_meta(pos) + local number = meta:get_string("node_number") + return PlayerActions[number] + elseif topic == "text" then + local meta = minetest.get_meta(pos) + meta:set_string("text", tostring(payload)) + else + return "unsupported" + end + end, +}) + +minetest.register_craft({ + type = "shapeless", + output = "techage:ta4_sensor_chest", + recipe = {"techage:chest_ta4", "techage:ta4_wlanchip"} +}) + diff --git a/lua_controller/server.lua b/lua_controller/server.lua new file mode 100644 index 0000000..d0ae8b6 --- /dev/null +++ b/lua_controller/server.lua @@ -0,0 +1,232 @@ +--[[ + + sl_controller + ============= + + Copyright (C) 2018 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + server.lua: + +]]-- + +local SERVER_CAPA = 5000 +local DEFAULT_MEM = { + size=0, + data={ + version = 1, + info = "SaferLua key/value Server", + } +} + +local function formspec(meta) + local names = meta:get_string("names") or "" + return "size[9,4]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "field[0.2,1;9,1;names;Allowed user names (spaces separated):;"..names.."]" .. + "button_exit[3.5,2.5;2,1;exit;Save]" +end + + +local function on_time(pos, elasped) + local meta = minetest.get_meta(pos) + local nvm = techage.get_nvm(pos) + if next(nvm) == nil then + nvm = table.copy(DEFAULT_MEM) + end + local number = meta:get_string("number") + meta:set_string("infotext", "Server "..number..": ("..(nvm.size or 0).."/"..SERVER_CAPA..")") + return true +end + +minetest.register_node("techage:ta4_server", { + description = "Central Server", + tiles = { + -- up, down, right, left, back, front + "techage_server_top.png", + "techage_server_top.png", + "techage_server_side.png", + "techage_server_side.png^[transformFX", + "techage_server_back.png", + { + image = "techage_server_front.png", + backface_culling = false, + animation = { + type = "vertical_frames", + aspect_w = 32, + aspect_h = 32, + length = 1, + }, + }, + }, + + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + { -3/16, -8/16, -7/16, 3/16, 6/16, 7/16}, + }, + }, + + after_place_node = function(pos, placer) + local meta = minetest.get_meta(pos) + local number = techage.add_node(pos, "techage:ta4_server") + meta:set_string("owner", placer:get_player_name()) + meta:set_string("number", number) + meta:set_string("formspec", formspec(meta)) + on_time(pos, 0) + minetest.get_node_timer(pos):start(20) + end, + + on_receive_fields = function(pos, formname, fields, player) + local meta = minetest.get_meta(pos) + local owner = meta:get_string("owner") + if player:get_player_name() == owner then + if fields.names and fields.names ~= "" then + local names = string.gsub(fields.names, " +", " ") + meta:set_string("names", names) + meta:set_string("formspec", formspec(meta)) + end + end + end, + + 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) + local number = meta:get_string("number") + techage.del_mem(pos) + minetest.node_dig(pos, node, puncher, pointed_thing) + techage.remove_node(pos) + end, + + on_timer = on_time, + + paramtype = "light", + sunlight_propagates = true, + paramtype2 = "facedir", + groups = {choppy=1, cracky=1, crumbly=1}, + is_ground_content = false, + sounds = default.node_sound_stone_defaults(), +}) + +minetest.register_craft({ + output = "techage:ta4_server", + recipe = { + {"default:steel_ingot", "dye:black", "default:steel_ingot"}, + {"techage:ta4_ramchip", "default:copper_ingot", "techage:ta4_ramchip"}, + {"techage:ta4_ramchip", "techage:ta4_wlanchip", "techage:ta4_ramchip"}, + }, +}) + +local function calc_size(v) + if type(v) == "number" then + return 1 + elseif v == nil then + return 0 + elseif type(v) == "string" then + return #v + elseif v.MemSize then + return v.MemSize + else + return nil + end +end + +local function get_memory(pos, num, name) + local info = techage.get_node_info(num) + if info and info.name == "techage:ta4_server" then + local meta = minetest.get_meta(info.pos) + local owner = meta:get_string("owner") + if name == owner then + local nvm = techage.get_nvm(pos) + if next(nvm) == nil then + nvm = table.copy(DEFAULT_MEM) + end + return nvm + end + local names = meta:get_string("names") + for _,n in ipairs(string.split(names, " ")) do + local nvm = techage.get_nvm(pos) + if name == n then + if next(nvm) == nil then + nvm = table.copy(DEFAULT_MEM) + end + return nvm + end + end + end +end + +local function write_value(nvm, key, item) + if nvm and nvm.size < SERVER_CAPA then + if nvm.data[key] then + nvm.size = nvm.size - calc_size(nvm.data[key]) + end + if type(item) == "table" then + item = safer_lua.datastruct_to_table(item) + end + nvm.size = nvm.size + calc_size(item) + nvm.data[key] = item + end +end + +local function read_value(nvm, key) + local item = nvm.data[key] + if type(item) == "table" then + item = safer_lua.table_to_datastruct(item) + end + return item +end + +techage.register_node({"techage:ta4_server"}, { + on_recv_message = function(pos, topic, payload) + return "unsupported" + end, + on_node_load = function(pos) + minetest.get_node_timer(pos):start(20) + end, +}) + + +techage.lua_ctlr.register_function("server_read", { + cmnd = function(self, num, key) + if type(key) == "string" then + local nvm = get_memory(self.meta.pos, num, self.meta.owner) + if nvm then + return read_value(nvm, key) + end + else + self.error("Invalid server_read parameter") + end + end, + help = " $server_read(num, key)\n".. + " Read a value from the server.\n".. + " 'key' must be a string.\n".. + ' example: state = $server_read("0123", "state")' +}) + +techage.lua_ctlr.register_action("server_write", { + cmnd = function(self, num, key, value) + if type(key) == "string" then + local nvm = get_memory(self.meta.pos, num, self.meta.owner) + if nvm then + write_value(nvm, key, value) + end + else + self.error("Invalid server_write parameter") + end + end, + help = " $server_write(num, key, value)\n".. + " Store a value on the server under the key 'key'.\n".. + " 'key' must be a string. 'value' can be either a\n".. + " number, string, boolean, nil or data structure.\n".. + ' example: $server_write("0123", "state", state)' +}) + + diff --git a/power/power_terminal.lua b/power/power_terminal.lua index da2efc6..ca858c2 100644 --- a/power/power_terminal.lua +++ b/power/power_terminal.lua @@ -212,7 +212,7 @@ minetest.register_node("techage:power_terminal", { on_rotate = screwdriver.disallow, sunlight_propagates = true, is_ground_content = false, - groups = {cracky = 1, level = 2}, + groups = {cracky = 2, level = 2}, sounds = default.node_sound_metal_defaults(), }) diff --git a/textures/techage_appl_sensor.png b/textures/techage_appl_sensor.png new file mode 100644 index 0000000..e3bdead Binary files /dev/null and b/textures/techage_appl_sensor.png differ