Difference between revisions of "User:FeXoR"

From Pandorabox
Jump to navigation Jump to search
Line 110: Line 110:
  
 
BUG:
 
BUG:
When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type "table" before probing keys!
+
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type "table" before probing keys!
 +
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)
 
TODO:
 
TODO:
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)
+
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!
- Changing the radius does not adjust instant_jump distance
+
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)
 
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that
 
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that
 
- Unify usage of linebuffer
 
- Unify usage of linebuffer
Line 121: Line 122:
  
 
-- ########
 
-- ########
-- Things that need to be run first to work
+
-- Optimization
 
-- ########
 
-- ########
 
-- Get LUA environment
 
local function get_LUA_environment_information()
 
local global_modules = ""
 
local modules = {}
 
for k, v in pairs(_G) do
 
global_modules = global_modules .. tostring(k) .. "(" .. type(v) ..") "
 
if type(v) == "table" then
 
modules[k] = ""
 
for m, c in pairs(v) do
 
modules[k] = modules[k] .. tostring(m) .. "(" .. type(c) .. ") "
 
end
 
end
 
end
 
local output = "Available in _G:\n" .. global_modules
 
for k, v in pairs(modules) do
 
if k ~= "_G" then
 
output = output .. "\n Available in " .. tostring(k) .. ":\n" .. tostring(v)
 
end
 
end
 
return output
 
end
 
if event.type == "program" then mem.environment_information = get_LUA_environment_information() end
 
 
-- Optimization
 
 
local math, os, string, table = math, os, string, table
 
local math, os, string, table = math, os, string, table
  
Line 154: Line 130:
 
-- Settings
 
-- Settings
 
-- ########
 
-- ########
 
local help = false
 
  
 
local permission = {authorised_users = {"FeXoR", "6r1d", "SX", "SwissalpS", "Huhhila", "BuckarooBanzai", "admin"}}
 
local permission = {authorised_users = {"FeXoR", "6r1d", "SX", "SwissalpS", "Huhhila", "BuckarooBanzai", "admin"}}
Line 171: Line 145:
 
end
 
end
  
local event_catcher = {touchscreen = {channel = "ts_ec", max_lines = 30}, monitor = {channel = "mon_ec"}}
+
local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = "mon_ec"}}
 
if mem.events == nil then mem.events = {count = 0} end
 
if mem.events == nil then mem.events = {count = 0} end
  
Line 179: Line 153:
 
if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {"Here the Jumpdrive's responses will be shown."} end
 
if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {"Here the Jumpdrive's responses will be shown."} end
 
end
 
end
 +
 +
local quarries = {channel = "quarry"}
 +
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end
  
 
local touchscreen = {
 
local touchscreen = {
 
channel = "ts",
 
channel = "ts",
 
pages = {"Events", "Jumpdrive", "LUA Libs"},
 
pages = {"Events", "Jumpdrive", "LUA Libs"},
 +
update_pages = {},
 
permissions = {"Open", "Users", "Locked"},
 
permissions = {"Open", "Users", "Locked"},
linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 50}}
+
linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}
 
}
 
}
if mem.page == nil then mem.page = 1 end
+
if mem.page == nil then
 +
mem.page = 1
 +
table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])
 +
end
 
if mem.ts_lock == nil then mem.ts_lock = 2 end
 
if mem.ts_lock == nil then mem.ts_lock = 2 end
  
Line 260: Line 241:
 
local function get_time_string(datetable)
 
local function get_time_string(datetable)
 
local date_table = datetable or os.datetable()
 
local date_table = datetable or os.datetable()
local date_string = date_table.year .. "." .. date_table.month .. "." .. date_table.day
+
local date_string = date_table.year .. "-" .. date_table.month .. "-" .. date_table.day
 
local time_string = date_table.hour .. ":" .. date_table.min .. ":" .. date_table.sec
 
local time_string = date_table.hour .. ":" .. date_table.min .. ":" .. date_table.sec
return date_string .. " " .. time_string
+
return date_string .. "T" .. time_string
 
end
 
end
  
Line 295: Line 276:
 
if message ~= nil and message.type == "interrupt" then message.time = get_time_string() end
 
if message ~= nil and message.type == "interrupt" then message.time = get_time_string() end
 
touchscreen_add_line(get_string(message))
 
touchscreen_add_line(get_string(message))
digiline_send(event_catcher.touchscreen.channel, {
 
{command = "clear"},
 
{
 
command = "add", element = "textarea",
 
name = "display",
 
label = "Events:",
 
default = table.concat(mem.event_catcher.touchscreen_line_table, "\n"),
 
X=0.2, Y=0.1, W=10.2, H=9.5
 
}
 
})
 
 
end
 
end
  
Line 353: Line 324:
 
listelements = {"1","2","3","4","5","6","7","8","9","10","11","12","13","14","15"},
 
listelements = {"1","2","3","4","5","6","7","8","9","10","11","12","13","14","15"},
 
selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})
 
selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})
+
 +
-- Message very long, split here
 +
-- digiline_send(touchscreen.channel, message)
 +
-- message = {}
 +
 
-- BUG The "H" parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8
 
-- BUG The "H" parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8
 
table.insert(message, {command = "add", element = "field", name = "jump_step_value", label = "Distance",
 
table.insert(message, {command = "add", element = "field", name = "jump_step_value", label = "Distance",
Line 360: Line 335:
 
table.insert(message, {command = "add", element = "button", name = "jump_step_decrease", label = "-", X=1.7,Y=2.6,W=1.1,H=0.5})
 
table.insert(message, {command = "add", element = "button", name = "jump_step_decrease", label = "-", X=1.7,Y=2.6,W=1.1,H=0.5})
 
--
 
--
table.insert(message, {command = "add", element = "button_exit", name = "xi", label = (help and "@ E") or "E", X=3.8,Y=1,W=1.5,H=0.5})
+
table.insert(message, {command = "add", element = "button_exit", name = "xi", label = "E", X=3.8,Y=1,W=1.5,H=0.5})
table.insert(message, {command = "add", element = "button_exit", name = "xd", label = (help and "@ W") or "W", X=3.8,Y=2.6,W=1.5,H=0.5})
+
table.insert(message, {command = "add", element = "button_exit", name = "xd", label = "W", X=3.8,Y=2.6,W=1.5,H=0.5})
table.insert(message, {command = "add", element = "button_exit", name = "yi", label = (help and "@ ^") or "^", X=5.3,Y=1,W=1.5,H=0.5})
+
table.insert(message, {command = "add", element = "button_exit", name = "yi", label = "^", X=5.3,Y=1,W=1.5,H=0.5})
table.insert(message, {command = "add", element = "button_exit", name = "yd", label = (help and "@ v") or "v", X=5.3,Y=2.6,W=1.5,H=0.5})
+
table.insert(message, {command = "add", element = "button_exit", name = "yd", label = "v", X=5.3,Y=2.6,W=1.5,H=0.5})
table.insert(message, {command = "add", element = "button_exit", name = "zi", label = (help and "@ N") or "N", X=6.8,Y=1,W=1.5,H=0.5})
+
table.insert(message, {command = "add", element = "button_exit", name = "zi", label = "N", X=6.8,Y=1,W=1.5,H=0.5})
table.insert(message, {command = "add", element = "button_exit", name = "zd", label = (help and "@ S") or "S", X=6.8,Y=2.6,W=1.5,H=0.5})
+
table.insert(message, {command = "add", element = "button_exit", name = "zd", label = "S", X=6.8,Y=2.6,W=1.5,H=0.5})
if help then
 
table.insert(message, {command = "add", element = "label", label = '<  I. J.  >', X=2.8,Y=1.25})
 
table.insert(message, {command = "add", element = "label", label = '<  I. J.  >', X=2.8,Y=2.85})
 
table.insert(message, {command = "add", element = "label", label = '< "@" means "Attempt Jump" v', X=8.3,Y=1.25})
 
table.insert(message, {command = "add", element = "label", label = "< Instant Jump components ( I. J. )", X=8.3,Y=2.85})
 
end
 
 
 
 
table.insert(message, {command = "add", element = "button", name = "reset_target", label = "Reset", X=2.8,Y=1.8,W=1,H=0.8})
 
table.insert(message, {command = "add", element = "button", name = "reset_target", label = "Reset", X=2.8,Y=1.8,W=1,H=0.8})
 
table.insert(message, {command = "add", element = "button", name = "set_target", label = "Set", X=8.3,Y=1.8,W=1,H=0.8})
 
table.insert(message, {command = "add", element = "button", name = "set_target", label = "Set", X=8.3,Y=1.8,W=1,H=0.8})
 
table.insert(message, {command = "add", element = "button", name = "simulate", label = "Test", X=9.4,Y=1.8,W=1,H=0.8})
 
table.insert(message, {command = "add", element = "button", name = "simulate", label = "Test", X=9.4,Y=1.8,W=1,H=0.8})
table.insert(message, {command = "add", element = "button_exit", name = "jump", label = (help and "Jump @") or "Jump", X=10.5,Y=1.8,W=1.5,H=0.8})
+
table.insert(message, {command = "add", element = "button_exit", name = "jump", label = "Jump", X=10.5,Y=1.8,W=1.5,H=0.8})
 +
 +
table.insert(message, {command = "add", element = "checkbox", name = "reset_quarries_on_jump", label = "Reset quarries on jump", selected = mem.reset_quarries_on_jump, X=8.5,Y=3})
 
else
 
else
 
table.insert(message, {command = "add", element = "label", label = "This page is not yet handled: " .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})
 
table.insert(message, {command = "add", element = "label", label = "This page is not yet handled: " .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})
Line 391: Line 362:
 
if i_page ~= mem.page then
 
if i_page ~= mem.page then
 
mem.page = i_page
 
mem.page = i_page
update_page(touchscreen.pages[mem.page])
+
table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])
 
end
 
end
 
elseif event.msg.lock ~= nil then
 
elseif event.msg.lock ~= nil then
Line 409: Line 380:
 
else send_to_monitors("Unhandled ts_lock value: " .. tostring(mem.ts_lock))
 
else send_to_monitors("Unhandled ts_lock value: " .. tostring(mem.ts_lock))
 
end
 
end
update_page(touchscreen.pages[mem.page])
+
table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])
 
end
 
end
 
else
 
else
Line 416: Line 387:
 
elseif touchscreen.pages[mem.page] == "Jumpdrive" then
 
elseif touchscreen.pages[mem.page] == "Jumpdrive" then
 
local authorised = permission.check(event.msg.clicker)
 
local authorised = permission.check(event.msg.clicker)
local jumpdrive_page_needs_update = false -- ToDo: change to table and gather pages to update in it then only update the current one if present
 
 
local min_jump_step_value = 2 * mem.jumpdrive.radius + 1
 
local min_jump_step_value = 2 * mem.jumpdrive.radius + 1
 
 
Line 422: Line 392:
 
if tonumber(event.msg.jump_step_value) < min_jump_step_value then mem.instant_jump.distance = min_jump_step_value
 
if tonumber(event.msg.jump_step_value) < min_jump_step_value then mem.instant_jump.distance = min_jump_step_value
 
else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end
 
else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end
if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then jumpdrive_page_needs_update = true end
+
if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, "Jumpdrive") end
 
end
 
end
 
 
Line 445: Line 415:
 
digiline_send(jumpdrive.channel, {command = "get"})
 
digiline_send(jumpdrive.channel, {command = "get"})
 
elseif event.msg.key_enter_field == "jump_step_value" then
 
elseif event.msg.key_enter_field == "jump_step_value" then
jumpdrive_page_needs_update = true
+
table.insert(touchscreen.update_pages, 1, "Jumpdrive")
 
else send_to_monitors("Unknown key_enter_field: " .. tostring(event.msg.key_enter_field)) end
 
else send_to_monitors("Unknown key_enter_field: " .. tostring(event.msg.key_enter_field)) end
 
 
Line 479: Line 449:
 
if target_jump_step_value > min_jump_step_value then mem.instant_jump.distance = target_jump_step_value
 
if target_jump_step_value > min_jump_step_value then mem.instant_jump.distance = target_jump_step_value
 
else mem.instant_jump.distance = min_jump_step_value end
 
else mem.instant_jump.distance = min_jump_step_value end
if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then jumpdrive_page_needs_update = true end
+
if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, "Jumpdrive") end
 
elseif event.msg.jump_step_decrease ~= nil then
 
elseif event.msg.jump_step_decrease ~= nil then
 
local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1
 
local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1
 
if target_jump_step_value > min_jump_step_value then mem.instant_jump.distance = target_jump_step_value
 
if target_jump_step_value > min_jump_step_value then mem.instant_jump.distance = target_jump_step_value
 
else mem.instant_jump.distance = min_jump_step_value end
 
else mem.instant_jump.distance = min_jump_step_value end
if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then jumpdrive_page_needs_update = true end
+
if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, "Jumpdrive") end
 
elseif event.msg.xi ~= nil then
 
elseif event.msg.xi ~= nil then
 
if authorised then
 
if authorised then
Line 539: Line 509:
 
send_to_monitors("You can't jump, " .. tostring(event.msg.clicker) .. " ;)")
 
send_to_monitors("You can't jump, " .. tostring(event.msg.clicker) .. " ;)")
 
end
 
end
end
+
elseif event.msg.reset_quarries_on_jump ~= nil then
+
if authorised then
if jumpdrive_page_needs_update == true then
+
-- mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump
update_page("Jumpdrive")
+
if event.msg.reset_quarries_on_jump == "true" then mem.reset_quarries_on_jump = true
 +
else mem.reset_quarries_on_jump = false
 +
end
 +
table.insert(touchscreen.update_pages, 1, "Jumpdrive")
 +
else
 +
send_to_monitors("You can't change this, " .. tostring(event.msg.clicker) .. " ;)")
 +
end
 
end
 
end
 
end
 
end
Line 569: Line 545:
 
 
 
if event.msg.success ~= nil then
 
if event.msg.success ~= nil then
if event.msg.success == true then output = output .. " Success!"
+
if event.msg.success == true then
 +
output = output .. " Success!"
 +
if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = "set", restart = true}) end
 
else output = output .. " Failure! (Best refresh and reset)"
 
else output = output .. " Failure! (Best refresh and reset)"
 
end
 
end
Line 585: Line 563:
 
end
 
end
 
 
update_page("Jumpdrive")
+
table.insert(touchscreen.update_pages, 1, "Jumpdrive")
 
end
 
end
  
  
 
-- ########
 
-- ########
-- Event Catcher
+
-- Event Catcher and update screens
 
-- ########
 
-- ########
 
if mem.event_catcher == nil then
 
if mem.event_catcher == nil then
mem.event_catcher = {touchscreen_line_table = {"Initialized at " .. get_time_string() .. ", " .. _VERSION, mem.environment_information}}
+
mem.event_catcher = {touchscreen_line_table = {"Initialized at " .. get_time_string() .. ", " .. _VERSION}}
 
end
 
end
  
 
send_to_monitors(event)
 
send_to_monitors(event)
update_page("Events")
 
  
 +
for i, page in ipairs(touchscreen.update_pages) do
 +
if page == touchscreen.pages[mem.page] then
 +
update_page(touchscreen.pages[mem.page])
 +
break
 +
end
 +
end
  
-- ########
 
-- Node Breakers
 
-- ########
 
--if event.type == "program" then port[breakers.port] = true else port[breakers.port] = false end
 
 
 
-- ########
 
-- Count events
 
-- ########
 
 
mem.events.count = mem.events.count + 1
 
mem.events.count = mem.events.count + 1
 
 
-- ########
 
-- DEBUG BEGIN
 
-- ########
 
if event.type == "program" then
 
-- send_to_monitors(tostring(permission.check("FeXoR")))
 
-- send_to_monitors("mem.jumpdrive = " .. get_string(mem.jumpdrive))
 
-- digiline_send("lv_battery_box", "get")
 
-- digiline_send("hv_battery_box", "get")
 
-- digiline_send("lv_switching_station", "get")
 
-- digiline_send("hv_switching_station", "get")
 
-- local test_string = "-4100,11713,1000"
 
-- send_to_monitors("test_string = " .. get_string(test_string) .. " (" .. type(test_string) .. ")")
 
-- local test_table = coordinates:to_table("-4100,11713,1000")
 
-- send_to_monitors("test_list = " .. get_string(test_list) .. " (" .. type(test_list) .. ")")
 
-- local test_table2 = {a = "Jabba", b = "Jurksel", c = "Harbsch"}
 
-- send_to_monitors("test_table = " .. get_string(test_table) .. " (" .. type(test_table) .. ")")
 
-- local test_merged = merge_shallow_tables(test_table, test_table2)
 
-- send_to_monitors("test_merged = " .. get_string(test_merged) .. " (" .. type(test_merged) .. ")")
 
 
-- tCoord = coordinates:to_table("some-Rubbish123---246-248-more-Junk")
 
-- for k, v in pairs(tCoord) do digiline_send("mon_ec", tostring(k)..":"..tostring(v)) end
 
-- if event.type == "on" then digiline_send("drawer", {name = "moretrees:rubber_tree_leaves", count = 7}) end
 
end
 
-- DEBUG END
 
 
  
  

Revision as of 19:09, 26 June 2023

Hiho Pandorabox o/


I love this place for the large variety of functional modifications like technic, mesecon, pipeworks, digiline and modifications extending those.

I also value the transparency of this server due to this wiki and its contents like the mod list.

The maintenance you pull off is awesome!


Thank you for this great place ;)


Builds

Glitchy things

LUA Controller Code

Event Catcher - Mooncontroller

-- Event Catcher code for the mooncontroller


-- ########
-- Helper functions
-- ########

local function get_string(data, maxdepth)
	local maxdepth = maxdepth or 3
	if type(data) == "string" then return data
	elseif type(data) == "table" and maxdepth > 0 then
		local oString = "{"
		for k, v in pairs(data) do
			local val = v
			if type(v) == "table" then val = get_string(val, maxdepth - 1) end
			oString = oString .. tostring(k) .. "=" .. tostring(val) .. ", "
		end
		return string.sub(oString, 1, -3) .. "}"
	else
		return tostring(data)
	end
	return "Something went wrong!"
end

local function get_time_string(datetable)
	local date_table = datetable or os.datetable()
	local date_string = date_table.year .. "-" .. date_table.month .. "-" .. date_table.day
	local time_string = date_table.hour .. ":" .. date_table.min .. ":" .. date_table.sec
	return date_string .. "T" .. time_string
end


-- ########
-- Event Catcher
-- ########

if mem.events == nil then mem.events = {count = 0} end

if event.type == "program" then
	print("Initialized at " .. get_time_string() .. ", LUA environment:")
 	print(_G)
end

print("#" .. tostring(mem.events.count) .. ", " .. get_time_string() .. ":")
print(get_string(event))

mem.events.count = (mem.events.count + 1)%1000


-- MIT License
-- 
-- Copyright (c) 2021 Florian Finke
-- 
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
-- 
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
-- 
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.

Some basic LUA code to log digiline messages and steer a jumpdrive with a touchscreen

--[[
Basic Pandorabox tools

BUG:
- When a numerical value is contained in event.msg the LUAc run will fail! Resolve by checking for type "table" before probing keys!
- Current coordinates in LUAc doesn't reset when a jump was requested but not executed (e.g. obstructed, Refresh+Reset fixes the state)
TODO:
- Refreshing the touchscreen repaints the formspec fully. Make use of replacing GUI elements!
- Changing the radius does not adjust instant_jump distance (This may result in jump into itself)
- Changing instant_jump distance too small is actually set to the smallest possible but late for the GUI to always show that
- Unify usage of linebuffer
- Hardcoded textarea name "display" (cut in logging to prevent logging itself inflating string size) prevents more than one such textarea per page
]]


-- ########
-- Optimization
-- ########
local math, os, string, table = math, os, string, table


-- ########
-- Settings
-- ########

local permission = {authorised_users = {"FeXoR", "6r1d", "SX", "SwissalpS", "Huhhila", "BuckarooBanzai", "admin"}}
if mem.permission == nil then mem.permission = {ignore = false} end
permission.check = function(user)
	if mem.permission.ignore == true then return true end
	local is_allowed = false
	for i, u in ipairs(permission.authorised_users) do
		if user == u then
			is_allowed = true
			break
		end
	end
	return is_allowed
end

local event_catcher = {touchscreen = {max_lines = 30}, monitor = {channel = "mon_ec"}}
if mem.events == nil then mem.events = {count = 0} end

local jumpdrive = {channel = "jumpdrive"}
if mem.linebuffer == nil then
	mem.linebuffer = {}
	if mem.linebuffer.jumpdrive == nil then mem.linebuffer.jumpdrive = {"Here the Jumpdrive's responses will be shown."} end
end

local quarries = {channel = "quarry"}
if mem.reset_quarries_on_jump == nil then mem.reset_quarries_on_jump = false end

local touchscreen = {
	channel = "ts",
	pages = {"Events", "Jumpdrive", "LUA Libs"},
	update_pages = {},
	permissions = {"Open", "Users", "Locked"},
	linebuffer = {jumpdrive = {memory = mem.linebuffer.jumpdrive, max_lines = 30}}
}
if mem.page == nil then
	mem.page = 1
	table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])
end
if mem.ts_lock == nil then mem.ts_lock = 2 end

local breakers = {port = "b"}

if mem.instant_jump == nil then mem.instant_jump = {distance = 50} end


-- ########
-- Helper functions
-- ########

local character = {}
character.is_numeric = function (sChar)
	if sChar:byte() >= 48 and sChar:byte() <= 57 then return true
	else return false
	end
end


local coordinates = {names = {"x", "y", "z"}}

-- TODO: Probably make more readable by by using character type definition with methods is_numeric and is_minus
function coordinates:to_table(sInput)
	local tNumberList = {}
	if type(sInput) == "string" then
		local bContinuous = false
		for nChar = 1, #sInput do
			local nByte = sInput:byte(nChar)
			-- A new numerical string starts - potentially - ignoring repetitions of "-"
			if (bContinuous == false and (nByte == 45 or (nByte >= 48 and nByte <= 57))) or (nByte == 45 and sInput:byte(nChar-1) ~= 45) then
				-- Override previous non valid numerical string "-"
				if #tNumberList > 0 and tNumberList[#tNumberList] == "-" then tNumberList[#tNumberList] = string.char(nByte)
				else table.insert(tNumberList, string.char(nByte))
				end
				bContinuous = true
			elseif bContinuous and (nByte >= 48 and nByte <= 57) then
				tNumberList[#tNumberList] = tostring(tNumberList[#tNumberList]) .. string.char(nByte)
			elseif nByte ~= 45 then
				bContinuous = false
			end
		end
		-- Remove tailing non valid numerical string "-"
		if tNumberList[#tNumberList] == "-" then tNumberList[#tNumberList] = nil end
	end
	local tOutput = {}
	for i, name in ipairs(self.names) do
		tOutput[name] = tonumber(tNumberList[i] or "0")
	end
	return tOutput
end

function coordinates:to_string(coordinate_table) return coordinate_table.x .. "," .. coordinate_table.y .. "," .. coordinate_table.z end


local function get_string(data, maxdepth)
	local maxdepth = maxdepth or 3
	if type(data) == "string" then return data
	elseif type(data) == "table" and maxdepth > 0 then
		local oString = "{"
		for k, v in pairs(data) do
			local val = v
			if type(v) == "table" then val = get_string(val, maxdepth - 1) end
			oString = oString .. tostring(k) .. "=" .. tostring(val) .. ", "
		end
		return string.sub(oString, 1, -3) .. "}"
	else
		return tostring(data)
	end
	return "Something went wrong!"
end

local function get_time_string(datetable)
	local date_table = datetable or os.datetable()
	local date_string = date_table.year .. "-" .. date_table.month .. "-" .. date_table.day
	local time_string = date_table.hour .. ":" .. date_table.min .. ":" .. date_table.sec
	return date_string .. "T" .. time_string
end

local function merge_shallow_tables(t1, t2)
	local t3 = {}
	for k, v in pairs(t1) do t3[k] = v end
	for k, v in pairs(t2) do t3[k] = v end
	return t3
end

local function add_line_to_buffer(linebuffer, message)
	-- expects linebuffer to be an array with keys memory (link to line table in mem) and max_lines (integer)
	if type(message) ~= "string" then message = get_string(message) end
	table.insert(linebuffer.memory, 1, message)
	while table.maxn(linebuffer.memory) > linebuffer.max_lines do table.remove(linebuffer.memory) end
end

local function touchscreen_add_line(msg)
	table.insert(mem.event_catcher.touchscreen_line_table, 1, tostring(mem.events.count) .. ": " .. tostring(msg))
	while table.maxn(mem.event_catcher.touchscreen_line_table) > event_catcher.touchscreen.max_lines do
		table.remove(mem.event_catcher.touchscreen_line_table)
	end
end

local function send_to_monitors(message)
	-- Omitt appending it's own and other "display" type content to avoid doubling the output
	if message ~= nil and message.msg ~= nil and message.msg.display ~= nil then message.msg.display = "<cut>" end
	
	if message.channel then digiline_send(event_catcher.monitor.channel, message.channel)
	elseif message.type then digiline_send(event_catcher.monitor.channel, message.type)
	else digiline_send(event_catcher.monitor.channel, get_string(message, 1)) end
	if message ~= nil and message.type == "interrupt" then message.time = get_time_string() end
	touchscreen_add_line(get_string(message))
end


-- ########
-- Touchscreen
-- ########
local function update_page(page)
	if touchscreen.pages[mem.page] == page then
		local message = {
			{command = "clear"},
			-- BUG background9 needs to be before bgcolor
 			-- {command = "add", element = "background9", X = 0, Y = 0, W = 0, H = 0, image = "ui_formbg_9_sliced.png", auto_clip = true, middle = 16},
			-- BUG focus doesn't seem to work at all: focus = "target"
			{command = "set", width = 13, height = 10, no_prepend = true, real_coordinates = true},
			{command = "add", element = "bgcolor", bgcolor = "#202040FF", fullscreen = "false", fbgcolor = "#10101040"},
			
			{command = "add", element = "label", label = "Page:", X=0.2,Y=0.3},
			{command = "add", element = "textlist", name = "page", listelements = touchscreen.pages, selected_id = mem.page, X=0.1,Y=0.5,W=1.5,H=7.7},
			
			{command = "add", element = "label", label = "Lock:", X=0.2,Y=8.5},
			{command = "add", element = "textlist", name = "lock", listelements = touchscreen.permissions, selected_id = mem.ts_lock, X=0.1,Y=8.7,W=1.5,H=1.2},
		}
		if page == "Events" then
			table.insert(message, {command = "add", element = "textarea", name = "display", label = "Events:",
				default = table.concat(mem.event_catcher.touchscreen_line_table, "\n"), X=1.5, Y=0.55, W=11.25, H=9.3})
		
		elseif page == "Jumpdrive" then
			table.insert(message, {command = "add", element = "button", name = "request_data", label = "Refresh", X=1.7,Y=0.25,W=1.2,H=0.5})
			table.insert(message, {command = "add", element = "label", X=3.1, Y=0.5,
				label = "Distance: " .. tostring(math.ceil(mem.jumpdrive.distance)) .. "  |  " ..
				"EU needed: " .. tostring(math.ceil(mem.jumpdrive.power_req)) .. " stored: " .. tostring(math.ceil(mem.jumpdrive.powerstorage))
			})
			
			-- BUG The "H" parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8
			-- BUG The "set" focus propperty doesn't seem to work. The first input field added get's the focus
			table.insert(message, {command = "add", element = "field", name = "target",
				label = "Target (Current: " .. coordinates:to_string(mem.jumpdrive.position) .. ")",
				default = coordinates:to_string(mem.jumpdrive.target),
				X=3.8, Y=1.8, W=4.5})
			
			 -- Needs to be behind (in GUI so in front in code) radius selection or that will be blocked by it's invisible label
			table.insert(message, {command = "add", element = "textarea", name = "display", label = "The Jumpdrive says:",
				default = table.concat(mem.linebuffer.jumpdrive, "\n"), X=1.6, Y=3.8, W=10.7, H=6})
			
			table.insert(message, {command = "add", element = "label", label = "Radius", X=12,Y=3.7})
			table.insert(message, {command = "add", element = "textlist", name = "radius",
				listelements = {"1","2","3","4","5","6","7","8","9","10","11","12","13","14","15"},
				selected_id = tonumber(mem.jumpdrive.radius), X=12.4,Y=3.85,W=0.5,H=6})
			
			-- Message very long, split here
-- 			digiline_send(touchscreen.channel, message)
-- 			message = {}
			
			-- BUG The "H" parameter only shifts a field instead of resizing it. Best leave it out (same as H=0.8
			table.insert(message, {command = "add", element = "field", name = "jump_step_value", label = "Distance",
				default = tostring(mem.instant_jump.distance), X=1.7, Y=1.8, W=1.1})
			table.insert(message, {command = "add", element = "button", name = "jump_step_increase", label = "+", X=1.7,Y=1,W=1.1,H=0.5})
			table.insert(message, {command = "add", element = "button", name = "jump_step_decrease", label = "-", X=1.7,Y=2.6,W=1.1,H=0.5})
-- 			
			table.insert(message, {command = "add", element = "button_exit", name = "xi", label = "E", X=3.8,Y=1,W=1.5,H=0.5})
			table.insert(message, {command = "add", element = "button_exit", name = "xd", label = "W", X=3.8,Y=2.6,W=1.5,H=0.5})
			table.insert(message, {command = "add", element = "button_exit", name = "yi", label = "^", X=5.3,Y=1,W=1.5,H=0.5})
			table.insert(message, {command = "add", element = "button_exit", name = "yd", label = "v", X=5.3,Y=2.6,W=1.5,H=0.5})
			table.insert(message, {command = "add", element = "button_exit", name = "zi", label = "N", X=6.8,Y=1,W=1.5,H=0.5})
			table.insert(message, {command = "add", element = "button_exit", name = "zd", label = "S", X=6.8,Y=2.6,W=1.5,H=0.5})
			
			table.insert(message, {command = "add", element = "button", name = "reset_target", label = "Reset", X=2.8,Y=1.8,W=1,H=0.8})
			table.insert(message, {command = "add", element = "button", name = "set_target", label = "Set", X=8.3,Y=1.8,W=1,H=0.8})
			table.insert(message, {command = "add", element = "button", name = "simulate", label = "Test", X=9.4,Y=1.8,W=1,H=0.8})
			table.insert(message, {command = "add", element = "button_exit", name = "jump", label = "Jump", X=10.5,Y=1.8,W=1.5,H=0.8})
			
			table.insert(message, {command = "add", element = "checkbox", name = "reset_quarries_on_jump", label = "Reset quarries on jump", selected = mem.reset_quarries_on_jump, X=8.5,Y=3})
		else
			table.insert(message, {command = "add", element = "label", label = "This page is not yet handled: " .. tostring(touchscreen.pages[mem.page]), X=4,Y=4})
		end
		
		digiline_send(touchscreen.channel, message)
	end
end

-- Handle touchscreen messages
if event.type == "digiline" and event.channel == touchscreen.channel and event.msg then
	if event.msg.page ~= nil then
		local i_page = tonumber(string.sub(event.msg.page, 5))
		if i_page ~= mem.page then
			mem.page = i_page
			table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])
		end
	elseif event.msg.lock ~= nil then
		local i_lock = tonumber(string.sub(event.msg.lock, 5))
		if permission.check(event.msg.clicker) then
			if i_lock ~= mem.ts_lock then
				mem.ts_lock = i_lock
				if mem.ts_lock == 1 then
					digiline_send(touchscreen.channel, {command = "unlock"})
					mem.permission.ignore = true
				elseif mem.ts_lock == 2 then
					digiline_send(touchscreen.channel, {command = "unlock"})
					mem.permission.ignore = false
				elseif mem.ts_lock == 3 then
					digiline_send(touchscreen.channel, {command = "lock"})
					mem.permission.ignore = false
				else send_to_monitors("Unhandled ts_lock value: " .. tostring(mem.ts_lock))
				end
				table.insert(touchscreen.update_pages, 1, touchscreen.pages[mem.page])
			end
		else
			send_to_monitors("You can't unlock, " .. tostring(event.msg.clicker) .. " ;)")
		end
	elseif touchscreen.pages[mem.page] == "Jumpdrive" then
		local authorised = permission.check(event.msg.clicker)
		local min_jump_step_value = 2 * mem.jumpdrive.radius + 1
		
		if event.msg.jump_step_value ~= nil then
			if tonumber(event.msg.jump_step_value) < min_jump_step_value then mem.instant_jump.distance = min_jump_step_value
			else mem.instant_jump.distance = tonumber(event.msg.jump_step_value) end
			if tonumber(event.msg.jump_step_value) ~= mem.instant_jump.distance then table.insert(touchscreen.update_pages, 1, "Jumpdrive") end
		end
		
		if event.msg.radius ~= nil then
			if authorised then
				mem.jumpdrive.radius = tonumber(string.sub(event.msg.radius, 5))
				if event.msg.target ~= nil then
					mem.jumpdrive.target = coordinates:to_table(event.msg.target)
					digiline_send(jumpdrive.channel, merge_shallow_tables({command = "set", r = mem.jumpdrive.radius, formupdate = true}, mem.jumpdrive.target))
				else
					digiline_send(jumpdrive.channel, {command = "set", r = mem.jumpdrive.radius, formupdate = true})
				end
				digiline_send(jumpdrive.channel, {command = "get"})
			else
				send_to_monitors("You can't change the radius, " .. tostring(event.msg.clicker) .. " ;)")
			end
		
		elseif event.msg.key_enter_field ~= nil then
			if event.msg.key_enter_field == "target" then
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = "set", formupdate = false}, mem.jumpdrive.target))
				digiline_send(jumpdrive.channel, {command = "get"})
			elseif event.msg.key_enter_field == "jump_step_value" then
				table.insert(touchscreen.update_pages, 1, "Jumpdrive")
			else send_to_monitors("Unknown key_enter_field: " .. tostring(event.msg.key_enter_field)) end
		
		elseif event.msg.reset_target ~= nil then
			digiline_send(jumpdrive.channel, {command = "reset"})
			digiline_send(jumpdrive.channel, {command = "get"})
		elseif event.msg.set_target ~= nil then
			if event.msg.target ~= nil then
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = "set", formupdate = false}, mem.jumpdrive.target))
				digiline_send(jumpdrive.channel, {command = "get"})
			end
		elseif event.msg.request_data ~= nil then
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = "set", formupdate = false}, mem.jumpdrive.target))
			digiline_send(jumpdrive.channel, {command = "get"})
		elseif event.msg.simulate ~= nil then
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)
			digiline_send(jumpdrive.channel, merge_shallow_tables({command = "set", formupdate = false}, mem.jumpdrive.target))
			digiline_send(jumpdrive.channel, {command = "simulate"})
		elseif event.msg.jump ~= nil then
			if authorised then
				mem.jumpdrive.target = coordinates:to_table(event.msg.target)
-- 				mem.jumpdrive.target = mem.jumpdrive.position
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = "set", formupdate = false}, mem.jumpdrive.target))
				digiline_send(jumpdrive.channel, {command = "jump"})
			else
				send_to_monitors("You can't jump, " .. tostring(event.msg.clicker) .. " ;)")
			end
			
		elseif event.msg.jump_step_increase ~= nil then
			local target_jump_step_value = tonumber(event.msg.jump_step_value) + 1
			if target_jump_step_value > min_jump_step_value then mem.instant_jump.distance = target_jump_step_value
			else mem.instant_jump.distance = min_jump_step_value end
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, "Jumpdrive") end
		elseif event.msg.jump_step_decrease ~= nil then
			local target_jump_step_value = tonumber(event.msg.jump_step_value) - 1
			if target_jump_step_value > min_jump_step_value then mem.instant_jump.distance = target_jump_step_value
			else mem.instant_jump.distance = min_jump_step_value end
			if mem.instant_jump.distance ~= tonumber(event.msg.jump_step_value) then table.insert(touchscreen.update_pages, 1, "Jumpdrive") end
		elseif event.msg.xi ~= nil then
			if authorised then
-- 				mem.jumpdrive.target = mem.jumpdrive.position
				mem.jumpdrive.target.x = mem.jumpdrive.position.x + mem.instant_jump.distance
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = "set", formupdate = false}, mem.jumpdrive.target))
				digiline_send(jumpdrive.channel, {command = "jump"})
			else
				send_to_monitors("You can't jump, " .. tostring(event.msg.clicker) .. " ;)")
			end
		elseif event.msg.xd ~= nil then
			if authorised then
-- 				mem.jumpdrive.target = mem.jumpdrive.position
				mem.jumpdrive.target.x = mem.jumpdrive.position.x - mem.instant_jump.distance
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = "set", formupdate = false}, mem.jumpdrive.target))
				digiline_send(jumpdrive.channel, {command = "jump"})
			else
				send_to_monitors("You can't jump, " .. tostring(event.msg.clicker) .. " ;)")
			end
		elseif event.msg.yi ~= nil then
			if authorised then
-- 				mem.jumpdrive.target = mem.jumpdrive.position
				mem.jumpdrive.target.y = mem.jumpdrive.position.y + mem.instant_jump.distance
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = "set", formupdate = false}, mem.jumpdrive.target))
				digiline_send(jumpdrive.channel, {command = "jump"})
			else
				send_to_monitors("You can't jump, " .. tostring(event.msg.clicker) .. " ;)")
			end
		elseif event.msg.yd ~= nil then
			if authorised then
-- 				mem.jumpdrive.target = mem.jumpdrive.position
				mem.jumpdrive.target.y = mem.jumpdrive.position.y - mem.instant_jump.distance
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = "set", formupdate = false}, mem.jumpdrive.target))
				digiline_send(jumpdrive.channel, {command = "jump"})
			else
				send_to_monitors("You can't jump, " .. tostring(event.msg.clicker) .. " ;)")
			end
		elseif event.msg.zi ~= nil then
			if authorised then
-- 				mem.jumpdrive.target = mem.jumpdrive.position
				mem.jumpdrive.target.z = mem.jumpdrive.position.z + mem.instant_jump.distance
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = "set", formupdate = false}, mem.jumpdrive.target))
				digiline_send(jumpdrive.channel, {command = "jump"})
			else
				send_to_monitors("You can't jump, " .. tostring(event.msg.clicker) .. " ;)")
			end
		elseif event.msg.zd ~= nil then
			if authorised then
-- 				mem.jumpdrive.target = mem.jumpdrive.position
				mem.jumpdrive.target.z = mem.jumpdrive.position.z - mem.instant_jump.distance
				digiline_send(jumpdrive.channel, merge_shallow_tables({command = "set", formupdate = false}, mem.jumpdrive.target))
				digiline_send(jumpdrive.channel, {command = "jump"})
			else
				send_to_monitors("You can't jump, " .. tostring(event.msg.clicker) .. " ;)")
			end
		elseif event.msg.reset_quarries_on_jump ~= nil then
			if authorised then
-- 				mem.reset_quarries_on_jump = event.msg.reset_quarries_on_jump
				if event.msg.reset_quarries_on_jump == "true" then mem.reset_quarries_on_jump = true
				else mem.reset_quarries_on_jump = false
				end
				table.insert(touchscreen.update_pages, 1, "Jumpdrive")
			else
				send_to_monitors("You can't change this, " .. tostring(event.msg.clicker) .. " ;)")
			end
		end
	end
end


-- ########
-- Jumpdrive
-- ########
if mem.jumpdrive == nil then
	mem.jumpdrive = {radius = 1, power_req = 0, distance = 0, powerstorage = 0, position = {x=0,y=0,z=0}, target = {x=0,y=0,z=0}, success = false, msg = "", time = 0}
end
if event.type == "program" then digiline_send(jumpdrive.channel, {command = "get"}) end

if event.type == "digiline" and event.channel == jumpdrive.channel and event.msg then
	local output = ""
	local updated = {}
	for k, v in pairs(event.msg) do
		if mem.jumpdrive[k] ~= nil then
-- 			if mem.jumpdrive[k] ~= v then output = output .. " " .. tostring(k) .. ": " .. get_string(mem.jumpdrive[k]) .. " -> " .. get_string(v) end
			mem.jumpdrive[k] = v
		else
			add_line_to_buffer(touchscreen.linebuffer.jumpdrive, "Unknown jumpdrive propperty: " .. get_string(k) .. ":" .. get_string(v))
		end
	end
	
	if event.msg.success ~= nil then
		if event.msg.success == true then
			output = output .. " Success!"
			if mem.reset_quarries_on_jump then digiline_send(quarries.channel, {command = "set", restart = true}) end
		else output = output .. " Failure! (Best refresh and reset)"
		end
	end
	if event.msg.msg then output = output .. " " .. event.msg.msg end
	if event.msg.time then
		output = output .. " Jumped (" .. tostring(event.msg.time) .. ")"
		mem.jumpdrive.position = mem.jumpdrive.target
 		digiline_send(jumpdrive.channel, {command = "get"})
	end
	if output ~= nil then
		if output ~= "" then add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. ":" .. output) end
	else
		add_line_to_buffer(touchscreen.linebuffer.jumpdrive, tostring(mem.events.count) .. ":" .. "Output was nil!")
	end
	
	table.insert(touchscreen.update_pages, 1, "Jumpdrive")
end


-- ########
-- Event Catcher and update screens
-- ########
if mem.event_catcher == nil then
 	mem.event_catcher = {touchscreen_line_table = {"Initialized at " .. get_time_string() .. ", " .. _VERSION}}
end

send_to_monitors(event)

for i, page in ipairs(touchscreen.update_pages) do
	if page == touchscreen.pages[mem.page] then
		update_page(touchscreen.pages[mem.page])
		break
	end
end

mem.events.count = mem.events.count + 1


-- MIT License
-- 
-- Copyright (c) 2021 Florian Finke
-- 
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
-- 
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
-- 
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.

Multi ingredient autocrafter LUA code with buffer chest

--[[ Multi Ingredient Autocrafter (MIA)

Setup:
	Input tube for stacks of ingredients
		Mithril Chest (Channel stored in mithril_chest)
			Digiline Injector (Channel stored in injector_excess)
				Output tube for excess/wrong stacks put in
			Digiline Injector (Channel stored in injector_craft)
				Autocrafter (Channel stored in autocrafter)
					Digiline Injector (Channel stored in injector_output)
						Output tube for crafted items

BUG	Hardcoded Stacksize, doesn't propperly work with buckets and unnecessarily resource intense for minegeld
]]

-- Settings
local recipe = {
	{"default:stick", "default:wood", "default:stick"},
	{"default:wood", "", "default:wood"},
	{"default:stick", "default:wood", "default:stick"}
}

local channels = {
	mithril_chest = "mc",
	autocrafter = "ac",
	injector_craft = "i_craft",
	injector_excess = "i_excess",
	injector_output = "i_out"
}

local cycle_length = 33
local max_cafts_per_cycle = cycle_length
local max_craft_ingredients_kept = max_cafts_per_cycle * 9

-- Functions
local function get_ingredient_list(reci)
	reci = reci or recipe
	local ingredients = {}
	for i, v in ipairs(reci) do
		for j, w in ipairs(v) do
			if w ~= "" then
				if ingredients[w] == nil then ingredients[w] = 1
				else ingredients[w] = ingredients[w] + 1
				end
			end
		end
	end
	local ingredient_list = {type = {}, amount = {}}
	for t, a in pairs(ingredients) do
		table.insert(ingredient_list.type, t)
		table.insert(ingredient_list.amount, a)
	end
	return ingredient_list
end

local function get_max_crafts (ingredient_list, content)
	ingredient_list = ingredient_list or get_ingredient_list(recipe)
	content = content or event.msg
	local available = {}
	for i, t in ipairs(ingredient_list.type) do table.insert(available, 0)	end
	for slot, stack in ipairs(content) do
		local i_seperator = stack:find(" ", 1, true)
		if i_seperator ~= nil then
			for i, t in ipairs(ingredient_list.type) do
				if stack:sub(1, i_seperator - 1) == t then
					available[i] = available[i] + tonumber(stack:sub(i_seperator + 1))
					break
				end
			end
		end
	end
	local max_crafts = 10000
	for i, a in ipairs(ingredient_list.amount) do
		available[i] = math.floor(available[i] / a)
		if max_crafts > available[i] then max_crafts = available[i] end
	end
	return max_crafts
end

-- Do stuff
if event.type == "program" then
	digiline_send(channels.autocrafter, recipe)
	digiline_send(channels.autocrafter, "on")
	interrupt(cycle_length + heat)
elseif event.type == "interrupt" then
	digiline_send(channels.mithril_chest, {command = "sort"})
	digiline_send(channels.mithril_chest, {command = "get_list"})
	for i = 1, 9 do digiline_send(channels.injector_output, {slotseq = 0, exmatch = false}) end
	interrupt(cycle_length + heat)
elseif event.type == "digiline" then
	if event.channel == channels.mithril_chest and type(event.msg) == "table" then
		local ingredient_list = get_ingredient_list()
		local crafts = math.min(get_max_crafts(ingredient_list), max_cafts_per_cycle)
		for i, a in ipairs(ingredient_list.amount) do
			local to_send = crafts * a
			-- BUG: Hardcoded Stacksize
			local full_stacks = math.floor(to_send / 99)
			for n = 1, full_stacks do
				digiline_send(channels.injector_craft, {slotseq = 0, exmatch = true, name = ingredient_list.type[i], count = 99})
			end
			to_send = to_send - 99 * full_stacks
			if to_send > 0 then
				digiline_send(channels.injector_craft, {slotseq = 0, exmatch = true, name = ingredient_list.type[i], count = to_send})
			end
			-- TODO Extract non fitting and overdue stacks
		end
	end
end