diff --git a/.gitignore b/.gitignore index f3b8f7c..10f4939 100644 --- a/.gitignore +++ b/.gitignore @@ -44,4 +44,6 @@ luac.out org.eclipse.* *.lua.new -test_*.lua \ No newline at end of file +test_*.lua + +shrink.py diff --git a/README.md b/README.md index b413b6b..e38eb56 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# TechPack V1.12 +# TechPack V1.13 TechPack, a Mining, Crafting, & Farming Modpack for Minetest. @@ -77,7 +77,7 @@ Gravelsieve optional: moreores, hopper, pipeworks tubelib_addons1 optional: unified_inventory -### History +### History - 2018-03-18 V1.00 * Tubelib, tubelib_addons1, tubelib_addons2, smartline, and gravelsieve combined to one modpack. - 2018-03-24 V1.01 * Support for Ethereal added - 2018-03-27 V1.02 * Timer improvements for unloaded areas @@ -91,3 +91,6 @@ tubelib_addons1 optional: unified_inventory - 2018-08-08 V1.10 * tubelib_addon3 with high performance nodes added - 2018-08-13 V1.11 * Detector node added - 2018-08-14 V1.12 * Teleporter node added +- 2018-08-28 V1.13 * Smartline Controller completely revised. Liquid Sampler added + +See releasenotes.txt for further information \ No newline at end of file diff --git a/releasenotes.md b/releasenotes.md new file mode 100644 index 0000000..eec6593 --- /dev/null +++ b/releasenotes.md @@ -0,0 +1,33 @@ +# Release Notes of the ModPack TechPack [techpack] + + + +## V1.13 Beta (2018-08-28) + + +### Additions + +- A Liquid Sampler node is added. It is able to take all kind or renewable liquids (registered via bucket.register_liquid) + Currently only useful for recipes where a water-bucket is needed. +- Smartline has a new IF-THIS-THEN-THAT Controller V2 which should be much simpler to use. + It will replace the current one (V1). + Currently both are active, but if you dig a controller V1 it will be converted to a controller V2. +- The new controller needs batteries. Thus, Smartline has now its own battery node. The sl_controller.battery will not be + needed any more. + + +### Removals + +- recipe for sl_controller/batteries removed. +- Recipe for Smartline controller V1 removed. + + +### Changes + +- Quarry can no go direct from FAULT into RUNNING without reset the digging position + + +### Fixes + +- bug in open/close door command for Minetest v0.4.17+ fixed + diff --git a/sl_controller/battery.lua b/sl_controller/battery.lua index e008d08..c9def62 100644 --- a/sl_controller/battery.lua +++ b/sl_controller/battery.lua @@ -9,6 +9,8 @@ See LICENSE.txt for more information battery.lua: + + REPLACED BY SMARTLINE BATTERY !!! ]]-- @@ -71,13 +73,13 @@ local function register_battery(ext, percent, nici) local percent = calc_percent(tonumber(oldmetadata.fields.content)) local stack if percent > 95 then - stack = ItemStack("sl_controller:battery") + stack = ItemStack("smartline:battery") elseif percent > 75 then - stack = ItemStack("sl_controller:battery75") + stack = ItemStack("smartline:battery75") elseif percent > 50 then - stack = ItemStack("sl_controller:battery50") + stack = ItemStack("smartline:battery50") elseif percent > 25 then - stack = ItemStack("sl_controller:battery25") + stack = ItemStack("smartline:battery25") else return end @@ -95,7 +97,7 @@ local function register_battery(ext, percent, nici) }) end -register_battery("", 1.0, 0) +register_battery("", 1.0, 1) register_battery("75", 0.75, 1) register_battery("50", 0.5, 1) register_battery("25", 0.25, 1) @@ -135,22 +137,22 @@ minetest.register_node("sl_controller:battery_empty", { }) -if minetest.global_exists("moreores") then - minetest.register_craft({ - output = "sl_controller:battery 2", - recipe = { - {"", "moreores:silver_ingot", ""}, - {"", "default:copper_ingot", ""}, - {"", "moreores:silver_ingot", ""}, - } - }) -else - minetest.register_craft({ - output = "sl_controller:battery 2", - recipe = { - {"", "default:tin_ingot", ""}, - {"", "default:copper_ingot", ""}, - {"", "default:tin_ingot", ""}, - } - }) -end +--if minetest.global_exists("moreores") then +-- minetest.register_craft({ +-- output = "sl_controller:battery 2", +-- recipe = { +-- {"", "moreores:silver_ingot", ""}, +-- {"", "default:copper_ingot", ""}, +-- {"", "moreores:silver_ingot", ""}, +-- } +-- }) +--else +-- minetest.register_craft({ +-- output = "sl_controller:battery 2", +-- recipe = { +-- {"", "default:tin_ingot", ""}, +-- {"", "default:copper_ingot", ""}, +-- {"", "default:tin_ingot", ""}, +-- } +-- }) +--end diff --git a/sl_controller/commands.lua b/sl_controller/commands.lua index c2473ee..ed42b91 100644 --- a/sl_controller/commands.lua +++ b/sl_controller/commands.lua @@ -181,6 +181,7 @@ sl_controller.register_action("door", { 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) diff --git a/sl_controller/controller.lua b/sl_controller/controller.lua index a86759e..9bd0913 100644 --- a/sl_controller/controller.lua +++ b/sl_controller/controller.lua @@ -230,7 +230,7 @@ local function compile(pos, meta, number) end local function battery(pos) - local battery_pos = minetest.find_node_near(pos, 1, {"sl_controller:battery"}) + local battery_pos = minetest.find_node_near(pos, 1, {"sl_controller:battery", "smartline:battery"}) if battery_pos then local meta = minetest.get_meta(pos) meta:set_string("battery", minetest.pos_to_string(battery_pos)) @@ -448,13 +448,13 @@ minetest.register_craft({ local function set_input(pos, number, input, val) if input then if Cache[number] and Cache[number].inputs then + Cache[number].inputs[input] = val -- only one event per second local t = minetest.get_us_time() if not Cache[number].last_event or Cache[number].last_event < t then - Cache[number].inputs[input] = val local meta = minetest.get_meta(pos) - minetest.after(0.1, call_loop, pos, meta, -1) - Cache[number].last_event = t + 1000000 -- add one second + minetest.after(0.01, call_loop, pos, meta, -1) + Cache[number].last_event = t + 500000 -- add 500 ms end end end diff --git a/smartline/commands.lua b/smartline/commands.lua index 84d5ffa..ba78d25 100644 --- a/smartline/commands.lua +++ b/smartline/commands.lua @@ -584,6 +584,7 @@ local function door_toggle(pos, owner, state) if door then local player = { get_player_name = function() return owner end, + is_player = function() return true end, } if state == "open" then door:open(player) diff --git a/smartline/controller.lua b/smartline/controller.lua index 06121fd..f9b10f7 100644 --- a/smartline/controller.lua +++ b/smartline/controller.lua @@ -10,6 +10,7 @@ controller.lua: + REPLACED BY SMARTLINE CONTROLLER2 !!! ]]-- @@ -63,7 +64,6 @@ The colors show, if conditions are true or false and if actions were already executed or not. It has a 'update' button to update the view. -For more information, see: goo.gl/fF5ap6 ]] local sOUTPUT = "Press 'help' for edit commands" @@ -117,8 +117,6 @@ local function exec_action(data, environ, number) ActnRunTimeHandlers[data.__idx__](data, environ, number) end -smartline = {} - -- -- API functions for condition/action registrations -- @@ -845,9 +843,10 @@ minetest.register_node("smartline:controller", { paramtype = "light", sunlight_propagates = true, paramtype2 = "facedir", - groups = {choppy=1, cracky=1, crumbly=1}, + groups = {choppy=1, cracky=1, crumbly=1, not_in_creative_inventory=1}, is_ground_content = false, sounds = default.node_sound_stone_defaults(), + drop = "smartline:controller2", }) diff --git a/smartline/icta/action.lua b/smartline/icta/action.lua new file mode 100644 index 0000000..ce0f8cc --- /dev/null +++ b/smartline/icta/action.lua @@ -0,0 +1,105 @@ +--[[ + + ICTA Controller + =============== + + Part of the SmartLine mod + + Copyright (C) 2018 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + action.lua + +]]-- + +local sl = smartline + +--local mail_exists = minetest.get_modpath("mail") and mail ~= nil TODO + +-- tables with all data from action registrations +local kvRegisteredActn = {} + +-- list of keys for actions +local aActnTypes = {} + +-- list of titles for actions +local aActnTitles = {} + +-- +-- API function for action registrations +-- +function sl.icta_register_action(key, tData) + table.insert(aActnTypes, key) + table.insert(aActnTitles, tData.title) + tData.__idx__ = #aActnTypes + if kvRegisteredActn[key] ~= nil then + print("[SmartLine] Action registration error "..key) + return + end + kvRegisteredActn[key] = tData + for _,item in ipairs(tData.formspec) do + if item.type == "textlist" then + item.tChoices = string.split(item.choices, ",") + item.num_choices = #item.tChoices + end + end +end + +-- return formspec string +function sl.actn_formspec(row, kvSelect) + return sl.submenu_generate_formspec( + row, "actn", "Action type", aActnTypes, aActnTitles, kvRegisteredActn, kvSelect) +end + +-- evaluate the row action input +-- and return new data +function sl.actn_eval_input(kvSelect, fields) + kvSelect = sl.submenu_eval_input(kvRegisteredActn, aActnTypes, aActnTitles, kvSelect, fields) + return kvSelect +end + + +-- return the Lua code +function sl.code_action(kvSelect, environ) + if kvSelect and kvRegisteredActn[kvSelect.choice] then + if smartline.submenu_verify(kvRegisteredActn, kvSelect) then + return kvRegisteredActn[kvSelect.choice].code(kvSelect, environ) + end + end + return nil +end + +sl.icta_register_action("default", { + title = "", + formspec = {}, + code = function(data, environ) return false, false end, + button = function(data, environ) return "..." end, +}) + +sl.icta_register_action("print", { + title = "print to output window", + formspec = { + { + type = "ascii", + name = "text", + label = "Output the following text", + default = "", + }, + { + type = "label", + name = "lbl", + label = "Hint: Use a '*' character as reference to any\ncondition state", + }, + }, + button = function(data, environ) + return 'print(...)' + end, + code = function(data, environ) + local s1 = 'local text = string.gsub("'..(smartline.escape(data.text))..'", "*", env.result[#])' + local s2 = 'output(env.pos, text)' + return s1.."\n\t"..s2 + end, +}) + diff --git a/smartline/icta/balancer.lua b/smartline/icta/balancer.lua new file mode 100644 index 0000000..fb92368 --- /dev/null +++ b/smartline/icta/balancer.lua @@ -0,0 +1,123 @@ +--[[ + + ICTA Controller + =============== + + Part of the SmartLine mod + + Copyright (C) 2018 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + balancer.lua + +]]-- + +local MAX_DIFF = 10 + +function smartline.balancer_condition(number1, number2, ratio1, ratio2, owner) + local cnt1 = tubelib.send_request(number1, "counter", nil) / ratio1 + local cnt2 = tubelib.send_request(number2, "counter", nil) / ratio2 + if cnt1 > cnt2 + MAX_DIFF then + tubelib.send_message(number1, owner, nil, "off", nil) + return number1 + elseif cnt2 > cnt1 + MAX_DIFF then + tubelib.send_message(number2, owner, nil, "off", nil) + return number2 + end +end + +smartline.icta_register_condition("ratio", { + title = "Balancer ratio", + formspec = { + { + type = "numbers", + name = "number1", + label = "Pusher1", + default = "", + }, + { + type = "digits", + name = "ratio1", + label = "Ratio1", + default = "", + }, + { + type = "numbers", + name = "number2", + label = "Pusher2", + default = "", + }, + { + type = "digits", + name = "ratio2", + label = "Ratio1", + default = "", + }, + { + type = "label", + name = "lbl", + label = "Hint: Pusher1:Pusher2 shall have a\nitem counter ratio of Ratio1:Ratio2.", + }, + }, + -- Return two chunks of executable Lua code for the controller, according: + -- return , + code = function(data, environ) + local s = 'smartline.balancer_condition("%s", "%s", %s, %s, "%s")' + return s:format(data.number1, data.number2, data.ratio1, data.ratio2, environ.owner), '~= nil' + end, + button = function(data, environ) + return "ratio("..(data.ratio1 or "???").."/"..(data.ratio2 or "???")..")" + end, +}) + +smartline.icta_register_action("balancer", { + title = "Balancer Control", + formspec = { + { + type = "label", + name = "lbl", + label = "Hint: Stop one Pusher and start\nit again after 'after' seconds.", + }, + }, + button = function(data, environ) + return 'balancer()' + end, + code = function(data, environ) + local s = 'tubelib.send_message(env.result[#], "%s", nil, "on", nil)' + return string.format(s, data.number, environ.owner) + end, +}) + +smartline.icta_register_action("clearcounter", { + title = "Balancer clear counter", + formspec = { + { + type = "numbers", + name = "number1", + label = "Pusher1", + default = "", + }, + { + type = "numbers", + name = "number2", + label = "Pusher2", + default = "", + }, + { + type = "label", + name = "lbl", + label = "Hint: Clear both Pusher counters.", + }, + }, + button = function(data, environ) + return 'balancer()' + end, + code = function(data, environ) + local s = [[tubelib.send_message("%s", "%s", nil, "clear_counter", nil) + tubelib.send_message("%s", "%s", nil, "clear_counter", nil)]] + return string.format(s, data.number1, environ.owner, data.number2, environ.owner) + end, +}) + diff --git a/smartline/icta/battery.lua b/smartline/icta/battery.lua new file mode 100644 index 0000000..6380664 --- /dev/null +++ b/smartline/icta/battery.lua @@ -0,0 +1,158 @@ +--[[ + + ICTA Controller + =============== + + Copyright (C) 2018 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + battery.lua + +]]-- + +local BATTERY_CAPACITY = 5000000 + +local function calc_percent(content) + local val = (BATTERY_CAPACITY - math.min(content or 0, BATTERY_CAPACITY)) + return 100 - math.floor((val * 100.0 / BATTERY_CAPACITY)) +end + +local function on_timer(pos, elapsed) + local meta = minetest.get_meta(pos) + local percent = calc_percent(meta:get_int("content")) + meta:set_string("infotext", "Battery ("..percent.."%)") + if percent == 0 then + local node = minetest.get_node(pos) + node.name = "smartline:battery_empty" + minetest.swap_node(pos, node) + return false + end + return true +end + +local function register_battery(ext, percent, nici) + minetest.register_node("smartline:battery"..ext, { + description = "Battery "..ext, + inventory_image = 'smartline_battery_inventory.png', + wield_image = 'smartline_battery_inventory.png', + tiles = { + -- up, down, right, left, back, front + "smartline.png", + "smartline.png", + "smartline.png", + "smartline.png", + "smartline.png", + "smartline.png^smartline_battery_green.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) + meta:set_int("content", BATTERY_CAPACITY * percent) + local node = minetest.get_node(pos) + node.name = "smartline:battery" + minetest.swap_node(pos, node) + on_timer(pos, 1) + minetest.get_node_timer(pos):start(30) + end, + + on_timer = on_timer, + + + after_dig_node = function(pos, oldnode, oldmetadata, digger) + local percent = calc_percent(tonumber(oldmetadata.fields.content)) + local stack + if percent > 95 then + stack = ItemStack("smartline:battery") + elseif percent > 75 then + stack = ItemStack("smartline:battery75") + elseif percent > 50 then + stack = ItemStack("smartline:battery50") + elseif percent > 25 then + stack = ItemStack("smartline:battery25") + else + return + end + local inv = minetest.get_inventory({type="player", name=digger:get_player_name()}) + inv:add_item("main", stack) + end, + + paramtype = "light", + sunlight_propagates = true, + paramtype2 = "facedir", + groups = {choppy=1, cracky=1, crumbly=1, not_in_creative_inventory=nici}, + drop = "", + is_ground_content = false, + sounds = default.node_sound_stone_defaults(), + }) +end + +register_battery("", 1.0, 0) +register_battery("75", 0.75, 1) +register_battery("50", 0.5, 1) +register_battery("25", 0.25, 1) + +minetest.register_node("smartline:battery_empty", { + description = "Battery", + tiles = { + -- up, down, right, left, back, front + "smartline.png", + "smartline.png", + "smartline.png", + "smartline.png", + "smartline.png", + "smartline.png^smartline_battery_red.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) + meta:set_int("content", 0) + end, + + paramtype = "light", + sunlight_propagates = true, + paramtype2 = "facedir", + groups = {choppy=1, cracky=1, crumbly=1, not_in_creative_inventory=1}, + drop = "", + is_ground_content = false, + sounds = default.node_sound_stone_defaults(), +}) + + +if minetest.global_exists("moreores") then + minetest.register_craft({ + output = "smartline:battery 2", + recipe = { + {"", "moreores:silver_ingot", ""}, + {"", "default:copper_ingot", ""}, + {"", "moreores:silver_ingot", ""}, + } + }) +else + minetest.register_craft({ + output = "smartline:battery 2", + recipe = { + {"", "default:tin_ingot", ""}, + {"", "default:copper_ingot", ""}, + {"", "default:tin_ingot", ""}, + } + }) +end + diff --git a/smartline/icta/commands.lua b/smartline/icta/commands.lua new file mode 100644 index 0000000..b2606cb --- /dev/null +++ b/smartline/icta/commands.lua @@ -0,0 +1,481 @@ +--[[ + + ICTA Controller + =============== + + Part of the SmartLine mod + + Copyright (C) 2018 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + command.lua: + + Register all controller commands + +]]-- + +local sl = smartline + +function sl.operand(s) + if s == "is" then + return "== " + else + return "~= " + end +end + +-- '#' is used as placeholder for rule numbers and has to be escaped +function smartline.escape(s) + return s:gsub("#", '"..string.char(35).."') +end + + +smartline.icta_register_condition("once", { + title = "once", + formspec = { + { + type = "label", + name = "lbl", + label = "Hint: Once after start.", + }, + }, + -- Return two chunks of executable Lua code for the controller, according: + -- return , + code = function(data, environ) + return 'env.ticks', '== 1' + end, + button = function(data, environ) return "once" end, +}) + +smartline.icta_register_condition("true", { + title = "true", + formspec = { + { + type = "label", + name = "lbl", + label = "Hint: Condition is always true.", + }, + }, + code = function(data, environ) + return '"true"', '== "true"' + end, + button = function(data, environ) return "true" end, +}) + +smartline.icta_register_condition("condition", { + title = "Condition", + formspec = { + { + type = "textlist", + name = "condition", + label = "the action is executed, if", + choices = "condition 1,condition 2,condition 3,condition 4,condition 5,condition 6,condition 7,condition 8", + default = "", + }, + { + type = "textlist", + name = "operand", + choices = "was true, was not true", + default = "was true", + }, + { + type = "label", + name = "lbl", + label = "Hint: Execute two or several actions\nbased on one condition.", + }, + }, + code = function(data, environ) + local idx = data.condition:byte(-1) - 0x30 + local expected_result = "== false" + if data.operand == "was true" then + expected_result = "== true" + end + return "env.condition["..idx.."]", expected_result + end, + button = function(data, environ) return data.condition or "???" end, +}) + +smartline.icta_register_condition("input", { + title = "inputs", + formspec = { + { + type = "digits", + name = "number", + label = "input from node with number", + default = "", + }, + { + type = "textlist", + name = "operand", + choices = "is,is not", + default = "is", + }, + { + type = "textlist", + name = "value", + choices = "on,off,false", + default = "on", + }, + { + type = "label", + name = "lbl", + label = "Hint: An input is only available,\nif a nodes sends on/of\ncommands to the controller.", + }, + }, + button = function(data, environ) -- default button label + return 'input('..(data.number or "???")..')' + end, + code = function(data, environ) + return 'env.input["'..data.number..'"]', + sl.operand(data.operand)..'"'..data.value..'"' + end, +}) + +smartline.icta_register_condition("state", { + title = "node state request", + formspec = { + { + type = "digits", + name = "number", + label = "state from node with number", + default = "", + }, + { + type = "textlist", + name = "operand", + label = "", + choices = "is,is not", + default = "is", + }, + { + type = "textlist", + name = "value", + label = "", + choices = "stopped,running,standby,blocked,fault", + default = "stopped", + }, + { + type = "label", + name = "lbl", + label = "Hint: Read the state from another node.\nWorks for Pusher, Harvester, Quarry,\nand others.", + }, + }, + button = function(data, environ) -- default button label + return 'state('..(data.number or "???")..')' + end, + code = function(data, environ) + return 'tubelib.send_request("'..data.number..'", "state", "")', + sl.operand(data.operand)..'"'..data.value..'"' + end, +}) + +smartline.icta_register_condition("fuel", { + title = "fuel state request", + formspec = { + { + type = "digits", + name = "number", + label = "fuel state from node with number", + default = "", + }, + { + type = "textlist", + name = "operand", + label = "", + choices = "is,is not", + default = "is", + }, + { + type = "textlist", + name = "value", + label = "", + choices = "full,loaded,empty", + default = "full" + }, + { + type = "label", + name = "lbl", + label = "Hint: Read the fuel state from another node.\nWorks for Harvester and Quarry", + }, + }, + button = function(data, environ) + return 'fuel('..(data.number or "???")..')' + end, + code = function(data, environ) + return 'tubelib.send_request("'..data.number..'", "fuel", "")', + sl.operand(data.operand)..'"'..data.value..'"' + end, +}) + +smartline.icta_register_condition("signaltower", { + title = "Signal Tower state request", + formspec = { + { + type = "digits", + name = "number", + label = "state from Signal Tower with number", + default = "", + }, + { + type = "textlist", + name = "operand", + label = "", + choices = "is,is not", + default = "is", + }, + { + type = "textlist", + name = "value", + label = "", + choices = "off,green,amber,red", + default = "off", + }, + { + type = "label", + name = "lbl", + label = "Hint: Read the state from a Signal Tower.", + }, + }, + button = function(data, environ) -- default button label + return 'tower('..(data.number or "???")..')' + end, + code = function(data, environ) + return 'tubelib.send_request("'..data.number..'", "state", "")', + sl.operand(data.operand)..'"'..data.value..'"' + end, +}) + +smartline.icta_register_action("signaltower", { + title = "Signal Tower command", + formspec = { + { + type = "numbers", + name = "number", + label = "set Signal Tower with number", + default = "", + }, + { + type = "textlist", + name = "value", + label = "to color", + choices = "off,green,amber,red", + default = "red", + }, + { + type = "label", + name = "lbl", + label = "Hint: Turn on a lamp from a Signal Tower.", + }, + }, + button = function(data, environ) + return 'tower('..(data.value or "???")..')' + end, + code = function(data, environ) + local s = 'tubelib.send_message("%s", "%s", nil, "%s", nil)' + return string.format(s, data.number, environ.owner, data.value) + end, +}) + +smartline.icta_register_action("switch", { + title = "node on/off command", + formspec = { + { + type = "numbers", + name = "number", + label = "set node with number", + default = "", + }, + { + type = "textlist", + name = "value", + label = "to state", + choices = "on,off", + default = "on", + }, + { + type = "label", + name = "lbl", + label = "Hint: Used for pushers, lamps, machines, gates,...", + }, + }, + button = function(data, environ) + return 'turn('..(data.value or "???")..')' + end, + code = function(data, environ) + local s = 'tubelib.send_message("%s", "%s", nil, "%s", nil)' + return string.format(s, data.number, environ.owner, data.value) + end, +}) + +smartline.icta_register_action("display", { + title = "Display: overwrite one line", + formspec = { + { + type = "numbers", + name = "number", + label = "output to Display with number", + default = "", + }, + { + type = "textlist", + name = "row", + label = "Display line", + choices = "1,2,3,4,5,6,7,8,9", + default = "1", + }, + { + type = "ascii", + name = "text", + label = "the following text", + default = "", + }, + { + type = "label", + name = "lbl", + label = "Hint: Use a '*' character as reference to any\ncondition state", + }, + }, + code = function(data, environ) + local s1 = string.format('local text = string.gsub("%s", "*", env.result[#])', smartline.escape(data.text)) + local s2 = string.format('local payload = {row = %s, str = text}', data.row) + local s3 = string.format('tubelib.send_message("%s", "%s", nil, "row", payload)', data.number, environ.owner) + return s1.."\n\t"..s2.."\n\t"..s3 + end, + button = function(data, environ) + return "display("..(data.number or "???")..")" + end, +}) + +smartline.icta_register_action("cleardisplay", { + title = "Display: Clear screen", + formspec = { + { + type = "numbers", + name = "number", + label = "Display number", + default = "", + }, + }, + code = function(data, environ) + return 'tubelib.send_message("'..data.number..'", "'..environ.owner..'", nil, "clear", nil)' + end, + button = function(data, environ) + return "clear("..(data.number or "???")..")" + end, +}) + +smartline.icta_register_action("chat", { + title = "chat send", + formspec = { + { + type = "ascii", + name = "text", + label = "send the message", + default = "", + }, + { + type = "label", + name = "lbl", + label = "Hint: The chat message is send to the\nController owner, only.", + }, + }, + code = function(data, environ) + return 'minetest.chat_send_player("'..environ.owner..'", "[SmartLine Controller] '..data.text..'")' + end, + button = function(data, environ) + return "chat(...)" + end, +}) + +function smartline.icta_door_toggle(pos, owner, state) + pos = minetest.string_to_pos("("..pos..")") + if pos then + local door = doors.get(pos) + if door then + local player = { + get_player_name = function() return owner end, + is_player = function() return true end, + } + if state == "open" then + door:open(player) + elseif state == "close" then + door:close(player) + end + end + end +end + +smartline.icta_register_action("door", { + title = "doors open/close", + formspec = { + { + type = "digits", + name = "pos", + label = "door position like: 123,7,-1200", + default = "", + }, + { + type = "textlist", + name = "door_state", + label = "set", + choices = "open,close", + default = "open", + }, + { + type = "label", + name = "lbl1", + label = "For standard doors like the Steel Doors.", + }, + { + type = "label", + name = "lbl2", + label = "Hint: Use the Tubelib Programmer to\ndetermine the door position.", + }, + }, + code = function(data, environ) + return 'smartline.icta_door_toggle("'..data.pos..'", "'..environ.owner..'", "'..data.door_state..'")' + end, + button = function(data, environ) + return "door("..(data.door_state or "???")..")" + end, +}) + +function smartline.icta_player_detect(number, name) + local state = tubelib.send_request(number, "name", nil) + if (name == "*" and state ~= "") or state == name then + return state + end + return nil +end + +smartline.icta_register_condition("playerdetector", { + title = "Player Detector: name request", + formspec = { + { + type = "digits", + name = "number", + label = "name from player detector with number", + default = "", + }, + { + type = "ascii", + name = "name", + label = "is", + default = "", + }, + { + type = "label", + name = "lbl", + label = "Hint: Read and check the name\nfrom a Player Detector.\n Use a '*' character for all player names.", + }, + }, + + code = function(data, environ) + return 'smartline.icta_player_detect("'..data.number..'", "'..data.name..'")', "~= nil" + end, + button = function(data, environ) + return "detector()" + end, +}) diff --git a/smartline/icta/condition.lua b/smartline/icta/condition.lua new file mode 100644 index 0000000..4a2c92d --- /dev/null +++ b/smartline/icta/condition.lua @@ -0,0 +1,76 @@ +--[[ + + ICTA Controller + =============== + + Part of the SmartLine mod + + Copyright (C) 2018 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + condition.lua + +]]-- + +local sl = smartline + +-- tables with all data from condition registrations +local kvRegisteredCond = {} + +-- list of keys for conditions +local aCondTypes = {} + +-- list of titles for conditions +local aCondTitles = {} + +-- +-- API functions for condition registrations +-- +function sl.icta_register_condition(key, tData) + table.insert(aCondTypes, key) + table.insert(aCondTitles, tData.title) + if kvRegisteredCond[key] ~= nil then + print("[SmartLine] Condition registration error "..key) + return + end + kvRegisteredCond[key] = tData + for _,item in ipairs(tData.formspec) do + if item.type == "textlist" then + item.tChoices = string.split(item.choices, ",") + item.num_choices = #item.tChoices + end + end +end + +-- return formspec string +function sl.cond_formspec(row, kvSelect) + return sl.submenu_generate_formspec( + row, "cond", "Condition type", aCondTypes, aCondTitles, kvRegisteredCond, kvSelect) +end + +-- evaluate the row condition input +-- and return new data +function sl.cond_eval_input(kvSelect, fields) + kvSelect = sl.submenu_eval_input(kvRegisteredCond, aCondTypes, aCondTitles, kvSelect, fields) + return kvSelect +end + +-- return the Lua code +function sl.code_condition(kvSelect, environ) + if kvSelect and kvRegisteredCond[kvSelect.choice] then + if smartline.submenu_verify(kvRegisteredCond, kvSelect) then + return kvRegisteredCond[kvSelect.choice].code(kvSelect, environ) + end + end + return nil, nil +end + +sl.icta_register_condition("default", { + title = "", + formspec = {}, + code = function(data, environ) return false, false end, + button = function(data, environ) return "..." end, +}) + diff --git a/smartline/icta/controller.lua b/smartline/icta/controller.lua new file mode 100644 index 0000000..a885b01 --- /dev/null +++ b/smartline/icta/controller.lua @@ -0,0 +1,463 @@ +--[[ + + ICTA Controller + =============== + + Part of the SmartLine mod + + Copyright (C) 2018 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + controller.lua + +]]-- + +-- +-- Helper functions +-- +local function gen_table(size, val) + local tbl = {} + for idx = 1,size do + if type(val) == "table" then + tbl[idx] = table.copy(val) + else + tbl[idx] = val + end + end + return tbl +end + +local function integer(s, min, max) + if s and s ~= "" and s:find("^%d+$") then + local num = tonumber(s) + if num < min then num = min end + if num > max then num = max end + return num + end + return min +end + +local sOUTPUT = "Edit commands" +local Cache = {} +local FS_DATA = gen_table(smartline.NUM_RULES, {}) + + +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) + meta:set_string("formspec", smartline.formspecOutput(meta)) +end + +----------------- template ------------------------------- +-- -- Rule 1 +-- if env.blocked[1] == false and env.ticks % == 0 then +-- env.result[1] = +-- env.blocked[1] = env.result[1] +-- if env.blocked[1] then +-- env.timer[1] = env.ticks + +-- end +-- env.conditions[1] = env.blocked[1] +-- else +-- env.conditions[1] = false +-- end +-- if env.blocked[1] and env.timer[1] == env.ticks then +-- +-- env.blocked[1] = false +-- end + +-- cyclic execution +local TemplCyc = [[ +-- Rule # +if env.blocked[#] == false and env.ticks %% %s == 0 then + env.result[#] = %s + env.blocked[#] = env.result[#] %s + if env.blocked[#] then + env.timer[#] = env.ticks + %s + end + env.condition[#] = env.blocked[#] +else + env.condition[#] = false +end +if env.blocked[#] and env.timer[#] == env.ticks then + %s + env.blocked[#] = false +end +]] + +-- event based execution +local TemplEvt = [[ +-- Rule # +if env.blocked[#] == false and env.event then + env.result[#] = %s + env.blocked[#] = env.result[#] %s + if env.blocked[#] then + env.timer[#] = env.ticks + %s + end + env.condition[#] = env.blocked[#] +else + env.condition[#] = false +end +if env.blocked[#] and env.timer[#] == env.ticks then + %s + env.blocked[#] = false +end +]] + +-- generate the Lua code from the NUM_RULES rules +local function generate(pos, meta, environ) + local fs_data = minetest.deserialize(meta:get_string("fs_data")) or FS_DATA + -- chunks are compiled as vararg functions. Parameters are available via: local a, b, c = ... + local tbl = {"local env, output = ...\n"} + for idx = 1,smartline.NUM_RULES do + local cycle = integer(fs_data[idx].cycle, 0, 1000) + local cond, result = smartline.code_condition(fs_data[idx].cond, environ) + local after = integer(fs_data[idx].after, 0, 1000) + local actn = smartline.code_action(fs_data[idx].actn, environ) + -- valid rule? + if cycle and cond and after and actn then + -- add rule number + local s + if cycle == 0 then -- event? + s = string.format(TemplEvt, cond, result, after, actn) + else -- cyclic + s = string.format(TemplCyc, cycle, cond, result, after, actn) + end + -- add to list of rules + tbl[#tbl+1] = s:gsub("#", idx) + elseif cond ~= nil and actn == nil then + output(pos, "Error in action in rule "..idx) + elseif cond == nil and actn ~= nil then + output(pos, "Error in condition in rule "..idx) + end + end + return table.concat(tbl) +end + +local function runtime_environ(pos) + return { + ticks = 0, + pos = pos, + timer = gen_table(8, 0), + blocked = gen_table(8, false), + result = gen_table(8, false), + condition = gen_table(8, false), + input = {}, -- node number is key + } +end + +local function compile(pos, meta, number) + local gen_environ = { + meta = meta, + pos = pos, + number = number, + owner = meta:get_string("owner"), + } + local text = generate(pos, meta, gen_environ) + if text then + local code, err = loadstring(text) + if code then + Cache[number] = { + code = code, + env = runtime_environ(pos), + } + return true + else + output(pos, err) + return false + end + end + return false +end + +local function execute(pos, number, event) + local code = Cache[number].code + local env = Cache[number].env + if event then + env.event = true + else + env.event = false + env.ticks = env.ticks + 1 + end + local res, err = pcall(code, env, output) + if not res then + output(pos, err) + return false + end + return true +end + + +local function battery(pos) + local battery_pos = minetest.find_node_near(pos, 1, {"smartline:battery", "sl_controller: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, meta) + local number = meta:get_string("number") + if not battery(pos) then + meta:set_string("formspec", smartline.formspecError(meta)) + return false + end + + meta:set_string("output", "") + meta:set_int("cpu", 0) + + if compile(pos, meta, number) then + meta:set_int("state", tubelib.RUNNING) + minetest.get_node_timer(pos):start(1) + meta:set_string("formspec", smartline.formspecOutput(meta)) + meta:set_string("infotext", "Controller "..number..": running") + return true + end + return false +end + +local function stop_controller(pos, meta) + local number = meta:get_string("number") + meta:set_int("state", tubelib.STOPPED) + minetest.get_node_timer(pos):stop() + meta:set_string("infotext", "Controller "..number..": stopped") + local fs_data = minetest.deserialize(meta:get_string("fs_data")) or FS_DATA + meta:set_string("formspec", smartline.formspecRules(meta, fs_data, sOUTPUT)) +end + +local function no_battery(pos) + local meta = minetest.get_meta(pos) + local number = meta:get_string("number") + meta:set_int("state", tubelib.STOPPED) + minetest.get_node_timer(pos):stop() + meta:set_string("infotext", "Controller "..number..": No battery") + meta:set_string("formspec", smartline.formspecError(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 on_timer(pos, elapsed) + local meta = minetest.get_meta(pos) + local t = minetest.get_us_time() + local number = meta:get_string("number") + if Cache[number] or compile(pos, meta, number) then + local res = execute(pos, number, elapsed == -1) + if res then + t = minetest.get_us_time() - t + if not update_battery(meta, t) then + no_battery(pos) + return false + end + end + --print("on_timer", t) + return res + end + return false +end + +local function on_receive_fields(pos, formname, fields, player) + local meta = minetest.get_meta(pos) + local owner = meta:get_string("owner") + if not player or not player:is_player() then + return + end + local readonly = player:get_player_name() ~= owner + + --print("fields", dump(fields)) + if fields.quit then -- cancel button + return + end + if fields.notes then -- notes tab? + meta:set_string("notes", fields.notes) + end + if fields.go then + if not readonly then + local fs_data = minetest.deserialize(meta:get_string("fs_data")) or FS_DATA + local output = smartline.edit_command(fs_data, fields.cmnd) + stop_controller(pos, meta) + meta:set_string("formspec", smartline.formspecRules(meta, fs_data, output)) + meta:set_string("fs_data", minetest.serialize(fs_data)) + end + end + if fields._type_ == "main" then + if not readonly then + smartline.store_main_form_data(meta, fields) + end + local key = smartline.main_form_button_pressed(fields) + if key then + -- store data before going into sub-menu + meta:set_string("fs_old", meta:get_string("fs_data")) + meta:set_string("formspec", smartline.formspecSubMenu(meta, key)) + end + elseif fields._col_ == "cond" then + smartline.cond_formspec_update(meta, fields) + elseif fields._col_ == "actn" then + smartline.actn_formspec_update(meta, fields) + end + if fields._exit_ == "ok" then -- exit from sub-menu? + if fields._button_ then + smartline.formspec_button_update(meta, fields) + end + -- simulate tab selection + fields.tab = "1" + elseif fields._cancel_ == "cancel" then -- abort from sub-menu? + -- restore old data + meta:set_string("fs_data", meta:get_string("fs_old")) + -- simulate tab selection + fields.tab = "1" + elseif fields.save == "Save" then -- abort from sub-menu? + -- store as old data + meta:set_string("fs_old", meta:get_string("fs_data")) + -- simulate tab selection + fields.tab = "1" + elseif fields.sb_help then + local evt = minetest.explode_scrollbar_event(fields.sb_help) + meta:set_string("formspec", smartline.formspecHelp(evt.value)) + end + if fields.update then + meta:set_string("formspec", smartline.formspecOutput(meta)) + elseif fields.clear then + meta:set_string("output", "") + meta:set_string("formspec", smartline.formspecOutput(meta)) + elseif fields.tab == "1" then + local fs_data = minetest.deserialize(meta:get_string("fs_data")) or FS_DATA + meta:set_string("formspec", smartline.formspecRules(meta, fs_data, sOUTPUT)) + elseif fields.tab == "2" then + meta:set_string("formspec", smartline.formspecOutput(meta)) + elseif fields.tab == "3" then + meta:set_string("formspec", smartline.formspecNotes(meta)) + elseif fields.tab == "4" then + meta:set_string("formspec", smartline.formspecHelp(1)) + elseif fields.start == "Start" then + local environ = { + meta = meta, + pos = pos, + number = meta:get_string("number"), + owner = meta:get_string("owner"), + } + --print("CODE:", generate(pos, meta, environ)) + start_controller(pos, meta) + minetest.log("action", player:get_player_name() .. + " starts the smartline_controller2 at ".. minetest.pos_to_string(pos)) + elseif fields.stop == "Stop" then + stop_controller(pos, meta) + end +end + +minetest.register_node("smartline:controller2", { + description = "SmartLine Controller", + inventory_image = "smartline_controller_inventory.png", + wield_image = "smartline_controller_inventory.png", + stack_max = 1, + tiles = { + -- up, down, right, left, back, front + "smartline.png", + "smartline.png", + "smartline.png", + "smartline.png", + "smartline.png", + "smartline.png^smartline_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 = tubelib.add_node(pos, "smartline:controller2") + local fs_data = FS_DATA + meta:set_string("fs_data", minetest.serialize(fs_data)) + meta:set_string("owner", placer:get_player_name()) + meta:set_string("number", number) + meta:set_int("state", tubelib.STOPPED) + meta:set_string("formspec", smartline.formspecRules(meta, fs_data, sOUTPUT)) + --meta:set_string("formspec", smartline.cond_formspec(1, 1, nil)) + meta:set_string("infotext", "SmartLine 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) + tubelib.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 = "smartline:controller2", +-- recipe = { +-- {"", "default:mese_crystal", ""}, +-- {"dye:blue", "default:copper_ingot", "tubelib:wlanchip"}, +-- {"", "default:mese_crystal", ""}, +-- }, +--}) + +-- write inputs from remote nodes +local function set_input(pos, own_number, rmt_number, val) + if rmt_number then + if Cache[own_number] and Cache[own_number].env.input then + Cache[own_number].env.input[rmt_number] = val + -- only two events per second + local t = minetest.get_us_time() + if not Cache[own_number].last_event or Cache[own_number].last_event < t then + minetest.after(0.01, on_timer, pos, -1) + Cache[own_number].last_event = t + 500000 -- add 500 ms + end + end + end +end + +tubelib.register_node("smartline:controller2", {}, { + 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 == "state" then + local state = meta:get_int("state") + return tubelib.statestring(state) + else + return "unsupported" + end + end, +}) + diff --git a/smartline/icta/edit.lua b/smartline/icta/edit.lua new file mode 100644 index 0000000..3e04ccb --- /dev/null +++ b/smartline/icta/edit.lua @@ -0,0 +1,40 @@ +--[[ + + ICTA Controller + =============== + + Copyright (C) 2018 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + edit.lua + +]]-- + +function smartline.edit_command(fs_data, text) + local cmnd, pos1, pos2 = text:match('^(%S)%s(%d+)%s(%d+)$') + if pos2 == nil then + cmnd, pos1 = text:match('^(%S)%s(%d+)$') + end + if cmnd and pos1 and pos2 then + pos1 = math.max(1, math.min(pos1, smartline.NUM_RULES)) + pos2 = math.max(1, math.min(pos2, smartline.NUM_RULES)) + + if cmnd == "x" then + local temp = fs_data[pos1] + fs_data[pos1] = fs_data[pos2] + fs_data[pos2] = temp + return "rows "..pos1.." and "..pos2.." exchanged" + end + if cmnd == "c" then + fs_data[pos2] = table.copy(fs_data[pos1]) + return "row "..pos1.." copied to "..pos2 + end + elseif cmnd == "d" and pos1 then + pos1 = math.max(1, math.min(pos1, smartline.NUM_RULES)) + fs_data[pos1] = {} + return "row "..pos1.." deleted" + end + return "Invalid command '"..text.."'" +end diff --git a/smartline/icta/formspec.lua b/smartline/icta/formspec.lua new file mode 100644 index 0000000..dd49afb --- /dev/null +++ b/smartline/icta/formspec.lua @@ -0,0 +1,360 @@ +--[[ + + SmartLine + ========= + + Part of the SmartLine mod + + Copyright (C) 2018 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + formspec.lua: + + controller formspecs +]]-- + +smartline.NUM_RULES = 8 + +local SIZE = "size[13,8]" + +local sHELP = [[SmartLine Controller Help + +Control other nodes by means of rules like: + IF THEN + +These rules allow to execute actions based on conditions. +Examples for conditions are: + - the Player Detector detects a player + - a button is pressed + - a node state is fault, blocked, standby,... + +Actions are: + - switch on/off tubelib nodes, like lamps, machines + - send chat messages to the owner + - output a text message to the display + +The controller executes all rules cyclically. +The cycle time for each rule is configurable +(1..1000 sec). +0 means, the rule will only be called, when +the controller received a command from +another node, like buttons. + +Actions can be deleyed. Therefore, the +after value can be set (0..1000 sec). + +Edit command examples: + - 'x 1 8' exchange rows 1 with row 8 + - 'c 1 2' copy row 1 to 2 + - 'd 3' delete row 3 + +The 'outp' tab is for debugging outputs via 'print' +The 'notes' tab for your notes. + +The controller needs battery power to work. +The battery pack has to be placed near the +controller (1 node distance). +The needed battery power is directly dependent +on the CPU time the controller consumes. + +For more information, see: goo.gl/fF5ap6 +]] + +-- to simplify the search for a pressed main form button (condition/action) +local lButtonKeys = {} + +for idx = 1,smartline.NUM_RULES do + lButtonKeys[#lButtonKeys+1] = "cond"..idx + lButtonKeys[#lButtonKeys+1] = "actn"..idx +end + +local function buttons(s) + return "button_exit[7.4,7.5;1.8,1;cancel;Cancel]".. + "button[9.3,7.5;1.8,1;save;Save]".. + "button[11.2,7.5;1.8,1;"..s.."]" +end + +function smartline.formspecError(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[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 button(data) + if data then + return data.button + else + return "..." + end +end + +local function formspec_rules(fs_data) + local tbl = {"field[0,0;0,0;_type_;;main]".. + "label[0.8,0;Cycle/s:]label[2.8,0;IF cond:]label[7,0;THEN action:]label[11.4,0;after/s:]"} + + for idx = 1,smartline.NUM_RULES do + local ypos = idx * 0.75 - 0.4 + tbl[#tbl+1] = "label[0,"..(0.2+ypos)..";"..idx.."]" + tbl[#tbl+1] = "field[0.9,"..(0.3+ypos)..";1.8,1;cycle"..idx..";;"..(fs_data[idx].cycle or "").."]" + tbl[#tbl+1] = "button[2.5,"..ypos..";4.3,1;cond"..idx..";"..button(fs_data[idx].cond).."]" + tbl[#tbl+1] = "button[6.8,"..ypos..";4.3,1;actn"..idx..";"..button(fs_data[idx].actn).."]" + tbl[#tbl+1] = "field[11.4,"..(0.3+ypos)..";1.8,1;after"..idx..";;"..(fs_data[idx].after or "").."]" + end + return table.concat(tbl) +end + +function smartline.store_main_form_data(meta, fields) + local fs_data = minetest.deserialize(meta:get_string("fs_data")) + for idx = 1,smartline.NUM_RULES do + fs_data[idx].cycle = fields["cycle"..idx] or "" + fs_data[idx].after = fields["after"..idx] or "0" + end + meta:set_string("fs_data", minetest.serialize(fs_data)) +end + +function smartline.main_form_button_pressed(fields) + for _,key in ipairs(lButtonKeys) do + if fields[key] then + return key + end + end + return nil +end + +function smartline.formspecSubMenu(meta, key) + local fs_data = minetest.deserialize(meta:get_string("fs_data")) + if key:sub(1,4) == "cond" then + local row = tonumber(key:sub(5,5)) + return smartline.cond_formspec(row, fs_data[row].cond) + else + local row = tonumber(key:sub(5,5)) + return smartline.actn_formspec(row, fs_data[row].actn) + end +end + +function smartline.formspec_button_update(meta, fields) + local fs_data = minetest.deserialize(meta:get_string("fs_data")) + local row = tonumber(fields._row_ or 1) + print("row", row) + if fields._col_ == "cond" then + fs_data[row].cond = smartline.cond_eval_input(fs_data[row].cond, fields) + elseif fields._col_ == "actn" then + fs_data[row].actn = smartline.actn_eval_input(fs_data[row].actn, fields) + end + meta:set_string("fs_data", minetest.serialize(fs_data)) +end + +function smartline.cond_formspec_update(meta, fields) + local fs_data = minetest.deserialize(meta:get_string("fs_data")) + local row = tonumber(fields._row_ or 1) + fs_data[row].cond = smartline.cond_eval_input(fs_data[row].cond, fields) + meta:set_string("formspec", smartline.cond_formspec(row, fs_data[row].cond)) + meta:set_string("fs_data", minetest.serialize(fs_data)) +end + +function smartline.actn_formspec_update(meta, fields) + local fs_data = minetest.deserialize(meta:get_string("fs_data")) + local row = tonumber(fields._row_ or 1) + fs_data[row].actn = smartline.actn_eval_input(fs_data[row].actn, fields) + meta:set_string("formspec", smartline.actn_formspec(row, fs_data[row].actn)) + meta:set_string("fs_data", minetest.serialize(fs_data)) +end + + +function smartline.formspecRules(meta, fs_data, output) + 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.. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "tabheader[0,0;tab;rules,outp,notes,help;1;;true]".. + formspec_rules(fs_data).. + "label[0.2,7.0;"..output.."]".. + "field[0.3,7.8;4,1;cmnd;;]".. + "button[4.0,7.5;1.5,1;go;GO]".. + buttons(cmnd) +end + +function smartline.formspecOutput(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.. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "tabheader[0,0;tab;rules,outp,notes,help;2;;true]".. + "textarea[0.3,0.2;13,8.3;output;Output:;"..output.."]".. + "button[7.4,7.5;1.8,1;clear;Clear]".. + "button[9.3,7.5;1.8,1;update;Update]".. + "button[11.2,7.5;1.8,1;"..cmnd.."]" +end + +function smartline.formspecNotes(meta) + local running = meta:get_int("state") == tubelib.RUNNING + local cmnd = running and "stop;Stop" or "start;Start" + local notes = meta:get_string("notes") + if notes == "" then notes = "" end + notes = minetest.formspec_escape(notes) + return SIZE.. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "tabheader[0,0;tab;rules,outp,notes,help;3;;true]".. + "textarea[0.3,0.2;13,8.3;notes;Notepad:;"..notes.."]".. + buttons(cmnd) +end + +function smartline.formspecHelp(offs) + return SIZE.. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "tabheader[0,0;tab;rules,outp,notes,help;4;;true]".. + "field[0,0;0,0;_type_;;help]".. + "label[0,"..(-offs/50)..";"..sHELP.."]".. + --"label[0.2,0;test]".. + "scrollbar[12,1;0.5,7;vertical;sb_help;"..offs.."]" +end + + + +--local function my_on_receive_fields(pos, formname, fields, player) +-- local meta = minetest.get_meta(pos) +-- local owner = meta:get_string("owner") +-- local state = meta:get_int("state") +-- if not player or not player:is_player() then +-- return +-- end +-- local fs_data = minetest.deserialize(meta:get_string("fs_data")) or {} +-- local output = "" +-- local readonly = player:get_player_name() ~= owner + +-- print("fields", dump(fields)) + +-- -- FIRST: test if command entered? +-- if fields.ok then +-- if not readonly then +-- output = edit_command(fs_data, fields.cmnd) +-- smartline.stop_controller(pos, fs_data) +-- meta:set_string("formspec", formspec_main(tubelib.STOPPED, fs_data, output)) +-- meta:set_string("fs_data", minetest.serialize(fs_data)) +-- end +-- -- SECOND: eval none edit events (events based in __type__)? +-- elseif fields.help then +-- meta:set_string("formspec", formspec_help(1)) +---- elseif fields.state then +---- meta:set_string("formspec", formspec_state(meta, fs_data)) +---- elseif fields.update then +---- meta:set_string("formspec", formspec_state(meta, fs_data)) +-- elseif fields._cancel_ then +-- fs_data = minetest.deserialize(meta:get_string("fs_old")) +-- meta:set_string("formspec", formspec_main(state, fs_data, sOUTPUT)) +-- elseif fields.close then +-- meta:set_string("formspec", formspec_main(state, fs_data, sOUTPUT)) +---- elseif fields.sb_help then +---- local evt = minetest.explode_scrollbar_event(fields.sb_help) +---- if evt.type == "CHG" then +---- meta:set_string("formspec", formspec_help(evt.value)) +---- end +---- elseif fields.button then +---- if not readonly then +---- local number = meta:get_string("number") +---- local state = meta:get_int("state") +---- if state == tubelib.RUNNING then +---- smartline.stop_controller(pos, fs_data) +---- meta:set_string("formspec", formspec_main(tubelib.STOPPED, fs_data, sOUTPUT)) +---- else +---- formspec2runtime_rule(number, owner, fs_data) +---- start_controller(pos, number, fs_data) +---- meta:set_string("formspec", formspec_main(tubelib.RUNNING, fs_data, sOUTPUT)) +---- end +---- end +---- -- THIRD: evaluate edit events from sub-menus +-- elseif fields._col_ == "cond" then +-- local row = tonumber(fields._row_ or 1) +-- fs_data["cond"..row] = smartline.cond_eval_input(fs_data["cond"..row], fields) +-- meta:set_string("formspec", smartline.cond_formspec(row, fs_data["cond"..row])) +-- meta:set_string("fs_data", minetest.serialize(fs_data)) +-- elseif fields._type_ == "main" then +-- fs_data = eval_formspec_main(meta, fs_data, fields, readonly) +-- meta:set_string("fs_data", minetest.serialize(fs_data)) +---- elseif fields._type_ == "label" then +---- fs_data = eval_formspec_label(meta, fs_data, fields, readonly) +---- meta:set_string("fs_data", minetest.serialize(fs_data)) +---- elseif fields._type_ == "cond" then +---- fs_data = eval_formspec_cond(meta, fs_data, fields, readonly) +---- meta:set_string("fs_data", minetest.serialize(fs_data)) +---- elseif fields._type_ == "oprnd" then +---- fs_data = eval_formspec_oprnd(meta, fs_data, fields, readonly) +---- meta:set_string("fs_data", minetest.serialize(fs_data)) +---- elseif fields._type_ == "actn" then +---- fs_data = eval_formspec_actn(meta, fs_data, fields, readonly) +---- meta:set_string("fs_data", minetest.serialize(fs_data)) +---- elseif fields._type_ == "help" then +---- meta:set_string("formspec", formspec_main(state, fs_data, sOUTPUT)) +---- elseif fields._type_ == "state" then +---- meta:set_string("formspec", formspec_main(state, fs_data, sOUTPUT)) +-- end +-- -- FOURTH: back to main menu +-- if fields._exit_ then +-- meta:set_string("formspec", formspec_main(state, fs_data, sOUTPUT)) +-- end +--end + +--function smartline.on_receive_fields(pos, formname, fields, player) +-- local meta = minetest.get_meta(pos) +-- local owner = meta:get_string("owner") +-- if not player or not player:is_player() then +-- return +-- end +-- local readonly = player:get_player_name() ~= owner + +-- print("fields", dump(fields)) + +-- if fields.cancel == nil then +-- if fields.rules then +-- --meta:set_string("rules", fields.rules) +-- meta:set_string("formspec", smartline.formspecRules(meta)) +-- elseif fields.notes then +-- meta:set_string("notes", fields.notes) +-- meta:set_string("formspec", formspecNotes(meta)) +-- end +-- end + +-- if fields.update then +-- meta:set_string("formspec", formspecOutput(meta)) +-- elseif fields.clear then +-- meta:set_string("output", "") +-- meta:set_string("formspec", formspecOutput(meta)) +-- elseif fields.tab == "1" then +-- meta:set_string("formspec", smartline.formspecRules(meta)) +-- elseif fields.tab == "2" then +-- meta:set_string("formspec", formspecOutput(meta)) +-- elseif fields.tab == "3" then +-- meta:set_string("formspec", formspecNotes(meta)) +-- elseif fields.tab == "4" then +-- meta:set_string("formspec", formspecHelp(1)) +-- 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) +-- end +--end + + diff --git a/smartline/icta/submenu.lua b/smartline/icta/submenu.lua new file mode 100644 index 0000000..ab77820 --- /dev/null +++ b/smartline/icta/submenu.lua @@ -0,0 +1,197 @@ +--[[ + + ICTA Controller + =============== + + Part of the SmartLine mod + + Copyright (C) 2018 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + submenu.lua + + A sub-menu control to generate a formspec sting for conditions and actions +]]-- + +local sl = smartline + +local function index(list, x) + for idx, v in ipairs(list) do + if v == x then return idx end + end + return nil +end + +-- generate the choice dependent part of the form +local function add_controls_to_table(tbl, kvDefinition, kvSelect) + local val = "" + local offs = 1.4 + if kvDefinition[kvSelect.choice] then + local lControls = kvDefinition[kvSelect.choice].formspec + for idx,elem in ipairs(lControls) do + if elem.label and elem.label ~= "" then + tbl[#tbl+1] = "label[0,"..offs..";"..elem.label.."]" + offs = offs + 0.4 + end + if elem.type == "numbers" or elem.type == "digits" or elem.type == "letters" + or elem.type == "ascii" then + val = kvSelect[elem.name] or elem.default + tbl[#tbl+1] = "field[0.3,"..(offs+0.2)..";8,1;"..elem.name..";;"..val.."]" + offs = offs + 1 + elseif elem.type == "textlist" then + local l = elem.choices:split(",") + val = index(l, kvSelect[elem.name]) or elem.default + tbl[#tbl+1] = "dropdown[0.0,"..(offs)..";8.5,1.4;"..elem.name..";"..elem.choices..";"..val.."]" + offs = offs + 1 + end + end + end + return tbl +end + +local function default_data(kvDefinition, kvSelect) + local lControls = kvDefinition[kvSelect.choice].formspec + for idx,elem in ipairs(lControls) do + kvSelect[elem.name] = elem.default + end + kvSelect.button = kvDefinition[kvSelect.choice].button(kvSelect) + return kvSelect +end + +-- Copy field/formspec data to the table kvSelect +-- kvDefinition: submenu formspec definition +-- kvSelect: form data +-- fields: formspec input +local function field_to_kvSelect(kvDefinition, kvSelect, fields) + local error = false + local lControls = kvDefinition[kvSelect.choice].formspec + for idx,elem in ipairs(lControls) do + if elem.type == "numbers" then + if fields[elem.name] then + if fields[elem.name]:find("^[%d ]+$") then + kvSelect[elem.name] = fields[elem.name] + else + kvSelect[elem.name] = elem.default + error = true + end + end + elseif elem.type == "digits" then -- including positions + if fields[elem.name] then + if fields[elem.name]:find("^[+%%-,%d]+$") then + kvSelect[elem.name] = fields[elem.name] + else + kvSelect[elem.name] = elem.default + error = true + end + end + elseif elem.type == "letters" then + if fields[elem.name] then + if fields[elem.name]:find("^[+-]?%a+$") then + kvSelect[elem.name] = fields[elem.name] + else + kvSelect[elem.name] = elem.default + error = true + end + end + elseif elem.type == "ascii" then + if fields[elem.name] then + kvSelect[elem.name] = fields[elem.name] + end + elseif elem.type == "textlist" then + if fields[elem.name] ~= nil then + kvSelect[elem.name] = fields[elem.name] + end + end + end + -- store user input of button text + if fields._button_ then + kvSelect._button_ = fields._button_ + end + -- select button text + if error then + kvSelect.button = "invalid" + elseif kvSelect._button_ and kvSelect._button_ ~= "" then + kvSelect.button = kvSelect._button_ + else + kvSelect.button = kvDefinition[kvSelect.choice].button(kvSelect) + end + return kvSelect +end + +function smartline.submenu_verify(kvDefinition, kvSelect) + local error = false + local lControls = kvDefinition[kvSelect.choice].formspec + for idx,elem in ipairs(lControls) do + if elem.type == "numbers" then + if not kvSelect[elem.name]:find("^[%d ]+$") then + error = true + end + elseif elem.type == "digits" then -- including positions + if not kvSelect[elem.name]:find("^[+%%-,%d]+$") then + error = true + end + elseif elem.type == "letters" then + if not kvSelect[elem.name]:find("^[+-]?%a+$") then + error = true + end + elseif elem.type == "ascii" then + if kvSelect[elem.name] == "" or kvSelect[elem.name] == nil then + error = true + end + elseif elem.type == "textlist" then + if kvSelect[elem.name] == "" or kvSelect[elem.name] == nil then + error = true + end + end + end + return (error == false) +end + +-- generate a formspec string from the given control definition +-- row, col: numbers to identify the control +-- title: Title text for the control +-- lKeys: list of keywords of selected choices according to fields +-- lChoice: list of possible choices for the control +-- kvDefinition: definitions of the choice dependent controls +-- kvSelect: data of the last selected item {choice, number, value, ...} +function smartline.submenu_generate_formspec(row, col, title, lKeys, lChoice, kvDefinition, kvSelect) + if kvSelect == nil or next(kvSelect) == nil then + kvSelect = {choice = "default"} + end + local tbl = {"size[8.2,9]".. + --default.gui_bg.. TODO + --default.gui_bg_img.. + --default.gui_slots.. + "field[0,0;0,0;_row_;;"..row.."]".. + "field[0,0;0,0;_col_;;"..col.."]"} + + local sChoice = table.concat(lChoice, ",") + local idx = index(lKeys, kvSelect.choice) or 1 + tbl[#tbl+1] = "label[0,0;"..title..":]" + tbl[#tbl+1] = "dropdown[0,0.5;8.5,1;choice;"..sChoice..";"..idx.."]" + tbl = add_controls_to_table(tbl, kvDefinition, kvSelect) + tbl[#tbl+1] = "field[0.2,8.7;4,1;_button_;Button text;"..(kvSelect._button_ or "").."]" + tbl[#tbl+1] = "button[4,8.4;2,1;_cancel_;cancel]" + tbl[#tbl+1] = "button[6,8.4;2,1;_exit_;ok]" + return table.concat(tbl) +end + + +-- return the selected and configured menu item based on user inputs (fields) +function smartline.submenu_eval_input(kvDefinition, lKeys, lChoice, kvSelect, fields) + -- determine selected choice + if fields.choice then + -- load with default values + local idx = index(lChoice, fields.choice) or 1 + kvSelect = {choice = lKeys[idx]} + kvSelect = default_data(kvDefinition, kvSelect) + kvSelect = field_to_kvSelect(kvDefinition, kvSelect, fields) + else + -- add real data + kvSelect = field_to_kvSelect(kvDefinition, kvSelect, fields) + end + return kvSelect +end + diff --git a/smartline/init.lua b/smartline/init.lua index 5833724..775ee8c 100644 --- a/smartline/init.lua +++ b/smartline/init.lua @@ -10,15 +10,29 @@ ]]-- +smartline = {} + +local MP = minetest.get_modpath("smartline") + if minetest.get_modpath("display_lib") and display_lib ~= nil and minetest.get_modpath("font_lib") and font_lib ~= nil then - dofile(minetest.get_modpath("smartline") .. "/display.lua") + dofile(MP.."/display.lua") end -dofile(minetest.get_modpath("smartline") .. "/button.lua") -dofile(minetest.get_modpath("smartline") .. "/signaltower.lua") -dofile(minetest.get_modpath("smartline") .. "/playerdetector.lua") -dofile(minetest.get_modpath("smartline") .. "/sequencer.lua") -dofile(minetest.get_modpath("smartline") .. "/timer.lua") -dofile(minetest.get_modpath("smartline") .. "/repeater.lua") -dofile(minetest.get_modpath("smartline") .. "/controller.lua") -dofile(minetest.get_modpath("smartline") .. "/commands.lua") \ No newline at end of file +dofile(MP.."/button.lua") +dofile(MP.."/signaltower.lua") +dofile(MP.."/playerdetector.lua") +dofile(MP.."/sequencer.lua") +dofile(MP.."/timer.lua") +dofile(MP.."/repeater.lua") +dofile(MP.."/controller.lua") +dofile(MP.."/commands.lua") +-- ICTA Controller +dofile(MP.."/icta/submenu.lua") +dofile(MP.."/icta/condition.lua") +dofile(MP.."/icta/action.lua") +dofile(MP.."/icta/formspec.lua") +dofile(MP.."/icta/controller.lua") +dofile(MP.."/icta/commands.lua") +dofile(MP.."/icta/edit.lua") +dofile(MP.."/icta/battery.lua") +dofile(MP.."/icta/balancer.lua") diff --git a/smartline/textures/smartline_battery_green.png b/smartline/textures/smartline_battery_green.png new file mode 100644 index 0000000..b66821f Binary files /dev/null and b/smartline/textures/smartline_battery_green.png differ diff --git a/smartline/textures/smartline_battery_inventory.png b/smartline/textures/smartline_battery_inventory.png new file mode 100644 index 0000000..c713955 Binary files /dev/null and b/smartline/textures/smartline_battery_inventory.png differ diff --git a/smartline/textures/smartline_battery_red.png b/smartline/textures/smartline_battery_red.png new file mode 100644 index 0000000..fb194d9 Binary files /dev/null and b/smartline/textures/smartline_battery_red.png differ diff --git a/tubelib_addons1/depends.txt b/tubelib_addons1/depends.txt index 50305c5..f20452d 100644 --- a/tubelib_addons1/depends.txt +++ b/tubelib_addons1/depends.txt @@ -1,5 +1,6 @@ tubelib default +bucket stairs? moreores? farming? diff --git a/tubelib_addons1/init.lua b/tubelib_addons1/init.lua index dc201d5..c24b7ef 100644 --- a/tubelib_addons1/init.lua +++ b/tubelib_addons1/init.lua @@ -20,4 +20,5 @@ dofile(minetest.get_modpath("tubelib_addons1") .. '/reformer.lua') dofile(minetest.get_modpath("tubelib_addons1") .. '/funnel.lua') dofile(minetest.get_modpath("tubelib_addons1") .. "/pusher_fast.lua") dofile(minetest.get_modpath("tubelib_addons1") .. "/detector.lua") -dofile(minetest.get_modpath("tubelib_addons1") .. '/chest.lua') \ No newline at end of file +dofile(minetest.get_modpath("tubelib_addons1") .. '/chest.lua') +dofile(minetest.get_modpath("tubelib_addons1") .. '/liquidsampler.lua') \ No newline at end of file diff --git a/tubelib_addons1/liquidsampler.lua b/tubelib_addons1/liquidsampler.lua new file mode 100644 index 0000000..01a769f --- /dev/null +++ b/tubelib_addons1/liquidsampler.lua @@ -0,0 +1,308 @@ +--[[ + + Tubelib Addons 1 + ================ + + Copyright (C) 2017,2018 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + liquidsampler.lua + +]]-- + +local CYCLE_TIME = 8 + +local function get_pos(pos, facedir, side) + local offs = {F=0, R=1, B=2, L=3, D=4, U=5} + local dst_pos = table.copy(pos) + facedir = (facedir + offs[side]) % 4 + local dir = minetest.facedir_to_dir(facedir) + return vector.add(dst_pos, dir) +end + + +local function test_liquid(node) + local liquiddef = bucket.liquids[node.name] + if liquiddef ~= nil and liquiddef.itemname ~= nil and + node.name == liquiddef.source then + return liquiddef.itemname + end +end + +local function sample_liquid(pos, meta) + local giving_back = test_liquid(minetest.get_node(pos)) + if giving_back then + local inv = meta:get_inventory() + if inv:room_for_item("dst", ItemStack(giving_back)) and + inv:contains_item("src", ItemStack("bucket:bucket_empty")) then + minetest.remove_node(pos) + inv:remove_item("src", ItemStack("bucket:bucket_empty")) + inv:add_item("dst", ItemStack(giving_back)) + return true -- success + else + return nil -- standby + end + else + return false -- fault + end +end + +local function formspec(meta, state) + return "size[9,8.5]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "list[context;src;0,0;1,4;]".. + "image[0,0;1,1;bucket.png]".. + "image[1,1;1,1;tubelib_gui_arrow.png]".. + "image_button[1,3;1,1;".. tubelib.state_button(state) ..";button;]".. + "list[context;dst;2,0;7,4;]".. + "list[current_player;main;0.5,4.5;8,4;]".. + "listring[current_player;main]".. + "listring[context;src]" .. + "listring[current_player;main]".. + "listring[context;dst]" .. + "listring[current_player;main]" +end + +local function switch_on(pos, node) + local meta = minetest.get_meta(pos) + local number = meta:get_string("number") + meta:set_int("running", tubelib.STATE_RUNNING) + meta:set_string("infotext", "Liquid Sampler "..number..": running") + meta:set_string("formspec", formspec(meta, tubelib.RUNNING)) + node.name = "tubelib_addons1:liquidsampler_active" + minetest.swap_node(pos, node) + minetest.get_node_timer(pos):start(CYCLE_TIME) + return false +end + +local function switch_off(pos, node) + local meta = minetest.get_meta(pos) + local number = meta:get_string("number") + meta:set_int("running", tubelib.STATE_STOPPED) + meta:set_string("infotext", "Liquid Sampler "..number..": stopped") + meta:set_string("formspec", formspec(meta, tubelib.STOPPED)) + node.name = "tubelib_addons1:liquidsampler" + minetest.swap_node(pos, node) + minetest.get_node_timer(pos):stop() + return false +end + +local function goto_fault(pos, node) + local meta = minetest.get_meta(pos) + local number = meta:get_string("number") + meta:set_int("running", tubelib.STATE_FAULT) + meta:set_string("infotext", "Liquid Sampler "..number..": fault") + meta:set_string("formspec", formspec(meta, tubelib.FAULT)) + node.name = "tubelib_addons1:liquidsampler" + minetest.swap_node(pos, node) + minetest.get_node_timer(pos):start(20) + return false +end + +local function goto_standby(pos, node) + local meta = minetest.get_meta(pos) + local number = meta:get_string("number") + meta:set_int("running", tubelib.STATE_STANDBY) + meta:set_string("infotext", "Liquid Sampler "..number..": standby") + meta:set_string("formspec", formspec(meta, tubelib.STANDBY)) + node.name = "tubelib_addons1:liquidsampler" + minetest.swap_node(pos, node) + minetest.get_node_timer(pos):start(20) + return false +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 + 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 + return stack:get_count() +end + +local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player) + if minetest.is_protected(pos, player:get_player_name()) then + return 0 + end + return count +end + +local function on_receive_fields(pos, formname, fields, sender) + if minetest.is_protected(pos, sender:get_player_name()) then + return + end + local meta = minetest.get_meta(pos) + local node = minetest.get_node(pos) + local running = meta:get_int("running") + if fields.button ~= nil then + if running == tubelib.STATE_RUNNING then + switch_off(pos, node) + meta:set_int("running", tubelib.STATE_STOPPED) + else + meta:set_int("running", tubelib.STATE_RUNNING) + switch_on(pos, node) + end + end +end + +local function keep_running(pos, elapsed) + local meta = minetest.get_meta(pos) + local running = meta:get_int("running") + local water_pos = minetest.string_to_pos(meta:get_string("water_pos")) + local res = sample_liquid(water_pos, meta) + + if res == nil then + local node = minetest.get_node(pos) + return goto_standby(pos, node) + elseif res == true then + if running <= 0 then + local node = minetest.get_node(pos) + return switch_on(pos, node) + end + elseif res == false then + if running > 0 then + local node = minetest.get_node(pos) + return goto_fault(pos, node) + end + end + meta:set_int("running", running) + return true +end + +minetest.register_node("tubelib_addons1:liquidsampler", { + description = "Liquid Sampler", + tiles = { + -- up, down, right, left, back, front + 'tubelib_front.png', + 'tubelib_front.png', + 'tubelib_addons1_liquidsampler.png', + 'tubelib_addons1_liquidsampler_passive.png', + 'tubelib_addons1_liquidsampler.png', + 'tubelib_addons1_liquidsampler.png', + }, + + on_construct = function(pos) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + inv:set_size("src", 4) + inv:set_size("dst", 28) + end, + + after_place_node = function(pos, placer) + local meta = minetest.get_meta(pos) + meta:set_string("player_name", placer:get_player_name()) + local number = tubelib.add_node(pos, "tubelib_addons1:liquidsampler") + meta:set_string("number", number) + local node = minetest.get_node(pos) + local water_pos = get_pos(pos, node.param2, "L") + meta:set_string("water_pos", minetest.pos_to_string(water_pos)) + switch_off(pos, node) + end, + + on_receive_fields = on_receive_fields, + + after_dig_node = function(pos) + tubelib.remove_node(pos) + end, + + on_timer = keep_running, + on_rotate = screwdriver.disallow, + + paramtype = "light", + sunlight_propagates = true, + paramtype2 = "facedir", + groups = {choppy=2, cracky=2, crumbly=2}, + is_ground_content = false, + sounds = default.node_sound_wood_defaults(), +}) + + +minetest.register_node("tubelib_addons1:liquidsampler_active", { + description = "Liquid Sampler", + tiles = { + -- up, down, right, left, back, front + 'tubelib_front.png', + 'tubelib_front.png', + 'tubelib_addons1_liquidsampler.png', + { + image = "tubelib_addons1_liquidsampler_active.png", + backface_culling = false, + animation = { + type = "vertical_frames", + aspect_w = 32, + aspect_h = 32, + length = 2, + }, + }, + 'tubelib_addons1_liquidsampler.png', + 'tubelib_addons1_liquidsampler.png', + }, + + on_receive_fields = on_receive_fields, + + on_timer = keep_running, + on_rotate = screwdriver.disallow, + + after_dig_node = function(pos) + tubelib.remove_node(pos) + end, + + paramtype = "light", + sunlight_propagates = true, + paramtype2 = "facedir", + groups = {crumbly=0, not_in_creative_inventory=1}, + is_ground_content = false, + drop = "tubelib_addons1:liquidsampler", + sounds = default.node_sound_wood_defaults(), +}) + +minetest.register_craft({ + output = "tubelib_addons1:liquidsampler", + recipe = { + {"group:wood", "default:steel_ingot", "group:wood"}, + {"default:mese_crystal", "bucket:bucket_empty", "tubelib:tube1"}, + {"group:wood", "default:steel_ingot", "group:wood"}, + }, +}) + +--------------------------------------------------------------- tubelib +tubelib.register_node("tubelib_addons1:liquidsampler", {"tubelib_addons1:liquidsampler_active"}, { + on_pull_item = function(pos, side) + local meta = minetest.get_meta(pos) + return tubelib.get_item(meta, "dst") + end, + on_push_item = function(pos, side, item) + local meta = minetest.get_meta(pos) + minetest.get_node_timer(pos):start(CYCLE_TIME) + return tubelib.put_item(meta, "src", item) + end, + on_unpull_item = function(pos, side, item) + local meta = minetest.get_meta(pos) + return tubelib.put_item(meta, "dst", item) + end, + + on_recv_message = function(pos, topic, payload) + local node = minetest.get_node(pos) + if topic == "on" then + return switch_on(pos, node) + elseif topic == "off" then + return switch_off(pos, node) + elseif topic == "state" then + local meta = minetest.get_meta(pos) + local running = meta:get_int("running") or tubelib.STATE_STOPPED + return tubelib.statestring(running) + else + return "not supported" + end + end, +}) +--------------------------------------------------------------- tubelib diff --git a/tubelib_addons1/quarry.lua b/tubelib_addons1/quarry.lua index d21360c..9b2c227 100644 --- a/tubelib_addons1/quarry.lua +++ b/tubelib_addons1/quarry.lua @@ -275,7 +275,7 @@ local function on_receive_fields(pos, formname, fields, player) local running = meta:get_int("running") or STOP_STATE if fields.button ~= nil then - if running > STOP_STATE or running == FAULT_STATE then + if running > STOP_STATE then stop_the_machine(pos) else start_the_machine(pos) diff --git a/tubelib_addons1/textures/tubelib_addons1_liquidsampler.png b/tubelib_addons1/textures/tubelib_addons1_liquidsampler.png new file mode 100644 index 0000000..5033f43 Binary files /dev/null and b/tubelib_addons1/textures/tubelib_addons1_liquidsampler.png differ diff --git a/tubelib_addons1/textures/tubelib_addons1_liquidsampler_active.png b/tubelib_addons1/textures/tubelib_addons1_liquidsampler_active.png new file mode 100644 index 0000000..b6d3066 Binary files /dev/null and b/tubelib_addons1/textures/tubelib_addons1_liquidsampler_active.png differ diff --git a/tubelib_addons1/textures/tubelib_addons1_liquidsampler_passive.png b/tubelib_addons1/textures/tubelib_addons1_liquidsampler_passive.png new file mode 100644 index 0000000..3a4e548 Binary files /dev/null and b/tubelib_addons1/textures/tubelib_addons1_liquidsampler_passive.png differ