techpack/smartline/controller.lua

985 lines
30 KiB
Lua
Raw Normal View History

2018-03-10 22:14:57 +01:00
--[[
SmartLine
=========
Copyright (C) 2017-2020 Joachim Stolberg
2018-03-10 22:14:57 +01:00
AGPL v3
2018-03-10 22:14:57 +01:00
See LICENSE.txt for more information
controller.lua:
REPLACED BY SMARTLINE CONTROLLER2 !!!
2018-03-10 22:14:57 +01:00
]]--
local NUM_RULES = 10
local mail_exists = minetest.get_modpath("mail") and mail ~= nil
2018-03-10 22:14:57 +01:00
local sHELP = [[SmartLine Controller Help
Control other nodes by means of rules, according to:
IF <cond1> and/or <cond2> THEN <action>
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,...
- a timer is expired
2018-03-10 22:14:57 +01:00
Actions are:
- switch on/off tubelib nodes, like lamps, door blocks, machines
- send mail/chat messages to the owner
- output a text message to the display
- set timer variables
2018-03-10 22:14:57 +01:00
- set/reset flag variables
Variables and timers:
- 8 flags (set/reset) can be used to store conditions
2018-03-10 22:14:57 +01:00
for later use.
- Action flags (one flag for each rule, set when action is executed)
The flag can be used as condition for subsequent rules.
- 8 timers (resolution in seconds) can be use
for delayed actions.
2018-03-10 22:14:57 +01:00
The controller executes all rules once per second.
2018-03-10 22:14:57 +01:00
Independent how long the input condition stays 'true',
the corresponding action will be triggered only once.
The condition has to become false and then true again, to
2018-03-10 22:14:57 +01:00
re-trigger/execute the action again.
The 'label' has no function. It is only used
to give rules a name.
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 state view shows the current state of all rules.
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.
]]
local sOUTPUT = "Press 'help' for edit commands"
2018-03-10 22:14:57 +01:00
--
-- Helper functions
--
local function create_kv_list(elem)
2018-03-10 22:14:57 +01:00
local a = {}
for i,v in ipairs(elem) do
a[v] = i
end
return a
end
local function output(label, prefix, postfix, kvTbl)
local tbl = {label..": "}
for k,v in pairs(kvTbl) do
tbl[#tbl+1] = prefix..k..postfix.."="..v..", "
end
return table.concat(tbl)
end
--
-- Conditions
--
-- tables with all data from condition/action registrations
local kvRegisteredCond = {}
local kvRegisteredActn = {}
-- list of keys for conditions/actions
local aCondTypes = {}
local aActnTypes = {}
-- list of titles for conditions/actions
local aCondTitles = {}
local aActnTitles = {}
-- table with runtime functions
local CondRunTimeHandlers = {}
local ActnRunTimeHandlers = {}
local function eval_cond(data, environ)
return CondRunTimeHandlers[data.__idx__](data, environ) and 1 or 0
2018-03-10 22:14:57 +01:00
end
local function exec_action(data, environ, number)
ActnRunTimeHandlers[data.__idx__](data, environ, number)
2018-03-10 22:14:57 +01:00
end
--
-- API functions for condition/action registrations
--
function smartline.register_condition(key, tData)
table.insert(CondRunTimeHandlers, tData.on_execute)
table.insert(aCondTypes, key)
table.insert(aCondTitles, tData.title)
tData.__idx__ = #aCondTypes
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
function smartline.register_action(key, tData)
table.insert(ActnRunTimeHandlers, tData.on_execute)
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
--
-- Formspec
--
-- Determine the selected submenu and return the corresponding
-- formspec definition.
-- postfix: row/culumn info like "11" or "a2"
-- type: "cond" or "actn"
-- fs_data: formspec data
local function get_active_subm_definition(postfix, type, fs_data)
local idx = 1
local fs_definition = {}
if type == "cond" then
idx = fs_data["subm"..postfix.."_cond"] or 1
local key = aCondTypes[idx]
fs_definition = kvRegisteredCond[key]
elseif type == "actn" then
idx = fs_data["subm"..postfix.."_actn"] or 1
local key = aActnTypes[idx]
fs_definition = kvRegisteredActn[key]
end
return idx, fs_definition
end
-- Extract runtime relevant data from the given submenu
2018-03-10 22:14:57 +01:00
-- postfix: row/culum info like "11" or "a2"
-- fs_definition: submenu formspec definition
-- fs_data: formspec data
local function get_subm_data(postfix, fs_definition, fs_data)
local data = {}
for idx,elem in ipairs(fs_definition.formspec) do
if elem.type == "field" then
2018-03-10 22:14:57 +01:00
data[elem.name] = fs_data["subm"..postfix.."_"..elem.name] or "?"
elseif elem.type == "textlist" then
2018-03-10 22:14:57 +01:00
local num = tonumber(fs_data["subm"..postfix.."_"..elem.name]) or 1
num = math.min(num, elem.num_choices)
data[elem.name] = num
data[elem.name.."_text"] = elem.tChoices[num]
end
end
-- type of the condition/action
data.__idx__ = fs_definition.__idx__
return data
end
-- Copy field/formspec data to the table fs_data
-- fs_definition: submenu formspec definituion
-- fields: formspec input
-- fs_data: formspec data
local function field2fs_data(fs_definition, fields, fs_data)
for idx,elem in ipairs(fs_definition.formspec) do
local key = "subm"..fields._postfix_.."_"..elem.name
if elem.type == "field" then
2018-03-10 22:14:57 +01:00
if fields[elem.name] then
fs_data[key] = fields[elem.name]
end
elseif elem.type == "textlist" then
2018-03-10 22:14:57 +01:00
local evt = minetest.explode_textlist_event(fields[elem.name])
if evt.type == "CHG" then
fs_data[key] = evt.index
end
end
if fs_data[key] == nil then
fs_data[key] = elem.default
end
end
return fs_data
end
local function add_controls_to_table(tbl, postfix, fs_data, fs_definition)
local val = ""
local offs = 2.4
for idx,elem in ipairs(fs_definition.formspec) do
if elem.label then
tbl[#tbl+1] = "label[0,"..offs..";"..elem.label.."]"
offs = offs + 0.5
end
if elem.type == "field" then
val = fs_data["subm"..postfix.."_"..elem.name] or elem.default
tbl[#tbl+1] = "field[0.3,"..(offs+0.2)..";8.2,1;"..elem.name..";;"..val.."]"
offs = offs + 0.9
elseif elem.type == "textlist" then
val = fs_data["subm"..postfix.."_"..elem.name] or elem.default
tbl[#tbl+1] = "textlist[0.0,"..(offs)..";8,1.4;"..elem.name..";"..elem.choices..";"..val.."]"
offs = offs + 1.8
end
end
return tbl
end
local function runtime_data(postfix, type, fs_data)
local _,fs_definition = get_active_subm_definition(postfix, type, fs_data)
return get_subm_data(postfix, fs_definition, fs_data)
end
local function decrement_timers(timers)
if timers ~= nil then
for idx,_ in pairs(timers) do
timers[idx] = tonumber(timers[idx])
if timers[idx] >= 0 then
timers[idx] = timers[idx] - 1
end
2018-03-10 22:14:57 +01:00
end
end
end
2018-03-19 22:59:42 +01:00
local function toggle_flag(meta, environ)
environ.toggle = (meta:get_int("runtime") or 0) % 4 >= 2
end
2018-03-10 22:14:57 +01:00
--
-- Condition formspec
--
local function formspec_cond(_postfix_, fs_data)
local tbl = {"size[8.2,9]"..
2018-03-10 22:14:57 +01:00
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"field[0,0;0,0;_type_;;cond]"..
"field[0,0;0,0;_postfix_;;".._postfix_.."]"}
2018-03-10 22:14:57 +01:00
local sConditions = table.concat(aCondTitles, ",")
local cond_idx, fs_definition = get_active_subm_definition(_postfix_, "cond", fs_data)
tbl[#tbl+1] = "label[0,0.1;Condition type:]"
tbl[#tbl+1] = "textlist[0,0.6;8,1.4;cond;"..sConditions..";"..cond_idx.."]"
tbl = add_controls_to_table(tbl, _postfix_, fs_data, fs_definition)
tbl[#tbl+1] = "button[4,8.4;2,1;_cancel_;cancel]"
tbl[#tbl+1] = "button[6,8.4;2,1;_exit_;ok]"
2018-03-10 22:14:57 +01:00
return table.concat(tbl)
end
-- evaluate the row condition
local function eval_formspec_cond(meta, fs_data, fields, readonly)
if readonly then return fs_data end
2018-03-10 22:14:57 +01:00
-- determine condition type
local cond = minetest.explode_textlist_event(fields.cond)
if cond.type == "CHG" then
fs_data["subm"..fields._postfix_.."_cond"] = cond.index
end
-- prepare data
local _, fs_definition = get_active_subm_definition(fields._postfix_, "cond", fs_data)
fs_data = field2fs_data(fs_definition, fields, fs_data)
local data = get_subm_data(fields._postfix_, fs_definition, fs_data)
-- update button for main menu
fs_data["cond"..fields._postfix_] = fs_definition.button_label(data)
2018-03-10 22:14:57 +01:00
if fields._exit_ == nil then
-- update formspec if exit is not pressed
meta:set_string("formspec", formspec_cond(fields._postfix_, fs_data))
end
return fs_data
end
2018-03-10 22:14:57 +01:00
--
-- Action formspec
--
local function formspec_actn(_postfix_, fs_data)
local tbl = {"size[8.2,9]"..
2018-03-10 22:14:57 +01:00
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"field[0,0;0,0;_type_;;actn]"..
"field[0,0;0,0;_postfix_;;".._postfix_.."]"}
2018-03-10 22:14:57 +01:00
local sActions = table.concat(aActnTitles, ",")
local actn_idx, fs_definition = get_active_subm_definition(_postfix_, "actn", fs_data)
tbl[#tbl+1] = "label[0,0.1;Action type:]"
tbl[#tbl+1] = "textlist[0,0.6;8,1.4;actn;"..sActions..";"..actn_idx.."]"
tbl = add_controls_to_table(tbl, _postfix_, fs_data, fs_definition)
tbl[#tbl+1] = "button[4,8.4;2,1;_cancel_;cancel]"
tbl[#tbl+1] = "button[6,8.4;2,1;_exit_;ok]"
2018-03-10 22:14:57 +01:00
return table.concat(tbl)
end
-- evaluate the row action
local function eval_formspec_actn(meta, fs_data, fields, readonly)
if readonly then return fs_data end
2018-03-10 22:14:57 +01:00
-- determine action type
local actn = minetest.explode_textlist_event(fields.actn)
if actn.type == "CHG" then
fs_data["subm"..fields._postfix_.."_actn"] = actn.index
end
-- prepare data
local _, fs_definition = get_active_subm_definition(fields._postfix_, "actn", fs_data)
fs_data = field2fs_data(fs_definition, fields, fs_data)
local data = get_subm_data(fields._postfix_, fs_definition, fs_data)
-- update button for main menu
fs_data["actn"..fields._postfix_] = fs_definition.button_label(data)
2018-03-10 22:14:57 +01:00
if fields._exit_ == nil then
-- update formspec if exit is not pressed
meta:set_string("formspec", formspec_actn(fields._postfix_, fs_data))
end
if fields._cancel_ then
fields._exit_ = true
end
return fs_data
end
--
-- Label text formspec
--
local function formspec_label(_postfix_, fs_data)
local label = fs_data["label".._postfix_] or "<any text>"
return "size[6,4]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"field[0,0;0,0;_type_;;label]"..
"field[0,0;0,0;_postfix_;;".._postfix_.."]"..
"label[0.2,0.3;Label:]"..
"field[0.3,1.5;5,1;label;;"..label.."]"..
"button[4.5,3;1.5,1;_exit_;ok]"
end
-- evaluate the row label
local function eval_formspec_label(meta, fs_data, fields, readonly)
if readonly then return fs_data end
2018-03-10 22:14:57 +01:00
fs_data["subml"..fields._postfix_.."_label"] = fields.label
if fields._exit_ == nil then
meta:set_string("formspec", formspec_label(fields._postfix_, fs_data))
end
-- set the button label of the main menu based on the given input in the submenu
fs_data["label"..fields._postfix_] = fs_data["subml"..fields._postfix_.."_label"]
return fs_data
end
--
-- Operand formspec
--
local function formspec_oprnd(_postfix_, fs_data)
local oprnd = fs_data["submo".._postfix_.."_oprnd"] or ""
return "size[6,4]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"field[0,0;0,0;_type_;;oprnd]"..
"field[0,0;0,0;_postfix_;;".._postfix_.."]"..
"label[0.2,0.3;Operand:]"..
"textlist[0,0.8;5.6,1.4;oprnd;or,and;"..oprnd.."]"..
"button[4.5,3;1.5,1;_exit_;ok]"
end
-- evaluate the row operand
local function eval_formspec_oprnd(meta, fs_data, fields, readonly)
if readonly then return fs_data end
2018-03-10 22:14:57 +01:00
local oprnd = minetest.explode_textlist_event(fields.oprnd)
if oprnd.type == "CHG" then
fs_data["submo"..fields._postfix_.."_oprnd"] = oprnd.index
end
if fields._exit_ == nil then
meta:set_string("formspec", formspec_oprnd(fields._postfix_, fs_data))
end
-- set the button label of the main menu based on the given input in the submenu
fs_data["oprnd"..fields._postfix_] = fs_data["submo"..fields._postfix_.."_oprnd"] == 1 and "or" or "and"
return fs_data
end
local function formspec_main(state, fs_data, output)
local tbl = {"size[15,9;true]"..
2018-03-10 22:14:57 +01:00
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"field[0,0;0,0;_type_;;main]"..
"label[0.8,0;label:]label[3.8,0;IF cond 1:]label[7,0;and/or]label[8.3,0;cond 2:]label[11.7,0;THEN action:]"}
2018-03-10 22:14:57 +01:00
for idx = 1,NUM_RULES do
local ypos = idx * 0.75 - 0.4
2018-03-10 22:14:57 +01:00
tbl[#tbl+1] = "label[0,"..(0.2+ypos)..";"..idx.."]"
tbl[#tbl+1] = "button[0.4,"..ypos..";3,1;label"..idx..";"..(fs_data["label"..idx] or "...").."]"
tbl[#tbl+1] = "button[3.5,"..ypos..";3.4,1;cond1"..idx..";"..(fs_data["cond1"..idx] or "...").."]"
tbl[#tbl+1] = "button[7,".. ypos..";1,1;oprnd".. idx..";"..(fs_data["oprnd"..idx] or "or").."]"
tbl[#tbl+1] = "button[8,".. ypos..";3.4,1;cond2"..idx..";"..(fs_data["cond2"..idx] or "...").."]"
tbl[#tbl+1] = "button[11.5,".. ypos..";3.4,1;actna"..idx..";"..(fs_data["actna"..idx] or "...").."]"
end
tbl[#tbl+1] = "image_button[14,8.1;1,1;".. tubelib.state_button(state) ..";button;]"
tbl[#tbl+1] = "button[10.6,8.2;1.5,1;state;state]"
tbl[#tbl+1] = "button[12.2,8.2;1.5,1;help;help]"
tbl[#tbl+1] = "label[0.2,8.4;"..output.."]"
tbl[#tbl+1] = "field[6.5,8.5;3,1;cmnd;;<cmnd>]"
tbl[#tbl+1] = "button[9.2,8.2;1,1;ok;OK]"
2018-03-10 22:14:57 +01:00
return table.concat(tbl)
end
local function eval_formspec_main(meta, fs_data, fields, readonly)
meta:set_string("fs_old", minetest.serialize(fs_data))
for idx = 1,NUM_RULES do
-- eval standard inputs
if not readonly then
fs_data["oprnd"..idx] = fields["oprnd"..idx] or fs_data["oprnd"..idx]
end
2018-03-10 22:14:57 +01:00
-- eval submenu button events
if fields["label"..idx] then
meta:set_string("formspec", formspec_label(idx, fs_data))
elseif fields["cond1"..idx] then
meta:set_string("formspec", formspec_cond("1"..idx, fs_data))
elseif fields["cond2"..idx] then
meta:set_string("formspec", formspec_cond("2"..idx, fs_data))
elseif fields["oprnd"..idx] then
meta:set_string("formspec", formspec_oprnd(idx, fs_data))
elseif fields["actna"..idx] then
meta:set_string("formspec", formspec_actn("a"..idx, fs_data))
end
end
2018-03-10 22:14:57 +01:00
return fs_data
end
local function formspec_help(offs)
return "size[15,9]"..
2018-03-10 22:14:57 +01:00
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"field[0,0;0,0;_type_;;help]"..
"label[0,"..(-offs/50)..";"..sHELP.."]"..
--"label[0.2,0;test]"..
"scrollbar[13.5,1;0.5,7;vertical;sb_help;"..offs.."]"..
"button[13.3,8.2;1.5,1;close;close]"
2018-03-10 22:14:57 +01:00
end
local function background(xpos, ypos, val)
if val == true then
return "box["..(xpos-0.1)..",".. ypos..";3.3,0.4;#008000]"
elseif val == false then
return "box["..(xpos-0.1)..",".. ypos..";3.3,0.4;#800000]"
else
return "box["..(xpos-0.1)..",".. ypos..";3.3,0.4;#202020]"
end
end
2018-03-10 22:14:57 +01:00
local function formspec_state(meta, fs_data)
local number = meta:get_string("number")
local state = meta:get_int("state")
local tbl = {"size[15,9;true]"..
2018-03-10 22:14:57 +01:00
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"field[0,0;0,0;_type_;;state]"..
"label[0.8,0;label:]label[3.8,0;IF cond 1:]label[7,0;and/or]label[8.3,0;cond 2:]label[11.7,0;THEN action:]"}
2018-03-10 22:14:57 +01:00
if state == tubelib.RUNNING and number then
local environ = tubelib.get_data(number, "environ")
local act_gate = tubelib.get_data(number, "act_gate")
local conds = tubelib.get_data(number, "conds")
if environ and act_gate and conds then
for idx = 1,NUM_RULES do
local ypos = idx * 0.6 + 0.2
local s1 = fs_data["cond1"..idx] or " ... "
local s2 = fs_data["cond2"..idx] or " ... "
local sa = fs_data["actna"..idx] or " ... "
if conds[idx] == nil then
tbl[#tbl+1] = background(3.7, ypos, nil)
tbl[#tbl+1] = background(8, ypos, nil)
else
tbl[#tbl+1] = background(3.7, ypos, conds[idx] == 1 or conds[idx] == 3)
tbl[#tbl+1] = background(8, ypos, conds[idx] == 2 or conds[idx] == 3)
end
tbl[#tbl+1] = background(11.5, ypos, act_gate[idx])
tbl[#tbl+1] = "label[0,".. ypos..";"..idx.."]"
tbl[#tbl+1] = "label[0.5,"..ypos..";"..(fs_data["label"..idx] or " ... ").."]"
tbl[#tbl+1] = "label[3.7,".. ypos..";"..s1.."]"
tbl[#tbl+1] = "label[7.2,".. ypos..";"..(fs_data["oprnd"..idx] or "or").."]"
tbl[#tbl+1] = "label[8,".. ypos..";"..s2.."]"
tbl[#tbl+1] = "label[11.5,".. ypos..";"..sa.."]"
if act_gate[idx] == true then
act_gate[idx] = false
end
2018-03-10 22:14:57 +01:00
end
tbl[#tbl+1] = "label[10,7; Seconds: "..(meta:get_int("runtime") or 1).."]"
2018-03-10 22:14:57 +01:00
tbl[#tbl+1] = "label[0,7;"..output("Inputs", "i(", ")", environ.inputs).."]"
tbl[#tbl+1] = "label[0,7.6;"..output("Timers", "t", "", environ.timers).."]"
tbl[#tbl+1] = "label[0,8.2;"..output("Flags", "f", "", environ.flags).."]"
tbl[#tbl+1] = "label[0,8.8;Hint:]"
tbl[#tbl+1] = "box[1.3,8.8;6,0.4;#008000]"
tbl[#tbl+1] = "label[1.4,8.8;condition true / action executed]"
tbl[#tbl+1] = "box[7.9,8.8;6,0.4;#800000]"
tbl[#tbl+1] = "label[8,8.8;condition false / action out-of-date]"
end
2018-03-10 22:14:57 +01:00
end
tbl[#tbl+1] = "button[13.3,6.9;1.7,1;update;update]"
tbl[#tbl+1] = "button[13.3,7.8;1.7,1;close;close]"
2018-03-10 22:14:57 +01:00
return table.concat(tbl)
end
local function execute(meta, number, debug)
local rt_rules = tubelib.get_data(number, "rt_rules")
local environ = tubelib.get_data(number, "environ")
local act_gate = tubelib.get_data(number, "act_gate")
local conds = tubelib.get_data(number, "conds")
if rt_rules and environ and act_gate and conds then
environ.actions = {}
decrement_timers(environ.timers)
2018-03-19 22:59:42 +01:00
toggle_flag(meta, environ)
for i,item in ipairs(rt_rules) do
local c1 = eval_cond(item.cond1, environ)
local c2 = eval_cond(item.cond2, environ)
conds[i] = c1 + c2*2
if c1 + c2 >= item.cond_cnt then
if act_gate[i] == nil then
-- execute action
exec_action(item.actn, environ, number)
environ.actions[i] = true
act_gate[i] = true
end
else
act_gate[i] = nil
2018-03-10 22:14:57 +01:00
end
end
end
end
local function check_rules(pos, elapsed)
if tubelib.data_not_corrupted(pos) then
--local t = minetest.get_us_time()
local meta = minetest.get_meta(pos)
meta:set_int("runtime", (meta:get_int("runtime") or 1) + 1)
local number = meta:get_string("number")
local state = meta:get_int("state")
if state == tubelib.RUNNING and number then
execute(meta, number, debug)
end
--print("time", minetest.get_us_time() - t)
return true
2018-03-10 22:14:57 +01:00
end
return false
2018-03-10 22:14:57 +01:00
end
local function switch_state(pos, state, fs_data)
local meta = minetest.get_meta(pos)
local number = meta:get_string("number")
meta:set_int("state", state)
meta:set_string("formspec", formspec_main(state, fs_data, sOUTPUT))
if state == tubelib.RUNNING then
meta:set_string("infotext", "SmartLine Controller "..number..": running")
minetest.get_node_timer(pos):start(1)
else
meta:set_string("infotext", "SmartLine Controller "..number..": stopped")
minetest.get_node_timer(pos):stop()
end
end
local function start_controller(pos, number, fs_data)
-- delete old data
tubelib.set_data(number, "environ", {
timers = {},
flags = {},
inputs = {}
})
tubelib.set_data(number, "conds", {})
tubelib.set_data(number, "act_gate", {})
2018-03-10 22:14:57 +01:00
switch_state(pos, tubelib.RUNNING, fs_data)
end
function smartline.stop_controller(pos, fs_data)
2018-03-10 22:14:57 +01:00
switch_state(pos, tubelib.STOPPED, fs_data)
end
local function formspec2runtime_rule(number, owner, fs_data)
local rt_rules = {}
local num2inp = {}
for idx = 1,NUM_RULES do
-- valid rule?
if fs_data["subm1"..idx.."_cond"] and fs_data["subm2"..idx.."_cond"]
2018-03-10 22:14:57 +01:00
and fs_data["subma"..idx.."_actn"] then
-- add to list of runtine rules
local rule = {
cond_cnt = fs_data["oprnd"..idx] == "and" and 2 or 1,
cond1 = runtime_data("1"..idx, "cond", fs_data),
cond2 = runtime_data("2"..idx, "cond", fs_data),
actn = runtime_data("a"..idx, "actn", fs_data),
}
rule.actn.owner = owner
table.insert(rt_rules, rule)
end
end
2018-03-10 22:14:57 +01:00
tubelib.set_data(number, "rt_rules", rt_rules)
end
local function get_keys(fs_data)
-- collect all keys and remove row information
local keys = {}
for k,v in pairs(fs_data) do
local key = string.sub(k,1,5).."*"..string.sub(k, 7)
if type(v) == 'number' then
keys[key] = 1 -- default value
else
keys[key] = "..." -- default value
end
end
return keys
end
local function exchange_rules(fs_data, pos1, pos2)
-- exchange elem by elem
for k,v in pairs(get_keys(fs_data)) do
local k1 = string.gsub(k, "*", pos1)
local k2 = string.gsub(k, "*", pos2)
local temp = fs_data[k1] or v
fs_data[k1] = fs_data[k2] or v
fs_data[k2] = temp
end
return fs_data
end
local function copy_rule(fs_data, pos1, pos2)
-- copy elem by elem
for k,v in pairs(get_keys(fs_data)) do
local k1 = string.gsub(k, "*", pos1)
local k2 = string.gsub(k, "*", pos2)
fs_data[k2] = fs_data[k1] or v
end
return fs_data
end
local function delete_rule(fs_data, pos)
for k,v in pairs(get_keys(fs_data)) do
local k1 = string.gsub(k, "*", pos)
fs_data[k1] = nil
end
return fs_data
end
local function 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, NUM_RULES))
pos2 = math.max(1, math.min(pos2, NUM_RULES))
if cmnd == "x" then
exchange_rules(fs_data, pos1, pos2)
2018-03-10 22:14:57 +01:00
return "rows "..pos1.." and "..pos2.." exchanged"
end
if cmnd == "c" then
copy_rule(fs_data, pos1, pos2)
2018-03-10 22:14:57 +01:00
return "row "..pos1.." copied to "..pos2
end
elseif cmnd == "d" and pos1 then
pos1 = math.max(1, math.min(pos1, NUM_RULES))
2018-03-10 22:14:57 +01:00
delete_rule(fs_data, pos1)
return "row "..pos1.." deleted"
end
return "Invalid command '"..text.."'"
end
local function 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
2018-03-10 22:14:57 +01:00
-- FIRST: test if command entered?
if fields.ok then
if not readonly then
2018-03-10 22:14:57 +01:00
output = edit_command(fs_data, fields.cmnd)
smartline.stop_controller(pos, fs_data)
2018-03-10 22:14:57 +01:00
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)
2018-03-10 22:14:57 +01:00
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._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))
-- smartline.stop_controller(pos, fs_data)
2018-03-10 22:14:57 +01:00
-- meta:set_string("fs_data", minetest.serialize(fs_data))
-- end
end
end
minetest.register_node("smartline:controller", {
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},
},
},
2018-03-10 22:14:57 +01:00
after_place_node = function(pos, placer)
local meta = minetest.get_meta(pos)
local number = tubelib.add_node(pos, "smartline:controller")
2018-03-10 22:14:57 +01:00
local fs_data = {}
meta:set_string("fs_data", minetest.serialize(fs_data))
2018-03-10 22:14:57 +01:00
meta:set_string("owner", placer:get_player_name())
meta:set_string("number", number)
meta:set_int("state", tubelib.STOPPED)
meta:set_int("debug", 0)
meta:set_string("formspec", formspec_main(tubelib.STOPPED, fs_data, sOUTPUT))
meta:set_string("infotext", "SmartLine Controller "..number..": stopped")
end,
on_receive_fields = on_receive_fields,
2018-03-10 22:14:57 +01:00
on_dig = function(pos, node, puncher, pointed_thing)
if minetest.is_protected(pos, puncher:get_player_name()) then
return
end
2018-03-10 22:14:57 +01:00
minetest.node_dig(pos, node, puncher, pointed_thing)
tubelib.remove_node(pos)
end,
2018-03-10 22:14:57 +01:00
on_timer = check_rules,
2018-03-10 22:14:57 +01:00
paramtype = "light",
sunlight_propagates = true,
paramtype2 = "facedir",
groups = {choppy=1, cracky=1, crumbly=1, not_in_creative_inventory=1},
2018-03-10 22:14:57 +01:00
is_ground_content = false,
sounds = default.node_sound_stone_defaults(),
on_blast = function() end,
drop = "smartline:controller2",
2018-03-10 22:14:57 +01:00
})
minetest.register_craft({
output = "smartline:controller",
recipe = {
{"", "default:mese_crystal", ""},
{"dye:blue", "default:copper_ingot", "tubelib:wlanchip"},
{"", "default:mese_crystal", ""},
},
})
local function set_input(meta, payload, val)
if payload then
2018-03-10 22:14:57 +01:00
local number = meta:get_string("number")
local environ = tubelib.get_data(number, "environ") or {}
if environ.inputs then
environ.inputs[payload] = val
tubelib.set_data(number, "environ", environ)
2018-03-10 22:14:57 +01:00
end
end
end
2018-03-10 22:14:57 +01:00
tubelib.register_node("smartline:controller", {}, {
on_recv_message = function(pos, topic, payload)
local meta = minetest.get_meta(pos)
if topic == "on" then
set_input(meta, payload, topic)
elseif topic == "off" then
set_input(meta, payload, topic)
elseif topic == "state" then
local state = meta:get_int("state")
return tubelib.statestring(state)
else
return "unsupported"
end
end,
2018-12-22 16:58:11 +01:00
on_node_load = function(pos)
local meta = minetest.get_meta(pos)
if meta:get_int("state") == tubelib.RUNNING then
minetest.get_node_timer(pos):start(1)
end
end,
})
2018-03-10 22:14:57 +01:00
-- List of Controller actions and conditions is dependent on loaded mods.
-- Therefore, the order of actions and conditions has to be re-assembled each time.
-- last order from last run is stored as meta data
2018-03-10 22:14:57 +01:00
local storage = minetest.get_mod_storage()
local function old_to_new(newTypes, oldTypes)
local res = {}
if #oldTypes == 0 then
return nil
end
local new = create_kv_list(newTypes)
for idx,key in ipairs(oldTypes) do
res[idx] = new[key] or 1
end
return res
end
local function update_node_database(meta)
local aOldCondTypes = minetest.deserialize(meta:get_string("aCondTypes")) or {}
local aOldActnTypes = minetest.deserialize(meta:get_string("aActnTypes")) or {}
local tOld2NewCond = old_to_new(aCondTypes, aOldCondTypes)
local tOld2NewActn = old_to_new(aActnTypes, aOldActnTypes)
meta:set_string("aCondTypes", minetest.serialize(aCondTypes))
meta:set_string("aActnTypes", minetest.serialize(aActnTypes))
2018-03-10 22:14:57 +01:00
return tOld2NewCond, tOld2NewActn
end
local function maintain_dataset(number)
local flags = tubelib.get_data(number, "flags")
if flags ~= nil then
local timers = tubelib.get_data(number, "timers") or {}
local inputs = tubelib.get_data(number, "inputs") or {}
tubelib.set_data(number, "environ", {
flags = flags,
timers = timers,
inputs = inputs,
vars = {}
})
tubelib.set_data(number, "inputs", nil)
tubelib.set_data(number, "timers", nil)
tubelib.set_data(number, "flags", nil)
end
end
--
-- Update formspec data for the case that rules order has changed or new rules were added
--
function smartline.update_fs_data(meta, fs_data)
local tOld2NewCond, tOld2NewActn = update_node_database(meta)
if tOld2NewCond and tOld2NewActn then
-- map from old to new indexes
for idx = 1,NUM_RULES do
fs_data["subm1"..idx.."_cond"] = tOld2NewCond[fs_data["subm1"..idx.."_cond"]]
fs_data["subm2"..idx.."_cond"] = tOld2NewCond[fs_data["subm2"..idx.."_cond"]]
fs_data["subma"..idx.."_actn"] = tOld2NewActn[fs_data["subma"..idx.."_actn"]]
end
end
return fs_data
end
2018-03-10 22:14:57 +01:00
minetest.register_lbm({
label = "[SmartLine] Controller update",
name = "smartline:update",
nodenames = {"smartline:controller"},
run_at_every_load = true,
action = function(pos, node)
local meta = minetest.get_meta(pos)
local fs_data = minetest.deserialize(meta:get_string("fs_data"))
fs_data = smartline.update_fs_data(meta, fs_data)
meta:set_string("fs_data", minetest.serialize(fs_data))
2018-03-10 22:14:57 +01:00
local number = meta:get_string("number")
local owner = meta:get_string("owner")
formspec2runtime_rule(number, owner, fs_data)
maintain_dataset(number)
2018-03-10 22:14:57 +01:00
end
})