summaryrefslogtreecommitdiff
path: root/mesecons/mesecons_microcontroller/init.lua
diff options
context:
space:
mode:
Diffstat (limited to 'mesecons/mesecons_microcontroller/init.lua')
-rw-r--r--mesecons/mesecons_microcontroller/init.lua692
1 files changed, 692 insertions, 0 deletions
diff --git a/mesecons/mesecons_microcontroller/init.lua b/mesecons/mesecons_microcontroller/init.lua
new file mode 100644
index 0000000..6778a10
--- /dev/null
+++ b/mesecons/mesecons_microcontroller/init.lua
@@ -0,0 +1,692 @@
+EEPROM_SIZE = 255
+
+local microc_rules = {}
+
+for a = 0, 1 do
+for b = 0, 1 do
+for c = 0, 1 do
+for d = 0, 1 do
+local nodename = "mesecons_microcontroller:microcontroller"..tostring(d)..tostring(c)..tostring(b)..tostring(a)
+local top = "jeija_microcontroller_top.png"
+if tostring(a) == "1" then
+ top = top.."^jeija_microcontroller_LED_A.png"
+end
+if tostring(b) == "1" then
+ top = top.."^jeija_microcontroller_LED_B.png"
+end
+if tostring(c) == "1" then
+ top = top.."^jeija_microcontroller_LED_C.png"
+end
+if tostring(d) == "1" then
+ top = top.."^jeija_microcontroller_LED_D.png"
+end
+if tostring(d)..tostring(c)..tostring(b)..tostring(a) ~= "0000" then
+ groups = {dig_immediate=2, not_in_creative_inventory=1, mesecon = 3, overheat = 1}
+else
+ groups = {dig_immediate=2, mesecon = 3, overheat = 1}
+end
+local rules={}
+if (a == 1) then table.insert(rules, {x = -1, y = 0, z = 0}) end
+if (b == 1) then table.insert(rules, {x = 0, y = 0, z = 1}) end
+if (c == 1) then table.insert(rules, {x = 1, y = 0, z = 0}) end
+if (d == 1) then table.insert(rules, {x = 0, y = 0, z = -1}) end
+
+local input_rules={}
+if (a == 0) then table.insert(input_rules, {x = -1, y = 0, z = 0, name = "A"}) end
+if (b == 0) then table.insert(input_rules, {x = 0, y = 0, z = 1, name = "B"}) end
+if (c == 0) then table.insert(input_rules, {x = 1, y = 0, z = 0, name = "C"}) end
+if (d == 0) then table.insert(input_rules, {x = 0, y = 0, z = -1, name = "D"}) end
+microc_rules[nodename] = rules
+
+local mesecons = {effector =
+{
+ rules = input_rules,
+ action_change = function (pos, node, rulename, newstate)
+ yc_update_real_portstates(pos, node, rulename, newstate)
+ update_yc(pos)
+ end
+}}
+if nodename ~= "mesecons_microcontroller:microcontroller0000" then
+ mesecons.receptor = {
+ state = mesecon.state.on,
+ rules = rules
+ }
+end
+
+minetest.register_node(nodename, {
+ description = "Microcontroller",
+ drawtype = "nodebox",
+ tiles = {
+ top,
+ "jeija_microcontroller_bottom.png",
+ "jeija_microcontroller_sides.png",
+ "jeija_microcontroller_sides.png",
+ "jeija_microcontroller_sides.png",
+ "jeija_microcontroller_sides.png"
+ },
+
+ sunlight_propagates = true,
+ paramtype = "light",
+ walkable = true,
+ groups = groups,
+ drop = "mesecons_microcontroller:microcontroller0000 1",
+ selection_box = {
+ type = "fixed",
+ fixed = { -8/16, -8/16, -8/16, 8/16, -5/16, 8/16 },
+ },
+ node_box = {
+ type = "fixed",
+ fixed = {
+ { -8/16, -8/16, -8/16, 8/16, -7/16, 8/16 }, -- bottom slab
+ { -5/16, -7/16, -5/16, 5/16, -6/16, 5/16 }, -- circuit board
+ { -3/16, -6/16, -3/16, 3/16, -5/16, 3/16 }, -- IC
+ }
+ },
+ on_construct = function(pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("code", "")
+ meta:set_string("formspec", "size[9,2.5]"..
+ "field[0.256,-0.2;9,2;code;Code:;]"..
+ "button[0 ,0.2;1.5,3;band;AND]"..
+ "button[1.5,0.2;1.5,3;bxor;XOR]"..
+ "button[3 ,0.2;1.5,3;bnot;NOT]"..
+ "button[4.5,0.2;1.5,3;bnand;NAND]"..
+ "button[6 ,0.2;1.5,3;btflop;T-Flop]"..
+ "button[7.5,0.2;1.5,3;brsflop;RS-Flop]"..
+ "button_exit[3.5,1;2,3;program;Program]")
+ meta:set_string("infotext", "Unprogrammed Microcontroller")
+ local r = ""
+ for i=1, EEPROM_SIZE+1 do r=r.."0" end --Generate a string with EEPROM_SIZE*"0"
+ meta:set_string("eeprom", r)
+ end,
+ on_receive_fields = function(pos, formanme, fields, sender)
+ local meta = minetest.get_meta(pos)
+ if fields.band then
+ fields.code = "sbi(C, A&B) :A and B are inputs, C is output"
+ elseif fields.bxor then
+ fields.code = "sbi(C, A~B) :A and B are inputs, C is output"
+ elseif fields.bnot then
+ fields.code = "sbi(B, !A) :A is input, B is output"
+ elseif fields.bnand then
+ fields.code = "sbi(C, !A|!B) :A and B are inputs, C is output"
+ elseif fields.btflop then
+ fields.code = "if(A)sbi(1,1);if(!A&#1)sbi(B,!B)sbi(1,0); if(C)off(B,1); :A is input, B is output (Q), C is reset, toggles with falling edge"
+ elseif fields.brsflop then
+ fields.code = "if(A)on(C);if(B)off(C); :A is S (Set), B is R (Reset), C is output (R dominates)"
+ end
+ if fields.code == nil then return end
+
+ meta:set_string("code", fields.code)
+ meta:set_string("formspec", "size[9,2.5]"..
+ "field[0.256,-0.2;9,2;code;Code:;"..minetest.formspec_escape(fields.code).."]"..
+ "button[0 ,0.2;1.5,3;band;AND]"..
+ "button[1.5,0.2;1.5,3;bxor;XOR]"..
+ "button[3 ,0.2;1.5,3;bnot;NOT]"..
+ "button[4.5,0.2;1.5,3;bnand;NAND]"..
+ "button[6 ,0.2;1.5,3;btflop;T-Flop]"..
+ "button[7.5,0.2;1.5,3;brsflop;RS-Flop]"..
+ "button_exit[3.5,1;2,3;program;Program]")
+ meta:set_string("infotext", "Programmed Microcontroller")
+ yc_reset (pos)
+ update_yc(pos)
+ end,
+ sounds = default.node_sound_stone_defaults(),
+ mesecons = mesecons,
+ after_dig_node = function (pos, node)
+ rules = microc_rules[node.name]
+ mesecon.receptor_off(pos, rules)
+ end,
+})
+end
+end
+end
+end
+
+minetest.register_craft({
+ output = 'craft "mesecons_microcontroller:microcontroller0000" 2',
+ recipe = {
+ {'mesecons_materials:silicon', 'mesecons_materials:silicon', 'group:mesecon_conductor_craftable'},
+ {'mesecons_materials:silicon', 'mesecons_materials:silicon', 'group:mesecon_conductor_craftable'},
+ {'group:mesecon_conductor_craftable', 'group:mesecon_conductor_craftable', ''},
+ }
+})
+
+function yc_reset(pos)
+ yc_action(pos, {a=false, b=false, c=false, d=false})
+ local meta = minetest.get_meta(pos)
+ meta:set_int("afterid", 0)
+ local r = ""
+ for i=1, EEPROM_SIZE+1 do r=r.."0" end --Generate a string with EEPROM_SIZE*"0"
+ meta:set_string("eeprom", r)
+end
+
+function update_yc(pos)
+ local meta = minetest.get_meta(pos)
+
+ if (mesecon.do_overheat(pos)) then
+ minetest.remove_node(pos)
+ minetest.after(0.2, function (pos)
+ mesecon.receptor_off(pos, mesecon.rules.flat)
+ end , pos) -- wait for pending parsings
+ minetest.add_item(pos, "mesecons_microcontroller:microcontroller0000")
+ end
+
+ local code = meta:get_string("code")
+ code = yc_code_remove_commentary(code)
+ code = string.gsub(code, " ", "") --Remove all spaces
+ code = string.gsub(code, " ", "") --Remove all tabs
+ if yc_parsecode(code, pos) == nil then
+ meta:set_string("infotext", "Code not valid!\n"..code)
+ else
+ meta:set_string("infotext", "Working Microcontroller\n"..code)
+ end
+end
+
+
+--Code Parsing
+function yc_code_remove_commentary(code)
+ local is_string = false
+ for i = 1, #code do
+ if code:sub(i, i) == '"' then
+ is_string = not is_string --toggle is_string
+ elseif code:sub(i, i) == ":" and not is_string then
+ return code:sub(1, i-1)
+ end
+ end
+ return code
+end
+
+function yc_parsecode(code, pos)
+ local meta = minetest.get_meta(pos)
+ local endi = 1
+ local Lreal = yc_get_real_portstates(pos)
+ local Lvirtual = yc_get_virtual_portstates(pos)
+ if Lvirtual == nil then return nil end
+ local c
+ local eeprom = meta:get_string("eeprom")
+ while true do
+ local command, params
+ command, endi = parse_get_command(code, endi)
+ if command == nil then return nil end
+ if command == true then break end --end of code
+ if command == "if" then
+ local r
+ r, endi = yc_command_if(code, endi, yc_merge_portstates(Lreal, Lvirtual), eeprom)
+ if r == nil then return nil end
+ if r == true then -- nothing
+ elseif r == false then
+ local endi_new = yc_skip_to_else (code, endi)
+ if endi_new == nil then --else > not found
+ endi = yc_skip_to_endif(code, endi)
+ else
+ endi = endi_new
+ end
+ if endi == nil then return nil end
+ end
+ else
+ params, endi = parse_get_params(code, endi)
+ if not params then return nil end
+ end
+ if command == "on" then
+ L = yc_command_on (params, Lvirtual)
+ elseif command == "off" then
+ L = yc_command_off(params, Lvirtual)
+ elseif command == "print" then
+ local su = yc_command_print(params, eeprom, yc_merge_portstates(Lreal, Lvirtual))
+ if su ~= true then return nil end
+ elseif command == "after" then
+ local su = yc_command_after(params, pos)
+ if su == nil then return nil end
+ elseif command == "sbi" then
+ local new_eeprom
+ new_eeprom, Lvirtual = yc_command_sbi (params, eeprom, yc_merge_portstates(Lreal, Lvirtual), Lvirtual)
+ if new_eeprom == nil then return nil
+ else eeprom = new_eeprom end
+ elseif command == "if" then --nothing
+ else
+ return nil
+ end
+ if Lvirtual == nil then return nil end
+ if eeprom == nil then return nil else
+ minetest.get_meta(pos):set_string("eeprom", eeprom) end
+ end
+ yc_action(pos, Lvirtual)
+ return true
+end
+
+function parse_get_command(code, starti)
+ i = starti
+ local s
+ while s ~= "" do
+ s = string.sub(code, i, i)
+ if s == "(" then
+ return string.sub(code, starti, i-1), i + 1 -- i: ( i+1 after (
+ end
+ if s == ";" and starti == i then
+ starti = starti + 1
+ i = starti
+ elseif s == ">" then
+ starti = yc_skip_to_endif(code, starti)
+ if starti == nil then return nil end
+ i = starti
+ else
+ i = i + 1
+ end
+ end
+
+ if starti == i-1 then
+ return true, true
+ end
+ return nil, nil
+end
+
+function parse_get_params(code, starti)
+ i = starti
+ local s
+ local params = {}
+ local is_string = false
+ while s ~= "" do
+ s = string.sub(code, i, i)
+ if code:sub(i, i) == '"' then
+ is_string = (is_string==false) --toggle is_string
+ end
+ if s == ")" and is_string == false then
+ table.insert(params, string.sub(code, starti, i-1)) -- i: ) i+1 after )
+ return params, i + 1
+ end
+ if s == "," and is_string == false then
+ table.insert(params, string.sub(code, starti, i-1)) -- i: ) i+1 after )
+ starti = i + 1
+ end
+ i = i + 1
+ end
+ return nil, nil
+end
+
+function yc_parse_get_eeprom_param(cond, starti)
+ i = starti
+ local s
+ local addr
+ while s ~= "" do
+ s = string.sub(cond, i, i)
+ if string.find("0123456789", s) == nil or s == "" then
+ addr = string.sub(cond, starti, i-1) -- i: last number i+1 after last number
+ return addr, i
+ end
+ if s == "," then return nil, nil end
+ i = i + 1
+ end
+ return nil, nil
+end
+
+function yc_skip_to_endif(code, starti)
+ local i = starti
+ local s = false
+ local open_ifs = 1
+ while s ~= nil and s~= "" do
+ s = code:sub(i, i)
+ if s == "i" and code:sub(i+1, i+1) == "f" then --if in µCScript
+ open_ifs = open_ifs + 1
+ end
+ if s == ";" then
+ open_ifs = open_ifs - 1
+ end
+ if open_ifs == 0 then
+ return i + 1
+ end
+ i = i + 1
+ end
+ return nil
+end
+
+function yc_skip_to_else(code, starti)
+ local i = starti
+ local s = false
+ local open_ifs = 1
+ while s ~= nil and s~= "" do
+ s = code:sub(i, i)
+ if s == "i" and code:sub(i+1, i+1) == "f" then --if in µCScript
+ open_ifs = open_ifs + 1
+ end
+ if s == ";" then
+ open_ifs = open_ifs - 1
+ end
+ if open_ifs == 1 and s == ">" then
+ return i + 1
+ end
+ i = i + 1
+ end
+ return nil
+end
+
+--Commands
+function yc_command_on(params, L)
+ local rules = {}
+ for i, port in ipairs(params) do
+ L = yc_set_portstate (port, true, L)
+ end
+ return L
+end
+
+function yc_command_off(params, L)
+ local rules = {}
+ for i, port in ipairs(params) do
+ L = yc_set_portstate (port, false, L)
+ end
+ return L
+end
+
+function yc_command_print(params, eeprom, L)
+ local s = ""
+ for i, param in ipairs(params) do
+ if param:sub(1,1) == '"' and param:sub(#param, #param) == '"' then
+ s = s..param:sub(2, #param-1)
+ else
+ r = yc_command_parsecondition(param, L, eeprom)
+ if r == "1" or r == "0" then
+ s = s..r
+ else return nil end
+ end
+ end
+ print(s) --don't remove
+ return true
+end
+
+function yc_command_sbi(params, eeprom, L, Lv)
+ if params[1]==nil or params[2]==nil or params[3] ~=nil then return nil end
+ local status = yc_command_parsecondition(params[2], L, eeprom)
+
+ if status == nil then return nil, nil end
+
+ if string.find("ABCD", params[1])~=nil and #params[1]==1 then --is a port
+ if status == "1" then
+ Lv = yc_set_portstate (params[1], true, Lv)
+ else
+ Lv = yc_set_portstate (params[1], false, Lv)
+ end
+ return eeprom, Lv;
+ end
+
+ --is an eeprom address
+ local new_eeprom = "";
+ for i=1, #eeprom do
+ if tonumber(params[1])==i then
+ new_eeprom = new_eeprom..status
+ else
+ new_eeprom = new_eeprom..eeprom:sub(i, i)
+ end
+ end
+ return new_eeprom, Lv
+end
+
+-- after (delay)
+function yc_command_after(params, pos)
+ if params[1] == nil or params[2] == nil or params[3] ~= nil then return nil end
+
+ --get time (maximum time is 200)
+ local time = tonumber(params[1])
+ if time == nil or time > 200 then
+ return nil
+ end
+
+ --get code in quotes "code"
+ if string.sub(params[2], 1, 1) ~= '"' or string.sub(params[2], #params[2], #params[2]) ~= '"' then return nil end
+ local code = string.sub(params[2], 2, #params[2] - 1)
+
+ local afterid = math.random(10000)
+ local meta = minetest.get_meta(pos)
+ meta:set_int("afterid", afterid)
+ minetest.after(time, yc_command_after_execute, {pos = pos, code = code, afterid = afterid})
+ return true
+end
+
+function yc_command_after_execute(params)
+ local meta = minetest.get_meta(params.pos)
+ if meta:get_int("afterid") == params.afterid then --make sure the node has not been changed
+ if yc_parsecode(params.code, params.pos) == nil then
+ meta:set_string("infotext", "Code in after() not valid!")
+ else
+ if code ~= nil then
+ meta:set_string("infotext", "Working Microcontroller\n"..code)
+ else
+ meta:set_string("infotext", "Working Microcontroller")
+ end
+ end
+ end
+end
+
+--If
+function yc_command_if(code, starti, L, eeprom)
+ local cond, endi = yc_command_if_getcondition(code, starti)
+ if cond == nil then return nil end
+
+ cond = yc_command_parsecondition(cond, L, eeprom)
+
+ local result
+ if cond == "0" then result = false
+ elseif cond == "1" then result = true end
+ if not result then end
+ return result, endi --endi from local cond, endi = yc_command_if_getcondition(code, starti)
+end
+
+--Condition parsing
+function yc_command_if_getcondition(code, starti)
+ i = starti
+ local s
+ local brackets = 1 --1 Bracket to close
+ while s ~= "" do
+ s = string.sub(code, i, i)
+
+ if s == ")" then
+ brackets = brackets - 1
+ end
+
+ if s == "(" then
+ brackets = brackets + 1
+ end
+
+ if brackets == 0 then
+ return string.sub(code, starti, i-1), i + 1 -- i: ( i+1 after (
+ end
+
+ i = i + 1
+ end
+ return nil, nil
+end
+
+function yc_command_parsecondition(cond, L, eeprom)
+ cond = string.gsub(cond, "A", tonumber(L.a and 1 or 0))
+ cond = string.gsub(cond, "B", tonumber(L.b and 1 or 0))
+ cond = string.gsub(cond, "C", tonumber(L.c and 1 or 0))
+ cond = string.gsub(cond, "D", tonumber(L.d and 1 or 0))
+
+
+ local i = 1
+ local l = string.len(cond)
+ while i<=l do
+ local s = cond:sub(i,i)
+ if s == "#" then
+ local addr, endi = yc_parse_get_eeprom_param(cond, i+1)
+ local buf = yc_eeprom_read(tonumber(addr), eeprom)
+ if buf == nil then return nil end
+ local call = cond:sub(i, endi-1)
+ cond = string.gsub(cond, call, buf)
+ i = 0
+ l = string.len(cond)
+ end
+ i = i + 1
+ end
+
+ cond = string.gsub(cond, "!0", "1")
+ cond = string.gsub(cond, "!1", "0")
+
+ local i = 2
+ local l = string.len(cond)
+ while i<=l do
+ local s = cond:sub(i,i)
+ local b = tonumber(cond:sub(i-1, i-1))
+ local a = tonumber(cond:sub(i+1, i+1))
+ if cond:sub(i+1, i+1) == nil then break end
+ if s == "=" then
+ if a==nil then return nil end
+ if b==nil then return nil end
+ if a == b then buf = "1" end
+ if a ~= b then buf = "0" end
+ cond = string.gsub(cond, b..s..a, buf)
+ i = 1
+ l = string.len(cond)
+ end
+ i = i + 1
+ end
+
+ local i = 2
+ local l = string.len(cond)
+ while i<=l do
+ local s = cond:sub(i,i)
+ local b = tonumber(cond:sub(i-1, i-1))
+ local a = tonumber(cond:sub(i+1, i+1))
+ if cond:sub(i+1, i+1) == nil then break end
+ if s == "&" then
+ if a==nil then return nil end
+ local buf = ((a==1) and (b==1))
+ if buf == true then buf = "1" end
+ if buf == false then buf = "0" end
+ cond = string.gsub(cond, b..s..a, buf)
+ i = 1
+ l = string.len(cond)
+ end
+ if s == "|" then
+ if a==nil then return nil end
+ local buf = ((a == 1) or (b == 1))
+ if buf == true then buf = "1" end
+ if buf == false then buf = "0" end
+ cond = string.gsub(cond, b..s..a, buf)
+ i = 1
+ l = string.len(cond)
+ end
+ if s == "~" then
+ if a==nil then return nil end
+ local buf = (((a == 1) or (b == 1)) and not((a==1) and (b==1)))
+ if buf == true then buf = "1" end
+ if buf == false then buf = "0" end
+ cond = string.gsub(cond, b..s..a, buf)
+ i = 1
+ l = string.len(cond)
+ end
+ i = i + 1
+ end
+
+ return cond
+end
+
+--Virtual-Hardware functions
+function yc_eeprom_read(number, eeprom)
+ if not number then return end
+ return eeprom:sub(number, number)
+end
+
+--Real I/O functions
+function yc_action(pos, L) --L-->Lvirtual
+ local Lv = yc_get_virtual_portstates(pos)
+ local name = "mesecons_microcontroller:microcontroller"
+ ..tonumber(L.d and 1 or 0)
+ ..tonumber(L.c and 1 or 0)
+ ..tonumber(L.b and 1 or 0)
+ ..tonumber(L.a and 1 or 0)
+ local node = minetest.get_node(pos)
+ minetest.swap_node(pos, {name = name, param2 = node.param2})
+
+ yc_action_setports(pos, L, Lv)
+end
+
+function yc_action_setports(pos, L, Lv)
+ local name = "mesecons_microcontroller:microcontroller"
+ local rules
+ if Lv.a ~= L.a then
+ rules = microc_rules[name.."0001"]
+ if L.a == true then mesecon.receptor_on(pos, rules)
+ else mesecon.receptor_off(pos, rules) end
+ end
+ if Lv.b ~= L.b then
+ rules = microc_rules[name.."0010"]
+ if L.b == true then mesecon.receptor_on(pos, rules)
+ else mesecon.receptor_off(pos, rules) end
+ end
+ if Lv.c ~= L.c then
+ rules = microc_rules[name.."0100"]
+ if L.c == true then mesecon.receptor_on(pos, rules)
+ else mesecon.receptor_off(pos, rules) end
+ end
+ if Lv.d ~= L.d then
+ rules = microc_rules[name.."1000"]
+ if L.d == true then mesecon.receptor_on(pos, rules)
+ else mesecon.receptor_off(pos, rules) end
+ end
+end
+
+function yc_set_portstate(port, state, L)
+ if port == "A" then L.a = state
+ elseif port == "B" then L.b = state
+ elseif port == "C" then L.c = state
+ elseif port == "D" then L.d = state
+ else return nil end
+ return L
+end
+
+function yc_update_real_portstates(pos, node, rulename, newstate)
+ local meta = minetest.get_meta(pos)
+ if rulename == nil then
+ meta:set_int("real_portstates", 1)
+ return
+ end
+ local n = meta:get_int("real_portstates") - 1
+ local L = {}
+ for i = 1, 4 do
+ L[i] = n%2
+ n = math.floor(n/2)
+ end
+ if rulename.x == nil then
+ for _, rname in ipairs(rulename) do
+ local port = ({4, 1, nil, 3, 2})[rname.x+2*rname.z+3]
+ L[port] = (newstate == "on") and 1 or 0
+ end
+ else
+ local port = ({4, 1, nil, 3, 2})[rulename.x+2*rulename.z+3]
+ L[port] = (newstate == "on") and 1 or 0
+ end
+ meta:set_int("real_portstates", 1 + L[1] + 2*L[2] + 4*L[3] + 8*L[4])
+end
+
+function yc_get_real_portstates(pos) -- determine if ports are powered (by itself or from outside)
+ local meta = minetest.get_meta(pos)
+ local L = {}
+ local n = meta:get_int("real_portstates") - 1
+ for _, index in ipairs({"a", "b", "c", "d"}) do
+ L[index] = ((n%2) == 1)
+ n = math.floor(n/2)
+ end
+ return L
+end
+
+function yc_get_virtual_portstates(pos) -- portstates according to the name
+ local name = minetest.get_node(pos).name
+ local b, a = string.find(name, ":microcontroller")
+ if a == nil then return nil end
+ a = a + 1
+
+ local Lvirtual = {a=false, b=false, c=false, d=false}
+ if name:sub(a , a ) == "1" then Lvirtual.d = true end
+ if name:sub(a+1, a+1) == "1" then Lvirtual.c = true end
+ if name:sub(a+2, a+2) == "1" then Lvirtual.b = true end
+ if name:sub(a+3, a+3) == "1" then Lvirtual.a = true end
+ return Lvirtual
+end
+
+function yc_merge_portstates(Lreal, Lvirtual)
+ local L = {a=false, b=false, c=false, d=false}
+ if Lvirtual.a or Lreal.a then L.a = true end
+ if Lvirtual.b or Lreal.b then L.b = true end
+ if Lvirtual.c or Lreal.c then L.c = true end
+ if Lvirtual.d or Lreal.d then L.d = true end
+ return L
+end