123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089 |
- -- ______
- -- |
- -- |
- -- | __ ___ _ __ _ _
- -- | | | | | |\ | | |_| | | | | |_ |_|
- -- |___| |______ |__| | \| | | \ |__| |_ |_ |_ |\ tube
- -- |
- -- |
- --
- -- Reference
- -- ports = get_real_port_states(pos): gets if inputs are powered from outside
- -- newport = merge_port_states(state1, state2): just does result = state1 or state2 for every port
- -- set_port(pos, rule, state): activates/deactivates the mesecons according to the port states
- -- set_port_states(pos, ports): Applies new port states to a Luacontroller at pos
- -- run_inner(pos, code, event): runs code on the controller at pos and event
- -- reset_formspec(pos, code, errmsg): installs new code and prints error messages, without resetting LCID
- -- reset_meta(pos, code, errmsg): performs a software-reset, installs new code and prints error message
- -- run(pos, event): a wrapper for run_inner which gets code & handles errors via reset_meta
- -- resetn(pos): performs a hardware reset, turns off all ports
- --
- -- The Sandbox
- -- The whole code of the controller runs in a sandbox,
- -- a very restricted environment.
- -- Actually the only way to damage the server is to
- -- use too much memory from the sandbox.
- -- You can add more functions to the environment
- -- (see where local env is defined)
- -- Something nice to play is appending minetest.env to it.
- local BASENAME = "pipeworks:lua_tube"
- local rules = {
- red = {x = -1, y = 0, z = 0, name = "red"},
- blue = {x = 1, y = 0, z = 0, name = "blue"},
- yellow = {x = 0, y = -1, z = 0, name = "yellow"},
- green = {x = 0, y = 1, z = 0, name = "green"},
- black = {x = 0, y = 0, z = -1, name = "black"},
- white = {x = 0, y = 0, z = 1, name = "white"},
- }
- local digiline_rules_luatube = {
- {x=0, y=0, z=-1},
- {x=1, y=0, z=0},
- {x=-1, y=0, z=0},
- {x=0, y=0, z=1},
- {x=1, y=1, z=0},
- {x=1, y=-1, z=0},
- {x=-1, y=1, z=0},
- {x=-1, y=-1, z=0},
- {x=0, y=1, z=1},
- {x=0, y=-1, z=1},
- {x=0, y=1, z=-1},
- {x=0, y=-1, z=-1},
- -- vertical connectivity
- {x=0, y=1, z=0},
- {x=0, y=-1, z=0},
- }
- ------------------
- -- Action stuff --
- ------------------
- -- These helpers are required to set the port states of the lua_tube
- local function update_real_port_states(pos, rule_name, new_state)
- local meta = minetest.get_meta(pos)
- if rule_name == nil then
- meta:set_int("real_portstates", 1)
- return
- end
- local n = meta:get_int("real_portstates") - 1
- local L = {}
- for i = 1, 6 do
- L[i] = n % 2
- n = math.floor(n / 2)
- end
- -- (0,0,-1) (0,-1,0) (-1,0,0) (1,0,0) (0,1,0) (0,0,1)
- local pos_to_side = { 5, 3, 1, nil, 2, 4, 6 }
- if rule_name.x == nil then
- for _, rname in ipairs(rule_name) do
- local port = pos_to_side[rname.x + (2 * rname.y) + (3 * rname.z) + 4]
- L[port] = (new_state == "on") and 1 or 0
- end
- else
- local port = pos_to_side[rule_name.x + (2 * rule_name.y) + (3 * rule_name.z) + 4]
- L[port] = (new_state == "on") and 1 or 0
- end
- meta:set_int("real_portstates",
- 1 +
- 1 * L[1] +
- 2 * L[2] +
- 4 * L[3] +
- 8 * L[4] +
- 16 * L[5] +
- 32 * L[6])
- end
- local port_names = {"red", "blue", "yellow", "green", "black", "white"}
- local function get_real_port_states(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 _, name in ipairs(port_names) do
- L[name] = ((n % 2) == 1)
- n = math.floor(n / 2)
- end
- return L
- end
- local function merge_port_states(ports, vports)
- return {
- red = ports.red or vports.red,
- blue = ports.blue or vports.blue,
- yellow = ports.yellow or vports.yellow,
- green = ports.green or vports.green,
- black = ports.black or vports.black,
- white = ports.white or vports.white,
- }
- end
- local function generate_name(ports)
- local red = ports.red and 1 or 0
- local blue = ports.blue and 1 or 0
- local yellow = ports.yellow and 1 or 0
- local green = ports.green and 1 or 0
- local black = ports.black and 1 or 0
- local white = ports.white and 1 or 0
- return BASENAME..white..black..green..yellow..blue..red
- end
- local function set_port(pos, rule, state)
- if state then
- mesecon.receptor_on(pos, {rule})
- else
- mesecon.receptor_off(pos, {rule})
- end
- end
- local function clean_port_states(ports)
- ports.red = ports.red and true or false
- ports.blue = ports.blue and true or false
- ports.yellow = ports.yellow and true or false
- ports.green = ports.green and true or false
- ports.black = ports.black and true or false
- ports.white = ports.white and true or false
- end
- local function set_port_states(pos, ports)
- local node = minetest.get_node(pos)
- local name = node.name
- clean_port_states(ports)
- local vports = minetest.registered_nodes[name].virtual_portstates
- local new_name = generate_name(ports)
- if name ~= new_name and vports then
- -- Problem:
- -- We need to place the new node first so that when turning
- -- off some port, it won't stay on because the rules indicate
- -- there is an onstate output port there.
- -- When turning the output off then, it will however cause feedback
- -- so that the lua_tube will receive an "off" event by turning
- -- its output off.
- -- Solution / Workaround:
- -- Remember which output was turned off and ignore next "off" event.
- local meta = minetest.get_meta(pos)
- local ign = minetest.deserialize(meta:get_string("ignore_offevents"), true) or {}
- if ports.red and not vports.red and not mesecon.is_powered(pos, rules.red) then ign.red = true end
- if ports.blue and not vports.blue and not mesecon.is_powered(pos, rules.blue) then ign.blue = true end
- if ports.yellow and not vports.yellow and not mesecon.is_powered(pos, rules.yellow) then ign.yellow = true end
- if ports.green and not vports.green and not mesecon.is_powered(pos, rules.green) then ign.green = true end
- if ports.black and not vports.black and not mesecon.is_powered(pos, rules.black) then ign.black = true end
- if ports.white and not vports.white and not mesecon.is_powered(pos, rules.white) then ign.white = true end
- meta:set_string("ignore_offevents", minetest.serialize(ign))
- minetest.swap_node(pos, {name = new_name, param2 = node.param2})
- if ports.red ~= vports.red then set_port(pos, rules.red, ports.red) end
- if ports.blue ~= vports.blue then set_port(pos, rules.blue, ports.blue) end
- if ports.yellow ~= vports.yellow then set_port(pos, rules.yellow, ports.yellow) end
- if ports.green ~= vports.green then set_port(pos, rules.green, ports.green) end
- if ports.black ~= vports.black then set_port(pos, rules.black, ports.black) end
- if ports.white ~= vports.white then set_port(pos, rules.white, ports.white) end
- end
- end
- -----------------
- -- Overheating --
- -----------------
- local function burn_controller(pos)
- local node = minetest.get_node(pos)
- node.name = BASENAME.."_burnt"
- minetest.swap_node(pos, node)
- minetest.get_meta(pos):set_string("lc_memory", "");
- -- Wait for pending operations
- minetest.after(0.2, mesecon.receptor_off, pos, mesecon.rules.flat)
- end
- local function overheat(pos, meta)
- if mesecon.do_overheat(pos) then -- If too hot
- burn_controller(pos)
- return true
- end
- end
- ------------------------
- -- Ignored off events --
- ------------------------
- local function ignore_event(event, meta)
- if event.type ~= "off" then return false end
- local ignore_offevents = minetest.deserialize(meta:get_string("ignore_offevents"), true) or {}
- if ignore_offevents[event.pin.name] then
- ignore_offevents[event.pin.name] = nil
- meta:set_string("ignore_offevents", minetest.serialize(ignore_offevents))
- return true
- end
- end
- -------------------------
- -- Parsing and running --
- -------------------------
- local function safe_print(param)
- local string_meta = getmetatable("")
- local sandbox = string_meta.__index
- string_meta.__index = string -- Leave string sandbox temporarily
- print(dump(param))
- string_meta.__index = sandbox -- Restore string sandbox
- end
- local function safe_date()
- return(os.date("*t",os.time()))
- end
- -- string.rep(str, n) with a high value for n can be used to DoS
- -- the server. Therefore, limit max. length of generated string.
- local function safe_string_rep(str, n)
- if #str * n > mesecon.setting("luacontroller_string_rep_max", 64000) then
- debug.sethook() -- Clear hook
- error("string.rep: string length overflow", 2)
- end
- return string.rep(str, n)
- end
- -- string.find with a pattern can be used to DoS the server.
- -- Therefore, limit string.find to patternless matching.
- local function safe_string_find(...)
- if (select(4, ...)) ~= true then
- debug.sethook() -- Clear hook
- error("string.find: 'plain' (fourth parameter) must always be true in a lua controlled tube")
- end
- return string.find(...)
- end
- local function remove_functions(x)
- local tp = type(x)
- if tp == "function" then
- return nil
- end
- -- Make sure to not serialize the same table multiple times, otherwise
- -- writing mem.test = mem in the lua controlled tube will lead to infinite recursion
- local seen = {}
- local function rfuncs(x)
- if x == nil then return end
- if seen[x] then return end
- seen[x] = true
- if type(x) ~= "table" then return end
- for key, value in pairs(x) do
- if type(key) == "function" or type(value) == "function" then
- x[key] = nil
- else
- if type(key) == "table" then
- rfuncs(key)
- end
- if type(value) == "table" then
- rfuncs(value)
- end
- end
- end
- end
- rfuncs(x)
- return x
- end
- -- The setting affects API so is not intended to be changeable at runtime
- local get_interrupt
- if mesecon.setting("luacontroller_lightweight_interrupts", false) then
- -- use node timer
- get_interrupt = function(pos, itbl, send_warning)
- return (function(time, iid)
- if type(time) ~= "number" then error("Delay must be a number") end
- if iid ~= nil then send_warning("Interrupt IDs are disabled on this server") end
- table.insert(itbl, function() minetest.get_node_timer(pos):start(time) end)
- end)
- end
- else
- -- use global action queue
- -- itbl: Flat table of functions to run after sandbox cleanup, used to prevent various security hazards
- get_interrupt = function(pos, itbl, send_warning)
- -- iid = interrupt id
- local function interrupt(time, iid)
- -- NOTE: This runs within string metatable sandbox, so don't *rely* on anything of the form (""):y
- -- Hence the values get moved out. Should take less time than original, so totally compatible
- if type(time) ~= "number" then error("Delay must be a number") end
- table.insert(itbl, function ()
- -- Outside string metatable sandbox, can safely run this now
- local luac_id = minetest.get_meta(pos):get_int("luac_id")
- -- Check if IID is dodgy, so you can't use interrupts to store an infinite amount of data.
- -- Note that this is safe from alter-after-free because this code gets run after the sandbox has ended.
- -- This runs outside of the timer and *shouldn't* harm perf. unless dodgy data is being sent in the first place
- iid = remove_functions(iid)
- local msg_ser = minetest.serialize(iid)
- if #msg_ser <= mesecon.setting("luacontroller_interruptid_maxlen", 256) then
- mesecon.queue:add_action(pos, "pipeworks:lc_tube_interrupt", {luac_id, iid}, time, iid, 1)
- else
- send_warning("An interrupt ID was too large!")
- end
- end)
- end
- return interrupt
- end
- end
- -- Given a message object passed to digiline_send, clean it up into a form
- -- which is safe to transmit over the network and compute its "cost" (a very
- -- rough estimate of its memory usage).
- --
- -- The cleaning comprises the following:
- -- 1. Functions (and userdata, though user scripts ought not to get hold of
- -- those in the first place) are removed, because they break the model of
- -- Digilines as a network that carries basic data, and they could exfiltrate
- -- references to mutable objects from one Luacontroller to another, allowing
- -- inappropriate high-bandwidth, no-wires communication.
- -- 2. Tables are duplicated because, being mutable, they could otherwise be
- -- modified after the send is complete in order to change what data arrives
- -- at the recipient, perhaps in violation of the previous cleaning rule or
- -- in violation of the message size limit.
- --
- -- The cost indication is only approximate; it’s not a perfect measurement of
- -- the number of bytes of memory used by the message object.
- --
- -- Parameters:
- -- msg -- the message to clean
- -- back_references -- for internal use only; do not provide
- --
- -- Returns:
- -- 1. The cleaned object.
- -- 2. The approximate cost of the object.
- local function clean_and_weigh_digiline_message(msg, back_references)
- local t = type(msg)
- if t == "string" then
- -- Strings are immutable so can be passed by reference, and cost their
- -- length plus the size of the Lua object header (24 bytes on a 64-bit
- -- platform) plus one byte for the NUL terminator.
- return msg, #msg + 25
- elseif t == "number" then
- -- Numbers are passed by value so need not be touched, and cost 8 bytes
- -- as all numbers in Lua are doubles.
- return msg, 8
- elseif t == "boolean" then
- -- Booleans are passed by value so need not be touched, and cost 1
- -- byte.
- return msg, 1
- elseif t == "table" then
- -- Tables are duplicated. Check if this table has been seen before
- -- (self-referential or shared table); if so, reuse the cleaned value
- -- of the previous occurrence, maintaining table topology and avoiding
- -- infinite recursion, and charge zero bytes for this as the object has
- -- already been counted.
- back_references = back_references or {}
- local bref = back_references[msg]
- if bref then
- return bref, 0
- end
- -- Construct a new table by cleaning all the keys and values and adding
- -- up their costs, plus 8 bytes as a rough estimate of table overhead.
- local cost = 8
- local ret = {}
- back_references[msg] = ret
- for k, v in pairs(msg) do
- local k_cost, v_cost
- k, k_cost = clean_and_weigh_digiline_message(k, back_references)
- v, v_cost = clean_and_weigh_digiline_message(v, back_references)
- if k ~= nil and v ~= nil then
- -- Only include an element if its key and value are of legal
- -- types.
- ret[k] = v
- end
- -- If we only counted the cost of a table element when we actually
- -- used it, we would be vulnerable to the following attack:
- -- 1. Construct a huge table (too large to pass the cost limit).
- -- 2. Insert it somewhere in a table, with a function as a key.
- -- 3. Insert it somewhere in another table, with a number as a key.
- -- 4. The first occurrence doesn’t pay the cost because functions
- -- are stripped and therefore the element is dropped.
- -- 5. The second occurrence doesn’t pay the cost because it’s in
- -- back_references.
- -- By counting the costs regardless of whether the objects will be
- -- included, we avoid this attack; it may overestimate the cost of
- -- some messages, but only those that won’t be delivered intact
- -- anyway because they contain illegal object types.
- cost = cost + k_cost + v_cost
- end
- return ret, cost
- else
- return nil, 0
- end
- end
- -- itbl: Flat table of functions to run after sandbox cleanup, used to prevent various security hazards
- local function get_digiline_send(pos, itbl, send_warning)
- if not minetest.global_exists("digilines") then return end
- local chan_maxlen = mesecon.setting("luacontroller_digiline_channel_maxlen", 256)
- local maxlen = mesecon.setting("luacontroller_digiline_maxlen", 50000)
- return function(channel, msg)
- -- NOTE: This runs within string metatable sandbox, so don't *rely* on anything of the form (""):y
- -- or via anything that could.
- -- Make sure channel is string, number or boolean
- if type(channel) == "string" then
- if #channel > chan_maxlen then
- send_warning("Channel string too long.")
- return false
- end
- elseif (type(channel) ~= "string" and type(channel) ~= "number" and type(channel) ~= "boolean") then
- send_warning("Channel must be string, number or boolean.")
- return false
- end
- local msg_cost
- msg, msg_cost = clean_and_weigh_digiline_message(msg)
- if msg == nil or msg_cost > maxlen then
- send_warning("Message was too complex, or contained invalid data.")
- return false
- end
- table.insert(itbl, function ()
- -- Runs outside of string metatable sandbox
- local luac_id = minetest.get_meta(pos):get_int("luac_id")
- mesecon.queue:add_action(pos, "pipeworks:lt_digiline_relay", {channel, luac_id, msg})
- end)
- return true
- end
- end
- local safe_globals = {
- -- Don't add pcall/xpcall unless willing to deal with the consequences (unless very careful, incredibly likely to allow killing server indirectly)
- "assert", "error", "ipairs", "next", "pairs", "select",
- "tonumber", "tostring", "type", "unpack", "_VERSION"
- }
- local function create_environment(pos, mem, event, itbl, send_warning)
- -- Make sure the tube hasn't broken.
- local vports = minetest.registered_nodes[minetest.get_node(pos).name].virtual_portstates
- if not vports then return {} end
- -- Gather variables for the environment
- local vports_copy = {}
- for k, v in pairs(vports) do vports_copy[k] = v end
- local rports = get_real_port_states(pos)
- -- Create new library tables on each call to prevent one Luacontroller
- -- from breaking a library and messing up other Luacontrollers.
- local env = {
- pin = merge_port_states(vports, rports),
- port = vports_copy,
- event = event,
- mem = mem,
- heat = mesecon.get_heat(pos),
- heat_max = mesecon.setting("overheat_max", 20),
- print = safe_print,
- interrupt = get_interrupt(pos, itbl, send_warning),
- digiline_send = get_digiline_send(pos, itbl, send_warning),
- string = {
- byte = string.byte,
- char = string.char,
- format = string.format,
- len = string.len,
- lower = string.lower,
- upper = string.upper,
- rep = safe_string_rep,
- reverse = string.reverse,
- sub = string.sub,
- find = safe_string_find,
- },
- math = {
- abs = math.abs,
- acos = math.acos,
- asin = math.asin,
- atan = math.atan,
- atan2 = math.atan2,
- ceil = math.ceil,
- cos = math.cos,
- cosh = math.cosh,
- deg = math.deg,
- exp = math.exp,
- floor = math.floor,
- fmod = math.fmod,
- frexp = math.frexp,
- huge = math.huge,
- ldexp = math.ldexp,
- log = math.log,
- log10 = math.log10,
- max = math.max,
- min = math.min,
- modf = math.modf,
- pi = math.pi,
- pow = math.pow,
- rad = math.rad,
- random = math.random,
- sin = math.sin,
- sinh = math.sinh,
- sqrt = math.sqrt,
- tan = math.tan,
- tanh = math.tanh,
- },
- table = {
- concat = table.concat,
- insert = table.insert,
- maxn = table.maxn,
- remove = table.remove,
- sort = table.sort,
- },
- os = {
- clock = os.clock,
- difftime = os.difftime,
- time = os.time,
- datetable = safe_date,
- },
- }
- env._G = env
- for _, name in pairs(safe_globals) do
- env[name] = _G[name]
- end
- return env
- end
- local function timeout()
- debug.sethook() -- Clear hook
- error("Code timed out!", 2)
- end
- local function create_sandbox(code, env)
- if code:byte(1) == 27 then
- return nil, "Binary code prohibited."
- end
- local f, msg = loadstring(code)
- if not f then return nil, msg end
- setfenv(f, env)
- -- Turn off JIT optimization for user code so that count
- -- events are generated when adding debug hooks
- if rawget(_G, "jit") then
- jit.off(f, true)
- end
- local maxevents = mesecon.setting("luacontroller_maxevents", 10000)
- return function(...)
- -- NOTE: This runs within string metatable sandbox, so the setting's been moved out for safety
- -- Use instruction counter to stop execution
- -- after luacontroller_maxevents
- debug.sethook(timeout, "", maxevents)
- local ok, ret = pcall(f, ...)
- debug.sethook() -- Clear hook
- if not ok then error(ret, 0) end
- return ret
- end
- end
- local function load_memory(meta)
- return minetest.deserialize(meta:get_string("lc_memory"), true) or {}
- end
- local function save_memory(pos, meta, mem)
- local memstring = minetest.serialize(remove_functions(mem))
- local memsize_max = mesecon.setting("luacontroller_memsize", 100000)
- if (#memstring <= memsize_max) then
- meta:set_string("lc_memory", memstring)
- meta:mark_as_private("lc_memory")
- else
- print("Error: lua_tube memory overflow. "..memsize_max.." bytes available, "
- ..#memstring.." required. Controller overheats.")
- burn_controller(pos)
- end
- end
- -- Returns success (boolean), errmsg (string), retval(any, return value of the user supplied code)
- -- run (as opposed to run_inner) is responsible for setting up meta according to this output
- local function run_inner(pos, code, event)
- local meta = minetest.get_meta(pos)
- -- Note: These return success, presumably to avoid changing LC ID.
- if overheat(pos) then return true, "", nil end
- if ignore_event(event, meta) then return true, "", nil end
- -- Load code & mem from meta
- local mem = load_memory(meta)
- local code = meta:get_string("code")
- -- 'Last warning' label.
- local warning = ""
- local function send_warning(str)
- warning = "Warning: " .. str
- end
- -- Create environment
- local itbl = {}
- local env = create_environment(pos, mem, event, itbl, send_warning)
- -- Create the sandbox and execute code
- local f, msg = create_sandbox(code, env)
- if not f then return false, msg, nil end
- -- Start string true sandboxing
- local onetruestring = getmetatable("")
- -- If a string sandbox is already up yet inconsistent, something is very wrong
- assert(onetruestring.__index == string)
- onetruestring.__index = env.string
- local success, msg = pcall(f)
- onetruestring.__index = string
- -- End string true sandboxing
- if not success then return false, msg, nil end
- if type(env.port) ~= "table" then
- return false, "Ports set are invalid.", nil
- end
- -- Actually set the ports
- set_port_states(pos, env.port)
- -- Save memory. This may burn the luacontroller if a memory overflow occurs.
- save_memory(pos, meta, env.mem)
- -- Execute deferred tasks
- for _, v in ipairs(itbl) do
- local failure = v()
- if failure then
- return false, failure, nil
- end
- end
- return true, warning, msg
- end
- local function reset_formspec(meta, code, errmsg)
- meta:set_string("code", code)
- meta:mark_as_private("code")
- code = minetest.formspec_escape(code or "")
- errmsg = minetest.formspec_escape(tostring(errmsg or ""))
- meta:set_string("formspec", "size[12,10]"
- .."background[-0.2,-0.25;12.4,10.75;jeija_luac_background.png]"
- .."label[0.1,8.3;"..errmsg.."]"
- .."textarea[0.2,0.2;12.2,9.5;code;;"..code.."]"
- .."image_button[4.75,8.75;2.5,1;jeija_luac_runbutton.png;program;]"
- .."image_button_exit[11.72,-0.25;0.425,0.4;jeija_close_window.png;exit;]"
- )
- end
- local function reset_meta(pos, code, errmsg)
- local meta = minetest.get_meta(pos)
- reset_formspec(meta, code, errmsg)
- meta:set_int("luac_id", math.random(1, 65535))
- end
- -- Wraps run_inner with LC-reset-on-error
- local function run(pos, event)
- local meta = minetest.get_meta(pos)
- local code = meta:get_string("code")
- local ok, errmsg, retval = run_inner(pos, code, event)
- if not ok then
- reset_meta(pos, code, errmsg)
- else
- reset_formspec(meta, code, errmsg)
- end
- return ok, errmsg, retval
- end
- local function reset(pos)
- set_port_states(pos, {red = false, blue = false, yellow = false,
- green = false, black = false, white = false})
- end
- local function node_timer(pos)
- if minetest.registered_nodes[minetest.get_node(pos).name].is_burnt then
- return false
- end
- run(pos, {type="interrupt"})
- return false
- end
- -----------------------
- -- A.Queue callbacks --
- -----------------------
- mesecon.queue:add_function("pipeworks:lc_tube_interrupt", function (pos, luac_id, iid)
- -- There is no lua_tube anymore / it has been reprogrammed / replaced / burnt
- if (minetest.get_meta(pos):get_int("luac_id") ~= luac_id) then return end
- if (minetest.registered_nodes[minetest.get_node(pos).name].is_burnt) then return end
- run(pos, {type="interrupt", iid = iid})
- end)
- mesecon.queue:add_function("pipeworks:lt_digiline_relay", function (pos, channel, luac_id, msg)
- if not digiline then return end
- -- This check is only really necessary because in case of server crash, old actions can be thrown into the future
- if (minetest.get_meta(pos):get_int("luac_id") ~= luac_id) then return end
- if (minetest.registered_nodes[minetest.get_node(pos).name].is_burnt) then return end
- -- The actual work
- digiline:receptor_send(pos, digiline_rules_luatube, channel, msg)
- end)
- -----------------------
- -- Node Registration --
- -----------------------
- local output_rules = {}
- local input_rules = {}
- local node_box = {
- type = "fixed",
- fixed = {
- pipeworks.tube_leftstub[1], -- tube segment against -X face
- pipeworks.tube_rightstub[1], -- tube segment against +X face
- pipeworks.tube_bottomstub[1], -- tube segment against -Y face
- pipeworks.tube_topstub[1], -- tube segment against +Y face
- pipeworks.tube_frontstub[1], -- tube segment against -Z face
- pipeworks.tube_backstub[1], -- tube segment against +Z face
- }
- }
- local selection_box = {
- type = "fixed",
- fixed = pipeworks.tube_selectboxes,
- }
- local digiline = {
- receptor = {},
- effector = {
- action = function(pos, node, channel, msg)
- msg = clean_and_weigh_digiline_message(msg)
- run(pos, {type = "digiline", channel = channel, msg = msg})
- end
- },
- wire = {
- rules = pipeworks.digilines_rules
- },
- }
- local function get_program(pos)
- local meta = minetest.get_meta(pos)
- return meta:get_string("code")
- end
- local function set_program(pos, code)
- reset(pos)
- reset_meta(pos, code)
- return run(pos, {type="program"})
- end
- local function on_receive_fields(pos, form_name, fields, sender)
- if not fields.program then
- return
- end
- local name = sender:get_player_name()
- if minetest.is_protected(pos, name) and not minetest.check_player_privs(name, {protection_bypass=true}) then
- minetest.record_protection_violation(pos, name)
- return
- end
- local ok, err = set_program(pos, fields.code)
- if not ok then
- -- it's not an error from the server perspective
- minetest.log("action", "Lua controller programming error: " .. tostring(err))
- end
- end
- local function go_back(velocity)
- local adjlist={{x=0,y=0,z=1},{x=0,y=0,z=-1},{x=0,y=1,z=0},{x=0,y=-1,z=0},{x=1,y=0,z=0},{x=-1,y=0,z=0}}
- local speed = math.abs(velocity.x + velocity.y + velocity.z)
- if speed == 0 then
- speed = 1
- end
- local vel = {x = velocity.x/speed, y = velocity.y/speed, z = velocity.z/speed,speed=speed}
- if speed >= 4.1 then
- speed = 4
- elseif speed >= 1.1 then
- speed = speed - 0.1
- else
- speed = 1
- end
- vel.speed = speed
- return pipeworks.notvel(adjlist, vel)
- end
- local tiles_base = {
- "pipeworks_mese_tube_plain_4.png", "pipeworks_mese_tube_plain_3.png",
- "pipeworks_mese_tube_plain_2.png", "pipeworks_mese_tube_plain_1.png",
- "pipeworks_mese_tube_plain_6.png", "pipeworks_mese_tube_plain_5.png"}
- for red = 0, 1 do -- 0 = off 1 = on
- for blue = 0, 1 do
- for yellow = 0, 1 do
- for green = 0, 1 do
- for black = 0, 1 do
- for white = 0, 1 do
- local cid = tostring(white)..tostring(black)..tostring(green)..
- tostring(yellow)..tostring(blue)..tostring(red)
- local node_name = BASENAME..cid
- local tiles = table.copy(tiles_base)
- if red == 1 then
- tiles[1] = tiles[1].."^(pipeworks_lua_tube_port_on.png^[transformR90)"
- tiles[2] = tiles[2].."^(pipeworks_lua_tube_port_on.png^[transformR90)"
- tiles[5] = tiles[5].."^(pipeworks_lua_tube_port_on.png^[transformR270)"
- tiles[6] = tiles[6].."^(pipeworks_lua_tube_port_on.png^[transformR90)"
- else
- tiles[1] = tiles[1].."^(pipeworks_lua_tube_port_off.png^[transformR90)"
- tiles[2] = tiles[2].."^(pipeworks_lua_tube_port_off.png^[transformR90)"
- tiles[5] = tiles[5].."^(pipeworks_lua_tube_port_off.png^[transformR270)"
- tiles[6] = tiles[6].."^(pipeworks_lua_tube_port_off.png^[transformR90)"
- end
- if blue == 1 then
- tiles[1] = tiles[1].."^(pipeworks_lua_tube_port_on.png^[transformR270)"
- tiles[2] = tiles[2].."^(pipeworks_lua_tube_port_on.png^[transformR270)"
- tiles[5] = tiles[5].."^(pipeworks_lua_tube_port_on.png^[transformR90)"
- tiles[6] = tiles[6].."^(pipeworks_lua_tube_port_on.png^[transformR270)"
- else
- tiles[1] = tiles[1].."^(pipeworks_lua_tube_port_off.png^[transformR270)"
- tiles[2] = tiles[2].."^(pipeworks_lua_tube_port_off.png^[transformR270)"
- tiles[5] = tiles[5].."^(pipeworks_lua_tube_port_off.png^[transformR90)"
- tiles[6] = tiles[6].."^(pipeworks_lua_tube_port_off.png^[transformR270)"
- end
- if yellow == 1 then
- tiles[3] = tiles[3].."^(pipeworks_lua_tube_port_on.png^[transformR180)"
- tiles[4] = tiles[4].."^(pipeworks_lua_tube_port_on.png^[transformR180)"
- tiles[5] = tiles[5].."^(pipeworks_lua_tube_port_on.png^[transformR180)"
- tiles[6] = tiles[6].."^(pipeworks_lua_tube_port_on.png^[transformR180)"
- else
- tiles[3] = tiles[3].."^(pipeworks_lua_tube_port_off.png^[transformR180)"
- tiles[4] = tiles[4].."^(pipeworks_lua_tube_port_off.png^[transformR180)"
- tiles[5] = tiles[5].."^(pipeworks_lua_tube_port_off.png^[transformR180)"
- tiles[6] = tiles[6].."^(pipeworks_lua_tube_port_off.png^[transformR180)"
- end
- if green == 1 then
- tiles[3] = tiles[3].."^pipeworks_lua_tube_port_on.png"
- tiles[4] = tiles[4].."^pipeworks_lua_tube_port_on.png"
- tiles[5] = tiles[5].."^pipeworks_lua_tube_port_on.png"
- tiles[6] = tiles[6].."^pipeworks_lua_tube_port_on.png"
- else
- tiles[3] = tiles[3].."^pipeworks_lua_tube_port_off.png"
- tiles[4] = tiles[4].."^pipeworks_lua_tube_port_off.png"
- tiles[5] = tiles[5].."^pipeworks_lua_tube_port_off.png"
- tiles[6] = tiles[6].."^pipeworks_lua_tube_port_off.png"
- end
- if black == 1 then
- tiles[1] = tiles[1].."^(pipeworks_lua_tube_port_on.png^[transformR180)"
- tiles[2] = tiles[2].."^pipeworks_lua_tube_port_on.png"
- tiles[3] = tiles[3].."^(pipeworks_lua_tube_port_on.png^[transformR90)"
- tiles[4] = tiles[4].."^(pipeworks_lua_tube_port_on.png^[transformR270)"
- else
- tiles[1] = tiles[1].."^(pipeworks_lua_tube_port_off.png^[transformR180)"
- tiles[2] = tiles[2].."^pipeworks_lua_tube_port_off.png"
- tiles[3] = tiles[3].."^(pipeworks_lua_tube_port_off.png^[transformR90)"
- tiles[4] = tiles[4].."^(pipeworks_lua_tube_port_off.png^[transformR270)"
- end
- if white == 1 then
- tiles[1] = tiles[1].."^pipeworks_lua_tube_port_on.png"
- tiles[2] = tiles[2].."^(pipeworks_lua_tube_port_on.png^[transformR180)"
- tiles[3] = tiles[3].."^(pipeworks_lua_tube_port_on.png^[transformR270)"
- tiles[4] = tiles[4].."^(pipeworks_lua_tube_port_on.png^[transformR90)"
- else
- tiles[1] = tiles[1].."^pipeworks_lua_tube_port_off.png"
- tiles[2] = tiles[2].."^(pipeworks_lua_tube_port_off.png^[transformR180)"
- tiles[3] = tiles[3].."^(pipeworks_lua_tube_port_off.png^[transformR270)"
- tiles[4] = tiles[4].."^(pipeworks_lua_tube_port_off.png^[transformR90)"
- end
- local groups = {snappy = 3, tube = 1, tubedevice = 1, overheat = 1}
- if red + blue + yellow + green + black + white ~= 0 then
- groups.not_in_creative_inventory = 1
- end
- output_rules[cid] = {}
- input_rules[cid] = {}
- if red == 1 then table.insert(output_rules[cid], rules.red) end
- if blue == 1 then table.insert(output_rules[cid], rules.blue) end
- if yellow == 1 then table.insert(output_rules[cid], rules.yellow) end
- if green == 1 then table.insert(output_rules[cid], rules.green) end
- if black == 1 then table.insert(output_rules[cid], rules.black) end
- if white == 1 then table.insert(output_rules[cid], rules.white) end
- if red == 0 then table.insert( input_rules[cid], rules.red) end
- if blue == 0 then table.insert( input_rules[cid], rules.blue) end
- if yellow == 0 then table.insert( input_rules[cid], rules.yellow) end
- if green == 0 then table.insert( input_rules[cid], rules.green) end
- if black == 0 then table.insert( input_rules[cid], rules.black) end
- if white == 0 then table.insert( input_rules[cid], rules.white) end
- local mesecons = {
- effector = {
- rules = input_rules[cid],
- action_change = function (pos, _, rule_name, new_state)
- update_real_port_states(pos, rule_name, new_state)
- run(pos, {type=new_state, pin=rule_name})
- end,
- },
- receptor = {
- state = mesecon.state.on,
- rules = output_rules[cid]
- },
- luacontroller = {
- get_program = get_program,
- set_program = set_program,
- },
- }
- minetest.register_node(node_name, {
- description = "Lua controlled Tube",
- drawtype = "nodebox",
- tiles = tiles,
- paramtype = "light",
- is_ground_content = false,
- groups = groups,
- drop = BASENAME.."000000",
- sunlight_propagates = true,
- selection_box = selection_box,
- node_box = node_box,
- on_construct = reset_meta,
- on_receive_fields = on_receive_fields,
- sounds = default.node_sound_wood_defaults(),
- mesecons = mesecons,
- digiline = digiline,
- -- Virtual portstates are the ports that
- -- the node shows as powered up (light up).
- virtual_portstates = {
- red = red == 1,
- blue = blue == 1,
- yellow = yellow == 1,
- green = green == 1,
- black = black == 1,
- white = white == 1,
- },
- after_dig_node = function(pos, node)
- mesecon.do_cooldown(pos)
- mesecon.receptor_off(pos, output_rules)
- pipeworks.after_dig(pos, node)
- end,
- is_luacontroller = true,
- on_timer = node_timer,
- tubelike = 1,
- tube = {
- connect_sides = {front = 1, back = 1, left = 1, right = 1, top = 1, bottom = 1},
- priority = 50,
- can_go = function(pos, node, velocity, stack)
- local src = {name = nil}
- -- add color of the incoming tube explicitly; referring to rules, in case they change later
- for color, rule in pairs(rules) do
- if (-velocity.x == rule.x and -velocity.y == rule.y and -velocity.z == rule.z) then
- src.name = rule.name
- break
- end
- end
- local succ, _, msg = run(pos, {
- type = "item",
- pin = src,
- itemstring = stack:to_string(),
- item = stack:to_table(),
- velocity = velocity,
- })
- if not succ or type(msg) ~= "string" then
- return go_back(velocity)
- end
- local r = rules[msg]
- return r and {r} or go_back(velocity)
- end,
- },
- after_place_node = pipeworks.after_place,
- on_blast = function(pos, intensity)
- if not intensity or intensity > 1 + 3^0.5 then
- minetest.remove_node(pos)
- return {string.format("%s_%s", name, dropname)}
- end
- minetest.swap_node(pos, {name = "pipeworks:broken_tube_1"})
- pipeworks.scan_for_tube_objects(pos)
- end,
- })
- end
- end
- end
- end
- end
- end
- ------------------------------------
- -- Overheated Lua controlled Tube --
- ------------------------------------
- local tiles_burnt = table.copy(tiles_base)
- tiles_burnt[1] = tiles_burnt[1].."^(pipeworks_lua_tube_port_burnt.png^[transformR90)"
- tiles_burnt[2] = tiles_burnt[2].."^(pipeworks_lua_tube_port_burnt.png^[transformR90)"
- tiles_burnt[5] = tiles_burnt[5].."^(pipeworks_lua_tube_port_burnt.png^[transformR270)"
- tiles_burnt[6] = tiles_burnt[6].."^(pipeworks_lua_tube_port_burnt.png^[transformR90)"
- tiles_burnt[1] = tiles_burnt[1].."^(pipeworks_lua_tube_port_burnt.png^[transformR270)"
- tiles_burnt[2] = tiles_burnt[2].."^(pipeworks_lua_tube_port_burnt.png^[transformR270)"
- tiles_burnt[5] = tiles_burnt[5].."^(pipeworks_lua_tube_port_burnt.png^[transformR90)"
- tiles_burnt[6] = tiles_burnt[6].."^(pipeworks_lua_tube_port_burnt.png^[transformR270)"
- tiles_burnt[3] = tiles_burnt[3].."^(pipeworks_lua_tube_port_burnt.png^[transformR180)"
- tiles_burnt[4] = tiles_burnt[4].."^(pipeworks_lua_tube_port_burnt.png^[transformR180)"
- tiles_burnt[5] = tiles_burnt[5].."^(pipeworks_lua_tube_port_burnt.png^[transformR180)"
- tiles_burnt[6] = tiles_burnt[6].."^(pipeworks_lua_tube_port_burnt.png^[transformR180)"
- tiles_burnt[3] = tiles_burnt[3].."^pipeworks_lua_tube_port_burnt.png"
- tiles_burnt[4] = tiles_burnt[4].."^pipeworks_lua_tube_port_burnt.png"
- tiles_burnt[5] = tiles_burnt[5].."^pipeworks_lua_tube_port_burnt.png"
- tiles_burnt[6] = tiles_burnt[6].."^pipeworks_lua_tube_port_burnt.png"
- tiles_burnt[1] = tiles_burnt[1].."^(pipeworks_lua_tube_port_burnt.png^[transformR180)"
- tiles_burnt[2] = tiles_burnt[2].."^pipeworks_lua_tube_port_burnt.png"
- tiles_burnt[3] = tiles_burnt[3].."^(pipeworks_lua_tube_port_burnt.png^[transformR90)"
- tiles_burnt[4] = tiles_burnt[4].."^(pipeworks_lua_tube_port_burnt.png^[transformR270)"
- tiles_burnt[1] = tiles_burnt[1].."^pipeworks_lua_tube_port_burnt.png"
- tiles_burnt[2] = tiles_burnt[2].."^(pipeworks_lua_tube_port_burnt.png^[transformR180)"
- tiles_burnt[3] = tiles_burnt[3].."^(pipeworks_lua_tube_port_burnt.png^[transformR270)"
- tiles_burnt[4] = tiles_burnt[4].."^(pipeworks_lua_tube_port_burnt.png^[transformR90)"
- minetest.register_node(BASENAME .. "_burnt", {
- drawtype = "nodebox",
- tiles = tiles_burnt,
- is_burnt = true,
- paramtype = "light",
- is_ground_content = false,
- groups = {snappy = 3, tube = 1, tubedevice = 1, not_in_creative_inventory=1},
- drop = BASENAME.."000000",
- sunlight_propagates = true,
- selection_box = selection_box,
- node_box = node_box,
- on_construct = reset_meta,
- on_receive_fields = on_receive_fields,
- sounds = default.node_sound_wood_defaults(),
- virtual_portstates = {red = false, blue = false, yellow = false,
- green = false, black = false, white = false},
- mesecons = {
- effector = {
- rules = mesecon.rules.alldirs,
- action_change = function(pos, _, rule_name, new_state)
- update_real_port_states(pos, rule_name, new_state)
- end,
- },
- },
- tubelike = 1,
- tube = {
- connect_sides = {front = 1, back = 1, left = 1, right = 1, top = 1, bottom = 1},
- priority = 50,
- },
- after_place_node = pipeworks.after_place,
- after_dig_node = pipeworks.after_dig,
- on_blast = function(pos, intensity)
- if not intensity or intensity > 1 + 3^0.5 then
- minetest.remove_node(pos)
- return {string.format("%s_%s", name, dropname)}
- end
- minetest.swap_node(pos, {name = "pipeworks:broken_tube_1"})
- pipeworks.scan_for_tube_objects(pos)
- end,
- })
- ------------------------
- -- Craft Registration --
- ------------------------
- minetest.register_craft({
- type = "shapeless",
- output = BASENAME.."000000",
- recipe = {"pipeworks:mese_tube_000000", "mesecons_luacontroller:luacontroller0000"},
- })
|